 |
This is an unofficial Wiki page for the Bantam project on java.net, hosted here.
Following are notes from email with Bantam project owner Tom Cellucci in late February 2007.
Starting from Scratch
Notes on what I did to get started (-- PatrickWright - 01 Mar 2007).
Notes are based on having full download of the project installed to $BANTAM_HOME.
I was able to load the bantam-examples WAR into a Tomcat 5.2 container prior to starting this experiment. Jini services deployed correctly and were accessible.
- copy
$BANTAM_HOME/webapps/bantam-examples to $BANTAM_HOME/webapps/newapp. We'll call this $NEWAPP_HOME.
- in the
$NEWAPP_HOME/build.xml file, replace all occurrences of bantam-examples with newapp.
- run th
build.xml clean target for newapp to remove any traces of the prior example app
- check if you have a
web.xml under $NEWAPP_HOME/web/WEB-INF. If you don't, copy one from the example project. You'll need to clean up references to the other project (such as bantam-examples) in the web.xml file and replace them with references to newapp.
- under
$NEWAPP_HOME/web/WEB_INF, modify the service-config.xml file to set references to the newapp project
- under
discoverySpec, change the URL for the locator from bantam-examples to newapp
- run the
dist target to create your new WAR file
- run the
deploy target to push it to your web container, or copy the WAR file from the dist directory to your web container
- once deployed, test the client process to make sure the service is properly exported
General
What's a one-line description of Bantam?
Bantam is essentially WAR packaging that allows you to host a set of Jini services on a J2EE? server (Glassfish, JBoss, etc) or web/servlet container (Tomcat, Resin, etc)
What version of Java is required?
Bantam is built with Java 1.5 (Tiger).
How do I get started?
You can take either of two tracks:
- Write a new service using standard Jini, for which Jan Newmarche's guide is a good starting place. I expect most Jini services can run in a Bantam-hosted environment without modification; you need only put the service JARs in the correct locations ( i.e., service-archives or remote-archives) and configure within service-config.xml.
- Use Java Annotations (e.g. Bantam-annotated service). This is a simplification of the process for writing a Jini service, given the assumption that it will run inside a Bantam WAR (the WAR is a more structured environment than what standard Jini expects).
There's a place for either, depending on circumstances. All the stock Jini services work well with XML since you really don't want to modify their sources, but there 's some convenience to be had going the other way for your own services. I think complexity is what has kept most people away from Jini, so I have high hopes for what the annotations might do.
Bantam Features
Why should I use Bantam's Java Annotations?
The goal of the annotations is to reduce the number of moving parts and required steps to make a remote service, analogous to the way JAX-WS does this in Java 6.
The annotations allow the Bantam runtime to automatically:
-
- set up an appropriate classloader and security policy for exporting your service
- generate a downloadable JAR that satisfies your service's codebase requirements
- launch your service with a set of lookup attributes ( i.e., service name, vendor, etc)
- populate any references your service makes to other services (see example in com.skillcorp.bantam.service.examples.admin.Test)
How does Bantam use the Bantam Annotations? What effect do they have?
To use the annotations, you need only include your class files in the /WEB-INF/service-classes directory (no need to make an entry in service-config.xml). The Bantam runtime searches for annotated classes in this directory at runtime.
You say that Bantam "set(s) up an appropriate classloader"--why? What is this for?
The classloader produces the "codebase annotation" URL associated with any loaded class. When you serialize an object and send it to a remote client, the client can access this annotation to get your class definition if he doesn't have it loaded already. For example, let's say you have a service with a remote method that returns an object of type Foo, where Foo is some interface. If the caller invokes your method, he gets back a serialized 'FooImpl', which is some hereforto unknown implementation of Foo you've come up with. The client will still be able to use your returned object by reading its codebase annotation, downloading the class FooImpl? from the annotation URL, then deserializing your object as an instance of that class. This is the "code mobility" feature of Jini or RMI. Without code mobility, every client would have to have implementations for any object your service returns-- a significant limitation and breakage of "separation of concerns".
By default, does the web container act as the host for its own LUS, service registrar and codebase server?
That's true of the example you deployed, but not generally true. Hosting the LUS service is optional in a WAR, but useful for the example. There's a stand-alone registry.war online if you'd like to deploy the LUS by itself; same with the javaspace. What is generally true is that any bantam WAR will proxy a LUS unicast discovery request (you can ask any Bantam war to send you a remote proxy for the LUS it knows about; it will attempt to get this remote proxy from jini://localhost:4160 by default).
What is the codebase for the services I'm exporting in the Bantam WAR?
Every Bantam WAR is the codebase server for services it hosts. This eliminates the need for redundant codebase serving, a topic which resurfaces on the Jini lists every so often. The web container hosting my Bantam WAR also acts as the codebase using the URL /[servlet-context-path]/codebase/*
What JERI endpoints are exposed by Bantam?
The JERI endpoint you see in the service-config (servletEndpoint??) is shared by all the services deployed in the WAR, analogous to Jini's =HttpServerEndpoint. The endpoint's I/O is sourced from the Servlet request / response streams.
You could choose to configure a different JERI endpoint that didn't leverage the servlet requests (for whatever reason), but that's not the default setup.
Are Bantam WAR files signed?
When you run the Bantam build script, you're likely to have trouble with a portion of the build script that signs bantam-servlet.jar. It's looking for a keystore and alias that needs to be set up in your build.properties file. Getting the jar signed makes it easier to secure a Bantam installation, since you don't have to put an entry in your policy file for each Bantam WAR that's deployed.
You can comment this piece out if you're using a simple "grant all" policy, or depending on your level of comfort with 'keytool', set up a keystore, create an alias, export the cert, get the cert for the alias imported into your trusted keystore... Might not be too bad if you're comfortable using 'keytool', otherwise I'd comment for now.
How often does Bantam scan the service-classes directory for annotations and changes?
At WAR deploy; it is also tied into the ServletContextListener callback so it happens each time the WAR is started.
When are the DL JAR files created by Bantam?
The presence of -dl JARs is checked when the WAR is started (ultimately from ServletContextListener callback), and created as necessary.
What is the lease of the exported services in Bantam?
The service is exported forever, but it's registration with the LookupService is leased. The lease duration is controlled by parameters you specify in the registry; it grants leases of a duration that it sees fit. The service implementation has a LeaseRenewalManager that takes care of renewing the lease with the registry periodically if the service is alive.
For services exported in my WAR, what endpoints are exposed? How are incoming requests processed?
When I deploy via Bantam, from the service-config I see I can set upmy endpoints--when that is done it's up to JERI to handle it, correct? We are bypassing the servlet/request stack in the web container, is that correct? This is all important for us to know what is tunable, and how, and how we can monitor and manage it.
Actually we don't bypass the servlet/request stack; all inbound remote requests are handled as HTTP requests by Bantam's remoting servlet. This means they are tunable via your web container's thread pool, and should scale like you'd want. Jini still manages to create some threads outside the servlet thread pool, but these are related to discovery of registries (async), and invoking other remote services (outbound requests). I believe the outbound threads come from a pool (Jini System Thread pool) that might be tunable, but i haven't looked into it at this point.
Configuration: Services and the service-config File
How are lookup groups and locators defined via the configuration file?
How exactly are the LUS, space and groups bound together in this case? I see the declaration for locator and group names under the discoverySpec bean, but I don't see any reference to link that to the registry or the javaspace bean configured later in the file. Is this wired up somehow via BantamDiscoveryManagement??
The LUS and the Space are reading config entries named 'initialLookupGroups' and 'initialLookupLocators'; these entries control how the services will find the LUS and each other. The values of these entries are autowired by name by Spring:
<spring-util:property-path id="initialLookupGroups" path="discoverySpec.groups" />
<spring-util:property-path id="initialLookupLocators" path="discoverySpec.locators " />
For instance, when the service with id 'registry' is instantiated, Spring sets, or autowires, the property 'initialLookupGroups' on the bean named 'com.sun.jini.reggie' to the value of a bean named 'initialLookupGroups'. The 'initialLookupGroups' bean is assigned the value of discoverySpec.getGroups(), as specified in the property-path tag above.
Is there an (straightforward) way to instantiate my service impl via Spring, so I can pull in dependencies?
I'm wondering
- how to declare the location of my context XML
- how to tell Bantam where to retrieve the instance
- if this is possible when using automagic configuration
Answer
- The
mayflower:service-config tag, located inside service-config.xml and part of your service definition, is a full-fledged Spring application context and you can use any valid spring tags inside it, including the import tag to include other contexts.
- The Bantam runtime will get your service implementation from that context if you name its bean definition following a pattern. The runtime is looking for a bean in that context named
"[service id].impl". So if your service is defined in the mayflower:service tag with an id of 'myService', then Bantam looks in the mayflower:service-config context for a bean named "myService.impl". This behavior is ONLY possible with automagic configuration at the moment, although that could be changed if need be.
- Even if you don't use the naming pattern to define your service impl, the Bantam runtime will still create your service impl using Spring, your bean will be autowired by name, and it will be registered as a singleton named
"[service id].impl" in the appContext.
Configuration: JAR Files
Where do i put shared third-party jars referenced by my service, e.g. Apache commons lang?
You can put them in /WEB-INF/lib or /WEB-INF/start-lib. Jars loaded from start-lib will be privileged, which could matter if you go to a more complex security model.
What are the different library directories for? What is the difference between: start-lib, WEB-INF/service-archives and WEB-INF/lib?
/WEB-INF/service-archives contains JARS that will be loaded by the service classloader. The hierarchy is like this:
WebappClassloader -> BantamRuntimeClassloader? -> BantamServiceClassloader?
corresponding directories for this hierarchy:
/WEB-INF/lib -> /WEB-INF/start-lib -> /WEB-INF/service-archives
I noticed you were using pack200 in the build file--is that necessary, or just a space-saver during the deploy?
Just a space saver.
What is the visibility for jars at different levels? Can i use Spring 1.2.6 if bantam is on Spring 2.0?
You actually can't use different versions of Spring, since Spring 2.0.2 is visible to the runtime and service classloaders. AFAIK, you should be able to use the newer Spring with an older application; Bantam itself was converted at one point.
Do you think it makes sense to pack any really common JARs, like for logging, into a web-container shared space like TOMCAT_HOME/common/lib?
Yes, but you have to make sure you can satisfy the dependencies without descending into classloader hell. Commons logging is an example of a tricky one to keep on the system classpath.
Development using Bantam
What JAR files from Bantam and Jini should I have in my build-time CLASSPATH?
For development within an IDE, my web projects are set up with the following libraries for compile time:
-
bantam-common.jar
-
bantam-lib.jar
-
jsk-lib.jar
-
jsk-platform.jar
-
mayflower-lib.jar
-
mayflower-config.jar
-
mayflower-start.jar
You pretty much want the combination of /WEB-INF/lib + /WEB-INF/start-lib + /WEB-INF/service-archive, as this is what your service will have at runtime. You probably don't need all the libraries at compile time ( e.g., spring jars aren't needed at compile time if you're purely using dependency injection), but it may be easier to simply include them all.
How do I pull in changes from Bantam, that is, when Bantam has been updated?
If you're developing Bantam services inside your own webapp, and you want to roll in changes from Bantam (maybe you pulled updates from bantam's cvs, for example)...
I'd run the top-level bantam script to recompile JARs and rebuild the war template, then unpack the template into your WAR structure (take care to back up any files you need to preserve that also exist in the template).
If your WAR follows the blueprints structure where you have a distinct /web and /build directory, then remove the contents of /build and unpack the template there. You should always compare files that exist in /web to files from the template in /build. For instance, it's worth a look to see if the template's web.xml or service-config.xml have changes; the files in your /web directory may need an update.
When you rebuild your WAR, the build script should overlay your files from /web on top of the content extracted from the template and the generated WAR should be up to date wrt Bantam.
Troubleshooting
I'm getting errors launching the example WAR file; the discovery process is failing
I just tried downloading the examples.war and starting it, using Tomcat 5.5.20. It appears it's looking for a registry, it loops on a lookup; here's a partial trace
INFO: exception occured during unicast discovery to localhost:8080 with constraints InvocationConstraints[reqs: {}, prefs: {}]
java.io.IOException: registry unavailable
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg.doUnicastDiscovery(BantamLocatorDiscovery.java:283)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg.tryGetProxy(BantamLocatorDiscovery.java:231)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery.regTryGetProxy(BantamLocatorDiscovery.java:1069)
and
Feb 28, 2007 6:10:51 PM net.jini.discovery.LookupDiscovery$UnicastDiscoveryTask run
INFO: exception occurred during unicast discovery to 10.1.1.212:4160 with constraints InvocationConstraints[reqs: {}, prefs: {}]
java.lang.ClassNotFoundException: com.sun.jini.reggie.ConstrainableRegistrarProxy
Answer: it looks as if the registry service inside BantamExamples? didn't start for you, which is leading to the failure of the unicast discovery for other services.
The first thing to check the serverLocation parameter in the web.xml of BantamExamples.war to make sure it matches your actual server and port number.
Also verify the 'discoverySpec' bean in the /WEB-INF/service-config.xml is pointed to the correct location (hostname and port should match that of serverLocation in web.xml).
If those are both correct, then the next most likely culprit is that your security policy has blocked the services from launching. Easiest way to fix that (at least temporarily) is to grant AllPermission? in the policy file that governs Catalina (Tomcat). That's usually $CATALINA_HOME/conf/catalina.policy and you'd add an entry like:
grant {
permission java.security.AllPermission;
};
(that's just a work around; you can secure Bantam more tightly than that but not as easy to explain).
There appears to be a mismatch between the WAR file name and the deployed service name
One problem seems to be that more than one place in the configuration is referencing BantamExamples?, while others are using bantam-examples. I've found one place in the template package, service-config, where this was the case, and corrected it, but in the logs I still see
WARNING: Problem accessing desired URL[http://localhost:8080/BantamExamples/codebase/test-dl.jar]:
java.io.FileNotFoundException: http://localhost:8080/BantamExamples/codebase/test-dl.jar.
Answer: the name of the war that you download from the site should be BantamExamples.war rather than bantam-examples.war. The WAR file name ends up determining the servlet context path, which needs to match what's defined in the web.xml and service-config.xml entries (above).
Until servlet spec 2.5, there's no way for a webapp to know it's actual context path at startup. If you commit to a particular web container you have more control, for example in Tomcat you can add a context.xml to your WAR that tells it what the servlet context to be.
More specifically on the web.xml, the <display-name> element contents determines the initial path segment in the codebase URL. This is because the call ServletContext.getServletContextName() returns <display-name> per the API specification.
I moved my webapp to another machine called dev1, and now I can't discover its services any longer.
If this is the problem, the exception stack trace may look like this:
Mar 1, 2007 5:19:34 PM
com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg
tryGetProxy
INFO: exception occured during unicast discovery to dev1:8080 with
constraints InvocationConstraints[reqs: {}, prefs: {}]
java.lang.RuntimeException:
.
.
.
Caused by: java.lang.ClassNotFoundException:
com.sun.jini.reggie.ConstrainableRegistrarProxy (could not determine
preferred setting; original codebase
: "http://localhost:8080/newapp/codebase/reggie-dl.jar
http://localhost:8080/newapp/codebase/jsk-dl.jar
http://localhost:8080/newapp/codebase/bantam-d
l.jar")
at net.jini.loader.pref.PreferredClassProvider.loadClass(PreferredClassProvider.java:580)
at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:247)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:197)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1575)
Check that the remote Bantam WAR is configured with the real hostname of the server that hosts it (i.e., dev1). Places to set this are:
1. service-context.xml - "discoverySpec" bean
1. web.xml - "serverLocation" context parameter
In the stack trace above, the webapp was still configured for "localhost".
Why do services exported by Bantam appear on the Jini Services Browser as "$ProxyN", where N is a number?
In this case the Service info is correct, just would have thought I'd see something like the classname. Is this normal, or a side-effect of Bantam?
Haven't figured out the "ProxyN" deal yet. That name is in fact the classname of the exported object, which is a dynamic proxy the Bantam runtime generates, but there must be some way to customize the display. It seems like I set all the right attributes and yet the browser behavior is as you say.
Why do I get an java.io.StreamCorruptedException when I try lookup on a service hosted with Bantam?
If you are using the unicast discovery over Http (using HttpLookupLocator) you might be referencing the wrong URL. The URL follows this form
http://<host>:<port>/<webapp name>/locator
The "locator" part is important, if missing, you'll get an exception relating to stream corruption, such as
Mar 6, 2007 10:00:33 AM com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg tryGetProxy
INFO: exception occured during unicast discovery to backdev1:8080 with constraints InvocationConstraints[reqs: {}, prefs: {}]
java.io.StreamCorruptedException: invalid stream header: 3C3F786D
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:783)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:280)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg.doUnicastDiscovery(BantamLocatorDiscovery.java:288)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery$LocatorReg.tryGetProxy(BantamLocatorDiscovery.java:231)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery.regTryGetProxy(BantamLocatorDiscovery.java:1069)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery.access$800(BantamLocatorDiscovery.java:69)
at com.skillcorp.bantam.discovery.BantamLocatorDiscovery$DiscoveryTask.tryOnce(BantamLocatorDiscovery.java:489)
at com.sun.jini.thread.RetryTask.run(RetryTask.java:131)
at com.sun.jini.thread.TaskManager$TaskThread.run(TaskManager.java:331)
I tried reading the TOC (jar tf) of the jar files in start-lib and got an error: Error in JAR file format. zip-style comment? For example with spring-bean.jar. The JAR is apparently otherwise readable, but wondering if this is a side-effect of using Pack200? Note the file had already been "unpacked" into what looked like a JAR.
I get the same result -- wierd. You could always remove the pack200 logic from the build script if you like.
-- PatrickWright - 01 Mar 2007
|