001: /*
002: * $Id: ServletClassLoader.java,v 1.19 2007/09/18 08:45:08 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.servlet.container;
008:
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.FileOutputStream;
012: import java.io.IOException;
013: import java.net.URL;
014: import java.net.URLClassLoader;
015: import java.util.ArrayList;
016: import java.util.List;
017: import java.util.StringTokenizer;
018: import java.util.jar.JarEntry;
019: import java.util.jar.JarInputStream;
020:
021: /**
022: * Class used to get the Servlet Class loader.
023: * The class loader returned is a child first class loader.
024: *
025: * @version $Revision: 1.19 $ $Date: 2007/09/18 08:45:08 $
026: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
027: */
028: public class ServletClassLoader {
029:
030: /**
031: * Use the current class loader to load the servlet and the libraries.
032: */
033: public static final int USE_CURRENT_CLASSPATH = 1;
034:
035: /**
036: * Load the Servlet code from the WAR file and use the current
037: * classpath for the libraries.
038: */
039: public static final int USE_CLASSPATH_LIB = 2;
040:
041: /**
042: * Load the servlet code from the WAR file and try to find the libraries
043: * in the same directory as this xins-common.jar and <:parent>/lib
044: * directory.
045: */
046: public static final int USE_XINS_LIB = 3;
047:
048: /**
049: * Load the servlet code and the libraries from the WAR file.
050: * This may take some time as the libraries need to be extracted from the
051: * WAR file.
052: */
053: public static final int USE_WAR_LIB = 4;
054:
055: /**
056: * Load the servlet code and the standard libraries from the CLASSPATH.
057: * Load the included external libraries from the WAR file.
058: */
059: public static final int USE_WAR_EXTERNAL_LIB = 5;
060:
061: /**
062: * Gest the class loader that will loader the servlet.
063: *
064: * @param warFile
065: * the WAR file containing the Servlet.
066: *
067: * @param mode
068: * the mode in which the servlet should be loaded. The possible values are
069: * <code>USE_CURRENT_CLASSPATH</code>, <code>USE_CLASSPATH_LIB</code>,
070: * <code>USE_XINS_LIB</code>, <code>USE_WAR_LIB</code>,
071: * <code>USE_WAR_EXTERNAL_LIB</code>.
072: *
073: * @return
074: * the Class loader to use to load the Servlet.
075: *
076: * @throws IOException
077: * if the file cannot be read or is incorrect.
078: */
079: public static ClassLoader getServletClassLoader(File warFile,
080: int mode) throws IOException {
081: if (mode == USE_CURRENT_CLASSPATH) {
082: return ServletClassLoader.class.getClassLoader();
083: }
084:
085: List urlList = new ArrayList();
086:
087: // Add the WAR file so that it can locate web pages included in the WAR file
088: urlList.add(warFile.toURL());
089:
090: if (mode != USE_WAR_EXTERNAL_LIB) {
091: URL classesURL = new URL("jar:file:"
092: + warFile.getAbsolutePath().replace(
093: File.separatorChar, '/')
094: + "!/WEB-INF/classes/");
095: urlList.add(classesURL);
096: }
097:
098: List standardLibs = new ArrayList();
099: if (mode == USE_XINS_LIB) {
100: String classLocation = ServletClassLoader.class
101: .getProtectionDomain().getCodeSource()
102: .getLocation().toString();
103: String commonJar = classLocation.substring(6).replace('/',
104: File.separatorChar);
105: if (!commonJar.endsWith("xins-common.jar")) {
106: String xinsHome = System.getenv("XINS_HOME");
107: commonJar = xinsHome + File.separator + "build"
108: + File.separator + "xins-common.jar";
109: }
110: File baseDir = new File(commonJar).getParentFile();
111: File[] xinsFiles = baseDir.listFiles();
112: for (int i = 0; i < xinsFiles.length; i++) {
113: if (xinsFiles[i].getName().endsWith(".jar")) {
114: urlList.add(xinsFiles[i].toURL());
115: }
116: }
117: File libDir = new File(baseDir, ".." + File.separator
118: + "lib");
119: File[] libFiles = libDir.listFiles();
120: for (int i = 0; i < libFiles.length; i++) {
121: if (libFiles[i].getName().endsWith(".jar")) {
122: urlList.add(libFiles[i].toURL());
123: }
124: }
125: }
126: if (mode == USE_CLASSPATH_LIB || mode == USE_WAR_EXTERNAL_LIB) {
127: String classPath = System.getProperty("java.class.path");
128: StringTokenizer stClassPath = new StringTokenizer(
129: classPath, File.pathSeparator);
130: while (stClassPath.hasMoreTokens()) {
131: String nextPath = stClassPath.nextToken();
132: if (nextPath.toLowerCase().endsWith(".jar")) {
133: standardLibs.add(nextPath.substring(nextPath
134: .lastIndexOf(File.separatorChar) + 1));
135: }
136: urlList.add(new File(nextPath).toURL());
137: }
138: }
139: if (mode == USE_WAR_LIB || mode == USE_WAR_EXTERNAL_LIB) {
140: JarInputStream jarStream = new JarInputStream(
141: new FileInputStream(warFile));
142: JarEntry entry = jarStream.getNextJarEntry();
143: while (entry != null) {
144: String entryName = entry.getName();
145: if (entryName.startsWith("WEB-INF/lib/")
146: && entryName.endsWith(".jar")
147: && !standardLibs.contains(entryName
148: .substring(12))) {
149: File tempJarFile = unpack(jarStream, entryName);
150: urlList.add(tempJarFile.toURL());
151: }
152: entry = jarStream.getNextJarEntry();
153: }
154: jarStream.close();
155: }
156: URL[] urls = new URL[urlList.size()];
157: for (int i = 0; i < urlList.size(); i++) {
158: urls[i] = (URL) urlList.get(i);
159: }
160: ClassLoader loader = new ChildFirstClassLoader(urls,
161: ServletClassLoader.class.getClassLoader());
162: Thread.currentThread().setContextClassLoader(loader);
163: return loader;
164: }
165:
166: /**
167: * Unpack the specified entry from the JAR file.
168: *
169: * @param jarStream
170: * The input stream of the JAR file positioned at the entry.
171: * @param entryName
172: * The name of the entry to extract.
173: *
174: * @return
175: * The extracted file. The created file is a temporary file in the
176: * temporary directory.
177: *
178: * @throws IOException
179: * if the JAR file cannot be read or is incorrect.
180: */
181: private static File unpack(JarInputStream jarStream,
182: String entryName) throws IOException {
183: String libName = entryName.substring(
184: entryName.lastIndexOf('/') + 1, entryName.length() - 4);
185: File tempJarFile = File
186: .createTempFile("tmp_" + libName, ".jar");
187: tempJarFile.deleteOnExit();
188: FileOutputStream out = new FileOutputStream(tempJarFile);
189:
190: // Transfer bytes from the JAR file to the output file
191: byte[] buf = new byte[8192];
192: int len;
193: while ((len = jarStream.read(buf)) > 0) {
194: out.write(buf, 0, len);
195: }
196: out.close();
197: return tempJarFile;
198: }
199:
200: /**
201: * An almost trivial no-fuss implementation of a class loader
202: * following the child-first delegation model.
203: *
204: * @author <a href="http://www.qos.ch/log4j/">Ceki Gulcu</a>
205: */
206: private static class ChildFirstClassLoader extends URLClassLoader {
207:
208: public ChildFirstClassLoader(URL[] urls) {
209: super (urls);
210: }
211:
212: public ChildFirstClassLoader(URL[] urls, ClassLoader parent) {
213: super (urls, parent);
214: }
215:
216: public void addURL(URL url) {
217: super .addURL(url);
218: }
219:
220: public Class loadClass(String name)
221: throws ClassNotFoundException {
222: return loadClass(name, false);
223: }
224:
225: /**
226: * We override the parent-first behavior established by
227: * java.land.Classloader.
228: * <p>
229: * The implementation is surprisingly straightforward.
230: *
231: * @param name
232: * the name of the class to load, should not be <code>null</code>.
233: *
234: * @param resolve
235: * flag that indicates whether the class should be resolved.
236: *
237: * @return
238: * the loaded class, never <code>null</code>.
239: *
240: * @throws ClassNotFoundException
241: * if the class could not be loaded.
242: */
243: protected Class loadClass(String name, boolean resolve)
244: throws ClassNotFoundException {
245:
246: // First, check if the class has already been loaded
247: Class c = findLoadedClass(name);
248:
249: // if not loaded, search the local (child) resources
250: if (c == null) {
251: try {
252: c = findClass(name);
253: } catch (ClassNotFoundException cnfe) {
254: // ignore
255: }
256: }
257:
258: // If we could not find it, delegate to parent
259: // Note that we do not attempt to catch any ClassNotFoundException
260: if (c == null) {
261: if (getParent() != null) {
262: c = getParent().loadClass(name);
263: } else {
264: c = getSystemClassLoader().loadClass(name);
265: }
266: }
267:
268: // Resolve the class, if required
269: if (resolve) {
270: resolveClass(c);
271: }
272:
273: return c;
274: }
275:
276: /**
277: * Override the parent-first resource loading model established by
278: * java.land.Classloader with child-first behavior.
279: *
280: * @param name
281: * the name of the resource to load, should not be <code>null</code>.
282: *
283: * @return
284: * a {@link URL} for the resource, or <code>null</code> if it could
285: * not be found.
286: */
287: public URL getResource(String name) {
288:
289: URL url = findResource(name);
290:
291: // If local search failed, delegate to parent
292: if (url == null) {
293: url = getParent().getResource(name);
294: }
295:
296: return url;
297: }
298: }
299: }
|