org.apache.catalina.loader.WebappLoader Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.catalina.loader;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import javax.management.ObjectName;
import javax.servlet.ServletContext;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Loader;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;
/**
* Classloader implementation which is specialized for handling web
* applications in the most efficient way, while being Catalina aware (all
* accesses to resources are made through
* {@link org.apache.catalina.WebResourceRoot}).
* This class loader supports detection of modified
* Java classes, which can be used to implement auto-reload support.
*
* This class loader is configured by adding the pathnames of directories,
* JAR files, and ZIP files with the addRepository()
method,
* prior to calling start()
. When a new class is required,
* these repositories will be consulted first to locate the class. If it
* is not present, the system class loader will be used instead.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class WebappLoader extends LifecycleMBeanBase
implements Loader, PropertyChangeListener {
// ----------------------------------------------------------- Constructors
/**
* Construct a new WebappLoader with no defined parent class loader
* (so that the actual parent will be the system class loader).
*/
public WebappLoader() {
this(null);
}
/**
* Construct a new WebappLoader with the specified class loader
* to be defined as the parent of the ClassLoader we ultimately create.
*
* @param parent The parent class loader
*/
public WebappLoader(ClassLoader parent) {
super();
this.parentClassLoader = parent;
}
// ----------------------------------------------------- Instance Variables
/**
* The class loader being managed by this Loader component.
*/
private WebappClassLoader classLoader = null;
/**
* The Context with which this Loader has been associated.
*/
private Context context = null;
/**
* The "follow standard delegation model" flag that will be used to
* configure our ClassLoader.
*/
private boolean delegate = false;
/**
* The Java class name of the ClassLoader implementation to be used.
* This class should extend WebappClassLoader, otherwise, a different
* loader implementation must be used.
*/
private String loaderClass =
"org.apache.catalina.loader.WebappClassLoader";
/**
* The parent class loader of the class loader we will create.
*/
private ClassLoader parentClassLoader = null;
/**
* The reloadable flag for this Loader.
*/
private boolean reloadable = false;
/**
* The string manager for this package.
*/
protected static final StringManager sm =
StringManager.getManager(Constants.Package);
/**
* The property change support for this component.
*/
protected final PropertyChangeSupport support = new PropertyChangeSupport(this);
/**
* Classpath set in the loader.
*/
private String classpath = null;
// ------------------------------------------------------------- Properties
/**
* Return the Java class loader to be used by this Container.
*/
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public Context getContext() {
return context;
}
@Override
public void setContext(Context context) {
if (this.context == context) {
return;
}
if (getState().isAvailable()) {
throw new IllegalStateException(
sm.getString("webappLoader.setContext.ise"));
}
// Deregister from the old Context (if any)
if (this.context != null) {
this.context.removePropertyChangeListener(this);
}
// Process this property change
Context oldContext = this.context;
this.context = context;
support.firePropertyChange("context", oldContext, this.context);
// Register with the new Container (if any)
if (this.context != null) {
setReloadable(this.context.getReloadable());
this.context.addPropertyChangeListener(this);
}
}
/**
* Return the "follow standard delegation model" flag used to configure
* our ClassLoader.
*/
@Override
public boolean getDelegate() {
return this.delegate;
}
/**
* Set the "follow standard delegation model" flag used to configure
* our ClassLoader.
*
* @param delegate The new flag
*/
@Override
public void setDelegate(boolean delegate) {
boolean oldDelegate = this.delegate;
this.delegate = delegate;
support.firePropertyChange("delegate", Boolean.valueOf(oldDelegate),
Boolean.valueOf(this.delegate));
}
/**
* Return the ClassLoader class name.
*/
public String getLoaderClass() {
return (this.loaderClass);
}
/**
* Set the ClassLoader class name.
*
* @param loaderClass The new ClassLoader class name
*/
public void setLoaderClass(String loaderClass) {
this.loaderClass = loaderClass;
}
/**
* Return the reloadable flag for this Loader.
*/
@Override
public boolean getReloadable() {
return this.reloadable;
}
/**
* Set the reloadable flag for this Loader.
*
* @param reloadable The new reloadable flag
*/
@Override
public void setReloadable(boolean reloadable) {
// Process this property change
boolean oldReloadable = this.reloadable;
this.reloadable = reloadable;
support.firePropertyChange("reloadable",
Boolean.valueOf(oldReloadable),
Boolean.valueOf(this.reloadable));
}
// --------------------------------------------------------- Public Methods
/**
* Add a property change listener to this component.
*
* @param listener The listener to add
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public void backgroundProcess() {
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}
public String[] getLoaderRepositories() {
if (classLoader == null) {
return new String[0];
}
URL[] urls = classLoader.getURLs();
String[] result = new String[urls.length];
for (int i = 0; i < urls.length; i++) {
result[i] = urls[i].toExternalForm();
}
return result;
}
public String getLoaderRepositoriesString() {
String repositories[]=getLoaderRepositories();
StringBuilder sb=new StringBuilder();
for( int i=0; i clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
return classLoader;
}
/**
* Configure associated class loader permissions.
*/
private void setPermissions() {
if (!Globals.IS_SECURITY_ENABLED)
return;
if (context == null)
return;
// Tell the class loader the root of the context
ServletContext servletContext = context.getServletContext();
// Assigning permissions for the work directory
File workDir =
(File) servletContext.getAttribute(ServletContext.TEMPDIR);
if (workDir != null) {
try {
String workDirPath = workDir.getCanonicalPath();
classLoader.addPermission
(new FilePermission(workDirPath, "read,write"));
classLoader.addPermission
(new FilePermission(workDirPath + File.separator + "-",
"read,write,delete"));
} catch (IOException e) {
// Ignore
}
}
for (URL url : context.getResources().getBaseUrls()) {
classLoader.addPermission(url);
}
}
/**
* Set the appropriate context attribute for our class path. This
* is required only because Jasper depends on it.
*/
private void setClassPath() {
// Validate our current state information
if (context == null)
return;
ServletContext servletContext = context.getServletContext();
if (servletContext == null)
return;
StringBuilder classpath = new StringBuilder();
// Assemble the class path information from our class loader chain
ClassLoader loader = getClassLoader();
if (delegate && loader != null) {
// Skip the webapp loader for now as delegation is enabled
loader = loader.getParent();
}
while (loader != null) {
if (!buildClassPath(classpath, loader)) {
break;
}
loader = loader.getParent();
}
if (delegate) {
// Delegation was enabled, go back and add the webapp paths
loader = getClassLoader();
if (loader != null) {
buildClassPath(classpath, loader);
}
}
this.classpath = classpath.toString();
// Store the assembled class path as a servlet context attribute
servletContext.setAttribute(Globals.CLASS_PATH_ATTR, this.classpath);
}
private boolean buildClassPath(StringBuilder classpath, ClassLoader loader) {
if (loader instanceof URLClassLoader) {
URL repositories[] =
((URLClassLoader) loader).getURLs();
for (int i = 0; i < repositories.length; i++) {
String repository = repositories[i].toString();
if (repository.startsWith("file://"))
repository = utf8Decode(repository.substring(7));
else if (repository.startsWith("file:"))
repository = utf8Decode(repository.substring(5));
else
continue;
if (repository == null)
continue;
if (classpath.length() > 0)
classpath.append(File.pathSeparator);
classpath.append(repository);
}
} else {
String cp = getClasspath(loader);
if (cp == null) {
log.info( "Unknown loader " + loader + " " + loader.getClass());
} else {
if (classpath.length() > 0)
classpath.append(File.pathSeparator);
classpath.append(cp);
}
return false;
}
return true;
}
private String utf8Decode(String input) {
String result = null;
try {
result = URLDecoder.decode(input, "UTF-8");
} catch (UnsupportedEncodingException uee) {
// Impossible. All JVMs are required to support UTF-8.
}
return result;
}
// try to extract the classpath from a loader that is not URLClassLoader
private String getClasspath( ClassLoader loader ) {
try {
Method m=loader.getClass().getMethod("getClasspath", new Class[] {});
if( log.isTraceEnabled())
log.trace("getClasspath " + m );
if( m==null ) return null;
Object o=m.invoke( loader, new Object[] {} );
if( log.isDebugEnabled() )
log.debug("gotClasspath " + o);
if( o instanceof String )
return (String)o;
return null;
} catch( Exception ex ) {
Throwable t = ExceptionUtils.unwrapInvocationTargetException(ex);
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled())
log.debug("getClasspath ", ex);
}
return null;
}
private static final org.apache.juli.logging.Log log=
org.apache.juli.logging.LogFactory.getLog( WebappLoader.class );
@Override
protected String getDomainInternal() {
return context.getDomain();
}
@Override
protected String getObjectNameKeyProperties() {
StringBuilder name = new StringBuilder("type=Loader");
name.append(",host=");
name.append(context.getParent().getName());
name.append(",context=");
String contextName = context.getName();
if (!contextName.startsWith("/")) {
name.append("/");
}
name.append(contextName);
return name.toString();
}
}