Thursday Mar 12, 2009
Thursday Mar 12, 2009
We just announced that we no longer support JDK 1.4 in JAXP standalone builds. The main motivation was that we found such requirement made it hard to maintain a synchronized source with JDK6. Recently, we have made some performance improvements which will be in the jdk6 update 14 release. Due to the requirement for supporting JDK 1.4 however, we couldn't put back the same changes we made in jdk6 since features such as generics, StringBuilder were not available in JDK 1.4.
Another consideration was that J2SE 1.4.2 has reached its End of Service Life (EOSL) on October 30th, 2008. It is no longer necessary for JAXP standalone to support JDK 1.4.
So download JAXP 1.4 Weekly or Nightly and give it a test. Our performance team's tests using SPECjvm2007/SPECjvm2008 have shown tremedous performance improvement in certain areas. Besides, there have also been critical fixes such as 6536111 SAX parser throws OutOfMemoryError and 6506304 java.net.MalformedURLException: unknown protoco: c which have "earned" many votes and comments demanding a fix on SDN. These fixes will also be included in JDK6 update 14.
Monday Mar 09, 2009
It's always fun to try something new. In the midst of a relatively quiet period in the JAXP project, I had the opportunity to try out the Sun GlassFish Mobility Platform (SGMP) and create my very first mobile application! To me, it has been nothing but fun!
To me, both the SGMP technology and the UI library LWUIT were new. I expected some learning curve. Surprisingly though, the SGMP client API has made it very easy to develop a client application. In all, I found I spent more time trying to twike the look and feels, including customizations over LWUIT implementation. In this and the next blog, I'll share the experiences in implementing the SGMP client API, and using LWUIT to build mobile applications on generic devices as well as Blackberry.
The application I created is a client for the Sun GlassFish Mobility Platform 1.1. It uses the Salesforce connector Rebecca designed to access account on salesforce developer site and demonstrate the ability to manipulate data from a public services such as SalesForce.com developer site via SGMP. The application is included in the SGMP download. See Rebecca's blog on how to install and set up for a test run.
The first design decision is the data model, or in SGMP term, BusinessObject (BO). A business object has a pair (name, blob). Here the name is defined as the unique identifier of the data object. If you have read Rebecca's blog, you know that the Salesforce connector handles a small set of data from the Salesforce developer site, e.g. Account, and Contacts and Tasks for each account, which is basically the first tab in the Salesforce page. It is easy to see that we can use the unique Account ID as the name of the Account object so that the pair looks like (Account ID, Account record). It's slightly tricky for Contacts and Tasks because the API does not currently support relationship. To guarantee the uniqueness, we needed to combine the Account ID and Contact ID or Task ID. See Rebecca's blog on how the BOs were designed.
Once this is decided, the implementation is easy. The BO interface only requires that get/setName are preserved since they meant to get and set the name of the object, and implement the serialize and deserialize methods. This part of work was originally done in the connector, so here we just need to copy the portion of the code from corresponding object classes. The benefit of the copy-n-paste is that it guarantees the contract between the client and connector. The rest of the BO class is up to the needs by the client application itself that includes the getter and setter methods for each field, and methods, in the case of Account, to handle relationships such as adding and removing tasks. Below is a snapshot of the Account class:
public class Account extends BusinessObject2 {
...
public Account(String name, String acctName) {
super(name, acctName);
}
public String getAcctName() {
return _acctName;
}
public void setAcctName(String name) {
setDisplayName(name);
this._acctName = name;
}
/**
* add a task and set it as current
* @param task
*/
public void addTask(Task task) {
if (_tasks == null) {
_tasks = new Vector();
}
_tasks.addElement(task);
setCurrentTaskIndex(_tasks.size() - 1);
}
/**
* Mark task (index) as removed
* @param index
*/
public void removeTask(int index) {
Task task = (Task)_tasks.elementAt(index);
_tasks.removeElementAt(index);
_currentTaskIndex = TASK_NONE;
if (_tasks.size() == 0) {
_tasks = null;
}
_storage.deleteObject(task);
}
public byte[] serialize() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dOut = new DataOutputStream(out);
dOut.writeUTF(Util.serializeChkNullString(_id));
dOut.writeUTF(Util.serializeChkNullString(_acctNo));
dOut.writeUTF(Util.serializeChkNullString(_acctName));
...
dOut.flush();
return out.toByteArray();
}
public void deserialize(byte[] array) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(array);
DataInputStream dIn = new DataInputStream(in);
_id = Util.deserializeChkNullString(dIn.readUTF());
_acctNo = Util.deserializeChkNullString(dIn.readUTF());
_acctName = Util.deserializeChkNullString(dIn.readUTF());
...
_tasks = null;
_contacts = null;
}
}
Once you have your data classes ready, the next thing needs to be done is to configure the synchronization engine to perform sync. The SGMP Sync engine supports six different type of synchronizations. The most commonly used one is the so-called FAST_SYNC which can also be named incremental sync. Fast sync checks both client and server and exchanges only modified data to bring the client and server in sync. In an application therefore, it should be selected as default normally.
In the Salesforce sample application, we provided the ability to clean up local storage so that users may remove the application without worrying about leaving trash data behind. The deletion would obviously break the synchronized status since it was not meant to delete the data on the backend. In case the application may be installed and run again, the synchronization must be done to restore the previous status. The sample application detects an initial sync and does a RESTORE_SYNC to bring over data from server, thus restore previous status. Here's the code:
_syncMgr.setCredentials(_config.getUsername(),_config.getPassword() + _config.getKey(), _config.getServerURL());
if (_config.isFirstSync()) {
_syncMgr.sync(SyncType.RESTORE_SYNC);
_config.setFirstSync(false);
} else {
_syncMgr.sync(getSelectedSyncType(_config.getSyncType()));
}
Another factor worth noting is that a fast sync only exchanges modification once. In the case of creating new record, an additional sync may be necessary. In the sample application for example, when there is a new task on the client, a fast sync would cause the server to create a new record. However, the client would have no way to know that the backend has added the record with a different key, therefore, the client and server are not completely in sync. The simplest way to handle the issue is to do another sync as we did. However, you have a smarter way to solve the problem without the need for the additional (immediate) sync. I'll leave that to you
The next task after synchronization is reading the data. The sync engine provides a mechanism called BusinessObjectStorage which returns a storage engine for use to read and write data. As stated above, the current implementation uses the file system and stores BOs as files. To avoid reading all of the files, the sample application makes use of naming convention to avoid reading files unnecessarily.
The sample application first returns names of all of the BOs and then parses through the list to find Account object to read, and put the Contact and Task objects in a cache. The code looks like this (actual code are in different classes):
boStorage = syncMgr.getBusinessObjectStorage(); //return storage engine
Vector files = boStorage.getNames(); //return a complete list of BOs
for (int i = 0; i < files.size(); i++) {
String tmpN = (String) files.elementAt(i);
//- strip file extentions
String fName = tmpN.substring(0, tmpN.length() - 4);
String accountId = fName.substring(0, Account.ID_LENGTH); //by the naming convention, name of Account Object starts with Account ID
Account account = (Account) _accounts.get(accountId);
if (account == null) {
//- found an account by either a task or contact, parent object does not exist yet. Create it. The parent will be found later.
account = new Account(accountId, _storage);
_accounts.put(accountId, account);
}
if (fName.length() > 20) { //either contact or task
int loc = fName.indexOf(Task.FILE_PREFIX);
if (loc > -1) { //task
account.addTaskFilename(fName); //- Just put in list; process array contents on 1st user selection of account
} else { //contact
account.addContactFilename(fName);
}
} else {
// an account, read it now
_storage.readObject(account);
_accountNames.addElement(account.getAcctName());
_accountIds.addElement(accountId);
}
}
This is all that's related to the SGMP client API. The rest is to develop a proper interface to present the data. As you see, the SGMP client API is very easy to use. I actually ended up spending most of the time learning and customizing LWUIT to design the user interface. I will share some of that experience in my next blog.