Thursday, December 31, 2009

Java, IO read file

protected ArrayList getKmlFromFile(String filename) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(new FileReader(filename));

ArrayList lineList = new ArrayList();
String line = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith( "#" )) {
continue;
}

if (line.trim().equalsIgnoreCase("")) {
continue;
}

lineList.add( line );
}

reader.close();

return lineList;
}

ThreadUtils

Sometimes, we need to save variable to thread, following ThreadUtils is handy to use.

import java.util.HashMap;
import java.util.Map;


public class ThreadUtils {
private static final ThreadLocal sThreadLocalMap = new ThreadLocal();

/**
* Create a ThreadLocal map for storing ThreadLocal variables.
* HashMap is
*/
static {
sThreadLocalMap.set( new HashMap() );
}

/**
* Returns the ThreadLocal variable with the given key.
*
* @param key - unique key of the object.
*
* @return object from given key
*/
public static Object getThreadLocal( final String key )
{
if ( sThreadLocalMap == null || sThreadLocalMap.get() == null ) {
return null;
}


return ((Map)sThreadLocalMap.get()).get( key );
}

/**
* Sets the given object to the Map with the given key.
*
* @param key - unique key of the object
* @param object - object
*/
public static void setThreadLocal( final String key, final Object object )
{
Map map = (Map)sThreadLocalMap.get();

if ( map == null ) {
map = new HashMap();
sThreadLocalMap.set( map );
}

map.put( key, object );
}

/**
* Removes the ThreadLocal variable from the map with the given key.
*
* @param key - key of the variable to remove.
*/
public static void removeThreadLocal( final String key )
{
if ( sThreadLocalMap == null || sThreadLocalMap.get() == null ) {
return;
}

((Map)sThreadLocalMap.get()).remove( key );
}

}

Wednesday, December 23, 2009

GWT SEO Title, meta description and keywords

GWT 2.0 provides a way of doing SEO by using HTMLUnit. Once GWT module is downloaded, the page is not refreshed, so title, meta description are not changed.

However, the page is changed with different token.

SEO required unique page tile, meta description and meta keywords (even though, it is said meta keywords are not used by Search engine anymore)

Here is the approach to change the page title, description and keyword with GWT.
1) Once get page history token, then get resource bundle content based on token, locale.
2) Create title, description string based on resource content and business objects if apply.

For title:
Window.setTitle(title);

For description:
final NodeList nodeList = Document.get().getElementsByTagName("meta");
rewriteMetaContent("description", description), nodeList);

private void rewriteMetaContent(
final String key, final String value, NodeList nodeList) {

if (nodeList == null) {
return;
}

for (int i=0; i Element element = nodeList.getItem(i);
if (key.equalsIgnoreCase(element.getAttribute("name"))) {
element.setAttribute("content", value);
return;
}
}
}

One thing needs to point out: SEO looks for DOM entry, not view source.
So if your HTMLUnit version and end user version of title, description are matched, it is good. Otherwise it is cloaking. Be careful for that.


Wednesday, December 2, 2009

Story of IHG GWT

In the following posts, I will write down the experience of using GWT, such as good/bad part of GWT, architecture/design decisions and challenges we were facing with as well as some trick/nice functions.



Last summer after I finished the task of fixing production memory leak, our manager Steve talked to me about new billion dollar project of relanuching of HolidayInn(HI) hotels worldwide. As development team, we needed to provide rich application for the new site to attract more users and increase conversion rate. At that moment, GWT came to our mind. A new team was created and most developers sit in the one big room (it is call HI war room). In the next two weeks, we went through and practiced most the functions of GWT.



GWT version: 1.5.3

Java JDK version: 1.5



Followings were what we come up with

The good parts of GWT are:

  • Outstanding user experience
  • RPC object based instead of text based like tradition AJAX.

  • Ease of development increase productivity with Java for JavaScript instead of pure JavaScript. Developer can write code and run/debug in host mode.

  • Performance is promising for some functions such as sorting executed in user's browser so there is not network traffic.

  • Support multi-browser.
  • No issue with scalability.

  • GWT supports standard accessibility guidelines
  • Debug client code in host mode.
The parts need workaround

  • Form based page, as lack of introspection, there is not form to object and object to form mapping capability such as Spring MVC provided. There were some third party packages addressing this, but none of them seems good enough for us to use. So we decided to use simple approach of using widget function to get/set the value to/from form bean object.
  • Could not change the http schema. Login page needed to post as secure https, per origin policy of GWT, we could not change the schema from http to https. So we decided to use tradition form for login page reside in JSP.
  • Learning curve for UI (css) developer as lack of the experience of Java.
