001: // ========================================================================
002: // Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014:
015: package org.mortbay.jetty.webapp;
016:
017: import java.io.File;
018: import java.io.FileOutputStream;
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.net.URL;
022: import java.net.URLClassLoader;
023: import java.security.CodeSource;
024: import java.security.PermissionCollection;
025: import java.util.StringTokenizer;
026:
027: import org.mortbay.log.Log;
028: import org.mortbay.resource.Resource;
029: import org.mortbay.util.IO;
030: import org.mortbay.util.LazyList;
031:
032: /* ------------------------------------------------------------ */
033: /** ClassLoader for HttpContext.
034: * Specializes URLClassLoader with some utility and file mapping
035: * methods.
036: *
037: * This loader defaults to the 2.3 servlet spec behaviour where non
038: * system classes are loaded from the classpath in preference to the
039: * parent loader. Java2 compliant loading, where the parent loader
040: * always has priority, can be selected with the
041: * {@link org.mortbay.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} method.
042: *
043: * If no parent class loader is provided, then the current thread context classloader will
044: * be used. If that is null then the classloader that loaded this class is used as the parent.
045: *
046: * @author Greg Wilkins (gregw)
047: */
048: public class WebAppClassLoader extends URLClassLoader implements
049: Cloneable {
050: private WebAppContext _context;
051: private ClassLoader _parent;
052:
053: /* ------------------------------------------------------------ */
054: /** Constructor.
055: */
056: public WebAppClassLoader(WebAppContext context) throws IOException {
057: this (null, context);
058: }
059:
060: /* ------------------------------------------------------------ */
061: /** Constructor.
062: */
063: public WebAppClassLoader(ClassLoader parent, WebAppContext context)
064: throws IOException {
065: super (
066: new URL[] {},
067: parent != null ? parent
068: : (Thread.currentThread()
069: .getContextClassLoader() != null ? Thread
070: .currentThread()
071: .getContextClassLoader()
072: : (WebAppClassLoader.class
073: .getClassLoader() != null ? WebAppClassLoader.class
074: .getClassLoader()
075: : ClassLoader
076: .getSystemClassLoader())));
077: _parent = getParent();
078: _context = context;
079: if (_parent == null)
080: throw new IllegalArgumentException("no parent classloader!");
081:
082: if (context.getExtraClasspath() != null)
083: addClassPath(context.getExtraClasspath());
084: }
085:
086: /* ------------------------------------------------------------ */
087: public Object clone() throws CloneNotSupportedException {
088: // TODO Auto-generated method stub
089: WebAppClassLoader clone = (WebAppClassLoader) super .clone();
090:
091: return clone;
092: }
093:
094: /* ------------------------------------------------------------ */
095: public WebAppContext getContext() {
096: return _context;
097: }
098:
099: /* ------------------------------------------------------------ */
100: /**
101: * @param classPath Comma or semicolon separated path of filenames or URLs
102: * pointing to directories or jar files. Directories should end
103: * with '/'.
104: */
105: public void addClassPath(String classPath) throws IOException {
106: if (classPath == null)
107: return;
108:
109: StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
110: while (tokenizer.hasMoreTokens()) {
111: Resource resource = Resource.newResource(tokenizer
112: .nextToken());
113: if (Log.isDebugEnabled())
114: Log.debug("Path resource=" + resource);
115:
116: // Resolve file path if possible
117: File file = resource.getFile();
118: if (file != null) {
119: URL url = resource.getURL();
120: addURL(url);
121: } else {
122: // Add resource or expand jar/
123: if (!resource.isDirectory() && file == null) {
124: InputStream in = resource.getInputStream();
125: File tmp_dir = _context.getTempDirectory();
126: if (tmp_dir == null) {
127: tmp_dir = File.createTempFile("jetty.cl.lib",
128: null);
129: tmp_dir.mkdir();
130: tmp_dir.deleteOnExit();
131: }
132: File lib = new File(tmp_dir, "lib");
133: if (!lib.exists()) {
134: lib.mkdir();
135: lib.deleteOnExit();
136: }
137: File jar = File.createTempFile("Jetty-", ".jar",
138: lib);
139:
140: jar.deleteOnExit();
141: if (Log.isDebugEnabled())
142: Log.debug("Extract " + resource + " to " + jar);
143: FileOutputStream out = null;
144: try {
145: out = new FileOutputStream(jar);
146: IO.copy(in, out);
147: } finally {
148: IO.close(out);
149: }
150:
151: URL url = jar.toURL();
152: addURL(url);
153: } else {
154: URL url = resource.getURL();
155: addURL(url);
156: }
157: }
158: }
159: }
160:
161: /* ------------------------------------------------------------ */
162: /** Add elements to the class path for the context from the jar and zip files found
163: * in the specified resource.
164: * @param lib the resource that contains the jar and/or zip files.
165: * @param append true if the classpath entries are to be appended to any
166: * existing classpath, or false if they replace the existing classpath.
167: * @see #setClassPath(String)
168: */
169: public void addJars(Resource lib) {
170: if (lib.exists() && lib.isDirectory()) {
171: String[] files = lib.list();
172: for (int f = 0; files != null && f < files.length; f++) {
173: try {
174: Resource fn = lib.addPath(files[f]);
175: String fnlc = fn.getName().toLowerCase();
176: if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip")) {
177: addClassPath(fn.toString());
178: }
179: } catch (Exception ex) {
180: Log.warn(Log.EXCEPTION, ex);
181: }
182: }
183: }
184: }
185:
186: /* ------------------------------------------------------------ */
187: public void destroy() {
188: this ._parent = null;
189: }
190:
191: /* ------------------------------------------------------------ */
192: public PermissionCollection getPermissions(CodeSource cs) {
193: // TODO check CodeSource
194: PermissionCollection permissions = _context.getPermissions();
195: PermissionCollection pc = (permissions == null) ? super
196: .getPermissions(cs) : permissions;
197: return pc;
198: }
199:
200: /* ------------------------------------------------------------ */
201: public synchronized URL getResource(String name) {
202: URL url = null;
203: boolean tried_parent = false;
204: if (_context.isParentLoaderPriority() || isSystemPath(name)) {
205: tried_parent = true;
206:
207: if (_parent != null)
208: url = _parent.getResource(name);
209: }
210:
211: if (url == null) {
212: url = this .findResource(name);
213:
214: if (url == null && name.startsWith("/")) {
215: if (Log.isDebugEnabled())
216: Log.debug("HACK leading / off " + name);
217: url = this .findResource(name.substring(1));
218: }
219: }
220:
221: if (url == null && !tried_parent) {
222: if (_parent != null)
223: url = _parent.getResource(name);
224: }
225:
226: if (url != null)
227: if (Log.isDebugEnabled())
228: Log.debug("getResource(" + name + ")=" + url);
229:
230: return url;
231: }
232:
233: /* ------------------------------------------------------------ */
234: public boolean isServerPath(String name) {
235: name = name.replace('/', '.');
236: while (name.startsWith("."))
237: name = name.substring(1);
238:
239: String[] server_classes = _context.getServerClasses();
240: if (server_classes != null) {
241: for (int i = 0; i < server_classes.length; i++) {
242: boolean result = true;
243: String c = server_classes[i];
244: if (c.startsWith("-")) {
245: c = c.substring(1); // TODO cache
246: result = false;
247: }
248:
249: if (c.endsWith(".")) {
250: if (name.startsWith(c))
251: return result;
252: } else if (name.equals(c))
253: return result;
254: }
255: }
256: return false;
257: }
258:
259: /* ------------------------------------------------------------ */
260: public boolean isSystemPath(String name) {
261: name = name.replace('/', '.');
262: while (name.startsWith("."))
263: name = name.substring(1);
264: String[] system_classes = _context.getSystemClasses();
265: if (system_classes != null) {
266: for (int i = 0; i < system_classes.length; i++) {
267: boolean result = true;
268: String c = system_classes[i];
269:
270: if (c.startsWith("-")) {
271: c = c.substring(1); // TODO cache
272: result = false;
273: }
274:
275: if (c.endsWith(".")) {
276: if (name.startsWith(c))
277: return result;
278: } else if (name.equals(c))
279: return result;
280: }
281: }
282:
283: return false;
284:
285: }
286:
287: /* ------------------------------------------------------------ */
288: public synchronized Class loadClass(String name)
289: throws ClassNotFoundException {
290: return loadClass(name, false);
291: }
292:
293: /* ------------------------------------------------------------ */
294: protected synchronized Class loadClass(String name, boolean resolve)
295: throws ClassNotFoundException {
296: Class c = findLoadedClass(name);
297: ClassNotFoundException ex = null;
298: boolean tried_parent = false;
299:
300: if (c == null
301: && _parent != null
302: && (_context.isParentLoaderPriority() || isSystemPath(name))) {
303: tried_parent = true;
304: try {
305: c = _parent.loadClass(name);
306: if (Log.isDebugEnabled())
307: Log.debug("loaded " + c);
308: } catch (ClassNotFoundException e) {
309: ex = e;
310: }
311: }
312:
313: if (c == null) {
314: try {
315: c = this .findClass(name);
316: } catch (ClassNotFoundException e) {
317: ex = e;
318: }
319: }
320:
321: if (c == null && _parent != null && !tried_parent
322: && !isServerPath(name))
323: c = _parent.loadClass(name);
324:
325: if (c == null)
326: throw ex;
327:
328: if (resolve)
329: resolveClass(c);
330:
331: if (Log.isDebugEnabled())
332: Log.debug("loaded " + c + " from " + c.getClassLoader());
333:
334: return c;
335: }
336:
337: /* ------------------------------------------------------------ */
338: public String toString() {
339: if (Log.isDebugEnabled())
340: return "ContextLoader@" + hashCode() + "("
341: + LazyList.array2List(getURLs()) + ") / " + _parent;
342: return "ContextLoader@" + hashCode();
343: }
344:
345: }
|