I have an embedded jetty server that hot deploys webapps. When deploying the other wars, I add a deployment descriptor.xml to the directory to configure the webappcontext. I wish to add a DIRECTORY of jars that gets built in another location to the classpath of the hot deployed war (/extJars). I see how to do this within the webapp descriptor.xml, and I can accomplish this by stating individual jars, but I have tried multiple configurations for trying to simply read ALL jars in this directory, and nothing has worked. Here is my otherWebApp.xml, we'll call it, with a configuration that works, and commented out configurations that don't work. Thanks.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="war"><SystemProperty name="JETTY_HOME" default="." />/extApps/otherWebApp.war</Set>
<Set name="contextPath">/otherWebApp</Set>
<Set name="tempDirectory" ><SystemProperty name="JETTY_HOME" />/jetty/webapps/otherWebApp</Set>
<Set name="extraClasspath"><SystemProperty name="JETTY_HOME" />/extJars/cadi-core-1.0.12.jar,<SystemProperty name="JETTY_HOME" />/extJars/cadi-aaf-1.0.12.jar,<SystemProperty name="JETTY_HOME" />/extJars/GLCookieDecryption-1.0.jar,<SystemProperty name="JETTY_HOME" />/extJars/rosetta-1.1.1.jar,<SystemProperty name="AJSC_HOME" />/extJars/env-1.4.2.jar,<SystemProperty name="JETTY_HOME" />/extJars/dme2-2.5.22.jar</Set>
<!-- <Set name="extraClasspath"><SystemProperty name="JETTY_HOME" />/extJars/</Set>
</Configure> doesn't work -->
<!-- <Set name="extraClasspath"><SystemProperty name="JETTY_HOME" />/extJars/*</Set>
</Configure> doesn't work -->
<!-- <Set name="extraClasspath"><SystemProperty name="JETTY_HOME" />/extJars/*.jar</Set>
</Configure> doesn't work -->
<!-- <Set name="extraClasspath"><SystemProperty name="JETTY_HOME" />/extJars</Set>
</Configure> doesn't work -->
You have 3 choices.
1: Use Parent Loader Priority
In embedded, tell the WebAppContext to use parent loader priority. (this will favor server classes over webapp classes)
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/example</Set>
<Set name="war">example.war</Set>
<Set name="parentLoaderPriority">true</Set>
</Configure>
This does have a side effect that if the server class does any caching of information (a common technique in many libraries) then that cache is now available for all webapps.
2: Use A Custom DeploymentManager Binding for managing WebAppClassloader
Each WebAppContext uses a WebAppClassloader that can be configured to:
- Expose certain classes from server classloader
- Establish what the WebAppContext does if there is a conflict between the webapp and the server.
Since you are using the DeploymentManager
, this can be standardized via the Bindings techniques.
The Binding itself: ExposeServerCommonBinding.java
package jetty;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
public class ExposeServerCommonBinding implements AppLifeCycle.Binding
{
public String[] getBindingTargets()
{
return new String[]
{ "deploying" };
}
public void processBinding(Node node, App app) throws Exception
{
ContextHandler handler = app.getContextHandler();
if (handler == null)
{
throw new NullPointerException("No Handler created for App: " + app);
}
if (handler instanceof WebAppContext)
{
WebAppContext webapp = (WebAppContext)handler;
// System classes (or namespaces) present in server classloader to expose to webapp
webapp.addSystemClass("org.apache.log4j.");
webapp.addSystemClass("org.slf4j.");
webapp.addSystemClass("org.apache.commons.logging.");
// Server classes that cannot be overridden by webapp
webapp.addServerClass("-org.apache.log4j.");
webapp.addServerClass("-org.slf4j.");
webapp.addServerClass("-org.apache.commons.logging.");
}
}
}
And how to use it
DeploymentManager mgr = new DeploymentManager();
WebAppProvider provider = new WebAppProvider();
provider.setMonitoredDirResource(Resource.newResource(new File("./webapps/")));
mgr.addAppProvider(provider);
mgr.addLifeCycleBinding(new ExposeServerCommonBinding());
This technique will apply to all WebAppContexts that are deployed via the DeploymentManager, allowing you to apply these rules equally to all webapps.
3: Use A Custom DeploymentManager Binding for managing extraClasspath
Here's another Binding alternative you can use to pre-build the extraClasspath at the server side, and when a WebApp is deployed, it automatically adds these extraClasspath to the WebApp.
package jetty;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
public class CommonExtraClasspathBinding implements AppLifeCycle.Binding
{
private List<File> extraClasspath = new ArrayList<>();
public String[] getBindingTargets()
{
return new String[] { "deploying" };
}
public void addAllJars(File dir)
{
for (File file : dir.listFiles())
{
if (!file.isFile())
{
continue;
}
if (file.getName().toLowerCase(Locale.ENGLISH).equals(".jar"))
{
addJar(file);
}
}
}
public void addJar(File jar)
{
if (jar.exists() && jar.isFile())
{
extraClasspath.add(jar);
}
}
public void processBinding(Node node, App app) throws Exception
{
ContextHandler handler = app.getContextHandler();
if (handler == null)
{
throw new NullPointerException("No Handler created for App: " + app);
}
if (handler instanceof WebAppContext)
{
WebAppContext webapp = (WebAppContext)handler;
StringBuilder xtraCp = new StringBuilder();
boolean delim = false;
for (File cp : extraClasspath)
{
if (delim)
{
xtraCp.append(File.pathSeparatorChar);
}
xtraCp.append(cp.getAbsolutePath());
delim = true;
}
webapp.setExtraClasspath(xtraCp.toString());
}
}
}