/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.jetty.deployment;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.transaction.UserTransaction;
import org.apache.geronimo.deployment.DeploymentException;
import org.apache.geronimo.deployment.service.GBeanHelper;
import org.apache.geronimo.deployment.util.DeploymentUtil;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.geronimo.gbean.jmx.GBeanMBean;
import org.apache.geronimo.j2ee.deployment.EARContext;
import org.apache.geronimo.j2ee.deployment.Module;
import org.apache.geronimo.j2ee.deployment.ModuleBuilder;
import org.apache.geronimo.j2ee.deployment.WebModule;
import org.apache.geronimo.j2ee.j2eeobjectnames.J2eeContext;
import org.apache.geronimo.j2ee.j2eeobjectnames.J2eeContextImpl;
import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
import org.apache.geronimo.jetty.JettyClassLoader;
import org.apache.geronimo.jetty.JettyWebAppContext;
import org.apache.geronimo.jetty.JettyWebAppJACCContext;
import org.apache.geronimo.naming.deployment.ENCConfigBuilder;
import org.apache.geronimo.naming.deployment.GBeanResourceEnvironmentBuilder;
import org.apache.geronimo.naming.java.ReadOnlyContext;
import org.apache.geronimo.schema.SchemaConversionUtils;
import org.apache.geronimo.security.deploy.Security;
import org.apache.geronimo.security.deployment.SecurityBuilder;
import org.apache.geronimo.transaction.OnlineUserTransaction;
import org.apache.geronimo.xbeans.geronimo.jetty.JettyDependencyType;
import org.apache.geronimo.xbeans.geronimo.jetty.JettyGbeanType;
import org.apache.geronimo.xbeans.geronimo.jetty.JettyWebAppDocument;
import org.apache.geronimo.xbeans.geronimo.jetty.JettyWebAppType;
import org.apache.geronimo.xbeans.j2ee.FilterMappingType;
import org.apache.geronimo.xbeans.j2ee.SecurityConstraintType;
import org.apache.geronimo.xbeans.j2ee.ServletMappingType;
import org.apache.geronimo.xbeans.j2ee.UrlPatternType;
import org.apache.geronimo.xbeans.j2ee.WebAppDocument;
import org.apache.geronimo.xbeans.j2ee.WebAppType;
import org.apache.geronimo.xbeans.j2ee.WebResourceCollectionType;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
/**
* @version $Rev: 57149 $ $Date: 2004-11-10 01:18:35 -0600 (Wed, 10 Nov 2004) $
*/
public class JettyModuleBuilder implements ModuleBuilder {
private final URI defaultParentId;
public JettyModuleBuilder(URI defaultParentId) {
this.defaultParentId = defaultParentId;
}
public Module createModule(File plan, JarFile moduleFile) throws DeploymentException {
return createModule(plan, moduleFile, "war", null, true);
}
public Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, URI earConfigId) throws DeploymentException {
return createModule(plan, moduleFile, targetPath, specDDUrl, false);
}
private Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, boolean standAlone) throws DeploymentException {
assert moduleFile != null: "moduleFile is null";
assert targetPath != null: "targetPath is null";
assert !targetPath.endsWith("/"): "targetPath must not end with a '/'";
// parse the spec dd
String specDD;
WebAppType webApp;
try {
if (specDDUrl == null) {
specDDUrl = DeploymentUtil.createJarURL(moduleFile, "WEB-INF/web.xml");
}
// read in the entire specDD as a string, we need this for getDeploymentDescriptor
// on the J2ee management object
specDD = DeploymentUtil.readAll(specDDUrl);
// parse it
XmlObject parsed = SchemaConversionUtils.parse(specDD);
WebAppDocument webAppDoc = SchemaConversionUtils.convertToServletSchema(parsed);
webApp = webAppDoc.getWebApp();
} catch (XmlException xmle) {
throw new DeploymentException("Error parsing web.xml", xmle);
} catch (Exception e) {
return null;
}
check(webApp);
// parse vendor dd
JettyWebAppType jettyWebApp = getJettyWebApp(plan, moduleFile, standAlone, targetPath, webApp);
// get the ids from either the application plan or for a stand alone module from the specific deployer
URI configId = null;
try {
configId = new URI(jettyWebApp.getConfigId());
} catch (URISyntaxException e) {
throw new DeploymentException("Invalid configId " + jettyWebApp.getConfigId(), e);
}
URI parentId = null;
if (jettyWebApp.isSetParentId()) {
try {
parentId = new URI(jettyWebApp.getParentId());
} catch (URISyntaxException e) {
throw new DeploymentException("Invalid parentId " + jettyWebApp.getParentId(), e);
}
} else {
parentId = defaultParentId;
}
WebModule module = new WebModule(standAlone, configId, parentId, moduleFile, targetPath, webApp, jettyWebApp, specDD);
module.setContextRoot(jettyWebApp.getContextRoot());
return module;
}
JettyWebAppType getJettyWebApp(Object plan, JarFile moduleFile, boolean standAlone, String targetPath, WebAppType webApp) throws DeploymentException {
JettyWebAppType jettyWebApp = null;
try {
// load the geronimo-jetty.xml from either the supplied plan or from the earFile
try {
if (plan instanceof XmlObject) {
jettyWebApp = (JettyWebAppType) SchemaConversionUtils.getNestedObjectAsType((XmlObject) plan,
"web-app",
JettyWebAppType.type);
} else {
JettyWebAppDocument jettyWebAppdoc = null;
if (plan != null) {
jettyWebAppdoc = JettyWebAppDocument.Factory.parse((File) plan);
} else {
URL path = DeploymentUtil.createJarURL(moduleFile, "WEB-INF/geronimo-jetty.xml");
jettyWebAppdoc = JettyWebAppDocument.Factory.parse(path);
}
if (jettyWebAppdoc != null) {
jettyWebApp = jettyWebAppdoc.getWebApp();
}
}
} catch (IOException e) {
}
// if we got one extract and validate it otherwise create a default one
if (jettyWebApp != null) {
jettyWebApp = (JettyWebAppType) SchemaConversionUtils.convertToGeronimoNamingSchema(jettyWebApp);
jettyWebApp = (JettyWebAppType) SchemaConversionUtils.convertToGeronimoSecuritySchema(jettyWebApp);
SchemaConversionUtils.validateDD(jettyWebApp);
} else {
String path;
if (standAlone) {
// default configId is based on the moduleFile name
path = new File(moduleFile.getName()).getName();
} else {
// default configId is based on the module uri from the application.xml
path = targetPath;
}
jettyWebApp = createDefaultPlan(path, webApp);
}
} catch (XmlException e) {
throw new DeploymentException(e);
}
return jettyWebApp;
}
private JettyWebAppType createDefaultPlan(String path, WebAppType webApp) {
String id = webApp.getId();
if (id == null) {
id = path;
if (id.endsWith(".war")) {
id = id.substring(0, id.length() - 4);
}
if (id.endsWith("/")) {
id = id.substring(0, id.length() - 1);
}
}
JettyWebAppType jettyWebApp = JettyWebAppType.Factory.newInstance();
// set the parentId, configId and context root
jettyWebApp.setParentId(defaultParentId.toString());
if (null != webApp.getId()) {
id = webApp.getId();
}
jettyWebApp.setConfigId(id);
jettyWebApp.setContextRoot(id);
return jettyWebApp;
}
public void installModule(JarFile earFile, EARContext earContext, Module module) throws DeploymentException {
try {
URI baseDir = URI.create(module.getTargetPath() + "/");
// add the warfile's content to the configuration
JarFile warFile = module.getModuleFile();
Enumeration entries = warFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
URI targetPath = baseDir.resolve(entry.getName());
if (entry.getName().equals("WEB-INF/web.xml")) {
// TODO gets rid of these tests when Jetty will use the serialized Geronimo DD.
earContext.addFile(targetPath, module.getOriginalSpecDD());
} else {
earContext.addFile(targetPath, warFile, entry);
}
}
// add the manifest classpath entries declared in the war to the class loader
// we have to explicitly add these since we are unpacking the web module
// and the url class loader will not pick up a manifiest from an unpacked dir
earContext.addManifestClassPath(warFile, URI.create(module.getTargetPath()));
// add the dependencies declared in the geronimo-jetty.xml file
JettyWebAppType jettyWebApp = (JettyWebAppType) module.getVendorDD();
JettyDependencyType[] dependencies = jettyWebApp.getDependencyArray();
for (int i = 0; i < dependencies.length; i++) {
earContext.addDependency(getDependencyURI(dependencies[i]));
}
} catch (IOException e) {
throw new DeploymentException("Problem deploying war", e);
}
}
public void initContext(EARContext earContext, Module module, ClassLoader cl) {
// web application do not add anything to the shared context
}
public String addGBeans(EARContext earContext, Module module, ClassLoader cl) throws DeploymentException {
J2eeContext earJ2eeContext = earContext.getJ2eeContext();
J2eeContext moduleJ2eeContext = new J2eeContextImpl(earJ2eeContext.getJ2eeDomainName(), earJ2eeContext.getJ2eeServerName(), earJ2eeContext.getJ2eeApplicationName(), module.getName(), null, null);
WebModule webModule = (WebModule) module;
WebAppType webApp = (WebAppType) webModule.getSpecDD();
JettyWebAppType jettyWebApp = (JettyWebAppType) webModule.getVendorDD();
// construct the webClassLoader
URI[] webClassPath = getWebClassPath(earContext, webModule);
URI baseUri = earContext.getTargetFile(URI.create(webModule.getTargetPath() + "/")).toURI();
URL[] webClassPathURLs = new URL[webClassPath.length];
for (int i = 0; i < webClassPath.length; i++) {
URI path = baseUri.resolve(webClassPath[i]);
try {
webClassPathURLs[i] = path.toURL();
} catch (MalformedURLException e) {
throw new DeploymentException("Invalid web class path element: path=" + path + ", baseUri=" + baseUri);
}
}
boolean contextPriorityClassLoader = false;
if (jettyWebApp != null) {
contextPriorityClassLoader = Boolean.valueOf(jettyWebApp.getContextPriorityClassloader()).booleanValue();
}
ClassLoader webClassLoader = new JettyClassLoader(webClassPathURLs, cl, contextPriorityClassLoader);
if (jettyWebApp != null) {
JettyGbeanType[] gbeans = jettyWebApp.getGbeanArray();
for (int i = 0; i < gbeans.length; i++) {
GBeanHelper.addGbean(new JettyGBeanAdapter(gbeans[i]), webClassLoader, earContext);
}
}
ObjectName webModuleName = null;
try {
webModuleName = NameFactory.getModuleName(null, null, null, null, NameFactory.WEB_MODULE, moduleJ2eeContext);
} catch (MalformedObjectNameException e) {
throw new DeploymentException("Could not construct module name", e);
}
UserTransaction userTransaction = new OnlineUserTransaction();
ReadOnlyContext compContext = buildComponentContext(earContext, webModule, webApp, jettyWebApp, userTransaction, webClassLoader);
Security security = SecurityBuilder.buildSecurityConfig(jettyWebApp.getSecurity());
GBeanMBean gbean;
try {
if (security == null) {
gbean = new GBeanMBean(JettyWebAppContext.GBEAN_INFO, webClassLoader);
} else {
gbean = new GBeanMBean(JettyWebAppJACCContext.GBEAN_INFO, webClassLoader);
gbean.setAttribute("securityConfig", security);
String policyContextID;
if (earContext.getApplicationObjectName() == null) {
policyContextID = module.getName();
} else {
policyContextID = earContext.getApplicationObjectName().toString();
}
gbean.setAttribute("policyContextID", policyContextID);
}
gbean.setAttribute("uri", URI.create(module.getTargetPath() + "/"));
gbean.setAttribute("componentContext", compContext);
gbean.setAttribute("userTransaction", userTransaction);
gbean.setAttribute("webClassPath", webClassPath);
// unsharableResources, applicationManagedSecurityResources
GBeanResourceEnvironmentBuilder rebuilder = new GBeanResourceEnvironmentBuilder(gbean);
ENCConfigBuilder.setResourceEnvironment(earContext, webModule.getModuleURI(), rebuilder, webApp.getResourceRefArray(), jettyWebApp.getResourceRefArray());
gbean.setAttribute("contextPath", webModule.getContextRoot());
gbean.setAttribute("contextPriorityClassLoader", Boolean.valueOf(contextPriorityClassLoader));
gbean.setReferencePattern("TransactionContextManager", earContext.getTransactionContextManagerObjectName());
gbean.setReferencePattern("TrackedConnectionAssociator", earContext.getConnectionTrackerObjectName());
gbean.setReferencePattern("JettyContainer", new ObjectName("*:type=WebContainer,container=Jetty")); // @todo configurable
} catch (Exception e) {
throw new DeploymentException("Unable to initialize webapp GBean", e);
}
earContext.addGBean(webModuleName, gbean);
return null;
}
private static URI[] getWebClassPath(EARContext earContext, WebModule webModule) {
LinkedList webClassPath = new LinkedList();
File baseDir = earContext.getTargetFile(URI.create(webModule.getTargetPath() + "/"));
File webInfDir = new File(baseDir, "WEB-INF");
// check for a classes dir
File classesDir = new File(webInfDir, "classes");
if (classesDir.isDirectory()) {
webClassPath.add(URI.create("WEB-INF/classes/"));
}
// add all of the libs
File libDir = new File(webInfDir, "lib");
if (libDir.isDirectory()) {
File[] libs = libDir.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".jar");
}
});
if (libs != null) {
for (int i = 0; i < libs.length; i++) {
File lib = libs[i];
webClassPath.add(URI.create("WEB-INF/lib/" + lib.getName()));
}
}
}
return (URI[]) webClassPath.toArray(new URI[webClassPath.size()]);
}
private ReadOnlyContext buildComponentContext(EARContext earContext, WebModule webModule, WebAppType webApp, JettyWebAppType jettyWebApp, UserTransaction userTransaction, ClassLoader cl) throws DeploymentException {
return ENCConfigBuilder.buildComponentContext(earContext,
webModule.getModuleURI(),
userTransaction,
webApp.getEnvEntryArray(),
webApp.getEjbRefArray(), jettyWebApp.getEjbRefArray(),
webApp.getEjbLocalRefArray(), jettyWebApp.getEjbLocalRefArray(),
webApp.getResourceRefArray(), jettyWebApp.getResourceRefArray(),
webApp.getResourceEnvRefArray(), jettyWebApp.getResourceEnvRefArray(),
webApp.getMessageDestinationRefArray(),
cl);
}
private URI getDependencyURI(JettyDependencyType dep) throws DeploymentException {
URI uri;
if (dep.isSetUri()) {
try {
uri = new URI(dep.getUri());
} catch (URISyntaxException e) {
throw new DeploymentException("Invalid dependency URI " + dep.getUri(), e);
}
} else {
// @todo support more than just jars
String id = dep.getGroupId() + "/jars/" + dep.getArtifactId() + '-' + dep.getVersion() + ".jar";
try {
uri = new URI(id);
} catch (URISyntaxException e) {
throw new DeploymentException("Unable to construct URI for groupId=" + dep.getGroupId() + ", artifactId=" + dep.getArtifactId() + ", version=" + dep.getVersion(), e);
}
}
return uri;
}
private static void check(WebAppType webApp) throws DeploymentException {
checkURLPattern(webApp);
checkMultiplicities(webApp);
}
private static void checkURLPattern(WebAppType webApp) throws DeploymentException {
FilterMappingType[] filterMappings = webApp.getFilterMappingArray();
for (int i = 0; i < filterMappings.length; i++) {
if (filterMappings[i].isSetUrlPattern()) {
checkString(filterMappings[i].getUrlPattern().getStringValue());
}
}
ServletMappingType[] servletMappings = webApp.getServletMappingArray();
for (int i = 0; i < servletMappings.length; i++) {
checkString(servletMappings[i].getUrlPattern().getStringValue());
}
SecurityConstraintType[] constraints = webApp.getSecurityConstraintArray();
for (int i = 0; i < constraints.length; i++) {
WebResourceCollectionType[] collections = constraints[i].getWebResourceCollectionArray();
for (int j = 0; j < collections.length; j++) {
UrlPatternType[] patterns = collections[j].getUrlPatternArray();
for (int k = 0; k < patterns.length; k++) {
checkString(patterns[k].getStringValue());
}
}
}
}
private static void checkString(String pattern) throws DeploymentException {
//j2ee_1_4.xsd explicitly requires preserving all whitespace. Do not trim.
if (pattern.indexOf(0x0D) >= 0) throw new DeploymentException("<url-pattern> must not contain CR(#xD)");
if (pattern.indexOf(0x0A) >= 0) throw new DeploymentException("<url-pattern> must not contain LF(#xA)");
}
private static void checkMultiplicities(WebAppType webApp) throws DeploymentException {
if (webApp.getSessionConfigArray().length > 1) throw new DeploymentException("Multiple <session-config> elements found");
if (webApp.getJspConfigArray().length > 1) throw new DeploymentException("Multiple <jsp-config> elements found");
if (webApp.getLoginConfigArray().length > 1) throw new DeploymentException("Multiple <login-config> elements found");
}
public static final GBeanInfo GBEAN_INFO;
static {
GBeanInfoBuilder infoBuilder = new GBeanInfoBuilder(JettyModuleBuilder.class);
infoBuilder.addAttribute("defaultParentId", URI.class, true);
infoBuilder.addInterface(ModuleBuilder.class);
infoBuilder.setConstructor(new String[] {"defaultParentId"});
GBEAN_INFO = infoBuilder.getBeanInfo();
}
public static GBeanInfo getGBeanInfo() {
return GBEAN_INFO;
}
}