Saturday, August 1, 2009

Creating Google Maps with Google Maps Data API

Google Maps Data API allows creating maps programmatically.
Using JavaScript and Ruby I extracted data from Wikipedia page List of cities proper by population and created Google map from this data.
Google Geocoding Service was used to retrieve coordinates of the cities.

Click on the image below to open the map and see 61 the most populous cities of the world.

Cities by Population Google Map

Friday, July 24, 2009

Parallel flows in Spring Web Flow and Struts2

Investigated issue with struts2webflow plugin for Struts2.
When SessionFlowExecKeyInterceptor is used for managing "flowExecutionKey" it should have different sessioinKey for each flow in order to make parallel flows possible.

Here is how this can be done.

1. Define SessionFlowExecKeyInterceptor with unique sessionKey for each flow.

<interceptor name="customSessionFlowExecKey" class="com.googlecode.struts2webflow.SessionFlowExecKeyInterceptor">
    <param name="sessionKey">SessionFlowExecKeyInterceptor.CUSTOM_FLOW_SESSION_KEY</param>
</interceptor>


2. Set this interceptor to the flow action.

<action name="customAction" class="com.googlecode.struts2webflow.FlowAction">
    <interceptor-ref name="customSessionFlowExecKey"/>
    ...
</action>


This issue is also described in struts2webflow here.

Thursday, July 23, 2009

Extracting data from web pages with JavaScript

Here is example how to extract data from Wikipedia by executing custom JavaScript on a page.

Create script customscript.js.

// this script converts table on a Wikipedia page to plain text
// and shows this text in a new window, works in Firefox
var cellDelimiter = "|";
var rowDelimiter = "\n";
var table = document.getElementById('sortable_table_id_0');

var content = "";
var rows = table.rows;
for (var i = 0; i < rows.length; i++) {
    var cells = rows[i].cells;
    var cellArray = [];
    for (var j = 0; j < cells.length; j++) {
        var cellText = cells[j].textContent.replace(/^\s+|\s+$/g, ""); // trim
        cellArray.push(cellText);
    }
    content += cellArray.join(cellDelimiter) + rowDelimiter;
}

var out = window.open().document;
out.open("text/plain");
out.write(content);
out.close();


Call this script with bookmarklet.

javascript:void(s=document.createElement('script'));
void(s.src='http://localhost:8080/webapp/scripts/customscript.js');
void(document.body.appendChild(s));

Here script is loaded from server which runs locally. It could also be loaded from local file system, but special preferences should be set in Firefox to allow this.

Results for List of national capitals page:

City|Country
Abu Dhabi|United Arab Emirates
Abuja|Nigeria
Accra|Ghana
Adamstown|Pitcairn Islands
Addis Ababa|Ethiopia
Algiers|Algeria
Alofi|Niue
Amman|Jordan
Amsterdam|Netherlands (official)
Andorra la Vella|Andorra
Ankara|Turkey
...

Sunday, July 19, 2009

Spring, Transactions and Serialization

Have investigated interesting Spring serialization issue in clustered environment. NotSerializableException was thrown during session externalization.

Here is the reason.

Spring, Transactions and Serialization

One Spring session bean has reference to another Spring bean for which declarative transaction management is applied. In fact this reference is reference to Spring AOP proxy which drives transactions. As described in Spring issue SPR-4662 this proxy cannot be serialized when HibernateTransactionManager is used.

This issue was fixed in Spring 3.0 RC1.
In earlier versions the simplest solutions is to eliminate serialization of Spring transactional beans. To solve this problem we just replaced the Spring session bean with alternative implementation.

Original exception thrown:

ERROR [org.jboss.web.tomcat.tc5.session.JBossCacheService] externalizeSession(): exception occurred externalizing session SessionBasedClusteredSession[...]
java.io.NotSerializableException: org.springframework.beans.factory.support.DefaultListableBeanFactory
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1081)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1375)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1347)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1290)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1079)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
at org.springframework.transaction.interceptor.TransactionInterceptor.writeObject(TransactionInterceptor.java:186)
...
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
at java.util.Hashtable.writeObject(Hashtable.java:813)
...
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
at org.jboss.web.tomcat.tc5.session.SessionBasedClusteredSession.writeExternal(SessionBasedClusteredSession.java:175)
at org.jboss.web.tomcat.tc5.session.JBossCacheService.externalizeSession(JBossCacheService.java:1023)
at org.jboss.web.tomcat.tc5.session.JBossCacheService.putSession(JBossCacheService.java:312)
at org.jboss.web.tomcat.tc5.session.JBossCacheClusteredSession.processSessionRepl(JBossCacheClusteredSession.java:121)
at org.jboss.web.tomcat.tc5.session.JBossCacheManager.processSessionRepl(JBossCacheManager.java:1093)
at org.jboss.web.tomcat.tc5.session.JBossCacheManager.storeSession(JBossCacheManager.java:648)
...

Sunday, June 21, 2009

JavaOne Search

Developed simple application with Google Web Toolkit (GWT) and Google App Engine.
It makes searching for JavaOne 2009 sessions easier.

Available at http://javaonesearch.appspot.com/.

JavaOne sessions http://developers.sun.com/learning/javaoneonline/.

Java Double Brace Initialization

Double brace initialization in Java is not a widely used feature, but sometimes it can be quite useful. Its usage in jMock (Java testing library) inspired me to apply this construct to some test scenarios.

Usual simple approach to test invalid data:
Data data;

data = createValidData();
data.setFieldOne("invalid value one");
testInvalid(data);

data = createValidData();
data.setFieldTwo("invalid value two");
testInvalid(data);

// do the same for other 100+ cases

And now more concise solution using double brace initialization:
testInvalid(new DataHolder() {{ data().setFieldOne("invalid value one"); }});
testInvalid(new DataHolder() {{ data().setFieldTwo("invalid value two"); }});

DataHolder class:
private static class DataHolder {
private Data data = createValidData();

public Data data() {
return data;
}
}