After few meetings and discussions, a decision was made to bring GWT into our develop platform.



At the same time, Agile methodology was adopted for the development. During the next few months, we have standing meeting every morning. Development and demo is iterated every three weeks.



Change History:

Author: Michael Wang, initial draft

Story of IHG GWT -- URL Structure

As SEO becomes more and more important, several meetings between develop team and business team had be held to finalize the URL.



For reservation:

http://www.holidayinn.com/hotels/us/en/reservation



For hotel detail:

http://www.holidayinn.com/hotels/us/en/atlca/hoteldetail

where atlca is hotel code



keyword "hotels" is replaced in the URL, the first place after domain, to increase the visibility for SEO.



us/en indicate the site for US user,

gb/en is for UK user

fr/fr is for French



We support 11 languages, but for the beta launch, only gb and us are provided.



Change History:

Author: Michael Wang, initial draft

Story of IHG GWT -- Execution Flow

After finalized URL pattern, we went to the phase to create prototype and architecture.

Following is the high level integrate chart for IHG GWT client and IHG backend servers.
Client side is GWT and backend is Spring MVC.




Flow of execution:
  1. Once user type in a url such as http://www.holidayinn.com/hi/us/en/atlca/hoteldetail, Spring DispatchServlet will be invoked and URLMappingInterceptor will be executed.
  2. URLMappingInterceptor will parse the URL to get region, brand, language and other env information (such as contextPath, URI etc), convert env to JSON object, save JSON object to request variable. Then flow will go to the Controller.
  3. The Controller will handle the request, validate request, session manager, call Business Object and forward control to JSP.
  4. In JSP, the request scope JSON object will be assigned to Javascript variable. The control will be back to browser.
  5. Browser will execute GWT nocache.js embedded through JSP and GWT Engine will be invoked.
  6. GWT Engine will extract and parse configuration xml file through navigation manager
  7. The page is rendered and displayed to the end user.

Navigation manager responsible for:
  • Create navigation flow to history listener.
  • Get populators through one RPC call and populate return values to widgets
  • Display resource bundle content with ResouceBundleWidget.
  • Use DayContentWidget to collect day content inputs and set to HashMap, then a single RPC call will be invoked to get all day contents.
  • DayContentWidget will re-populate day content value to user.
Terms used:
  • Day is content management tool IHG used. The content can be changed dynamically with Day author.
  • populator is internal term used for dropdown and/or check box list items, such as "Rate Preference". dropdown list.
  • navigation manager is feature we create to read/parse xml file with defer binding and used to manage navigation as well as populator and day content.


Change History:

Author: Michael Wang, initial draft



Story of IHG GWT -- Architecture - Configurable Navigation Manager

Each module has its own config file. It is loaded and parsed with GWT defer binding.

The pupose of having config file is:
  • to increases the productive, as developer is able to focus on business logic rather than navigation control and common function such as populators.
  • to redirect in server side to navigate to specific token page.
Take reservation as example.

ReservationProcessConfig.xml has token view and its implementation. In addition to that, other features such as populators are defined within the token view section.

After GWT.create(ReservationProcessConfig.class); the list of views (history token) is saved in memory. Once token changed, onHistoryChanged is invoked and particular view is instantiated. The functions related to that view are performed such as RPC call to get Day content, and RPC call to get populator

The lazy instantiation for each view is applied to help to load view quickly.

Sample of config file:
<?xml version="1.0" encoding="UTF-8"?>
<application>
<views>
<!-- home -->
<view name="home" class="com.ihg.reservation.client.navigation.HomeView">
<properties>
<property name="populators">
<map>
<entry key="roomPreferencePopulator">com.ihg.populators.RoomPreferencePopulator </entry>
<entry key="brandsPopulator">com.ihg.populators.BrandsPopulator</entry>
</map>
</property>
</properties>
</view>

<!-- roomrate -->
<view name="roomrate" class="com.ihg.reservation.client.navigation.RoomRateView">
</view>
</views>
</application>


Change History:
Author:
Michael Wang, initial draft

Story of IHG GWT -- Other Architecture Consideration

Few considerations specific to GWT app had been applied since the beginning of the project:
  • Multiple modules, to reduce the down load file size. Two major modules were created, one for reservation and one for hotel detail.
  • Remote logging
  • DTO to reduce the object size. Whenever we got backend object, we convert it to client side object. Client object only contains the attributes used in the page.

