Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Sunday, November 9, 2014

JNLP BasicService, PersistenceService, and Desktop Muffins

Java provides JNLP downloaded desktop applications with a mechanism to record client-side data called "muffins".  Like their web counterparts "cookies", muffins are intended to store small amounts of data to personalize the application.  While the desktop app may prefer to store all information on the server, muffin saving code is orders of magnitude faster than even a speedy web service or RMI call.


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.

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);
 } 

With the muffin created, you can get a handle to the FileContents.

        FileContents fc = ps.get(codebase); 
    InputStream is = null;
Properties properties = new Properties();
     is = fc.getInputStream(); 
    properties.load( is );
     is.close();

I'm using a Properties object for the structure of my muffin.  That's optional.  Anything -- objects, text, binary -- can be put in FileContents.  I prefer Properties because it's human-readable and the serialization code (load/store) is simple.

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();


The result of the create and OutputStream code is a pair of files in the muffin folder.  These are Oracle Java 8 internals, so it's subject to change it other JDKs.

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.

1 comment:

  1. This article is a gem! I appreciate the detailed research you put into it. It’s refreshing to see such thoroughness!

    ReplyDelete