No iPhone rival from Microsoft
Isn't Microsoft to old to compete?
This news is quite disappointing. iPhone has many design flaws, so competing with it can be quite easy. Anyway Microsoft decided to pull themselves from a competition. Maybe they were discouraged to any competition after Zune fiasco. And again Microsoft had all chances to create a DAP better than iPod, or at least on the same step. Microsoft already has platform for mobile device, but very promising platform over 5 years ago now becomes ugly and outdated. I do not know why Microsoft doesn't want to invest in this direction. Obviously portable devices have a big feature and company with sufficient cash on hands can dig this market quite successfully. It is quite surprising why Gates wanted a PC on every desk, but not in every pocket.
Using the ODBC Data Source Administrator
Vista Help
How do I add a data source and driver to my computer?
Open ODBC data source administrator by clicking the Start buttonHowever Additional options in my case had only Java icon. So what to do? I tried a new search feature of Vista, so I press start and typed in search box odbc and press enter. Can you believe what happened after? Yes, I got the ODBC Data Source Administrator.
Using Aldan3 library for writing simple web applications
Yet another library or revolutionary approach?
The Article demo downloadable zip file
There is a number of different servlet frameworks available helping in a rapid development of web applications. A wide selection of tools is always a good thing because it allows choosing one most suitable for particular requirements. Aldan3 is a fullstack framework designed primary for creation small applications with codebase around 1000-10000 lines. This guide provides an illustration based on using Eclipse IDE, but it can be transferred to another IDE of your choice or just a simple Java code editor.
Requirements
Create a web application for publishing polls with less than 20 items, allowing custom items, and immune against SPAM and flood robots. Polls have to be created using a simple XML definition, like below:
<?xml version=”1.0”?>
<poll>
<title>poll title is here</title>
<type>single or multiselect</type>
<show-result>yes/no</show-result>
<vote>
<title>a vote title is here</title>
<fill-required> yes/no /<fill-required>
</vote>
….
<vote …>
……
</poll>
Result of polls can be viewable and stored in XML file as well in the following format:
<?xml version=”1.0”?>
<poll-result>
<vote>
<title>a vote title is here</vote>
<chosen>number</chosen>
<entered>a user input</entered>
…..
<entered>a user input</entered>
</vote>
</poll-result>
An administration of a poll is possible by using config files.
A poll can be added to any web site in a simple <div> or <iframe> block.
Design
Since this illustration was created for Aldan3 framework, it assumes that a usage of the framework is mandatory and it is a part of requirements. Certainly Ruby, PHP, or Python based realization can be simpler, however less portable.
Aldan3 dictates the following design patterns for implementing any web applications:
-
Front Controller
-
MVC based services
The Front Controller redirects requests to a respective service, which is represented basically by a MVC pattern. There is no any reason to touch Front Controller, so a design any application is just a matter of defining services and splitting them further on model, view and controller parts. The following decisions have to be made while doing the design:
- One or multiple services have to be involved
- How to track voters
- Ajax or traditional HTML approach
- How to parse and generate XML
Any UI service can be multifunctional, however a usage of separate services is much clearer even for a very similar tasks. An inheritance can also help in taking an extra work of implementation every new similar service. So the following two services are introduced:
- Poll display and processing
- Poll result display
A service class can include or not a model implementation. Although a model can be included in a service implementation, it is reasonable to separate a model in a dedicated class as for illustration purpose, as for a consideration of future extensions, The model can take care of a persistency as well. Since a poll has quite simplistic UI, a traditional HTML generation is preferable to cover wider specter of browsers. Xpath seems to be suitable for parsing a poll definition. When XML generation can be done just using a direct tag/data writing. Reading poll data back can utilize SAX parsing. Tracking voters can be done using cookies. Flood and SPAM control can be organized on a black list approach.
Implementation
Aldan3 UI services class takes care of processing user input and preparing a model used by a view. Every service has to extend BasePageService abstract class. Eclipse will provide all required methods with an empty body. It is recommended to create an intermediate base class taking care of common required methods especially for big projects. However this possibility will be illustrated even for this small one. Let’s name Poll display and processing service as Voter. The following code was generated by Eclipse:
import org.aldan3.servlet.BasePageService;
public class Voter extends BasePageService {
@Override
protected Object doControl() {
// TODO Auto-generated method stub
return null;
}
@Override
protected Object getModel() {
// TODO Auto-generated method stub
return null;
}
@Override
protected String getSubmitPage() {
// TODO Auto-generated method stub
return null;
}
@Override
protected String getUnauthorizedPage() {
// TODO Auto-generated method stub
return null;
}
@Override
protected String getViewName() {
// TODO Auto-generated method stub
return null;
}
public boolean isThreadFriendly() {
// TODO Auto-generated method stub
return false;
}
public boolean isThreadSafe() {
// TODO Auto-generated method stub
return false;
}
public String getPreferredServiceName() {
// TODO Auto-generated method stub
return null;
}
public Object getServiceProvider() {
// TODO Auto-generated method stub
return null;
}
}
Let's start from defenition of information and configuration methods first. There are two required service methods getPreferredServiceName and getServiceProvider . Name of service used for registration it and accessibility. It doesn’t play much role for UI services, so it can be just a name of service class. Aldan3 Front Controller uses a class name anyway to find a service. Service provider makes sense when an actual service is delegated, so returning this is an appropriate implementation.
public String getPreferredServiceName() {
return "Voter"; // usually name of class
}
public Object getServiceProvider() {
return this; // no delegated services so return ourselves
}
A group of methods telling about multithreading abilities of a service is the next target of implementation. If one instance of a service can serve concurrent requests, then method isThreadFriendly may return true. Here it needs to be careful.Generally the base service class isn't thread friendly since uses class members, however a service can be thread friendly when it doesn't deal with http request parameters. Method isThreadSafe tells a possibility to make concurrent calls when only one instances of a service is instantiated. Services have to be implemented to be at least thread safe, so this methods returns true in most implementations.
public boolean isThreadFriendly() {
return false; // one instance of service class can manage concurrent requests
}
public boolean isThreadSafe() {
return true; // services can run concurrently
}
Aldan3 services are access controllable; it means that every service authorizes accesses. By default all services are not accessible unless a user is authenticated. A selective authorization can be added as well. Several methods provide a flexibility of this mechanism. In our case, all services have to be public accessible, it is specified by overriding
@Override
public boolean isPublic() {
return true; // allow public access, no authorization
}
Non-authorized requests can be redirected to a specific service, which can be defined in a method: getUnauthorizedPage. Implementing the method for public accessible services doesn’t make much sense, however an attempt of returning the same service name can issue infinitive loop at page access and should be avoided.
@Override
protected String getUnauthorizedPage() {
throw new IllegalAccessError ("Check authorization mechanism settings");
}
Aldan3 services usually use external views which are a template, JSP, or JavaScript based. Template based views are assumed by default. A view dispatching mechanism can be defined by overriding certain methods. For example JSP and different template engine driven views can be combined in the same application. A view name has to be retuned in method getViewName, unless views are not used. It is convenient to select view name the same as a service name, so an implementation of this method can look like this:
@Override
protected String getViewName() {
return getPreferredServiceName().toLowerCase()+".htm";
}
So we are mostly done with common methods, which can be shared by all UI services of the web application. One more method has to be taken care for this illustration. Aldan3 provides automatic multi lingual UI based on resource files keeping labels in different languages. Since this example doesn’t utilize this capability, Aldan3 machinery has to be notified about that, so the following method needs to be overridden:
@Override
protected boolean useLabels() {
return false;
}
Service specific methods implementation can be started now . getModel passes model to a view. Generally this method will delegate a call to a model. Sometimes it can pre-process model data for easy use by a view. For example for client side views (Ajax), a model can be converted in XML(unless it is already in this format) or JSON data payload. Aldan3 template engine accepts model data represented in a map, so the method does such job.
@Override
protected Object getModel() {
HashMap<String,PollModel> hm = new HashMap<String,PollModel>(1);
hm.put("model", getPollModel());
return hm;
}
getPollModel is a custom method, which implementation will be discussed below. Finally let’s discuss two control functionality related methods. Method getSubmitPage provides a general inter services navigation. It returns a service name when the current service finishes all own UI tasks and a user has to deal with another UI service. This is only a convenience method, since a user has other possibilities to navigate to other services of an application. Aldan3 concept is one UI service per one functional unit of an application. Since we defined a two services application, this method has to return a name of a service to show a poll result.
@Override
protected String getSubmitPage() {
return "Result"; // service reused
}
Processing of a user input (an actual control) happens in method: doControl. This method validates a user input, and then changes the model state. Generally it can keep a user on the same page or move a navigation to another service. doControl can return data which are considered as data for the service view. In this case the view will be invoked again. It gives a chance to notify users about incorrect data or just ask for input confirmation. If the method returns null, then it is considered as service finishes its work and navigation will be moved to a service defined by method: getSubmitPage.
@Override
protected Object doControl() {
PollModel model = getPollModel();
String title;
model.addVote(title = getStringParameterValue("vote", null, 0));
if (title != null) {
model.addVoteChoice(title, getStringParameterValue(title, null, 0));
}
return null;
}
The implementation uses some convenient method to access user input data from a servlet request. Data validation can be done on client side in JavaScript. The control code is immune to invalid data, so no additional validation happens here.
Let’s move on implementation of a model. All decisions about model format and parsing/generation XML data were made in design parts, so model implementation code is a straightforward.
public class PollModel implements ServletContextListener {
public static final String MODEL_ATTR_NAME = "PollModel";
// TODO collect init params constants here
private String pollTitle;
private List<Vote> votes;
private HashMap<String, Vote> voteResults;
private HashMap<String, Set<String>> choiceResults;
private boolean canDisplay;
private boolean multiSelect;
private int pollDuration;
private int maxChoices = 1000;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public String getTitle() {
return pollTitle;
}
public List<Vote> getVotes() {
return votes;
}
public boolean isShowable() {
return canDisplay;
}
public boolean isMultiSelectable() {
return multiSelect;
}
public int getPollDuration() {
return pollDuration;
}
public void contextDestroyed(ServletContextEvent sce) {
// store result
ServletContext sc = sce.getServletContext();
String pollFile = sc.getInitParameter("poll_result_file");
OutputStreamWriter osw = null;
try {
saveXMLResult(osw = new OutputStreamWriter(new FileOutputStream(pollFile), "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (osw != null)
try {
osw.close();
} catch (IOException e) {
}
}
}
public void contextInitialized(ServletContextEvent sce) {
ServletContext sc = sce.getServletContext();
// read a poll definition
XPath xp = XPathFactory.newInstance().newXPath();
try {
String pollLoc = sc.getInitParameter("poll_url");
URL pollUrl = sc.getResource(pollLoc);
if (pollUrl == null)
pollUrl = new URL(pollLoc);
Node document = (Node) xp.evaluate("/*", new InputSource(pollUrl.openStream()), XPathConstants.NODE);
// TODO in first evaluate /poll
document = (Node) xp.evaluate("//poll", document, XPathConstants.NODE);
pollTitle = (String) xp.evaluate("title", document, XPathConstants.STRING);
String pollDureationStr = (String) xp.evaluate("duration", document, XPathConstants.STRING);
if (pollDureationStr != null && pollDureationStr.length() > 1) {
pollDuration = 1;
switch(pollDureationStr.charAt(pollDureationStr.length()-1)) {
case 'w':
pollDuration *= 7;
case 'd':
pollDuration *= 24;
case 'h':
pollDuration *= 60;
case 'm':
pollDuration *= 60;
pollDureationStr = pollDureationStr.substring(0, pollDureationStr.length()-1);
}
}
try {
pollDuration = pollDuration * Integer.parseInt(pollDureationStr);
} catch (NumberFormatException e) {
}
canDisplay = "yess".equals(xp.evaluate("show-result", document, XPathConstants.STRING));
multiSelect = "mulitselect".equals(xp.evaluate("type", document, XPathConstants.STRING));
NodeList nodes = (NodeList) xp.evaluate("vote", document, XPathConstants.NODESET);
int nodesLen = nodes.getLength();
votes = new ArrayList<Vote>(nodesLen);
voteResults = new HashMap<String, Vote>(nodesLen);
choiceResults = new HashMap<String, Set<String>>(nodesLen);
for (int p = 0; p < nodesLen; p++) {
Vote v = new Vote();
v.title = (String) xp.evaluate("title", nodes.item(p), XPathConstants.STRING);
v.fillable = "yes".equals(xp.evaluate("fill-required", nodes.item(p), XPathConstants.STRING));
votes.add(v);
voteResults.put(v.title, v);
if (v.fillable)
choiceResults.put(v.title, new HashSet<String>());
}
pollLoc = sc.getInitParameter("poll_result_file");
if (pollLoc != null) {
FileInputStream fis = null;
File resultFile = new File(pollLoc);
if (resultFile.exists())
try {
readXMLResult(fis = new FileInputStream(resultFile));
} finally {
if (fis != null)
fis.close();
}
}
// TODO read maxChoices
sc.setAttribute(MODEL_ATTR_NAME, this);
} catch (XPathExpressionException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// read already accumulated result to show
}
public void addVote(String voteName) {
if (validate(voteName, null) == false)
return;
rwl.writeLock().lock();
try {
voteResults.get(voteName).count++;
} finally {
rwl.writeLock().unlock();
}
}
public void addVoteChoice(String voteName, String choice) {
if (validate(voteName, choice) == false || choice == null)
return;
rwl.writeLock().lock();
try {
if (maxChoices > 0 && maxChoices > choiceResults.get(voteName).size())
choiceResults.get(voteName).add(choice.toLowerCase());
} finally {
rwl.writeLock().unlock();
}
}
public static final List<Vote> calculatePercentage(List<Vote> absVotes) {
List<Vote> pr = new ArrayList<Vote>(absVotes.size());
int sum = 0;
for (Vote v : absVotes) {
Vote nv = new Vote();
nv.title = v.title;
nv.count = v.count;
sum += nv.count;
pr.add(nv);
}
if (sum > 0)
for (Vote v : pr) {
v.count = v.count * 100 / sum;
}
// TODO do sum of % and last item calculate as 100% - sum%
return pr;
}
private void saveXMLResult(Writer w) throws IOException {
w.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
w.write("<poll-result>");
rwl.readLock().lock();
try {
for (Vote v : voteResults.values()) {
w.write("<vote>");
w.write("<title>");
w.write(HttpUtils.htmlEncode(v.title));
w.write("</title>");
w.write("<chosen>");
w.write(String.valueOf(v.count));
w.write("</chosen>");
if (v.fillable) {
for (String s : choiceResults.get(v.title)) {
w.write("<entered>");
w.write(HttpUtils.htmlEncode(s));
w.write("</entered>");
}
}
w.write("</vote>");
}
} finally {
rwl.readLock().unlock();
}
w.write("</poll-result>");
}
private void readXMLResult(InputStream is) throws IOException {
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(is, new DefaultHandler() {
private Vote cv;
private StringBuffer sb = new StringBuffer();
@Override
public void characters(char[] chars, int start, int length) throws SAXException {
sb.append(chars, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("title".equals(qName)) {
cv = voteResults.get(sb.toString());
} else if ("chosen".equals(qName)) {
if (cv != null)
cv.count = Integer.parseInt(sb.toString());
} else if ("entered".equals(qName)) {
if (cv != null)
choiceResults.get(cv.title).add(sb.toString());
}
}
@Override
public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs)
throws SAXException {
sb.setLength(0);
}
});
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
private boolean validate(String voteName, String choice) {
rwl.readLock().lock();
try {
if (voteResults.get(voteName) == null)
return false;
if (choice != null)
if (choiceResults.get(voteName) == null)
return false;
} finally {
rwl.readLock().unlock();
}
return true;
}
static public class Vote {
public String title;
public boolean fillable;
public int count;
@Override
public boolean equals(Object arg0) {
if (arg0 == this)
return true;
if (arg0 instanceof Vote == false)
return false;
Vote anotherVote = (Vote) arg0;
return title != null && title.equals(anotherVote.title) && fillable == anotherVote.fillable;
}
@Override
public int hashCode() {
if (title != null)
return title.hashCode() ^ (fillable ? 1 : 0);
return super.hashCode();
}
@Override
public String toString() {
return "Vote:" + title + " (" + fillable;
}
}
}
Note that the model is wrapped in servlet context listener interface, that gives benefits like being easy sharable between servlets of the same application, be prepared and ready to use prior servlet initialization. Aldan3 doesn’t dictate any particular approaches of models or other sharable code initialization; it can be done as in different servlet listeners, as in an overriding initialization part of Aldan3 servlet framework. Every approach has own benefits and decision should be done based on particular requirements or personal tastes. The model is thread friendly and uses readwrite locker offered by concurrency library of JDK 1.5 and above. Processing a poll definition data isn’t separated in a method, however it can be done in future.
An implementation of second service is a way simpler. It is reasonable to inherit it from Voter and override just few methods:
public class Result extends Voter {
@Override
protected Object doControl() {
throw new IllegalAccessError ("No control functionality provided by the service");
}
@Override
public boolean isPublic() {
return true; // allow public access, no authorization
}
@Override
protected String getSubmitPage() {
return getUnauthorizedPage();
}
@Override
protected String getUnauthorizedPage() {
throw new IllegalAccessError ("Check authorization mechanism settings");
}
public boolean isThreadFriendly() {
return false;
}
public String getPreferredServiceName() {
return "Result";
}
}
No voter control mechanism was discussed above in the design part so any voter can do a poll as many times as he/she/it wants. A cookie based state of voting can help here and it requires very small code changes. Aldan3 authorization mechanism can be used here. It can tell that Voter service isn’t accessible for users already performed a poll, here the changes are:
@Override
public boolean isPublic() {
return getCookie(COOKIE_NAME) == null;
}
@Override
protected String getUnauthorizedPage() {
return getSubmitPage();
}
A code to set cookie has to be added in doControl in case of a successful poll:
@Override
protected Object doControl() {
PollModel model = getPollModel();
String title;
model.addVote(title = getStringParameterValue("vote", null, 0));
if (title != null) {
model.addVoteChoice(title, getStringParameterValue(title, null, 0));
// setting a cookie to keep state of made poll
Cookie vc = new Cookie(COOKIE_NAME, title);
vc.setMaxAge(model.getPollDuration());
resp.addCookie(vc);
}
return null;
}
Note that there is no reason to keep a vote state forever, so cookie lifetime is set.
Now we can move to views implementation. Aldan3 template engine is very simplistic and allows iterative operations, switches, access to object public fields and method calls.
<div>
<div id="title">@model.getTitle*()*@</div>
<div id="error_ban"></div>
<form name="poll-@model.getTitle*()*@">
@model.isMultiSelectable*()*{
@true(@model.getVotes*()*(
<div id="vote_option"><input type="checkbox" name="vote" value="@element.title@">@element.title@
@element.fillable{
@true(<input type="text" name="@element.title@">)@
}@
</div>
)@
)@
@(@model.getVotes*()*(
<div id="vote_option"><input type="radio" name="vote" value="@element.title@">@element.title@
@element.fillable{
@true(<input type="text" name="@element.title@">)@
}@
</div>
)@
)@
}@
<input type=hidden name="submit.x" value="1">
<input type=button value="Vote" onclick="submitForm()"> <a href="Result">View result</a>
</form>
</div>
Constructions like @name(..)@ are used against collection model objects. A model object can be used in switches like @name{ @case1(..)@ @case2(..)@ @(..default..)@}@. Member’s access uses a dot-separated notation. Method calls uses ‘*(‘, ‘*)’ parenthesizes for inclosing a list of parameters. Aldan3 separates view and control requests based on “submit.x” request parameter. If it is presented, then the request is considered as control one, otherwise view one. The final implementation adds a JavaScript code for a poll data validation and styles. The view code of Result service is even simpler:
<div id="title">@model.getTitle*()*@</div>
<div>Poll result</div>
@javaarchitect.servlet.examples.poll.PollModel.calculatePercentage*(java.util.List^@model.getVotes*()*@)*(
<div id="vote_option">@element.title@ : @element.count@%
</div>
)@
Now time is to work on deployment descriptors and configuration data.
Deployment
Web.xml includes the following context init parameters used by the model:
<context-param>
<param-name>poll_url</param-name>
<param-value>/WEB-INF/classes/javaarchitect/servlet/examples/poll/resource/photo-feature-poll.xml</param-value>
<description>Defines poll definition XML file location</description>
</context-param>
<context-param>
<param-name>poll_result_file</param-name>
<param-value>./poll_result.xml</param-value>
<description>Defines poll result XML file location</description>
</context-param>
The parameter poll_url specifies a poll definition file location and generally it can be just a context related path. Another parameter poll_result_file specifies where a poll result is stored.
Servlet definition is straightforward:
<servlet>
<servlet-name>Poll</servlet-name>
<servlet-class>org.aldan3.servlet.Main</servlet-class>
<init-param>
<param-name>properties</param-name>
<param-value>/WEB-INF/config/poll.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Poll</servlet-name>
<url-pattern>/poll/*</url-pattern>
</servlet-mapping>
Aldan3 fullstack framework main class has to be specified as a servlet class. The class can be extended though. It takes one mandatory parameter properties. This parameter specifies location of Aldan3 configuration file.
Finally a model class has to be specified as a listener:
<listener>
<listener-class>javaarchitect.servlet.examples.poll.PollModel</listener-class>
</listener>
Aldan3 configuration file looks as below:
# UI services package prefix for Front Controller
WorkerPrefix=javaarchitect.servlet.examples.poll.
## Views HTML template path
TEMPLATEROOT=/javaarchitect/servlet/examples/poll/resource
# Default UI service name when not specified
DefaultServant=Voter
# Page generation char set
charset=KOI8-r
# Debug flag
DEBUG=0
It is a standard Java properties file. The property WorkerPrefix specifies a package prefix of UI services, so Front Controller can automatically resolve a full class name and call a respective service. The property TEMPLATEROOT makes sense only when Aldan3 template engine is used and specifies location of view template based implementations. The property DefaultServant gives a name of a default UI service. Properties charset and DEBUG give a char set name and a debug mode respectively.
All components of the poll web application have to be properly packaged in a .war file which can be deployed under any J2EE application server. An example of poll definition file can look like:
<?xml version="1.0"?>
<poll>
<title>Which is most important next feature of MediaCChest for iPod?</title>
<duration>20w</duration>
<type>singleselect</type>
<show-result>yes</show-result>
<vote>
<title>Photo library</title>
<fill-required>no</fill-required>
</vote>
<vote>
<title>Ripping music</title>
<fill-required>no</fill-required>
</vote>
<vote>
<title>Another feature, please specify</title>
<fill-required>yes</fill-required>
</vote>
</poll>
A complete source code of the illustration with 7Bee building script is supplied as the zip file.
Problems, Open Issues, and Limitations
There are several problems of this implementation. Most important that poll data are stored only at time of closing web application, so there is a risk of losing poll result in case of hardware failures. Another risk is of overflowing web container memory with too many choices entering for polls with custom entries. The current implementation just hardcoded no accept trigger at 1000 entries. And finally a robot not setting cookies can compromise poll result. As an idea of solution can be in maintaining a black list of IPs generating too many votes. Among of limitations you can notice a fixed cookie name, so no multiple polls can be run from the same host/URI. However one poll servlet can't provide multiple polls due the design. So every new poll has to use a different servlet with a distinctive access path that automatically resolves the cookie problem (no domain scope of a cookie).