Change History:

Author: Michael Wang, initial draft

Story of IHG GWT - Architecture - Code Reuse

For the page with form input, we create pattern with following components:
  • formEntry, define all form entities, like firstName, lastName text boxes. The responsible is to fill the object to widget as well as to get value from widget, handle click or other event, call RPC etc.
  • displayWidget, responsible for putting form entity such as firstName to the correct location.
  • validate responsible for validating the form input.
  • view, which is defined in the config xml file, is the starting point to tie formEntry and dispalyWidget together.
Benefit of the pattern:
  • to separate developer responsibilities, UI (CSS) developer is able to only deal with displayWidget to display the page as required. While Java developer only works on formEntry/Validation with business logic.
  • to easily change the layout of the component. For example, in the attached graphic for form section, three different layouts of quick search form are required, the function is the same. By providing different displayWidget, it is easy to change the layout.




Page views:
http://www.holidayinn.com/hotels/us/en/atlib/hoteldetail
http://www.holidayinn.com/hotels/us/en/atlib/hoteldetail#Directions
http://www.holidayinn.com/hotels/us/en/atlib/hoteldetail#Hotel-Room-Rates

Change History:
Author:
Michael Wang, initial draft

Story of IHG GWT – Pass data from server to client

There are two kinds of data which are needed to pass from server to client when page is loaded.

One is environment parameters and one for logged in Priority Club Rewards (PCR) user.

Environment parameters are the collection of variables from server settings and user browser info. Language id, country id, date format, cookieDomain name are the example of them.

PCR object contains the information such as login status(anonymous
,implicitly, explicitly), number of points etc for the user who accesses our web site.

Both data are passed through JSP variable and set to Javascript variables, then client will read those JavaScript variables. Those variables can be view with View Page Source with the broswer. Followings are the code example.

Environment parameters:
For env object, JSON is applied to it in the consideration that the value of env will be used in CSS.

In server side of Spring interceptor, convert env object named appMappingElem to Json, then save to request.
JSONObject jsonEnv = JSONObject.fromObject( appMappingElem );
request.setAttribute( ENV_JSON, jsonEnv );


In JSP header Javascript section
var envJson = '${envJson}';

In GW T client Java code:
public static native String getEnvJson() /*-{
return $wnd.envJson;
}-*/;

public static UrlMappingElem buildMappingElem() {
final String envJson = getEnvJson();
final JSONObject jsonObj = (JSONObject)JSONParser.parse(envJson);

final UrlMappingElem urlMappingElem = new UrlMappingElem();

if (jsonObj.get("brand") != null) {
urlMappingElem.setBrand( getStringValue(jsonObj.get("brand"))) ;
}

if (jsonObj.get("languageId") != null) {
urlMappingElem.setLanguageId( getStringValue(jsonObj.get("languageId"))) ;
}
}

PCR object:
RPC utility class is used to impose PCR profile object to Javascript variable through JSP. Other than JSON, the PCR profile object is constructed directly from serialized data.

In server side:
String serizlizedString = RPC.encodeResponseForSuccess( PCRProfileService.class.getDeclaredMethod("getPCRProfileBean" ), profileBean);
request.setAttribute(PCR_PROFILE_SESSION_SERIALIZED_KEY, serizlizedString );

In JSP:
var pcrSerializProfile = '${pcrSerializProfile}';

In GWT client side:
SerializationStreamFactory factory = (SerializationStreamFactory)GWT.create( PCRProfileService.class );
SerializationStreamReader reader;

reader = factory.createStreamReader( serializedOnServer );
PCRProfileBean pcrProfileBean = (PCRProfileBean)reader.readObject();


Change History:
Author:
Michael Wang, initial draft

GWT - Form auto fill

Google AutoFill and Roboform will remember your form input, so you can click Autofill to get the form auto filled.

Auto fill is based on html form, that is
<form
...
</form>

For GWT simple panel, the form submit is not bind with <form> tag, so Autofill is not working.

Two ways to fix it.
1) use FormPanel and add form widget to it. FormPanel will create <form> automatically.
2) add <form> starting tag and close tag </form> manually.

Tuesday, December 1, 2009

Story of IHG GWT - Session backup

In the reservation process, there is a navigation bar to indicate which steps (search result, room rate, guest info) the user pass through. When user refreshes the page or comes back to reservation process from other module, the page is reloaded, at that moment, all data are gone.

A mechanism is built to restore those data from server session. The logic is straight forward.

