001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.servlet;
018:
019: import java.io.File;
020: import java.io.FileReader;
021: import java.io.FilenameFilter;
022: import java.io.IOException;
023: import java.io.LineNumberReader;
024: import java.lang.reflect.Constructor;
025: import java.lang.reflect.InvocationTargetException;
026: import java.net.MalformedURLException;
027: import java.net.URL;
028: import java.util.ArrayList;
029: import java.util.List;
030:
031: import javax.servlet.Servlet;
032: import javax.servlet.ServletConfig;
033: import javax.servlet.ServletException;
034: import javax.servlet.ServletRequest;
035: import javax.servlet.ServletResponse;
036: import javax.servlet.http.HttpServlet;
037:
038: /**
039: * This servlet builds a classloading sandbox and runs another servlet inside
040: * that sandbox. The purpose is to shield the libraries and classes shipped with
041: * the web application from any other classes with the same name that may exist
042: * in the system, such as Xerces and Xalan versions included in JDK 1.4.
043: * <p>
044: * This servlet propagates all initialisation parameters to the sandboxed
045: * servlet, and accepts the parameters <code>servlet-class</code> and
046: * <code>paranoid-classpath</code>.
047: * <ul>
048: * <li><code>servlet-class</code> defines the sandboxed servlet class, the
049: * default is {@link CocoonServlet}
050: * <li><code>paranoid-classpath</code> expects the name of a text file that
051: * can contain lines begining with
052: * <code>class-dir:<code> (directory containing classes),
053: * <code>lib-dir:<code> (directory containing JAR or ZIP libraries) and <code>#</code>
054: * (for comments). <br/>
055: * All other lines are considered as URLs.
056: * <br/>
057: * It is also possible to use a the pseudo protocol prefix<code>context:/<code> which
058: * is resolved to the basedir of the servlet context.
059: * </ul>
060: *
061: * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
062: * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
063: * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
064: * @version CVS $Id: ParanoidCocoonServlet.java 433543 2006-08-22 06:22:54Z crossley $
065: */
066:
067: public class ParanoidCocoonServlet extends HttpServlet {
068:
069: /**
070: * The name of the actual servlet class.
071: */
072: public static final String DEFAULT_SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet";
073:
074: protected static final String CONTEXT_PREFIX = "context:";
075:
076: protected static final String FILE_PREFIX = "file:";
077:
078: protected Servlet servlet;
079:
080: protected ClassLoader classloader;
081:
082: public void init(ServletConfig config) throws ServletException {
083:
084: super .init(config);
085:
086: // Create the classloader in which we will load the servlet
087: // this can either be specified by an external file configured
088: // as a parameter in web.xml or (the default) all jars and
089: // classes from WEB-INF/lib and WEB-INF/classes are used.
090: final String externalClasspath = config
091: .getInitParameter("paranoid-classpath");
092: final URL[] classPath = (externalClasspath == null) ? getClassPath(getContextDir())
093: : getClassPath(externalClasspath, getContextDir());
094:
095: final String classLoaderName = config
096: .getInitParameter("classloader-class");
097: if (classLoaderName != null) {
098: log("Using classloader " + classLoaderName);
099: }
100: this .classloader = createClassLoader(classLoaderName, classPath);
101:
102: String servletName = config.getInitParameter("servlet-class");
103: if (servletName == null) {
104: servletName = DEFAULT_SERVLET_CLASS;
105: }
106: log("Loading servlet class " + servletName);
107:
108: // Create the servlet
109: try {
110:
111: Class servletClass = this .classloader
112: .loadClass(servletName);
113: this .servlet = (Servlet) servletClass.newInstance();
114:
115: } catch (Exception e) {
116: throw new ServletException("Cannot load servlet "
117: + servletName, e);
118: }
119:
120: // Always set the context classloader. JAXP uses it to find a
121: // ParserFactory,
122: // and thus fails if it's not set to the webapp classloader.
123: final ClassLoader old = Thread.currentThread()
124: .getContextClassLoader();
125: try {
126: Thread.currentThread().setContextClassLoader(
127: this .classloader);
128:
129: // Inlitialize the actual servlet
130: this .initServlet();
131: } finally {
132: Thread.currentThread().setContextClassLoader(old);
133: }
134:
135: }
136:
137: /**
138: * Initialize the wrapped servlet. Subclasses (see {@link BootstrapServlet}
139: * change the <code>ServletConfig</code> given to the servlet.
140: *
141: * @throws ServletException
142: */
143: protected void initServlet() throws ServletException {
144: this .servlet.init(this .getServletConfig());
145: }
146:
147: /**
148: * Get the web application context directory.
149: *
150: * @return the context dir
151: * @throws ServletException
152: */
153: protected File getContextDir() throws ServletException {
154: String result = getServletContext().getRealPath("/");
155: if (result == null) {
156: throw new ServletException(this .getClass().getName()
157: + " cannot run in an undeployed WAR file");
158: }
159: return new File(result);
160: }
161:
162: protected URL[] getClassPath(final File contextDir)
163: throws ServletException {
164: List urlList = new ArrayList();
165:
166: try {
167: File classDir = new File(contextDir + "/WEB-INF/classes");
168: if (classDir.exists()) {
169: if (!classDir.isDirectory()) {
170: throw new ServletException(classDir
171: + " exists but is not a directory");
172: }
173:
174: URL classURL = classDir.toURL();
175: log("Adding class directory " + classURL);
176: urlList.add(classURL);
177:
178: }
179:
180: // List all .jar and .zip
181: File libDir = new File(contextDir + "/WEB-INF/lib");
182: File[] libraries = libDir.listFiles(new JarFileFilter());
183:
184: for (int i = 0; i < libraries.length; i++) {
185: URL lib = libraries[i].toURL();
186: log("Adding class library " + lib);
187: urlList.add(lib);
188: }
189: } catch (MalformedURLException mue) {
190: throw new ServletException(mue);
191: }
192:
193: URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
194:
195: return urls;
196: }
197:
198: protected URL[] getClassPath(final String externalClasspath,
199: final File contextDir) throws ServletException {
200: final List urlList = new ArrayList();
201:
202: File file = new File(externalClasspath);
203: if (!file.isAbsolute()) {
204: file = new File(contextDir, externalClasspath);
205: }
206:
207: log("Adding classpath from " + file);
208: try {
209: FileReader fileReader = new FileReader(file);
210: LineNumberReader lineReader = new LineNumberReader(
211: fileReader);
212:
213: String line;
214: do {
215: line = lineReader.readLine();
216: if (line != null) {
217: if (line.startsWith("class-dir:")) {
218: line = line.substring("class-dir:".length())
219: .trim();
220: if (line.startsWith(CONTEXT_PREFIX)) {
221: line = contextDir
222: + line.substring(CONTEXT_PREFIX
223: .length());
224: }
225: URL url = new File(line).toURL();
226: log("Adding class directory " + url);
227: urlList.add(url);
228:
229: } else if (line.startsWith("lib-dir:")) {
230: line = line.substring("lib-dir:".length())
231: .trim();
232: if (line.startsWith(CONTEXT_PREFIX)) {
233: line = contextDir
234: + line.substring(CONTEXT_PREFIX
235: .length());
236: }
237: File dir = new File(line);
238: File[] libraries = dir
239: .listFiles(new JarFileFilter());
240: log("Adding " + libraries.length
241: + " libraries from " + dir.toURL());
242: for (int i = 0; i < libraries.length; i++) {
243: URL url = libraries[i].toURL();
244: urlList.add(url);
245: }
246: } else if (line.startsWith("#")) {
247: // skip it (consider it as comment)
248: } else {
249: // Consider it as a URL
250: final URL lib;
251: if (line.startsWith(CONTEXT_PREFIX)) {
252: line = FILE_PREFIX
253: + "/"
254: + contextDir
255: + line.substring(
256: CONTEXT_PREFIX.length())
257: .trim();
258: }
259: if (line.indexOf(':') == -1) {
260: File entry = new File(line);
261: lib = entry.toURL();
262: } else {
263: lib = new URL(line);
264: }
265: log("Adding class URL " + lib);
266: urlList.add(lib);
267: }
268: }
269: } while (line != null);
270: lineReader.close();
271: } catch (IOException io) {
272: throw new ServletException(io);
273: }
274:
275: URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
276:
277: return urls;
278: }
279:
280: protected ClassLoader createClassLoader(final String className,
281: final URL[] classPath) throws ServletException {
282: if (className != null) {
283: try {
284: final Class classLoaderClass = Class.forName(className);
285: final Class[] parameterClasses = new Class[] { ClassLoader.class };
286: final Constructor constructor = classLoaderClass
287: .getConstructor(parameterClasses);
288: final Object[] parameters = new Object[] { this
289: .getClass().getClassLoader() };
290: final ClassLoader classloader = (ClassLoader) constructor
291: .newInstance(parameters);
292: return classloader;
293: } catch (InstantiationException e) {
294: throw new ServletException("", e);
295: } catch (IllegalAccessException e) {
296: throw new ServletException("", e);
297: } catch (ClassNotFoundException e) {
298: throw new ServletException("", e);
299: } catch (SecurityException e) {
300: throw new ServletException("", e);
301: } catch (NoSuchMethodException e) {
302: throw new ServletException("", e);
303: } catch (IllegalArgumentException e) {
304: throw new ServletException("", e);
305: } catch (InvocationTargetException e) {
306: throw new ServletException("", e);
307: }
308: } else {
309: return ParanoidClassLoader.newInstance(classPath, this
310: .getClass().getClassLoader());
311: }
312: }
313:
314: /**
315: * Get the classloader that will be used to create the actual servlet. Its
316: * classpath is defined by the WEB-INF/classes and WEB-INF/lib directories
317: * in the context dir.
318: * @deprecated
319: */
320: protected ClassLoader getClassLoader(File contextDir)
321: throws ServletException {
322: return createClassLoader(null, getClassPath(contextDir));
323: }
324:
325: /**
326: * Get the classloader that will be used to create the actual servlet. Its
327: * classpath is defined by an external file.
328: * @deprecated
329: */
330: protected ClassLoader getClassLoader(
331: final String externalClasspath, final File contextDir)
332: throws ServletException {
333: return createClassLoader(null, getClassPath(externalClasspath,
334: contextDir));
335: }
336:
337: /**
338: * Service the request by delegating the call to the real servlet
339: */
340: public void service(ServletRequest request, ServletResponse response)
341: throws ServletException, IOException {
342:
343: final ClassLoader old = Thread.currentThread()
344: .getContextClassLoader();
345: try {
346: Thread.currentThread().setContextClassLoader(
347: this .classloader);
348: this .servlet.service(request, response);
349: } finally {
350: Thread.currentThread().setContextClassLoader(old);
351: }
352: }
353:
354: /**
355: * Destroy the actual servlet
356: */
357: public void destroy() {
358:
359: if (this .servlet != null) {
360: final ClassLoader old = Thread.currentThread()
361: .getContextClassLoader();
362: try {
363: Thread.currentThread().setContextClassLoader(
364: this .classloader);
365: this .servlet.destroy();
366: } finally {
367: Thread.currentThread().setContextClassLoader(old);
368: }
369: }
370:
371: super .destroy();
372: }
373:
374: private static class JarFileFilter implements FilenameFilter {
375: public boolean accept(File dir, String name) {
376: return name.endsWith(".zip") || name.endsWith(".jar");
377: }
378: }
379:
380: }
|