001: /**********************************************************************
002: Copyright (c) 2004 Andy Jefferson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: 2004 Erik Bengtson - added extra classForName method
017: ...
018: **********************************************************************/package org.jpox;
019:
020: import java.io.IOException;
021: import java.net.URL;
022: import java.security.AccessController;
023: import java.security.PrivilegedAction;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Enumeration;
027: import java.util.HashSet;
028: import java.util.List;
029: import java.util.Map;
030:
031: import org.jpox.exceptions.ClassNotResolvedException;
032: import org.jpox.exceptions.JPOXException;
033: import org.jpox.util.JPOXLogger;
034: import org.jpox.util.JavaUtils;
035: import org.jpox.util.Localiser;
036: import org.jpox.util.WeakValueMap;
037:
038: /**
039: * A basic implementation of a ClassLoaderResolver for JDO. A ClassLoaderResolver
040: * provides a series of methods for resolving classes from their names.
041: * It supports up to 3 class loaders.
042: *
043: * <H3>JDO2 Spec section 12.5</H3>
044: * The class loaders will be used in this order:
045: * <OL>
046: * <LI>The loader that loaded the class or instance referred to in the API that caused this class to be loaded.</LI>
047: * <UL>
048: * <LI>In case of query, this is the loader of the candidate class, or the loader of the object passed to the newQuery</LI>
049: * <LI>In case of navigation from a persistent instance, this is the loader of the class of the instance.</LI>
050: * <LI>In the case of getExtent with subclasses, this is the loader of the candidate class.</LI>
051: * <LI>In the case of getObjectById, this is the loader of the object id instance.</LI>
052: * <LI>Other cases do not have an explicit loader.</LI>
053: * </UL>
054: * <LI>The loader returned in the current context by Thread.getContextClassLoader().</LI>
055: * <LI>The loader returned by Thread.getContextClassLoader() at the time of PersistenceManagerFactory.getPersistenceManager().</LI>
056: * <LI>The loader registered for dynamically creating and loading classes at runtime.</LI>
057: * </OL>
058: *
059: * @version $Revision: 1.38 $
060: */
061: public class JDOClassLoaderResolver implements ClassLoaderResolver {
062: /** Localisation utility for output messages */
063: protected static final Localiser LOCALISER = Localiser
064: .getInstance("org.jpox.Localisation");
065:
066: /** ClassLoader initialised by the PM creation. */
067: protected final ClassLoader pmContextLoader;
068:
069: /** Hash code cache for performance improvement */
070: protected int pmContextLoaderHashCode = 0;
071:
072: /** ClassLoader registered to load runtime created classes. */
073: protected ClassLoader registeredLoader;
074:
075: /** ClassLoader registered to load classes. */
076: protected ClassLoader userRegisteredLoader;
077:
078: /** Hash code cache for performance improvement */
079: protected int registeredLoaderHashCode = 0;
080:
081: /** Hash code cache for performance improvement */
082: protected int userRegisteredLoaderHashCode = 0;
083:
084: /** Cache for loaded classes */
085: protected Map loadedClasses = new WeakValueMap();
086:
087: /** Cache for loaded classes */
088: protected Map unloadedClasses = new WeakValueMap();
089:
090: /** Cache for resources */
091: protected Map resources = new WeakValueMap();
092:
093: /** The primary class */
094: ThreadLocal primary = new ThreadLocal();
095:
096: /**
097: * Constructor for PersistenceManager cases.
098: * @param pmLoader Loader from PM initialisation time.
099: */
100: public JDOClassLoaderResolver(ClassLoader pmLoader) {
101: pmContextLoader = pmLoader;
102: if (pmContextLoader != null) {
103: pmContextLoaderHashCode = pmContextLoader.hashCode();
104: }
105: }
106:
107: /**
108: * Constructor for non-PersistenceManager cases so there is no
109: * PM context loader.
110: */
111: public JDOClassLoaderResolver() {
112: pmContextLoader = null;
113: }
114:
115: /**
116: * JDO's Class Loading mechanism (Spec 1.0.1 Chapter 12.5).
117: * Try 3 loaders, starting with user-supplied loader, then try
118: * the current thread's loader, and finally try the PM context
119: * loader. This method does not initialize the class
120: * @param name Name of the Class to be loaded
121: * @param primary primary ClassLoader to use (or null)
122: * @return The class given the name, using the required loader.
123: * @throws ClassNotResolvedException if the class can't be found in the classpath
124: */
125: public Class classForName(String name, ClassLoader primary) {
126: if (name == null) {
127: // Avoid the NPE and just throw a "not resolved"
128: String msg = LOCALISER.msg("001000", name);
129: throw new ClassNotResolvedException(msg);
130: }
131: if (name.equals(ClassNameConstants.BYTE)) {
132: return byte.class;
133: } else if (name.equals(ClassNameConstants.CHAR)) {
134: return char.class;
135: } else if (name.equals(ClassNameConstants.INT)) {
136: return int.class;
137: } else if (name.equals(ClassNameConstants.LONG)) {
138: return long.class;
139: } else if (name.equals(ClassNameConstants.DOUBLE)) {
140: return double.class;
141: } else if (name.equals(ClassNameConstants.FLOAT)) {
142: return float.class;
143: } else if (name.equals(ClassNameConstants.SHORT)) {
144: return short.class;
145: } else if (name.equals(ClassNameConstants.BOOLEAN)) {
146: return boolean.class;
147: } else if (name.equals(ClassNameConstants.JAVA_LANG_STRING)) {
148: return String.class;
149: }
150:
151: ClassLoader contextClassLoader = Thread.currentThread()
152: .getContextClassLoader();
153: String cacheKey = newCacheKey(name, primary, contextClassLoader);
154: //lookup in loaded and unloaded classes cache
155: Class cls = (Class) loadedClasses.get(cacheKey);
156:
157: if (cls != null) {
158: return cls;
159: }
160:
161: cls = (Class) unloadedClasses.get(cacheKey);
162:
163: if (cls != null) {
164: return cls;
165: }
166:
167: // Try the supplied loader first
168: if (cls == null) {
169: cls = classOrNull(name, primary);
170: }
171:
172: // Try the primary for this current thread
173: if (cls == null && this .primary.get() != null) {
174: cls = classOrNull(name, (ClassLoader) this .primary.get());
175: }
176:
177: // Try the loader for the current thread
178: if (cls == null) {
179: cls = classOrNull(name, contextClassLoader);
180: }
181:
182: // Try the loader for the PM context
183: if (cls == null) {
184: cls = classOrNull(name, pmContextLoader);
185: }
186:
187: // Try the registered loader for runtime created classes
188: if (cls == null && registeredLoader != null) {
189: cls = classOrNull(name, registeredLoader);
190: }
191:
192: // Try the user registered loader for classes
193: if (cls == null && userRegisteredLoader != null) {
194: cls = classOrNull(name, userRegisteredLoader);
195: }
196:
197: if (cls == null) {
198: if (JPOXLogger.CLASSLOADING.isDebugEnabled()) {
199: // Log the class loading failure in case its of use in debugging
200: if (JavaUtils.isJRE1_4OrAbove()) {
201: StackTraceElement[] traceElements = new Exception()
202: .getStackTrace();
203: StackTraceElement element = null;
204: int i = 0;
205: boolean processingClassLoaderResolverCalls = false;
206: while (element == null) {
207: if (processingClassLoaderResolverCalls
208: && traceElements[i].getClassName()
209: .indexOf(
210: this .getClass()
211: .getName()) < 0) {
212: // Next class in the stack trace after the JDOClassLoaderResolver calls
213: element = traceElements[i];
214: } else if (traceElements[i].getClassName()
215: .startsWith("org.jpox")) {
216: processingClassLoaderResolverCalls = true;
217: }
218: i++;
219: }
220: // Dont give stack trace, just the method that asked for class-resolving
221: JPOXLogger.CLASSLOADING.debug(LOCALISER.msg(
222: "001004", name, element.getClassName()
223: + "." + element.getMethodName(), ""
224: + element.getLineNumber()));
225: } else {
226: JPOXLogger.CLASSLOADING.debug(LOCALISER.msg(
227: "001005", name));
228: }
229: }
230: String msg = LOCALISER.msg("001000", name);
231: throw new ClassNotResolvedException(msg);
232: }
233:
234: //put in unloaded cache, since it was not loaded here
235: unloadedClasses.put(cacheKey, cls);
236:
237: return cls;
238: }
239:
240: /**
241: * JDO's Class Loading mechanism (Spec 1.0.1 Chapter 12.5).
242: * Try 3 loaders, starting with user-supplied loader, then try
243: * the current thread's loader, and finally try the PM context
244: * loader.
245: * @param name Name of the Class to be loaded
246: * @param primary primary ClassLoader to use (or null)
247: * @return The class given the name, using the required loader.
248: * @throws ClassNotResolvedException if the class can't be found in the classpath
249: */
250: private Class classForNameWithInitialize(String name,
251: ClassLoader primary) {
252: if (name == null) {
253: // Avoid the NPE and just throw a "not resolved"
254: String msg = LOCALISER.msg("001000", name);
255: throw new ClassNotResolvedException(msg);
256: }
257: if (name.equals(ClassNameConstants.BYTE)) {
258: return byte.class;
259: } else if (name.equals(ClassNameConstants.CHAR)) {
260: return char.class;
261: } else if (name.equals(ClassNameConstants.INT)) {
262: return int.class;
263: } else if (name.equals(ClassNameConstants.LONG)) {
264: return long.class;
265: } else if (name.equals(ClassNameConstants.DOUBLE)) {
266: return double.class;
267: } else if (name.equals(ClassNameConstants.FLOAT)) {
268: return float.class;
269: } else if (name.equals(ClassNameConstants.SHORT)) {
270: return short.class;
271: } else if (name.equals(ClassNameConstants.BOOLEAN)) {
272: return boolean.class;
273: } else if (name.equals(ClassNameConstants.JAVA_LANG_STRING)) {
274: return String.class;
275: }
276:
277: ClassLoader contextClassLoader = Thread.currentThread()
278: .getContextClassLoader();
279: String cacheKey = newCacheKey(name, primary, contextClassLoader);
280: //only lookup in loaded classes cache
281: Class cls = (Class) loadedClasses.get(cacheKey);
282:
283: if (cls != null) {
284: return cls;
285: }
286:
287: // Try the supplied loader first
288: if (cls == null) {
289: cls = ClassOrNullWithInitialize(name, primary);
290: }
291:
292: // Try the primary for this current thread
293: if (cls == null && this .primary.get() != null) {
294: cls = ClassOrNullWithInitialize(name,
295: (ClassLoader) this .primary.get());
296: }
297:
298: // Try the loader for the current thread
299: if (cls == null) {
300: cls = ClassOrNullWithInitialize(name, contextClassLoader);
301: }
302:
303: // Try the loader for the PM context
304: if (cls == null) {
305: cls = ClassOrNullWithInitialize(name, pmContextLoader);
306: }
307:
308: // Try the registered loader for runtime created classes
309: if (cls == null && registeredLoader != null) {
310: cls = ClassOrNullWithInitialize(name, registeredLoader);
311: }
312:
313: if (cls == null) {
314: String msg = LOCALISER.msg("001000", name);
315: throw new ClassNotResolvedException(msg);
316: }
317: loadedClasses.put(cacheKey, cls);
318:
319: return cls;
320: }
321:
322: /**
323: * Compute the key hashCode based for all classLoaders
324: * @param prefix the key prefix
325: * @param primary the primary ClassLoader, or null
326: * @param contextClassLoader the context ClassLoader, or null
327: * @return the computed hashCode
328: */
329: private String newCacheKey(String prefix, ClassLoader primary,
330: ClassLoader contextClassLoader) {
331: int h = 3;
332: if (primary != null) {
333: h = h ^ primary.hashCode();
334: }
335: if (contextClassLoader != null) {
336: h = h ^ contextClassLoader.hashCode();
337: }
338: h = h ^ pmContextLoaderHashCode;
339: h = h ^ registeredLoaderHashCode;
340: h = h ^ userRegisteredLoaderHashCode;
341: return prefix + h;
342: }
343:
344: /**
345: * JDO's Class Loading mechanism (Spec 1.0.1 Chapter 12.5)
346: * @param name Name of the Class to be loaded
347: * @param primary the primary ClassLoader to use (or null)
348: * @param initialize whether to initialize the class or not.
349: * @return The Class given the name, using the specified ClassLoader
350: * @throws ClassNotResolvedException if the class can't be found in the classpath
351: */
352: public Class classForName(String name, ClassLoader primary,
353: boolean initialize) {
354: if (initialize) {
355: return classForNameWithInitialize(name, primary);
356: } else {
357: return classForName(name, primary);
358: }
359: }
360:
361: /**
362: * JDO's Class Loading mechanism (Spec 1.0.1 Chapter 12.5). This method does not initialize the class
363: * @param name Name of the Class to be loaded
364: * @return The class given the name, using the required loader.
365: */
366: public Class classForName(String name) {
367: return classForName(name, null);
368: }
369:
370: /**
371: * JDO's Class Loading mechanism (Spec 1.0.1 Chapter 12.5)
372: * @param name Name of the Class to be loaded
373: * @param initialize whether to initialize the class or not.
374: * @return The Class given the name, using the specified ClassLoader
375: * @throws ClassNotResolvedException if the class can't be found in the classpath
376: */
377: public Class classForName(String name, boolean initialize) {
378: return classForName(name, null, initialize);
379: }
380:
381: /**
382: * Utility to check the assignability of 2 classes in accordance with JDO's
383: * Class Loading mechanism. This will check
384: * <I>class_1.isAssignableFrom(class_2);</I>
385: * @param class_name_1 Name of first class
386: * @param class_name_2 Name of second class
387: * @return Whether Class 2 is assignable from Class 1
388: */
389: public boolean isAssignableFrom(String class_name_1,
390: String class_name_2) {
391: if (class_name_1 == null || class_name_2 == null) {
392: return false;
393: }
394:
395: if (class_name_1.equals(class_name_2)) {
396: // Shortcut for case of the same class
397: return true;
398: }
399:
400: Class class_1 = classForName(class_name_1);
401: Class class_2 = classForName(class_name_2);
402:
403: return class_1.isAssignableFrom(class_2);
404: }
405:
406: /**
407: * Utility to check the assignability of 2 classes in accordance with JDO's
408: * Class Loading mechanism. This will check
409: * <I>class_1.isAssignableFrom(class_2);</I>
410: * @param class_name_1 Name of first class
411: * @param class_2 Second class
412: * @return Whether Class 2 is assignable from Class 1
413: */
414: public boolean isAssignableFrom(String class_name_1, Class class_2) {
415: if (class_name_1 == null || class_2 == null) {
416: return false;
417: }
418:
419: if (class_name_1.equals(class_2.getName())) {
420: // Shortcut for case of the same class
421: return true;
422: }
423:
424: try {
425: Class class_1 = null;
426: if (class_2.getClassLoader() != null) {
427: // Use class_2's loader if possible
428: class_1 = class_2.getClassLoader().loadClass(
429: class_name_1);
430: } else {
431: // Use the boot class loader
432: class_1 = Class.forName(class_name_1);
433: }
434: return class_1.isAssignableFrom(class_2);
435: } catch (Exception e) {
436: return false;
437: }
438: }
439:
440: /**
441: * Utility to check the assignability of 2 classes in accordance with JDO's
442: * Class Loading mechanism. This will check
443: * <I>class_1.isAssignableFrom(class_2);</I>
444: * @param class_1 First class
445: * @param class_name_2 Name of second class
446: * @return Whether Class 2 is assignable from Class 1
447: */
448: public boolean isAssignableFrom(Class class_1, String class_name_2) {
449: if (class_1 == null || class_name_2 == null) {
450: return false;
451: }
452:
453: if (class_1.getName().equals(class_name_2)) {
454: // Shortcut for case of the same class
455: return true;
456: }
457:
458: try {
459: Class class_2 = null;
460: if (class_1.getClassLoader() != null) {
461: // Use class_1's loader if possible
462: class_2 = class_1.getClassLoader().loadClass(
463: class_name_2);
464: } else {
465: // Use the boot class loader
466: class_2 = Class.forName(class_name_2);
467: }
468: return class_1.isAssignableFrom(class_2);
469: } catch (Exception e) {
470: return false;
471: }
472: }
473:
474: /**
475: * Returns the Class. This method does not initialize the class
476: * @param name the class name
477: * @param loader the ClassLoader
478: * @return Class the class loaded; null if not found
479: */
480: private Class classOrNull(String name, ClassLoader loader) {
481: try {
482: // JDK1.6+ needs Class.forName(...) here rather than loader.loadClass(...) since
483: // array types dont load in JDK1.6+ due to some change of default
484: return loader == null ? null : Class.forName(name, false,
485: loader);
486: } catch (ClassNotFoundException cnfe) {
487: // Ignore
488: } catch (NoClassDefFoundError ncdfe) {
489: // Ignore
490: }
491: return null;
492: }
493:
494: /**
495: * Returns the Class. Initializes it if found
496: * @param name the class name
497: * @param loader the ClassLoader
498: * @return Class the class loaded; null if not found
499: */
500: private Class ClassOrNullWithInitialize(String name,
501: ClassLoader loader) {
502: try {
503: return loader == null ? null : Class.forName(name, true,
504: loader);
505: } catch (ClassNotFoundException cnfe) {
506: return null;
507: } catch (NoClassDefFoundError ncdfe) {
508: // Some Windows JRE's throw this
509: return null;
510: }
511: }
512:
513: /**
514: * ClassLoader registered to load classes created at runtime
515: * @param loader The ClassLoader in which classes are defined
516: */
517: public void registerClassLoader(ClassLoader loader) {
518: this .registeredLoader = loader;
519: if (registeredLoader == null) {
520: registeredLoaderHashCode = 0;
521: } else {
522: registeredLoaderHashCode = loader.hashCode();
523: }
524: }
525:
526: /**
527: * ClassLoader registered by users to load classes. One ClassLoader can
528: * be registered, and if one ClassLoader is already registered, the registered ClassLoader
529: * is replaced by <code>loader</code>.
530: * @param loader The ClassLoader in which classes are loaded
531: */
532: public void registerUserClassLoader(ClassLoader loader) {
533: this .userRegisteredLoader = loader;
534: if (userRegisteredLoader == null) {
535: userRegisteredLoaderHashCode = 0;
536: } else {
537: userRegisteredLoaderHashCode = loader.hashCode();
538: }
539: }
540:
541: /**
542: * Finds all the resources with the given name.
543: * @param resourceName the resource name. If <code>resourceName</code> starts with "/", remove it before searching.
544: * @param primary the primary ClassLoader to use (or null)
545: * @return An enumeration of URL objects for the resource. If no resources could be found, the enumeration will be empty.
546: * Resources that the class loader doesn't have access to will not be in the enumeration.
547: * @throws IOException If I/O errors occur
548: * @see ClassLoader#getResources(java.lang.String)
549: */
550: public Enumeration getResources(final String resourceName,
551: final ClassLoader primary) throws IOException {
552: final List list = new ArrayList();
553: final ClassLoader userClassLoader = (ClassLoader) this .primary
554: .get();
555: AccessController.doPrivileged(new PrivilegedAction() {
556: public Object run() {
557: try {
558: String name = resourceName;
559: if (name.startsWith("/")) {
560: name = name.substring(1);
561: }
562: if (primary != null) {
563: Enumeration primaryResourceEnum = primary
564: .getResources(name);
565: while (primaryResourceEnum.hasMoreElements()) {
566: list.add(primaryResourceEnum.nextElement());
567: }
568: }
569:
570: if (userClassLoader != null) {
571: Enumeration primaryResourceEnum = userClassLoader
572: .getResources(name);
573: while (primaryResourceEnum.hasMoreElements()) {
574: list.add(primaryResourceEnum.nextElement());
575: }
576: }
577:
578: ClassLoader ctxClassLoader = Thread.currentThread()
579: .getContextClassLoader();
580: if (ctxClassLoader != null) {
581: Enumeration resourceEnum = ctxClassLoader
582: .getResources(name);
583: while (resourceEnum.hasMoreElements()) {
584: list.add(resourceEnum.nextElement());
585: }
586: }
587:
588: if (pmContextLoader != null) {
589: Enumeration pmResourceEnum = pmContextLoader
590: .getResources(name);
591: while (pmResourceEnum.hasMoreElements()) {
592: list.add(pmResourceEnum.nextElement());
593: }
594: }
595:
596: if (registeredLoader != null) {
597: Enumeration loaderResourceEnum = registeredLoader
598: .getResources(name);
599: while (loaderResourceEnum.hasMoreElements()) {
600: list.add(loaderResourceEnum.nextElement());
601: }
602: }
603:
604: if (userRegisteredLoader != null) {
605: Enumeration loaderResourceEnum = userRegisteredLoader
606: .getResources(name);
607: while (loaderResourceEnum.hasMoreElements()) {
608: list.add(loaderResourceEnum.nextElement());
609: }
610: }
611: } catch (IOException ex) {
612: throw new JPOXException(ex.getMessage(), ex);
613: }
614: return null;
615: }
616: });
617:
618: // remove duplicates and return as Enumeration
619: return Collections.enumeration(new HashSet(list));
620: }
621:
622: /**
623: * Finds the resource with the given name.
624: * @param resourceName the path to resource name relative to the classloader root path.
625: * If <code>resourceName</code> starts with "/", remove it before searching.
626: * @param primary the primary ClassLoader to use (or null)
627: * @return A URL object for reading the resource, or null if the resource could not be found or the invoker
628: * doesn't have adequate privileges to get the resource.
629: * @throws IOException If I/O errors occur
630: * @see ClassLoader#getResource(java.lang.String)
631: */
632: public URL getResource(final String resourceName,
633: final ClassLoader primary) {
634: final ClassLoader userClassLoader = (ClassLoader) this .primary
635: .get();
636: URL url = (URL) AccessController
637: .doPrivileged(new PrivilegedAction() {
638: public Object run() {
639: String resName = resourceName;
640: URL url = (URL) resources.get(resName);
641: if (url != null) {
642: return url;
643: }
644:
645: if (resName.startsWith("/")) {
646: resName = resName.substring(1);
647: }
648:
649: if (primary != null) {
650: url = primary.getResource(resName);
651: if (url != null) {
652: resources.put(resName, url);
653: return url;
654: }
655: }
656:
657: if (userClassLoader != null) {
658: url = userClassLoader.getResource(resName);
659: if (url != null) {
660: resources.put(resName, url);
661: return url;
662: }
663: }
664:
665: ClassLoader ctxClassLoader = Thread
666: .currentThread()
667: .getContextClassLoader();
668: if (ctxClassLoader != null) {
669: url = ctxClassLoader.getResource(resName);
670: if (url != null) {
671: resources.put(resName, url);
672: return url;
673: }
674: }
675:
676: if (pmContextLoader != null) {
677: url = pmContextLoader.getResource(resName);
678: if (url != null) {
679: resources.put(resName, url);
680: return url;
681: }
682: }
683:
684: if (registeredLoader != null) {
685: url = registeredLoader.getResource(resName);
686: if (url != null) {
687: resources.put(resName, url);
688: return url;
689: }
690: }
691:
692: if (userRegisteredLoader != null) {
693: url = userRegisteredLoader
694: .getResource(resName);
695: if (url != null) {
696: resources.put(resName, url);
697: return url;
698: }
699: }
700: return null;
701: }
702: });
703:
704: return url;
705: }
706:
707: /**
708: * Sets the primary classloader for the current thread
709: * @param primary the primary classloader
710: */
711: public void setPrimary(ClassLoader primary) {
712: this .primary.set(primary);
713: }
714:
715: /**
716: * Unsets the primary classloader for the current thread
717: */
718: public void unsetPrimary() {
719: this.primary.set(null);
720: }
721: }
|