Once a reservation RPC is call, such as search for hotel, the user's input object and search result object are saved into server Http session. If for particular page, the data to render the page is not available, a session back up RPC is invoked to retrieve the data for that page from the server. If data still not available, then redirect to home page, otherwise render the page with retrieved the data.

Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue - load Google map key on demand

We used to load Google Map key with JavaScirpt embed in JSP.
The Google map is used in the second page (search result page) once user click map view.

The speed is highly depended on network condition and Google map server. Sometime, it would slow down the page load.

We do not like to slow down the speed to load home page.
So we use Google AjaxLoader to load Google Map Key on demand on hotel search result page.

Following is the code example:
boolean isSecure = assign the value based on whether the request is secure or not.

AjaxLoader.init(null, isSecure);

AjaxLoaderOptions options = AjaxLoaderOptions.newInstance();
options.setOtherParms("sensor=false&client=ourclientid&oe=utf-8");

AjaxLoader.loadApi("maps", "2", new Runnable() {
public void run() {
mapsLoaded();
}
}, options);

Where mapsLoaded() is call back function to display Google map with customized token.

Note: There is an issue with secure request, IE will pop up secure/non-secure warning.
I posted workaround with http://code.google.com/p/gwt-google-apis/issues/detail?id=283


Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues

Before we were going to celebrate to push the code into Unix box successfully, we fail the load test. We had performance issues as well as other issues:

1) For Internet Explore, the home page and guest information page (the page get customer personal info, firstname, lastname) took over one minute to load.
2) Login took long time, sometime took over 2 minutes.
3) IE6 gave secure/non-secure warning for secure (https) page.
4) Out of memory
5) Timezone issue, the tester in UK got rate for the day before.
6) FGC took long time.
7) Page was not loaded for IE8.
8) YSlow score was low.

In the following post, I will address above issues.

Change History:
Author:
Michael Wang, initial draft

Story of IHG GWT - Performance issues continue - Timezone issue

The issue:
when a java.util.Date is sent via an RPC from another time zone than the server, the date changes on the server due to the server interpreting the long sent via RPC from the client into
its own time zone.

Currently, we pushed in a temp fix with the solution provided in the below link.
http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/fd7b80de11507908/43363d713487a0f2?lnk=gst&q=date+serialize#43363d713487a0f2

Also, we noticed that there is API in GWT 1.6 with timezone function.
http://google-web-toolkit.googlecode.com/svn/javadoc/1.6/com/google/gwt/i18n/client/TimeZone.html

We pretty sure, we will come up final solution after we upgrade to GWT 1.7

Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue -Page was not loaded for IE8

GWT 1.5.3 is not working well with IE 8, some pages are not fully loaded.

A temperate solution is to add meta data in JSP to tread it as IE7.

<!-- Mimic Internet Explorer 7 -->
<meta equiv="X-UA-Compatible" content="IE=EmulateIE7">

Once we upgrade to GWT 1.7, we sure this will solve IE8 problem.

Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue -FGC took long time

During the load test, we found out it took minutes for Full Garbage Collection.

After the search for the solution in the internet, we found out that -Xmn can be used to determine the space of young and old. So I adjusted the value of -Xmn several times, the FGC time is fine now.

Good reference of tuning up.http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue - IE6 secure/non-secure warning

Found interesting thing with this issue: most developers use Firefox for development while test engineers are using IE for test.

So the problem was skipped from developers and found by our tester.

The problem is easy to identify and solution is easy to come up with. The iframe for GWT history needs to provide empty html file for src attribute.



Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue - Login took long time

It happened when pre-loading test in production environment, as the data set in production is much larger than test environment.

The solution is kind of simple, apply index in Oracle database table and add some cache.


Change History:
Author: Michael Wang, initial draft

Story of IHG GWT - Performance issues continue - fixing long time to load

No surprise, we had the similar issue as some other folks got. The performance was bad in test environment, even though we already paid attention during the development such as using DTO, loading contents with single RPC call, lazy loading of view, using multiple modules etc.



Few refectories were done to enhance the performance:

  • Reduce object size further and only load necessary objects.
  • Apply gzip to compress the download file traveling through the network.
  • Group third party Javascripts and loaded them at the bottom of the page.
  • Remove the Javascript of downloading Google map key from home page. And load Google map key on demand when user selects to view map function.
  • Fix the out of memory issue.
  • In server side, make backend call for content with multi-thread.
With those changes, the home page is loaded pretty quickly now. At same time, Yslow socpe is up too.



Change History:

Author:
Michael Wang, initial draft