If you're distributing your desktop application via Web Start, you inherit an execution context containing the useful services BasicService and PersistenceService. A Web Start client interacts with this context and the download source server using the Java Network Launch Protocol (JNLP). The JNLP API is the set of interfaces your client programs when it needs to figure out which server it came (BasicService) from or save information to the file system (PersistenceService).
BasicService
There isn't much to the BasicService, but it will be of great value to you if you're using the JNLP Download Servlet. BasicService provides you with the way to extract the value of $$codebase, say for a server-side call back for a RESTful web service. This makes your JNLP file portable across servers for different environments or different customers.
To use both the BasicService and the PersistenceService, use a static lookup call on ServiceManager.
PersistenceService ps;
BasicService bs;
public void init() {
try {
ps = (PersistenceService)ServiceManager.lookup("javax.jnlp.PersistenceService");
bs = (BasicService)ServiceManager.lookup("javax.jnlp.BasicService");
} catch (UnavailableServiceException e) {
log.error( "cannot init persistence and basic services", e );
}
}
If available, the following call will return a URL of the codebase.
URL codebase = bs.getCodeBase();
So from this JNLP
<jnlp spec="6.0" codebase="http://www.bekwam.com/mpu" href="http://www.bekwam.com/mpu/mpu-test.jnlp" version="1.2.0">
You'd get
http://www.bekwam.com/mpu
BasicService also let's you check an offline flag and determine if a browser is available. For this post, BasicService will provide a codebase parameter for use in the PersistenceService.
PersistenceService
Muffins -- a term from Oracle -- stores files on the client. For example, my PersistenceService calls add files to the /Users/carlwalker/Library/Application Support/Oracle/Java/Deployment/cache/6.0/muffin folder on my Mac. While it's more rigorous to maintain a profile record on the server-side, it's much quicker to work locally. Recently, I timed 1,000 calls to a Muffin-based DAO. This took 1,726 ms. A well-performing RESTful service that I'm working with took 400ms for a single call.
These are informal numbers, but they indicate a huge performance boost.
These are informal numbers, but they indicate a huge performance boost.
Codebase
Muffins are organized by URL and driven off the codebase. Security dictates that your app can only get to the muffins stored in the codebase and derivatives. This allows for inter-app sharing of information. For example, the app with the codebase http://www.bekwam.com/mpu can get the muffins for http://www.bekwam.com/mpu, http://www.bekwam.com/mpu/app1, and http://www.bekwam.com/mpu/app2.
Tags
Additionally, muffins are tagged as TEMPORARY, CACHED, or DIRTY. CACHED and DIRTY are used to maintain a relationship between the local muffin and a server-side resource. You can modify and save the muffin in performance-critical code, flag it as DIRTY, then do the server-side update when convenient (and resetting to CACHED). TEMPORARY indicates that this muffin can be recreated without regard for the server. This post uses the TEMPORARY tag.
Create, Read, Write
To create the muffin, use the create() method optionally followed by the setTag() method. This code has added protection to prevent creating more than one muffin.
String [] muffins = ps.getNames(codebase);
if( muffins == null || muffins.length == 0 ) {
ps.create(codebase, FC_MAX_SIZE);
ps.setTag(codebase, PersistenceService.TEMPORARY);
}
FileContents fc = ps.get(codebase);
InputStream is = null;
Properties properties = new Properties();
is = fc.getInputStream();
properties.load( is );
is.close();
To write to the store, you get an OutputStream. As with my input code, I'm serializing to a Properties object.
OutputStream os = null;
Properties properties = new Properties();
os = fc.getOutputStream(true); // true -> overwrite
properties.put(key, value);
properties.store( os, comment );
os.close();
Carls-Mac-mini:6.0 carlwalker$ ls -al muffin
total 40
drwxr-xr-x 5 carlwalker staff 170 Nov 9 11:59 .
drwxr-xr-x 75 carlwalker staff 2550 Nov 8 20:46 ..
-rw-r--r--@ 1 carlwalker staff 10244 Nov 9 11:59 .DS_Store
-rw-r--r-- 1 carlwalker staff 123 Nov 9 11:59 7745b91f-3a43b6d8
-rw-r--r-- 1 carlwalker staff 36 Nov 9 11:59 7745b91f-3a43b6d8.muf
Carls-Mac-mini:6.0 carlwalker$
And the contents
Carls-Mac-mini:6.0 carlwalker$ cat muffin/7745b91f-3a43b6d8
#jnlp muffins from mpu app
#Sun Nov 09 11:59:37 EST 2014
favoritesCSV=/Users/carlwalker/Desktop/git/mavenpomupdater-repos1
With a hundred-fold increase in saving speed, you can record a user preference with a muffin quicker than a server-side call. This mechanism works off-the-shelf, so it's better than writing your own Java IO code for use in the desktop. Because of the potential for the data to be out-of-sync with the server or completely wiped out by the clients Java system config screens, I don't recommend this for critical information. However, if you have to store a security token, a list of recently selected items, or a preferred stylesheet, muffins can keep your app performant and personalized.
This article is a gem! I appreciate the detailed research you put into it. It’s refreshing to see such thoroughness!
ReplyDelete