001: /*
002: * @(#)AppletClassLoader.java 1.84 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.applet;
029:
030: import java.net.URL;
031: import java.net.URLClassLoader;
032: import java.net.SocketPermission;
033: import java.net.URLConnection;
034: import java.net.MalformedURLException;
035: import java.net.InetAddress;
036: import java.net.UnknownHostException;
037: import java.io.File;
038: import java.io.FilePermission;
039: import java.io.IOException;
040: import java.io.InputStream;
041: import java.util.Enumeration;
042: import java.util.NoSuchElementException;
043: import java.security.AccessController;
044: import java.security.AccessControlContext;
045: import java.security.PrivilegedAction;
046: import java.security.PrivilegedExceptionAction;
047: import java.security.PrivilegedActionException;
048: import java.security.CodeSource;
049: import java.security.PermissionCollection;
050: import sun.awt.AppContext;
051: import sun.awt.SunToolkit;
052:
053: /**
054: * This class defines the class loader for loading applet classes and
055: * resources. It extends URLClassLoader to search the applet code base
056: * for the class or resource after checking any loaded JAR files.
057: */
058: public class AppletClassLoader extends URLClassLoader {
059: private URL base; /* applet code base URL */
060: private CodeSource codesource; /* codesource for the base URL */
061: private AccessControlContext acc;
062:
063: /*
064: * Creates a new AppletClassLoader for the specified base URL.
065: */
066: protected AppletClassLoader(URL base) {
067: super (new URL[0]);
068: this .base = base;
069: this .codesource = new CodeSource(base, null);
070: acc = AccessController.getContext();
071: }
072:
073: /*
074: * Returns the applet code base URL.
075: */
076: URL getBaseURL() {
077: return base;
078: }
079:
080: /*
081: * Returns the URLs used for loading classes and resources.
082: */
083: public URL[] getURLs() {
084: URL[] jars = super .getURLs();
085: URL[] urls = new URL[jars.length + 1];
086: System.arraycopy(jars, 0, urls, 0, jars.length);
087: urls[urls.length - 1] = base;
088: return urls;
089: }
090:
091: /*
092: * Adds the specified JAR file to the search path of loaded JAR files.
093: */
094: void addJar(String name) {
095: URL url;
096: try {
097: url = new URL(base, name);
098: } catch (MalformedURLException e) {
099: throw new IllegalArgumentException("name");
100: }
101: addURL(url);
102: // DEBUG
103: //URL[] urls = getURLs();
104: //for (int i = 0; i < urls.length; i++) {
105: // System.out.println("url[" + i + "] = " + urls[i]);
106: //}
107: }
108:
109: /*
110: * Override loadClass so that class loading errors can be caught in
111: * order to print better error messages.
112: */
113: public synchronized Class loadClass(String name, boolean resolve)
114: throws ClassNotFoundException {
115: // First check if we have permission to access the package. This
116: // should go away once we've added support for exported packages.
117: int i = name.lastIndexOf('.');
118: if (i != -1) {
119: SecurityManager sm = System.getSecurityManager();
120: if (sm != null)
121: sm.checkPackageAccess(name.substring(0, i));
122: }
123: try {
124: return super .loadClass(name, resolve);
125: } catch (ClassNotFoundException e) {
126: //printError(name, e.getException());
127: throw e;
128: } catch (RuntimeException e) {
129: //printError(name, e);
130: throw e;
131: } catch (Error e) {
132: //printError(name, e);
133: throw e;
134: }
135: }
136:
137: /*
138: * Finds the applet class with the specified name. First searches
139: * loaded JAR files then the applet code base for the class.
140: */
141: protected Class findClass(String name)
142: throws ClassNotFoundException {
143: // check loaded JAR files
144: try {
145: return super .findClass(name);
146: } catch (ClassNotFoundException e) {
147: }
148: // Otherwise, try loading the class from the code base URL
149: final String path = name.replace('.', '/').concat(".class");
150: try {
151: byte[] b = (byte[]) AccessController.doPrivileged(
152: new PrivilegedExceptionAction() {
153: public Object run() throws IOException {
154: return getBytes(new URL(base, path));
155: }
156: }, acc);
157: if (b != null) {
158: return defineClass(name, b, 0, b.length, codesource);
159: } else {
160: throw new ClassNotFoundException(name);
161: }
162: } catch (PrivilegedActionException e) {
163: throw new ClassNotFoundException(name, e.getException());
164: }
165: }
166:
167: /**
168: * Returns the permissions for the given codesource object.
169: * The implementation of this method first calls super.getPermissions,
170: * to get the permissions
171: * granted by the super class, and then adds additional permissions
172: * based on the URL of the codesource.
173: * <p>
174: * If the protocol is "file"
175: * and the path specifies a file, permission is granted to read all files
176: * and (recursively) all files and subdirectories contained in
177: * that directory. This is so applets with a codebase of
178: * file:/blah/some.jar can read in file:/blah/, which is needed to
179: * be backward compatible. We also add permission to connect back to
180: * the "localhost".
181: *
182: * @param codesource the codesource
183: * @return the permissions granted to the codesource
184: */
185: protected PermissionCollection getPermissions(CodeSource codesource) {
186: final PermissionCollection perms = super
187: .getPermissions(codesource);
188: URL url = codesource.getLocation();
189: if (url.getProtocol().equals("file")) {
190: String path = url.getFile()
191: .replace('/', File.separatorChar);
192: if (!path.endsWith(File.separator)) {
193: int endIndex = path.lastIndexOf(File.separatorChar);
194: if (endIndex != -1) {
195: path = path.substring(0, endIndex + 1) + "-";
196: perms.add(new FilePermission(path, "read"));
197: }
198: }
199: perms.add(new SocketPermission("localhost",
200: "connect,accept"));
201: AccessController.doPrivileged(new PrivilegedAction() {
202: public Object run() {
203: try {
204: String host = InetAddress.getLocalHost()
205: .getHostName();
206: perms.add(new SocketPermission(host,
207: "connect,accept"));
208: } catch (UnknownHostException uhe) {
209: }
210: return null;
211: }
212: });
213: if (base.getProtocol().equals("file")) {
214: String bpath = base.getFile().replace('/',
215: File.separatorChar);
216: if (bpath.endsWith(File.separator)) {
217: bpath += "-";
218: }
219: perms.add(new FilePermission(bpath, "read"));
220: }
221: }
222: return perms;
223: }
224:
225: /*
226: * Returns the contents of the specified URL as an array of bytes.
227: */
228: private static byte[] getBytes(URL url) throws IOException {
229: URLConnection uc = url.openConnection();
230: if (uc instanceof java.net.HttpURLConnection) {
231: java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc;
232: int code = huc.getResponseCode();
233: if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
234: throw new IOException("open HTTP connection failed.");
235: }
236: }
237: int len = uc.getContentLength();
238: InputStream in = uc.getInputStream();
239: byte[] b;
240: try {
241: if (len != -1) {
242: // Read exactly len bytes from the input stream
243: b = new byte[len];
244: while (len > 0) {
245: int n = in.read(b, b.length - len, len);
246: if (n == -1) {
247: throw new IOException("unexpected EOF");
248: }
249: len -= n;
250: }
251: } else {
252: // Read until end of stream is reached
253: b = new byte[1024];
254: int total = 0;
255: while ((len = in.read(b, total, b.length - total)) != -1) {
256: total += len;
257: if (total >= b.length) {
258: byte[] tmp = new byte[total * 2];
259: System.arraycopy(b, 0, tmp, 0, total);
260: b = tmp;
261: }
262: }
263: // Trim array to correct size, if necessary
264: if (total != b.length) {
265: byte[] tmp = new byte[total];
266: System.arraycopy(b, 0, tmp, 0, total);
267: b = tmp;
268: }
269: }
270: } finally {
271: in.close();
272: }
273: return b;
274: }
275:
276: /*
277: * Finds the applet resource with the specified name. First checks
278: * loaded JAR files then the applet code base for the resource.
279: */
280: public URL findResource(String name) {
281: // check loaded JAR files
282: URL url = super .findResource(name);
283: if (url == null) {
284: // otherwise, try the code base
285: try {
286: url = new URL(base, name);
287: // check if resource exists
288: if (!resourceExists(url))
289: url = null;
290: } catch (Exception e) {
291: // all exceptions, including security exceptions, are caught
292: url = null;
293: }
294: }
295: return url;
296: }
297:
298: private boolean resourceExists(URL url) {
299: // Check if the resource exists.
300: // It almost works to just try to do an openConnection() but
301: // HttpURLConnection will return true on HTTP_BAD_REQUEST
302: // when the requested name ends in ".html", ".htm", and ".txt"
303: // and we want to be able to handle these
304: //
305: // Also, cannot just open a connection for things like FileURLConnection,
306: // because they suceed when connecting to a non-existant file.
307: // So, in those cases we open and close an input stream.
308: boolean ok = true;
309: try {
310: URLConnection conn = url.openConnection();
311: if (conn instanceof java.net.HttpURLConnection) {
312: java.net.HttpURLConnection hconn = (java.net.HttpURLConnection) conn;
313: int code = hconn.getResponseCode();
314: if (code == java.net.HttpURLConnection.HTTP_OK) {
315: return true;
316: }
317: if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) {
318: return false;
319: }
320: } else {
321: // our best guess for the other cases
322: InputStream is = url.openStream();
323: is.close();
324: }
325: } catch (Exception ex) {
326: ok = false;
327: }
328: return ok;
329: }
330:
331: /*
332: * Returns an enumeration of all the applet resources with the specified
333: * name. First checks loaded JAR files then the applet code base for all
334: * available resources.
335: */
336: public Enumeration findResources(String name) throws IOException {
337: URL u = new URL(base, name);
338: if (!resourceExists(u)) {
339: u = null;
340: }
341: final Enumeration e = super .findResources(name);
342: final URL url = u;
343: return new Enumeration() {
344: private boolean done;
345:
346: public Object nextElement() {
347: if (!done) {
348: if (e.hasMoreElements()) {
349: return e.nextElement();
350: }
351: done = true;
352: if (url != null) {
353: return url;
354: }
355: }
356: throw new NoSuchElementException();
357: }
358:
359: public boolean hasMoreElements() {
360: return !done && (e.hasMoreElements() || url != null);
361: }
362: };
363: }
364:
365: /*
366: * Load and resolve the file specified by the applet tag CODE
367: * attribute. The argument can either be the relative path
368: * of the class file itself or just the name of the class.
369: */
370: Class loadCode(String name) throws ClassNotFoundException {
371: // first convert any '/' or native file separator to .
372: name = name.replace('/', '.');
373: name = name.replace(File.separatorChar, '.');
374: // save that name for later
375: String fullName = name;
376: // then strip off any suffixes
377: if (name.endsWith(".class") || name.endsWith(".java")) {
378: name = name.substring(0, name.lastIndexOf('.'));
379: }
380: try {
381: return loadClass(name);
382: } catch (ClassNotFoundException e) {
383: }
384: // then if it didn't end with .java or .class, or in the
385: // really pathological case of a class named class or java
386: return loadClass(fullName);
387: }
388:
389: /*
390: * The threadgroup that the applets loaded by this classloader live
391: * in. In the sun.* implementation of applets, the security manager's
392: * (AppletSecurity) getThreadGroup returns the thread group of the
393: * first applet on the stack, which is the applet's thread group.
394: */
395: private AppletThreadGroup threadGroup;
396: private AppContext appContext;
397:
398: synchronized ThreadGroup getThreadGroup() {
399: if (threadGroup == null || threadGroup.isDestroyed()) {
400: AccessController.doPrivileged(new PrivilegedAction() {
401: public Object run() {
402: threadGroup = new AppletThreadGroup(base
403: + "-threadGroup");
404: // threadGroup.setDaemon(true);
405: // threadGroup is now destroyed by AppContext.dispose()
406:
407: // Create the new AppContext from within a Thread belonging
408: // to the newly created ThreadGroup, and wait for the
409: // creation to complete before returning from this method.
410: AppContextCreator creatorThread = new AppContextCreator(
411: threadGroup);
412: // Since this thread will later be used to launch the
413: // applet's AWT-event dispatch thread and we want the applet
414: // code executing the AWT callbacks to use their own class
415: // loader rather than the system class loader, explicitly
416: // set the context class loader to the AppletClassLoader.
417: creatorThread
418: .setContextClassLoader(AppletClassLoader.this );
419: synchronized (creatorThread.syncObject) {
420: creatorThread.start();
421: try {
422: creatorThread.syncObject.wait();
423: } catch (InterruptedException e) {
424: }
425: appContext = creatorThread.appContext;
426: }
427: return null;
428: }
429: });
430: }
431: return threadGroup;
432: }
433:
434: /*
435: * Get the AppContext, if any, corresponding to this AppletClassLoader.
436: */
437: AppContext getAppContext() {
438: return appContext;
439: }
440:
441: int usageCount = 0;
442:
443: /**
444: * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they
445: * won't be destroyed.
446: */
447: synchronized void grab() {
448: usageCount++;
449: getThreadGroup(); // Make sure ThreadGroup/AppContext exist
450: }
451:
452: /**
453: * Release this AppletClassLoader and its ThreadGroup/AppContext.
454: * If nothing else has grabbed this AppletClassLoader, its ThreadGroup
455: * and AppContext will be destroyed.
456: *
457: * Because this method may destroy the AppletClassLoader's ThreadGroup,
458: * this method should NOT be called from within the AppletClassLoader's
459: * ThreadGroup.
460: */
461: synchronized void release() {
462: if (usageCount > 1) {
463: --usageCount;
464: } else {
465: if (appContext != null) {
466: try {
467: appContext.dispose(); // nuke the world!
468: } catch (IllegalThreadStateException e) {
469: }
470: }
471: usageCount = 0;
472: appContext = null;
473: threadGroup = null;
474: }
475: }
476:
477: private static AppletMessageHandler mh = new AppletMessageHandler(
478: "appletclassloader");
479:
480: /*
481: * Prints a class loading error message.
482: */
483: private static void printError(String name, Throwable e) {
484: String s = null;
485: if (e == null) {
486: s = mh.getMessage("filenotfound", name);
487: } else if (e instanceof IOException) {
488: s = mh.getMessage("fileioexception", name);
489: } else if (e instanceof ClassFormatError) {
490: s = mh.getMessage("fileformat", name);
491: } else if (e instanceof ThreadDeath) {
492: s = mh.getMessage("filedeath", name);
493: } else if (e instanceof Error) {
494: s = mh.getMessage("fileerror", e.toString(), name);
495: }
496: if (s != null) {
497: System.err.println(s);
498: }
499: }
500: }
501:
502: /*
503: * The AppContextCreator class is used to create an AppContext from within
504: * a Thread belonging to the new AppContext's ThreadGroup. To wait for
505: * this operation to complete before continuing, wait for the notifyAll()
506: * operation on the syncObject to occur.
507: */
508: class AppContextCreator extends Thread {
509: Object syncObject = new Object();
510: AppContext appContext = null;
511:
512: AppContextCreator(ThreadGroup group) {
513: super (group, "AppContextCreator");
514: }
515:
516: public void run() {
517: synchronized (syncObject) {
518: appContext = SunToolkit.createNewAppContext();
519: syncObject.notifyAll();
520: }
521: } // run()
522: } // class AppContextCreator
|