Stocking Content Under the JSR-124 API

The API for stocking content is simple. The process consists of 1) obtaining the ProvisioningContext; 2) getting the BundleRepository; 3) getting the InputStream of the PAR file and 4) invoking the addParMethod. After that it is up to the BundleRepository to store (or cache) the catalog information and binaries.

public void doPost(HttpServletRequest request, HttpServletResponse response) {
    ProvisioningContext provisioningContext =
        (ProvisioningContext) this.getServletContext()
            .getAttribute("javax.provisioning.ProvisioningContext");
    ....logic for obtaining the InputStream of the PAR file......
    BundleRepository repository = provisioningContext.getBundleRepository();
    repository.addParFile(inputStream);
}
            

The problem is that there is no standard way for handling preprocessing of the PAR file. Say we want to remove JNLP files, add DRM instrumentation or even block content larger than 100KB.

Stocking Content under JVending

JVending introduces a stocking framework with the notion of a StockingHandler. The stocking framework loads the stocking-handlers.xml file and sets up a list of StockingFilters and a DataSink, which is reponsible for ultimately stocking the content. The config file also defines a number of stocking policies that are used for providing information such as maximum stocking size, allowed content types, etc. Using the StockingHandler is not that different than the BundleRepository

StockingContext stockingContext = (StockingContext)
this.getServletContext().getAttribute("org.jvending.provisioning.stocking.StockingContext");
StockingHandler stockingHandler = stockingContext.getStockingHandler("GENERIC");
ProviderContext providerContext = stockingContext.getProviderContext(request);
stockingHandler.addParFile(inputStream, providerContext);
    

Now let's walk through how the stocking framework works.

Using the Stocking Handler

A content provider is going to stock the content within the provisioning server by uploading a PAR file. We want a way to associate a specific provider with a stocking handler, which provides a set of rules and filters for stocking. To do this, we must have a notion of provider characteristics. Similar to the DeliveryContext JVending introduces a ProviderContext.

The provider submits the PAR file through a web page (1). The application invokes StockingContext.getProvisioningContext and gets back the ProviderContext (2). The application then uses the information within the ProviderContext (user ID) to determine which StockingHandler to use (3). Thus the application determines what the rights are for the respective content provider. The Application invokes StockingContext.getStockingHandler using the name of the handler obtained in the previous step (4). Finally the application invokes StockingHandler.addParFile, passing in the ProviderContext and PAR InputStream (5). Assuming no IOException is thrown, the PAR file is processed with the filters configured for that provider.

Stocking Filters

From the application developer's view point, they don't have to deal directly with the StockingFilter instances. This is all taken care of by the StockingHandler.

The StockingHandler uses the StockingFactory to create a FilterTask. The filter task contains a reference to a unique filter task ID, the ProviderContext, the StockingHandlerConfig, a Map of content and a org.jvending.provisioning.stocking.par.ProvisionsingArchive. This is everything that a StockingFilter needs to do its job. (1, 2). Next the StockingHandler invokes StockingHandlerConfig.getStockingFilters to get a list of StockingFilters that are associated with this specific handler config (3). The StockingHandler iterates over the list of StockingFilter instances, invoking each filters doFilter method (4).

The following configuration within the stocking-handlers.xml file is what the StockingHandler uses to determine the appropriate filters. In this case, the FormatFilter removes white space from the provisioning descriptor. The MimeTypeFilter looks for content bundles that do not have a mime-type assigned and tries to determine the mime-type through the URL reference of the content. It then puts the information back into the provisioning descriptor. The JnlpFilter removes all JNLP content from the PAR file.

<stocking-filters>
    <stocking-filter>org.jvending.provisioning.stocking.filter.FormatFilter</stocking-filter>
    <stocking-filter>org.jvending.provisioning.stocking.filter.MimeTypeFilter</stocking-filter>
    <stocking-filter>org.jvending.provisioning.stocking.filter.JnlpFilter</stocking-filter>
</stocking-filters>
                

The filter modifies the reference to the provisioning descriptor and content. After the filters finish, the StockingHandler instance gets the content from the FitlerTask (5) and converts it to an PAR file (6) as an InputStream (7). Finally, the StockingHandler invokes DataSink.addParFile (8) and the content is stocked.

In the current JVending implementation, the same class implements the javax.provisioning.BundleRepository and the org.jvending.provisioning.stocking.DataSink interface, so that the filters sit on top of the same method that the developer uses to stock without filters.

Adding Stocking Filters

Stocking Filter Class Diagram

Excluding Content From a PAR

The org.jvending.provisioning.stocking.filter.BundleFilter class is an abstract class that employees a template patten that makes excluding content from a PAR relatively simple. All you need to do is to create a class that extends the BundleFilter and that implements the BundleFilter.validateBundle method. Consider the following JnlpFilter example.

public class JnlpFilter extends BundleFilter {

    public void validateBundle(ClientBundle clientBundle, Map content) throws StockingException {
        DescriptorFile descriptorFile = (DescriptorFile) clientBundle.getDescriptorFile();
        if (descriptorFile != null) {
            String mimeType = descriptorFile.getMimeType();
            if (mimeType.equals("application/x-java-jnlp-file")) {
                throw new StockingException("Excluding JNLP files from the PAR");
            }
        }
    }
}

The JnlpFilter instance invokes ClientBundle.getDescriptorFile().getMimeType() to determine whether the ClientBundle is for a JNLP file. If it is, the method throws a StockingException. Throwing the exception notifies the base class BundleFilter that it should remove the ClientBundle from the PAR file. As you can see in the filter class diagram, the ClientBundle contains the catalog information (vendor, description, copyright, etc), so you can exclude a file based on any number of criteria. You also have access to the content with the PAR file. Thus you can inspect the content itself to determine whether you will keep the respective ClientBundle

