Sunday, June 05, 2011

jsp pre-compile and proper servlet classloading

Following my previous post, I configured JSP pre-compiling and adjusted my simple WebAppContext a bit to get the servlet classloader working properly. Without fixing the classloader, I couldn't actually use the pre-compiled JSPs. After getting the classloading worked out, I switched from WebAppContext to WebAppProvider: 1) WebAppProvider allows polling for redeploy of the WAR and 2) the WebAppContext is deprecated so I felt like I should use non-deprecated APIs in this new code.

The i-Jetty project is a pretty interesting project but goes a different route than what I'm doing. I followed some of the ideas there but simplified for what I need.

added configuration to WebAppContext



// start the Web server for config and maintenance
webServer = new Server(8080);
try {
WebAppContext webapp = new WebAppContext();
webapp.setConfigurationClasses(new String[] { DexWebInfConfiguration.class.getName(),
JettyWebXmlConfiguration.class.getName(), WebXmlConfiguration.class.getName() });
webapp.setContextPath("/");
webapp.setTempDirectory(new File("/sdcard/TGP-work/"));
webapp.setWar("/sdcard/TGP-webapps/TGP-ROOT.war");
webServer.setHandler(webapp);
}
catch (Exception e) {
Log.d(LOG_TAG, "unexpected exception setting temp: " + e);
}


DexWebInfConfiguration.java


When I started this class, I thought that there would be more to it so it is a regular class. I'll probably make it an inner class if it doesn't get any more complex that this. Right now, this is all that is needed to use the dex version of the servlets in my WAR.


/**
* Adds dex support to the classloader used in the Jetty WebInfConfiguration.
*
* <code>webapp.setConfigurationClasses(new String[] { DexWebInfConfiguration.class.getName() });</code>
*/
public class DexWebInfConfiguration extends WebInfConfiguration {
public void preConfigure(WebAppContext context) throws Exception {
context.setClassLoader(new DexClassLoader(context.getTempDirectory().getCanonicalPath()
+ "/webapp/WEB-INF/lib/classes.zip", context.getTempDirectory().getCanonicalPath(), null, getClass()
.getClassLoader()));
super.preConfigure(context);
}
}


the WAR file


I'm using a simple Maven project within Eclipse for my WAR file. I'm using the standard structure and added the following to the POM. The first chunk pre-compiles the JSPs and then the second chunk runs dex on all of the compiled code in the WAR (i.e. the JSPs as well as the regular servlets).

The first chunk was pretty must straight out of the jetty-jspc-maven-plugin docs and the second from i-Jetty.

<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
<version>7.3.0.v20110203</version>
<executions>
<execution>
<id>jspc</id>
<goals>
<goal>jspc</goal>
</goals>
<configuration>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<!-- use web.xml from jspc rather than src version -->
<webXml>${project.build.directory}/web.xml</webXml>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>generate-dex</id>
<phase>process-classes</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${android.home}/platform-tools/dx</executable>
<arguments>
<argument>--dex</argument>
<argument>--verbose</argument>
<argument>--core-library</argument>
<argument>--output=${project.build.directory}/classes.dex</argument>
<argument>--positions=lines</argument>
<argument>${project.build.directory}/classes/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>copydex</id>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<mkdir
dir="${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/lib" />
<jar basedir="${project.build.directory}" update="true"
includes="classes.dex"
destfile="${project.build.directory}/${project.artifactId}-${project.version}/WEB-INF/lib/classes.zip" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>


switch to WebAppProvider


I thought that I'd just need to change the WebAppContext stuff but I ended up also having to adjust the configuration as well. Temp directories are handled differently in the provider stuff and I don't totally understand it. Here's where I'm at with the code that sets up the server and then the configuration.

MyApp.class onCreate


// start the Web server for config and maintenance
webServer = new Server(8080);
try {
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection();
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });
webServer.setHandler(handlers);

DeploymentManager deployer = new DeploymentManager();
deployer.setContexts(contexts);
webServer.addBean(deployer);

WebAppProvider webAppProvider = new WebAppProvider();
webAppProvider.setConfigurationClasses(new String[] { DexWebInfConfiguration.class.getName(),
JettyWebXmlConfiguration.class.getName(), WebXmlConfiguration.class.getName() });
webAppProvider.setExtractWars(true);
webAppProvider.setScanInterval(10);
webAppProvider.setMonitoredDirName("/sdcard/jetty/webapps/");
deployer.addAppProvider(webAppProvider);
}
catch (Exception e) {
Log.d(LOG_TAG, "unexpected exception setting temp: " + e);
}

try {
webServer.start();
Log.d(LOG_TAG, "started Web server @ " + getPublicInetAddress());

NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification( R.drawable.web_server_icon, "WebServer", System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT; // Notification.FLAG_NO_CLEAR;
Intent notificationIntent = new Intent(this, MyApp.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(getApplicationContext(), "Web Server" , "Web Server Listening @ " + getPublicInetAddress(), contentIntent);
notificationManager.notify(NOTIFICATION_ID_WEB_SERVER, notification);
}
catch (Exception e) {
Log.d(LOG_TAG, "unexpected exception starting Web server: " + e);
}


DexWebInfConfiguration.java


/**
* Adds dex support to the classloader used in the Jetty WebInfConfiguration.
*
* webapp.setConfigurationClasses(new String[] { DexWebInfConfiguration.class.getName() });
*/
public class DexWebInfConfiguration extends WebInfConfiguration {
/**
* The parent preConfigure is going to load the JAR and class files in so we
* need to adjust the class loader before
*/
@Override
public void preConfigure(WebAppContext context) throws Exception {
String tempDir = getCanonicalNameForWebAppTmpDir(context);
if (tempDir != null) {
tempDir = Environment.getExternalStorageDirectory() + "/" + tempDir;
String dexZip = tempDir + "/webapp/WEB-INF/lib/classes.zip";
context.setClassLoader(new DexClassLoader(dexZip, tempDir, null, getClass().getClassLoader()));
Log.d("Jetty", "Added DexClassLoader for " + dexZip);
}
super.preConfigure(context);
}
}

No comments: