001: /*
002: * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
003: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
004: */
005:
006: package javax.xml.bind;
007:
008: import java.io.BufferedReader;
009: import java.io.IOException;
010: import java.io.InputStream;
011: import java.io.InputStreamReader;
012: import java.io.UnsupportedEncodingException;
013: import java.lang.reflect.InvocationTargetException;
014: import java.lang.reflect.Method;
015: import java.net.URL;
016: import java.util.Map;
017: import java.util.Properties;
018: import java.util.StringTokenizer;
019: import java.util.logging.ConsoleHandler;
020: import java.util.logging.Level;
021: import java.util.logging.Logger;
022: import java.security.AccessController;
023: import java.security.PrivilegedAction;
024:
025: import static javax.xml.bind.JAXBContext.JAXB_CONTEXT_FACTORY;
026:
027: //import java.lang.reflect.InvocationTargetException;
028:
029: /**
030: * This class is package private and therefore is not exposed as part of the
031: * JAXB API.
032: *
033: * This code is designed to implement the JAXB 1.0 spec pluggability feature
034: *
035: * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
036: * @version $Revision: 1.25 $
037: * @see JAXBContext
038: */
039: class ContextFinder {
040: private static final Logger logger;
041: static {
042: logger = Logger.getLogger("javax.xml.bind");
043: try {
044: if (AccessController.doPrivileged(new GetPropertyAction(
045: "jaxb.debug")) != null) {
046: // disconnect the logger from a bigger framework (if any)
047: // and take the matters into our own hands
048: logger.setUseParentHandlers(false);
049: logger.setLevel(Level.ALL);
050: ConsoleHandler handler = new ConsoleHandler();
051: handler.setLevel(Level.ALL);
052: logger.addHandler(handler);
053: } else {
054: // don't change the setting of this logger
055: // to honor what other frameworks
056: // have done on configurations.
057: }
058: } catch (Throwable t) {
059: // just to be extra safe. in particular System.getProperty may throw
060: // SecurityException.
061: }
062: }
063:
064: /**
065: * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
066: * throw the wrapped exception.
067: */
068: private static void handleInvocationTargetException(
069: InvocationTargetException x) throws JAXBException {
070: Throwable t = x.getTargetException();
071: if (t != null) {
072: if (t instanceof JAXBException)
073: // one of our exceptions, just re-throw
074: throw (JAXBException) t;
075: if (t instanceof RuntimeException)
076: // avoid wrapping exceptions unnecessarily
077: throw (RuntimeException) t;
078: if (t instanceof Error)
079: throw (Error) t;
080: }
081: }
082:
083: /**
084: * Determine if two types (JAXBContext in this case) will generate a ClassCastException.
085: *
086: * For example, (targetType)originalType
087: *
088: * @param originalType
089: * The Class object of the type being cast
090: * @param targetType
091: * The Class object of the type that is being cast to
092: * @return JAXBException to be thrown.
093: */
094: private static JAXBException handleClassCastException(
095: Class originalType, Class targetType) {
096: final URL targetTypeURL = which(targetType);
097:
098: return new JAXBException(Messages.format(
099: Messages.ILLEGAL_CAST,
100: // we don't care where the impl class is, we want to know where JAXBContext lives in the impl
101: // class' ClassLoader
102: originalType.getClassLoader().getResource(
103: "javax/xml/bind/JAXBContext.class"),
104: targetTypeURL));
105: }
106:
107: /**
108: * Create an instance of a class using the specified ClassLoader
109: */
110: static JAXBContext newInstance(String contextPath,
111: String className, ClassLoader classLoader, Map properties)
112: throws JAXBException {
113: try {
114: Class spiClass;
115: if (classLoader == null) {
116: spiClass = Class.forName(className);
117: } else {
118: spiClass = classLoader.loadClass(className);
119: }
120:
121: /*
122: * javax.xml.bind.context.factory points to a class which has a
123: * static method called 'createContext' that
124: * returns a javax.xml.JAXBContext.
125: */
126:
127: Object context = null;
128:
129: // first check the method that takes Map as the third parameter.
130: // this is added in 2.0.
131: try {
132: Method m = spiClass.getMethod("createContext",
133: String.class, ClassLoader.class, Map.class);
134: // any failure in invoking this method would be considered fatal
135: context = m.invoke(null, contextPath, classLoader,
136: properties);
137: } catch (NoSuchMethodException e) {
138: // it's not an error for the provider not to have this method.
139: }
140:
141: if (context == null) {
142: // try the old method that doesn't take properties. compatible with 1.0.
143: // it is an error for an implementation not to have both forms of the createContext method.
144: Method m = spiClass.getMethod("createContext",
145: String.class, ClassLoader.class);
146: // any failure in invoking this method would be considered fatal
147: context = m.invoke(null, contextPath, classLoader);
148: }
149:
150: if (!(context instanceof JAXBContext)) {
151: // the cast would fail, so generate an exception with a nice message
152: handleClassCastException(context.getClass(),
153: JAXBContext.class);
154: }
155: return (JAXBContext) context;
156: } catch (ClassNotFoundException x) {
157: throw new JAXBException(Messages.format(
158: Messages.PROVIDER_NOT_FOUND, className), x);
159: } catch (InvocationTargetException x) {
160: handleInvocationTargetException(x);
161: // for other exceptions, wrap the internal target exception
162: // with a JAXBException
163: Throwable e = x;
164: if (x.getTargetException() != null)
165: e = x.getTargetException();
166:
167: throw new JAXBException(Messages.format(
168: Messages.COULD_NOT_INSTANTIATE, className, e), e);
169: } catch (RuntimeException x) {
170: // avoid wrapping RuntimeException to JAXBException,
171: // because it indicates a bug in this code.
172: throw x;
173: } catch (Exception x) {
174: // can't catch JAXBException because the method is hidden behind
175: // reflection. Root element collisions detected in the call to
176: // createContext() are reported as JAXBExceptions - just re-throw it
177: // some other type of exception - just wrap it
178: throw new JAXBException(Messages.format(
179: Messages.COULD_NOT_INSTANTIATE, className, x), x);
180: }
181: }
182:
183: /**
184: * Create an instance of a class using the specified ClassLoader
185: */
186: static JAXBContext newInstance(Class[] classes, Map properties,
187: String className) throws JAXBException {
188: ClassLoader cl = Thread.currentThread().getContextClassLoader();
189: Class spi;
190: try {
191: logger.fine("Trying to load " + className);
192: if (cl != null)
193: spi = cl.loadClass(className);
194: else
195: spi = Class.forName(className);
196: } catch (ClassNotFoundException e) {
197: throw new JAXBException(e);
198: }
199:
200: if (logger.isLoggable(Level.FINE)) {
201: // extra check to avoid costly which operation if not logged
202: logger.fine("loaded " + className + " from " + which(spi));
203: }
204:
205: Method m;
206: try {
207: m = spi
208: .getMethod("createContext", Class[].class,
209: Map.class);
210: } catch (NoSuchMethodException e) {
211: throw new JAXBException(e);
212: }
213: try {
214: Object context = m.invoke(null, classes, properties);
215: if (!(context instanceof JAXBContext)) {
216: // the cast would fail, so generate an exception with a nice message
217: throw handleClassCastException(context.getClass(),
218: JAXBContext.class);
219: }
220: return (JAXBContext) context;
221: } catch (IllegalAccessException e) {
222: throw new JAXBException(e);
223: } catch (InvocationTargetException e) {
224: handleInvocationTargetException(e);
225:
226: Throwable x = e;
227: if (e.getTargetException() != null)
228: x = e.getTargetException();
229:
230: throw new JAXBException(x);
231: }
232: }
233:
234: static JAXBContext find(String factoryId, String contextPath,
235: ClassLoader classLoader, Map properties)
236: throws JAXBException {
237:
238: // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
239:
240: final String jaxbContextFQCN = JAXBContext.class.getName();
241:
242: // search context path for jaxb.properties first
243: StringBuilder propFileName;
244: StringTokenizer packages = new StringTokenizer(contextPath, ":");
245: String factoryClassName;
246:
247: if (!packages.hasMoreTokens())
248: // no context is specified
249: throw new JAXBException(Messages
250: .format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
251:
252: logger.fine("Searching jaxb.properties");
253:
254: while (packages.hasMoreTokens()) {
255: String packageName = packages.nextToken(":").replace('.',
256: '/');
257: // com.acme.foo - > com/acme/foo/jaxb.properties
258: propFileName = new StringBuilder().append(packageName)
259: .append("/jaxb.properties");
260:
261: Properties props = loadJAXBProperties(classLoader,
262: propFileName.toString());
263: if (props != null) {
264: if (props.containsKey(factoryId)) {
265: factoryClassName = props.getProperty(factoryId);
266: return newInstance(contextPath, factoryClassName,
267: classLoader, properties);
268: } else {
269: throw new JAXBException(Messages.format(
270: Messages.MISSING_PROPERTY, packageName,
271: factoryId));
272: }
273: }
274: }
275:
276: logger.fine("Searching the system property");
277:
278: // search for a system property second (javax.xml.bind.JAXBContext)
279: factoryClassName = AccessController
280: .doPrivileged(new GetPropertyAction(jaxbContextFQCN));
281: if (factoryClassName != null) {
282: return newInstance(contextPath, factoryClassName,
283: classLoader, properties);
284: }
285:
286: logger.fine("Searching META-INF/services");
287:
288: // search META-INF services next
289: BufferedReader r;
290: try {
291: final StringBuilder resource = new StringBuilder().append(
292: "META-INF/services/").append(jaxbContextFQCN);
293: final InputStream resourceStream = classLoader
294: .getResourceAsStream(resource.toString());
295:
296: if (resourceStream != null) {
297: r = new BufferedReader(new InputStreamReader(
298: resourceStream, "UTF-8"));
299: factoryClassName = r.readLine().trim();
300: r.close();
301: return newInstance(contextPath, factoryClassName,
302: classLoader, properties);
303: } else {
304: logger.fine("Unable to load:" + resource.toString());
305: }
306: } catch (UnsupportedEncodingException e) {
307: // should never happen
308: throw new JAXBException(e);
309: } catch (IOException e) {
310: throw new JAXBException(e);
311: }
312:
313: // else no provider found
314: logger.fine("Trying to create the platform default provider");
315: return newInstance(contextPath, PLATFORM_DEFAULT_FACTORY_CLASS,
316: classLoader, properties);
317: }
318:
319: // TODO: log each step in the look up process
320: static JAXBContext find(Class[] classes, Map properties)
321: throws JAXBException {
322:
323: // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP?
324:
325: final String jaxbContextFQCN = JAXBContext.class.getName();
326: String factoryClassName;
327:
328: // search for jaxb.properties in the class loader of each class first
329: for (final Class c : classes) {
330: // this classloader is used only to load jaxb.properties, so doing this should be safe.
331: ClassLoader classLoader = AccessController
332: .doPrivileged(new PrivilegedAction<ClassLoader>() {
333: public ClassLoader run() {
334: return c.getClassLoader();
335: }
336: });
337: Package pkg = c.getPackage();
338: if (pkg == null)
339: continue; // this is possible for primitives, arrays, and classes that are loaded by poorly implemented ClassLoaders
340: String packageName = pkg.getName().replace('.', '/');
341:
342: // TODO: do we want to optimize away searching the same package? org.Foo, org.Bar, com.Baz
343: // classes from the same package might come from different class loades, so it might be a bad idea
344:
345: // TODO: it's easier to look things up from the class
346: // c.getResourceAsStream("jaxb.properties");
347:
348: // build the resource name and use the property loader code
349: String resourceName = packageName + "/jaxb.properties";
350: logger.fine("Trying to locate " + resourceName);
351: Properties props = loadJAXBProperties(classLoader,
352: resourceName);
353: if (props == null) {
354: logger.fine(" not found");
355: } else {
356: logger.fine(" found");
357: if (props.containsKey(JAXB_CONTEXT_FACTORY)) {
358: // trim() seems redundant, but adding to satisfy customer complaint
359: factoryClassName = props.getProperty(
360: JAXB_CONTEXT_FACTORY).trim();
361: return newInstance(classes, properties,
362: factoryClassName);
363: } else {
364: throw new JAXBException(Messages.format(
365: Messages.MISSING_PROPERTY, packageName,
366: JAXB_CONTEXT_FACTORY));
367: }
368: }
369: }
370:
371: // search for a system property second (javax.xml.bind.JAXBContext)
372: logger.fine("Checking system property " + jaxbContextFQCN);
373: factoryClassName = AccessController
374: .doPrivileged(new GetPropertyAction(jaxbContextFQCN));
375: if (factoryClassName != null) {
376: logger.fine(" found " + factoryClassName);
377: return newInstance(classes, properties, factoryClassName);
378: }
379: logger.fine(" not found");
380:
381: // search META-INF services next
382: logger.fine("Checking META-INF/services");
383: BufferedReader r;
384: try {
385: final String resource = new StringBuilder(
386: "META-INF/services/").append(jaxbContextFQCN)
387: .toString();
388: ClassLoader classLoader = Thread.currentThread()
389: .getContextClassLoader();
390: URL resourceURL;
391: if (classLoader == null)
392: resourceURL = ClassLoader.getSystemResource(resource);
393: else
394: resourceURL = classLoader.getResource(resource);
395:
396: if (resourceURL != null) {
397: logger.fine("Reading " + resourceURL);
398: r = new BufferedReader(new InputStreamReader(
399: resourceURL.openStream(), "UTF-8"));
400: factoryClassName = r.readLine().trim();
401: return newInstance(classes, properties,
402: factoryClassName);
403: } else {
404: logger.fine("Unable to find: " + resource);
405: }
406: } catch (UnsupportedEncodingException e) {
407: // should never happen
408: throw new JAXBException(e);
409: } catch (IOException e) {
410: throw new JAXBException(e);
411: }
412:
413: // else no provider found
414: logger.fine("Trying to create the platform default provider");
415: return newInstance(classes, properties,
416: PLATFORM_DEFAULT_FACTORY_CLASS);
417: }
418:
419: private static Properties loadJAXBProperties(
420: ClassLoader classLoader, String propFileName)
421: throws JAXBException {
422:
423: Properties props = null;
424:
425: try {
426: URL url;
427: if (classLoader == null)
428: url = ClassLoader.getSystemResource(propFileName);
429: else
430: url = classLoader.getResource(propFileName);
431:
432: if (url != null) {
433: logger.fine("loading props from " + url);
434: props = new Properties();
435: InputStream is = url.openStream();
436: props.load(is);
437: is.close();
438: }
439: } catch (IOException ioe) {
440: logger.log(Level.FINE, "Unable to load " + propFileName,
441: ioe);
442: throw new JAXBException(ioe.toString(), ioe);
443: }
444:
445: return props;
446: }
447:
448: /**
449: * Search the given ClassLoader for an instance of the specified class and
450: * return a string representation of the URL that points to the resource.
451: *
452: * @param clazz
453: * The class to search for
454: * @param loader
455: * The ClassLoader to search. If this parameter is null, then the
456: * system class loader will be searched
457: * @return
458: * the URL for the class or null if it wasn't found
459: */
460: static URL which(Class clazz, ClassLoader loader) {
461:
462: String classnameAsResource = clazz.getName().replace('.', '/')
463: + ".class";
464:
465: if (loader == null) {
466: loader = ClassLoader.getSystemClassLoader();
467: }
468:
469: return loader.getResource(classnameAsResource);
470: }
471:
472: /**
473: * Get the URL for the Class from it's ClassLoader.
474: *
475: * Convenience method for {@link #which(Class, ClassLoader)}.
476: *
477: * Equivalent to calling: which(clazz, clazz.getClassLoader())
478: *
479: * @param clazz
480: * The class to search for
481: * @return
482: * the URL for the class or null if it wasn't found
483: */
484: static URL which(Class clazz) {
485: return which(clazz, clazz.getClassLoader());
486: }
487:
488: /**
489: * When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
490: * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext
491: * because if it has, it will take precedence over any file that applications have
492: * in their jar files.
493: *
494: * <p>
495: * When the user bundles his own JAXB implementation, we'd like to use it, and we
496: * want the platform default to be used only when there's no other JAXB provider.
497: *
498: * <p>
499: * For this reason, we have to hard-code the class name into the API.
500: */
501: private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.bind.v2.ContextFactory";
502: }
|