Directly Implementing the StockingFilter Interface

Directly implementing the StockingFilter will require a bit more work, but will allow you to do a lot more than just excluding a file. Depending on your needs, you can create all sorts of interesting, custom filters. For example, you could use the ProviderContext contained within the FilterTask to determine which content provider is making the stocking request. You could then add, to the provisioning descriptor, the copyright and vendor information that is associated with their profile.

public void doFilter(FilterTask filterTask) {
    String providerId = filterTask.getProviderContext().getUser();
    ....use the provider id to do a lookup through a DAO and get back the copyright....
    ProvisioningArchive archive = filterTask.getProvisioningArchive();
    for(Iterator i = archive.getClientBundle().iterator(); i.hasNext(); ) {
        ClientBundleType bundle = (ClientBundleType) i.next();
        //Copyrights for multiple locales (so you could add different copyrights depending on locale and provider)
        for(Iterator i = bundle.getCopyright().iterator(); i.hasNext(); ) {
            CopyrightType type = (CopyrightType) i.next();
            type.setValue("All rights reserved by Big Provider");
        }
    }
}

Adding Filters to the Configuration File

Within the stocking-handlers config file, you will see the following elements.

<stocking-filters>
    <stocking-filter>org.jvending.provisioning.stocking.filter.FormatFilter</stocking-filter>
    <stocking-filter>org.jvending.provisioning.stocking.filter.MimeTypeFilter</stocking-filter>
    <stocking-filter>org.jvending.provisioning.stocking.filter.JnlpFilter</stocking-filter>
</stocking-filters>
                

Add another entry containing the class name of your filter and it will be included within the filter process. Keep in mind that the filters are executed in order, so its a good idea to include your filter somewhere after the FormatFilter and the MimeTypeFilter. These filters remove whitespace (so you don't have to deal with trimming information within the ClientBundle), make the content references proper URIs and add mime-types to the meta-information if it does not already exist.

Tricks

If you need access to a repository (or a DAO) within a StockingFilter implementation, do the following:

 RepositoryRegistry registry = (RepositoryRegistry) filterTask.getStockingHandlerConfig()
    .getStockingContext().getServletContext().getAttribute("org.jvending.registry.RepositoryRegistry");
                

Now use the registry to find your Repository instance.

Stocking New Content-Types

Adding Generic Content

It is simple to add generic content, which is content that can be treated as a straight binary. Just add the extension to mime-type mapping within the MimeType.properties. There are over 120 content types already mapped so that should handle most of your needs.

Adding Descriptor Based Content

When you add new content types that have a descriptor (MIDP/JNLP), the provisioning framework is not aware of how the descriptor file maps to the content, so you must provide this information through an implementation of the org.jvending.provisioning.stocking.handler.DescriptorHandler interface. More specifically, a DescriptorHandler implementation is used to assist the provisioning framework in the mapping between the JAXB generated classes and the JavaBean classes that Hibernate uses to persist the client bundle to the database. So take a look at the following method within the DescriptorHandler interface.

List getContentFiles(DescriptorFileType descriptorFileType, Map contentMap) throws StockingException;
            

The method is required to inspect a org.jvending.provisioning.stocking.par.DescriptorFileType and return a list of content from the content map. To see how this is done take a look at the following sample from the JnlpHandler

1. byte[] descriptorBytes = (byte[]) contentMap.get(descriptorFileType.getValue());
2. KXmlParser parser = new KXmlParser();
3. parser.setInput(new ByteArrayInputStream(descriptorBytes), null);

4. int eventType = parser.getEventType();
5. do {
6.     if (eventType == parser.START_TAG) {
7.     String href = parser.getAttributeValue(null, "href");
8.        if (href != null && !isRemoteUri(href)) {
9.            byte[] content = (byte[]) contentMap.get(href);
10.           ByteArrayInputStream is = new ByteArrayInputStream(content);
11.           Blob contentBlob = Hibernate.createBlob(is, content.length);
12.           ContentFile contentFile = new ContentFile();
13.           contentFile.setContent(contentBlob);
14.           contentFile.setFileUri(href);
15.           contentFile.setMimeType("application/x-java-jnlp-file");
16.           contentFile.setBytes(content);
17.           contents.add(contentFile);
18.       }
19.   }
20.   eventType = parser.next();
21. } while (eventType != parser.END_DOCUMENT);

The method gets the JNLP descriptor file byes out of the content map (remember that the DescriptorFileType contains information about the descriptor file and does not actually contain the JNLP descriptor) (1). Next, the method puts the JNLP descriptor into a KXmlParser (3) and begins iterating over the tags (5, 6). If it finds an href reference it means that it the reference may exist locally within the content map (8), so it pulls out the byte array of the content (9), converts it to a BLOB (11) and contructs a ContentFile, which is a Hibernate JavaBean. So you can now see the JAXB DescriptorFileType that we got from the provisioning.xml file is now mapped to a bean for Hibernate. The method then adds the content file to a list(17). When it is done, it returns the entire list of ContentFile instances. The provisioning framework will take care of persisting these to the provisioning catalog (DB).

Finally, you will need to add the extension,mime-type mapping to the MimeType.properties and you will need to add the configuration information for your descriptor handler to the stocking-handlers.xml file. Just add a new descriptor-handler entry, setting the mime-type and class name of your handler. After that, the stocking framework knows how to stock your custom descriptors and content files.

<descriptor-handlers>
    <descriptor-handler>
        <mime-type>text/vnd.sun.j2me.app-descriptor</mime-type>
        <descriptor-handler-class>
                    org.jvending.provisioning.stocking.handler.JadHandler
        </descriptor-handler-class>
    </descriptor-handler>
</descriptor-handlers>