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>