Platinum Partner
netbeans,jee,wicket,netbeans platform,rest,servlet,jsr-311,jersey

Using the NetBeans Platform on the Server with Wicket on the Client

Let's suppose you have developed a rather complex desktop application based on the NetBeans Platform, and later you discover that you could reuse lots of things for another customer's project, this time a regular web application. Well, what to do? You can't use the NetBeans Platform to create a componentized server-side application, right? It looks like you have to disassemble your desktop application, take the .jars out of the NetBeans .nbm modules (a thing that I informally call "flattening" a platform) and so on. Of course, you perhaps reuse some code, but completely lose the componentized architecture advantages. Right?

Wrong. You can use the NetBeans Platform to create a server side application! Your components just stay the same (of course, if you properly separated the models and controllers from the GUIs) and you can just reuse the binaries (OSGi people are probably acquainted with that, especially since when Glassfish supports it - the news is that the NetBeans Platform can be used as well).

Indeed, Jaroslav Tulach already talked two years ago about the NetBeans Platform on the server side (hell, is there in the world something that that guy hasn't done yet?), with a project name "DVB Central". As Tim Boudreau wrote, DVB central is a "bare bone" Platform (basically the BootStrap, Startup, File System, Utilities, and Module System APIs) deprived of any Swing part, that contains a small HTTP server used for remote administration.

My needs (because - you guessed it - I'm talking of a real-world scenario here) are more complex: I need to reuse more NetBeans Platform modules (e.g. DataObjects); the application should not be a "standalone application embedding an HTTP server" but a regular web application deployable as a .war within Tomcat; and I also need to use a web framework for it (namely, Wicket). Furthermore, the application needs to expose a WebDAV interface and some REST modules.

While it took a while to get to it, the currently polished solution is cleaner than I expected, and I'm going to show you how it works. The project that is build on it is blueOcean, a new member of the blueMarine family. It is basically a "server side" blueMarine, with the non GUI modules that implements the cataloguing system. While it's not yet entirely available as a complete application (it's rather a base platform that I'm using to create the customer's application, which has the user interface), it is available since one or two days at blueocean.dev.java.net and you can look at it for seeing the technical solutions I'm going to illustrate (and in a matter of weeks the website blueocean.tidalwave.it should be born). Please be aware that this article assumes that you have some basic knowledge about deploying .war applications and also about how NetBeans Platform applications are made.

Step 1: Prepare the Wrapper Modules

Since you are going to completely develop a web application as a componentized NetBeans Platform application, all of the required libraries and frameworks need to be placed in NetBeans Module Wrappers. These are the ones I prepared for blueOcean (you can see them in the Libraries folder):

  1. ServletAPI (servlet-api.jar) for the basic web functions.
  2. Wicket and WicketExtensions for the Wicket framework.
  3. JSR311, Jersey and Jettison for REST features (plus JSON support).
  4. Milton for the WebDAV interface.
  5. Libraries required by the above listed stuff (e.g. SLF4J, ASM, GlazedLists, Apache Commons Fileupload etc..., as specified by each framework documentation).

Be especially careful in step #5, that is, make sure that all the dependencies are satisfied (so, for instance, the Wicket Module declares a dependency on SLF4J, Jersey on JSR311 and ASM, etc), otherwise you'll get some ClassNotFound exceptions / errors that can be tricky to understand (they will refer to classes that actually are in the classpath, but have missing dependencies).

For reasons that will be explained soon, you need an extra thing for each framework: any framework-relevant Servlet or Filter (usually one per framework, described in their documentation) must be registered as a META-INF service. To accomplish that, you need to create a META-INF/services directory with the following files (for Wicket, Jersey and Milton):

org.apache.wicket.protocol.http.WicketFilter
com.bradmcevoy.http.MiltonServlet 
com.sun.jersey.spi.container.servlet.ServletContainer 

Each file must be composed of a single line containing the same text as the file name. 

 

Step 2: Develop Your Application

Now you can create your application as usual, using the required APIs as appropriate. That is, for creating the User Interface you create new Modules that declare a dependency on Servlet API and Wicket, and so on. A potential source of troubles is where to place the HTML files for the UI: with Wicket (that I like for many reasons, including the one I'm telling you now) they can stay in the same folder as .java sources and get packed into Java binaries, since they will be accessed at runtime as resources. I didn't investigate other scenarios, for instance JSPs, where the web pages must be placed in a specific directory separate from code.

 

Step 3: Create a Web Application Wrapper

Now, the most interesting part. You need to create the "glue" between Tomcat and your Platform based application; and since I like to do things in the standard way, I like to have this glue to take the form of a regular .war application, that can be deployed as usual. Keep in mind that you have many variants: the .war could be all-inclusive, that is it would include your application AND the platform, or it could merely point to a platform that is placed in some other place. The latter solution, for instance, would allow to add and remove modules by means of the NetBeans Update Centers, instead of redeploying a new .war. It's up to you. As a starting point, and also because at the moment I don't need Update Centers, I've gone the way of the all-inclusive .war file.

The basic point of "glueing" is about class loaders: both Tomcat and the Platform have their own class loaders. How to connect them? The lucky thing is that the interface between Tomcat and a Web Application is mainly composed of only two things: Servlets and/or Filters. Most of the web frameworks around just need to declare a Servlet or a Filter in the web.xml file, and those components will act as a single "front controller" from the framework.

So my solution is to provide a pair of universal decorators (a Servlet and a Filter) that create a bridge between the two worlds. I've put them in a very small JSE library that, as you'll see later, just needs to be included in a .war project and configured (the sources can be checked out from https://blueocean.dev.java.net/svn/blueocean/trunk/src/NetBeansPlatformWebAdapter, revision 11).

This is the Servlet Decorator:

package it.tidalwave.netbeans.servlet;

import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.openide.util.Lookup;

public class NetBeansPlatformDecoratorServlet implements Servlet
{
private ServletConfig config;
private Servlet delegate;

public void init (final ServletConfig config)
throws ServletException
{
try
{
NetBeansPlatformUtils.boot(config.getServletContext());
}
catch (Exception e)
{
throw new ServletException(e);
}

final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
this.config = config;
final String className = config.getInitParameter("netbeans.servlet.delegate");
final Class delegateClass = Thread.currentThread().getContextClassLoader().loadClass(className);
delegate = (Servlet)Lookup.getDefault().lookup(delegateClass);

if (delegate == null)
{
throw new RuntimeException("Can't lookup " + className);
}

delegate.init(config);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public ServletConfig getServletConfig()
{
return delegate.getServletConfig();
}

public void service (final ServletRequest req, final ServletResponse res)
throws ServletException, IOException
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.service(req, res);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public String getServletInfo()
{
return delegate.getServletInfo();
}

public void destroy()
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.destroy();
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}
}

These are the main concepts:

  1. As usual for decorators, the behaviour is forwarded to a delegate.
  2. The decoration consists in setting the context class loader (Thread.currentThread().getClassLoader()) from the NetBeans Platform and restoring it at the end of the method call (taking care of using a finally block for safety). The details are implemented in the NetBeansPlatformUtils class, discussed below.
  3. The delegate instance is retrieved by asking it to the Lookup class, as usual for NetBeans Platform applications (this is why you had to declare all the relevant Servlets and Filters in META-INF/services as previously described). While the common idiom is to pass the class literal as argument (e.g. MyService s = Lookup.getDefault().lookup(MyService.class)), here we can't do that for two reasons: first, because we want the code to be universal, so the class name must be passed in a parameter ("netbeans.servlet.delegate"); and second, because we couldn't get the literal in any way, since it would be never available in the static compilation classpath (there are no good ways to have a .war statically include the Platform stuff; if you tried to extract the .jar files from the .nbm modules - which per se is a cumbersome approach - you would face possible ClassCastExceptions since you would have the same class available in both Tomcat and the Platform class loaders - remember that the same bytecode in two different classloaders is treated as two different classes).
  4. Last but not least, at the first invocation of a Servlet or Filter delegate you must initialize the Platform: that's the task of NetBeansPlatformUtils.boot(). 

For completeness, this is the NetBeansPlatformDecoratorFilter code, definitely similar to the Servlet:

package it.tidalwave.netbeans.servlet;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.openide.util.Lookup;

public class NetBeansPlatformDecoratorFilter implements Filter
{
private Filter delegate;

public void init (final FilterConfig config)
throws ServletException
{
try
{
NetBeansPlatformUtils.boot(config.getServletContext());
}
catch (Exception e)
{
throw new ServletException(e);
}

final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
final String className = config.getInitParameter("netbeans.filter.delegate");
final Class delegateClass = Thread.currentThread().getContextClassLoader().loadClass(className);
delegate = (Filter)Lookup.getDefault().lookup(delegateClass);

if (delegate == null)
{
throw new RuntimeException("Can't lookup " + className);
}

delegate.init(config);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public void doFilter (final ServletRequest request,
final ServletResponse response,
final FilterChain chain)
throws IOException, ServletException
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.doFilter(request, response, chain);
}
catch (Exception e)
{
throw new ServletException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}

public void destroy()
{
final ClassLoader classLoaderSave = NetBeansPlatformUtils.installPlatformClassLoader();

try
{
delegate.destroy();
}
finally
{
Thread.currentThread().setContextClassLoader(classLoaderSave);
}
}
}

Now, let's have a look at the NetBeansPlatformUtils class. First, the method to install the Platform class loader:

    private final static URL[] NO_URLS = new URL[0];
public static ClassLoader installPlatformClassLoader()
{
final ClassLoader classLoaderSave = new URLClassLoader(NO_URLS, Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(Lookup.getDefault().lookup(ClassLoader.class));

return classLoaderSave;
}

The only note worth mentioning is that the Platform class loader is wrapped by a URLClassLoader: I've experienced some ClassCastExceptions by the JSP facility of Tomcat, that expects the relevant class loader to be URL - based. While I'm not interested in JSPs (and I noted that the exceptions were harmless for my application), the less obscure exceptions at runtime, the better.

Then, the boot() method, which just mimics the needed initialization parts performed by the NetBeans launcher:

    private static volatile boolean initialized;

public synchronized static void boot (final ServletContext servletContext)
throws Exception
{
if (!initialized)
{
initialized = true;
final String fallbackNetBeansHome = servletContext.getInitParameter("netBeansHome");
final String fallbackNetBeansUser = servletContext.getInitParameter("netBeansUserDir");
final String netBeansHome = resolveProperties(System.getProperty("netbeans.home", fallbackNetBeansHome));
final String netBeansUser = resolveProperties(System.getProperty("netbeans.user", fallbackNetBeansUser));
final String netBeansDirs = findClusters(netBeansHome);

System.setProperty("netbeans.osenv.nullsep", "false");
System.setProperty("netbeans.home", netBeansHome);
System.setProperty("netbeans.dirs", netBeansDirs);
System.setProperty("netbeans.logger.console", "true");
System.setProperty("netbeans.user", netBeansUser);

new File(netBeansHome + "/config").mkdirs();
new File(netBeansUser).getParentFile().mkdirs();

org.netbeans.Main.main(new String[] { "--nosplash", "--userdir", netBeansUser });
}

Worth mentioning is the "--nosplash" argument, that prevents the pop up window showing the initialization process from appearing at startup. The "netbeans.dirs" property must point to all the directories where a cluster (a set of related modules) has been prepared; you could statically configure this property with a property file, while I prefer to have it discovered at runtime (it's a matter of taste - I preferred this approach for blueMarine too, which has got a custom launcher):

    private static String findClusters (final String netBeansHome)
throws IOException
{
final StringBuilder clusters = new StringBuilder();
final File[] files = new File(netBeansHome).listFiles();

if (files == null)
{
throw new IllegalArgumentException("Platform directory empty: " + netBeansHome);
}

for (final File file : files)
{
if (file.isDirectory())
{
final File updateTrackingFolder = new File(file, "update_tracking");

if (updateTrackingFolder.exists())
{
// platform* is already added as the main directory
// if (!file.getName().startsWith("platform"))
{
if (clusters.length() > 0)
{
clusters.append(File.pathSeparator);
}

clusters.append(file.getCanonicalPath());
}
}
}
}

final String buildDir = System.getProperty("netbeans.build.dir");

if (buildDir != null)
{
clusters.append(File.pathSeparator);
clusters.append(buildDir);
}

return clusters.toString();
}

The boot code also needs to know about two directories, specified as parameters:

  1. "netBeansHome": where the Platform runtime is installed (root of all clusters)
  2. "netBeansUserDir": where the Platform configuration files will be placed (and, eventually, the application ones too).

For convenience, I've implemented a simple macro-substitution facility for system properties - you'll see later that this is pretty useful for facilitating both development and deployment:

    private static String resolveProperties (final String string)
{
String result = string;

for (final Entry<Object, Object> entry : System.getProperties().entrySet())
{
final String propertyName = (String)entry.getKey();
final String propertyValue = (String)entry.getValue();
result = result.replace("{" + propertyName +"}", propertyValue);
}

return result;
}

 

Step 4: Configure the Web Deployment Descriptor

The Web deployment descriptor is up to you, since it depends on how many and which frameworks you want to use with your application; each one will need a specific configuration. This is why the code I've just introduced has been packaged as a simple .jar library, rather than a complete .war application. 

For instance, this is how I integrated Wicket in blueOcean:

    <filter>
<filter-name>WicketFilter</filter-name>
<filter-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorFilter</filter-class>
<init-param>
<param-name>netbeans.filter.delegate</param-name>
<param-value>org.apache.wicket.protocol.http.WicketFilter</param-value>
</init-param>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>it.tidalwave.blueocean.webapp.BlueOceanWebApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WicketFilter</filter-name>
<url-pattern>/app/*</url-pattern>
</filter-mapping>

As you can see, the instantiated Filter is the decorated one, while the name of the real Wicket filter is passed as a parameter; applicationClassName is instead a Wicket-specific configuration, and of course it is specified too. It's the name of the class that represents the whole application; it's part of a regular Platform module.

This is how I integrated Milton, a server-side WebDAV implementation, that is based on a servlet front controller:

    <servlet>
<servlet-name>MiltonServlet</servlet-name>
<servlet-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorServlet</servlet-class>
<init-param>
<param-name>netbeans.servlet.delegate</param-name>
<param-value>it.tidalwave.blueocean.milton.MiltonPatchedServlet</param-value>
</init-param>
<init-param>
<param-name>resource.factory.class</param-name>
<param-value>it.tidalwave.blueocean.milton.MiltonResourceFactory</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MiltonServlet</servlet-name>
<url-pattern>/webdav/*</url-pattern>
</servlet-mapping>

In this case, I had to provide a patched version of the Servlet, since Milton unfortunately uses Class.forName(...) to dynamically instantiate classes, instead of using the current class loader (consider this a bug/missing feature of Milton). resource.factory.class is a configuration parameter of Milton somewhat similar to the applicationClassName of Wicket.

REST is not used in blueOcean yet, but it is already used in the customer's project based on it. This is how the web.xml for it looks like:

    <servlet>
<servlet-name>JerseyServlet</servlet-name>
<servlet-class>it.tidalwave.netbeans.servlet.NetBeansPlatformDecoratorServlet</servlet-class>
<init-param>
<param-name>netbeans.servlet.delegate</param-name>
<param-value>com.sun.jersey.spi.container.servlet.ServletContainer</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>customer.package1;customer.package2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JerseyServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

The ServletContainer in Jersey contains an annotation processor (called ResourceConfig), which scans all the classes in the classpath looking for JSR-311-specific annotations. Unfortunately the default implementation expects the classpath to be the default one for web-applications (WEB-INF/lib etc...) so it doesn't work for the integrated Platform. An alternate annotation processor (PackagesResourceConfig) had to be used, in which you explicitly specify the names of the packages that contains JSR-311 annotations. It's an acceptable trade-off for now; it is clearly possible to write a customized ResourceConfig that scans a Platform classpath - but I need to learn more from Jersey before I'm able to write it. 

At last, web.xml must contain the Platform configuration as context initialization parameters:

    <context-param>
<param-name>netBeansHome</param-name>
<param-value>{catalina.base}/webapps/blueOcean/WEB-INF/lib/blueocean</param-value>
</context-param>
<context-param>
<param-name>netBeansUserDir</param-name>
<param-value>{user.home}/.blueOcean/NetBeans</param-value>
</context-param>

As explained earlier, braces are used as macro-expansion, their content will be replaced by the value of the related system property. Two things are worth mentioning here:

  1. I'm supposing that I'll deploy the whole application as a single .war file. In this case, I will unpack the platform files under WEB-INF/lib/blueocean, that is pointed by the "netBeansHome" parameter. I'm relying on the fact that the property "catalina.base" points to the directory where Tomcat in installed.
  2. If you're using a different Web Container (e.g. Glassfish), this part has to be modified. Also, I'm relying on the fact that the Web Container will actually expand a .war file on the disk (they are not required to do that, and could work in memory). The server needs to be properly configured for these requirements.

A properly packed, all-inclusive .war file can be created with this ant target:

    <target name="war" depends="build-zip">
<zip destfile="dist/blueocean.war">
<zipfileset src="WebApplicationWrapper/dist/WebApplicationWrapper.war" />
<zipfileset src="dist/blueocean.zip" prefix="WEB-INF/lib"/>
</zip>
</target>

which relies upon the Platform "build-zip" target for packaging your application into a complete .zip file.

During the development, re-creating the whole .war and redeploying it could take too long. In this case, you can override the web.xml settings with these two system properties like these (the most practical way to do is to create a new instance of Tomcat inside the NetBeans IDE):

-Dnetbeans.home=/Users/fritz/Business/Tidalwave/Projects/blueOcean/trunk/src/lib/platform 
-Dnetbeans.build.dir=/Users/fritz/Business/Tidalwave/Projects/blueOcean/trunk/src/build/cluster

Both of them point to your working directory; the former to the base platform you're using for developing the application (in my case, a custom platform); the latter to the "build/cluster" directory, where NetBeans prepares the binary files for your application, laid out as a valid platform.

Now you just need to deploy the .war once, and then just compile your changes, and restart the .war inside Tomcat. Probably, working on it, it could be possible to play with the class loaders so you don't have to restart the .war every time. In any case, consider that the .war is very small, so it restarts almost immediately.

Step 5: Patch Tomcat's Common Classloader

Some "boot" libraries of the NetBeans Platform need to be placed in Tomcat's common class loader. They are:

-rw-rw----  1 fritz  staff  255877 Jan  2 17:35 boot.jar
-rw-rw---- 1 fritz staff 619165 Jan 2 17:35 core.jar
-rw-rw---- 1 fritz staff 563443 Jan 2 17:35 org-openide-filesystems.jar
-rw-rw---- 1 fritz staff 23335 Jan 2 17:35 org-openide-modules.jar
-rw-rw---- 1 fritz staff 625986 Jan 2 17:35 org-openide-util.jar

For instance, you can put them in a tomcat-libs directory; in this case, modify the conf/catalina.properties file so there is this line:

common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,some/path/to/tomcat-libs/*.jar

This is an annoyance - could be also a problem in some cases (e.g. providers with virtualized hosts don't give you access to the Tomcat configuration). For sure it would be better if you could place these libraries under WEB-INF/lib, but trying that leads to very strange errors. Still investigating on this.

 

A Final Word of Wisdom

The thing works, and at least for the Wicket part I'm having it in production since a couple of months (the WebDAV and REST part are still in development). As usual, you must be very careful when dealing with class loaders, since they can lead to obscure errors when you don't expect (for instance, it suffices that a part of the frameworks you're using does a Class.forName(...) - see how I had to patch Milton, for instance). So, before going that way, prototype and test toroughly. Of course, if you only deal with FLOSS software, problems should be always trackable and fixable in some way. 

One should also investigate how the thing scales - for instance, being the NetBeans Platform designed primarlily for the Desktop (= single user applications), it could contain bottlenecks when used with concurrent users. Only testing can tell - so far, so good, but I'm not running yet with a high number of concurrent customers. In my scenario, I think that I know quite well the NetBeans Platform so I could tweak it if necessary, and this little risk is worth while since I'm getting a huge code reuse (in any case, I've got a "backup plan" for my customer in case I find some blocking issue). In other scenarios, one might want to be more careful. For sure, using the NetBeans Platform in this way is an architectural choice that needs to be explored.

It is also to be said that the NetBeans Platform contains some unnecessary hardwired dependencies on Swing, for instance if you use the DataObjects. In this case, you will need to bring into your application some Swing-related modules to resolve that dependencies, even though they will be not used. Also in this case, I didn't experience problems, but you have to test it in our scenario.

Last but not least, it would be interesting to see how many parts of the NetBeans Platform can be effectively used in a server-side application. For instance, I'm not using Nodes - but they can be useful as well, as they associate actions (menus) to objects and could be used together with Wicket's AJAX capabilities to generate dynamic context menus. On the other hands I fear that they, being related to the single-thread model of Swing, could be a serious bottleneck. I guess this is matter for further investigation (and a further article).

For the record, I've proposed that the small adapter library is added to the PlatformX project. Also, I've submitted a speech proposal about these topics at JavaOne 2009 - so, if you like this article, please speak loud. ;-)

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}