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