1 /* 2 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.xml.bind; 27 28 import java.io.BufferedReader; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.io.UnsupportedEncodingException; 33 import java.lang.reflect.InvocationTargetException; 34 import java.lang.reflect.Method; 35 import java.net.URL; 36 import java.security.AccessController; 37 import java.util.Map; 38 import java.util.Properties; 39 import java.util.StringTokenizer; 40 import java.util.logging.ConsoleHandler; 41 import java.util.logging.Level; 42 import java.util.logging.Logger; 43 44 45 /** 46 * This class is package private and therefore is not exposed as part of the 47 * JAXB API. 48 * 49 * This code is designed to implement the JAXB 1.0 spec pluggability feature 50 * 51 * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul> 52 * @see JAXBContext 53 */ 54 class ContextFinder { 55 56 /** 57 * When JAXB is in J2SE, rt.jar has to have a JAXB implementation. 58 * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext 59 * because if it has, it will take precedence over any file that applications have 60 * in their jar files. 61 * 62 * <p> 63 * When the user bundles his own JAXB implementation, we'd like to use it, and we 64 * want the platform default to be used only when there's no other JAXB provider. 65 * 66 * <p> 67 * For this reason, we have to hard-code the class name into the API. 68 */ 69 private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory"; 70 71 // previous value of JAXBContext.JAXB_CONTEXT_FACTORY, using also this to ensure backwards compatibility 72 private static final String JAXB_CONTEXT_FACTORY_DEPRECATED = "javax.xml.bind.context.factory"; 73 74 private static final Logger logger; 75 76 static { 77 logger = Logger.getLogger("javax.xml.bind"); 78 try { 79 if (AccessController.doPrivileged(new GetPropertyAction("jaxb.debug")) != null) { 80 // disconnect the logger from a bigger framework (if any) 81 // and take the matters into our own hands 82 logger.setUseParentHandlers(false); 83 logger.setLevel(Level.ALL); 84 ConsoleHandler handler = new ConsoleHandler(); 85 handler.setLevel(Level.ALL); 86 logger.addHandler(handler); 87 } else { 88 // don't change the setting of this logger 89 // to honor what other frameworks 90 // have done on configurations. 91 } 92 } catch (Throwable t) { 93 // just to be extra safe. in particular System.getProperty may throw 94 // SecurityException. 95 } 96 } 97 98 private static ServiceLoaderUtil.ExceptionHandler<JAXBException> EXCEPTION_HANDLER = new ServiceLoaderUtil.ExceptionHandler<JAXBException>() { 99 @Override 100 public JAXBException createException(Throwable throwable, String message) { 101 return new JAXBException(message, throwable); 102 } 103 }; 104 105 /** 106 * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped, 107 * throw the wrapped exception. 108 */ 109 private static void handleInvocationTargetException(InvocationTargetException x) throws JAXBException { 110 Throwable t = x.getTargetException(); 111 if (t != null) { 112 if (t instanceof JAXBException) 113 // one of our exceptions, just re-throw 114 throw (JAXBException) t; 115 if (t instanceof RuntimeException) 116 // avoid wrapping exceptions unnecessarily 117 throw (RuntimeException) t; 118 if (t instanceof Error) 119 throw (Error) t; 120 } 121 } 122 123 124 /** 125 * Determine if two types (JAXBContext in this case) will generate a ClassCastException. 126 * 127 * For example, (targetType)originalType 128 * 129 * @param originalType 130 * The Class object of the type being cast 131 * @param targetType 132 * The Class object of the type that is being cast to 133 * @return JAXBException to be thrown. 134 */ 135 private static JAXBException handleClassCastException(Class originalType, Class targetType) { 136 final URL targetTypeURL = which(targetType); 137 138 return new JAXBException(Messages.format(Messages.ILLEGAL_CAST, 139 // we don't care where the impl class is, we want to know where JAXBContext lives in the impl 140 // class' ClassLoader 141 getClassClassLoader(originalType).getResource("javax/xml/bind/JAXBContext.class"), 142 targetTypeURL)); 143 } 144 145 /** 146 * Create an instance of a class using the specified ClassLoader 147 */ 148 static JAXBContext newInstance(String contextPath, 149 String className, 150 ClassLoader classLoader, 151 Map properties) throws JAXBException { 152 153 try { 154 Class spFactory = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader); 155 return newInstance(contextPath, spFactory, classLoader, properties); 156 } catch (ClassNotFoundException x) { 157 throw new JAXBException(Messages.format(Messages.PROVIDER_NOT_FOUND, className), x); 158 159 } catch (RuntimeException x) { 160 // avoid wrapping RuntimeException to JAXBException, 161 // because it indicates a bug in this code. 162 throw x; 163 } catch (Exception x) { 164 // can't catch JAXBException because the method is hidden behind 165 // reflection. Root element collisions detected in the call to 166 // createContext() are reported as JAXBExceptions - just re-throw it 167 // some other type of exception - just wrap it 168 throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, className, x), x); 169 } 170 } 171 172 static JAXBContext newInstance(String contextPath, Class spFactory, ClassLoader classLoader, Map properties) throws JAXBException { 173 174 try { 175 /* 176 * javax.xml.bind.context.factory points to a class which has a 177 * static method called 'createContext' that 178 * returns a javax.xml.JAXBContext. 179 */ 180 181 Object context = null; 182 183 // first check the method that takes Map as the third parameter. 184 // this is added in 2.0. 185 try { 186 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class, Map.class); 187 // any failure in invoking this method would be considered fatal 188 context = m.invoke(null, contextPath, classLoader, properties); 189 } catch (NoSuchMethodException e) { 190 // it's not an error for the provider not to have this method. 191 } 192 193 if (context == null) { 194 // try the old method that doesn't take properties. compatible with 1.0. 195 // it is an error for an implementation not to have both forms of the createContext method. 196 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class); 197 // any failure in invoking this method would be considered fatal 198 context = m.invoke(null, contextPath, classLoader); 199 } 200 201 if (!(context instanceof JAXBContext)) { 202 // the cast would fail, so generate an exception with a nice message 203 throw handleClassCastException(context.getClass(), JAXBContext.class); 204 } 205 return (JAXBContext) context; 206 } catch (InvocationTargetException x) { 207 handleInvocationTargetException(x); 208 // for other exceptions, wrap the internal target exception 209 // with a JAXBException 210 Throwable e = x; 211 if (x.getTargetException() != null) 212 e = x.getTargetException(); 213 214 throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, e), e); 215 } catch (RuntimeException x) { 216 // avoid wrapping RuntimeException to JAXBException, 217 // because it indicates a bug in this code. 218 throw x; 219 } catch (Exception x) { 220 // can't catch JAXBException because the method is hidden behind 221 // reflection. Root element collisions detected in the call to 222 // createContext() are reported as JAXBExceptions - just re-throw it 223 // some other type of exception - just wrap it 224 throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, x), x); 225 } 226 } 227 228 /** 229 * Create an instance of a class using the thread context ClassLoader 230 */ 231 static JAXBContext newInstance(Class[] classes, Map properties, String className) throws JAXBException { 232 233 Class spi; 234 try { 235 spi = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, getContextClassLoader()); 236 } catch (ClassNotFoundException e) { 237 throw new JAXBException(e); 238 } 239 240 if (logger.isLoggable(Level.FINE)) { 241 // extra check to avoid costly which operation if not logged 242 logger.log(Level.FINE, "loaded {0} from {1}", new Object[]{className, which(spi)}); 243 } 244 245 return newInstance(classes, properties, spi); 246 } 247 248 static JAXBContext newInstance(Class[] classes, 249 Map properties, 250 Class spFactory) throws JAXBException { 251 try { 252 Method m = spFactory.getMethod("createContext", Class[].class, Map.class); 253 Object context = m.invoke(null, classes, properties); 254 if (!(context instanceof JAXBContext)) { 255 // the cast would fail, so generate an exception with a nice message 256 throw handleClassCastException(context.getClass(), JAXBContext.class); 257 } 258 return (JAXBContext) context; 259 } catch (NoSuchMethodException e) { 260 throw new JAXBException(e); 261 } catch (IllegalAccessException e) { 262 throw new JAXBException(e); 263 } catch (InvocationTargetException e) { 264 handleInvocationTargetException(e); 265 266 Throwable x = e; 267 if (e.getTargetException() != null) 268 x = e.getTargetException(); 269 270 throw new JAXBException(x); 271 } 272 } 273 274 static JAXBContext find(String factoryId, String contextPath, ClassLoader classLoader, Map properties) throws JAXBException { 275 276 // TODO: do we want/need another layer of searching in $java.home/lib/jaxb.properties like JAXP? 277 278 StringTokenizer packages = new StringTokenizer(contextPath, ":"); 279 if (!packages.hasMoreTokens()) { 280 // no context is specified 281 throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH)); 282 } 283 284 // search for jaxb.properties in the class loader of each class first 285 logger.fine("Searching jaxb.properties"); 286 while (packages.hasMoreTokens()) { 287 // com.acme.foo - > com/acme/foo/jaxb.properties 288 String factoryClassName = classNameFromPackageProperties(classLoader, packages.nextToken(":").replace('.', '/'), factoryId, JAXB_CONTEXT_FACTORY_DEPRECATED); 289 if (factoryClassName != null) return newInstance(contextPath, factoryClassName, classLoader, properties); 290 } 291 292 String factoryName = classNameFromSystemProperties(); 293 if (factoryName != null) return newInstance(contextPath, factoryName, classLoader, properties); 294 295 // TODO: SPEC change required! java.util.ServiceLoader 296 JAXBContextFactory obj = ServiceLoaderUtil.firstByServiceLoader(JAXBContextFactory.class, logger, EXCEPTION_HANDLER); 297 if (obj != null) return obj.createContext(contextPath, classLoader, properties); 298 299 // to ensure backwards compatibility 300 factoryName = firstByServiceLoaderDeprecated(JAXBContext.class, classLoader); 301 if (factoryName != null) return newInstance(contextPath, factoryName, classLoader, properties); 302 303 Class ctxFactory = (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger); 304 if (ctxFactory != null) { 305 return newInstance(contextPath, ctxFactory, classLoader, properties); 306 } 307 308 // else no provider found 309 logger.fine("Trying to create the platform default provider"); 310 return newInstance(contextPath, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader, properties); 311 } 312 313 static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JAXBException { 314 315 // search for jaxb.properties in the class loader of each class first 316 logger.fine("Searching jaxb.properties"); 317 for (final Class c : classes) { 318 // this classloader is used only to load jaxb.properties, so doing this should be safe. 319 if (c.getPackage() == null) continue; // this is possible for primitives, arrays, and classes that are loaded by poorly implemented ClassLoaders 320 321 // TODO: do we want to optimize away searching the same package? org.Foo, org.Bar, com.Baz 322 // classes from the same package might come from different class loades, so it might be a bad idea 323 // TODO: it's easier to look things up from the class 324 // c.getResourceAsStream("jaxb.properties"); 325 326 String factoryClassName = classNameFromPackageProperties(getClassClassLoader(c), c.getPackage().getName().replace('.', '/'), JAXBContext.JAXB_CONTEXT_FACTORY, JAXB_CONTEXT_FACTORY_DEPRECATED); 327 if (factoryClassName != null) return newInstance(classes, properties, factoryClassName); 328 } 329 330 String factoryClassName = classNameFromSystemProperties(); 331 if (factoryClassName != null) return newInstance(classes, properties, factoryClassName); 332 333 // TODO: SPEC change required! java.util.ServiceLoader 334 JAXBContextFactory factory = ServiceLoaderUtil.firstByServiceLoader(JAXBContextFactory.class, logger, EXCEPTION_HANDLER); 335 if (factory != null) return factory.createContext(classes, properties); 336 337 // to ensure backwards compatibility 338 String className = firstByServiceLoaderDeprecated(JAXBContext.class, getContextClassLoader()); 339 if (className != null) return newInstance(classes, properties, className); 340 341 logger.fine("Trying to create the platform default provider"); 342 Class ctxFactoryClass = (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger); 343 if (ctxFactoryClass != null) { 344 return newInstance(classes, properties, ctxFactoryClass); 345 } 346 347 // else no provider found 348 logger.fine("Trying to create the platform default provider"); 349 return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS); 350 } 351 352 353 /** 354 * first factoryId should be the preffered one, more of those can be provided to support backwards compatibility 355 */ 356 private static String classNameFromPackageProperties(ClassLoader classLoader, String packageName, String ... factoryIds) throws JAXBException { 357 String resourceName = packageName + "/jaxb.properties"; 358 logger.log(Level.FINE, "Trying to locate {0}", resourceName); 359 Properties props = loadJAXBProperties(classLoader, resourceName); 360 if (props != null) { 361 for(String factoryId : factoryIds) { 362 if (props.containsKey(factoryId)) { 363 return props.getProperty(factoryId); 364 } 365 } 366 throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryIds[0])); 367 } 368 return null; 369 } 370 371 private static String classNameFromSystemProperties() throws JAXBException { 372 String factoryClassName = getSystemProperty(JAXBContext.JAXB_CONTEXT_FACTORY); 373 if (factoryClassName != null) { 374 return factoryClassName; 375 } 376 // leave this here to assure compatibility 377 factoryClassName = getDeprecatedSystemProperty(JAXB_CONTEXT_FACTORY_DEPRECATED); 378 if (factoryClassName != null) { 379 return factoryClassName; 380 } 381 // leave this here to assure compatibility 382 factoryClassName = getDeprecatedSystemProperty(JAXBContext.class.getName()); 383 if (factoryClassName != null) { 384 return factoryClassName; 385 } 386 return null; 387 } 388 389 private static String getDeprecatedSystemProperty(String property) { 390 String value = getSystemProperty(property); 391 if (value != null) { 392 logger.log(Level.WARNING, "Using non-standard property: {0}. Property {1} should be used instead.", 393 new Object[] {property, JAXBContext.JAXB_CONTEXT_FACTORY}); 394 } 395 return value; 396 } 397 398 private static String getSystemProperty(String property) { 399 logger.log(Level.FINE, "Checking system property {0}", property); 400 String value = AccessController.doPrivileged(new GetPropertyAction(property)); 401 if (value != null) { 402 logger.log(Level.FINE, " found {0}", value); 403 } else { 404 logger.log(Level.FINE, " not found"); 405 } 406 return value; 407 } 408 409 private static Properties loadJAXBProperties(ClassLoader classLoader, String propFileName) throws JAXBException { 410 411 Properties props = null; 412 try { 413 URL url; 414 if (classLoader == null) 415 url = ClassLoader.getSystemResource(propFileName); 416 else 417 url = classLoader.getResource(propFileName); 418 419 if (url != null) { 420 logger.log(Level.FINE, "loading props from {0}", url); 421 props = new Properties(); 422 InputStream is = url.openStream(); 423 props.load(is); 424 is.close(); 425 } 426 } catch (IOException ioe) { 427 logger.log(Level.FINE, "Unable to load " + propFileName, ioe); 428 throw new JAXBException(ioe.toString(), ioe); 429 } 430 431 return props; 432 } 433 434 435 /** 436 * Search the given ClassLoader for an instance of the specified class and 437 * return a string representation of the URL that points to the resource. 438 * 439 * @param clazz 440 * The class to search for 441 * @param loader 442 * The ClassLoader to search. If this parameter is null, then the 443 * system class loader will be searched 444 * @return 445 * the URL for the class or null if it wasn't found 446 */ 447 static URL which(Class clazz, ClassLoader loader) { 448 449 String classnameAsResource = clazz.getName().replace('.', '/') + ".class"; 450 451 if (loader == null) { 452 loader = getSystemClassLoader(); 453 } 454 455 return loader.getResource(classnameAsResource); 456 } 457 458 /** 459 * Get the URL for the Class from it's ClassLoader. 460 * 461 * Convenience method for {@link #which(Class, ClassLoader)}. 462 * 463 * Equivalent to calling: which(clazz, clazz.getClassLoader()) 464 * 465 * @param clazz 466 * The class to search for 467 * @return 468 * the URL for the class or null if it wasn't found 469 */ 470 static URL which(Class clazz) { 471 return which(clazz, getClassClassLoader(clazz)); 472 } 473 474 @SuppressWarnings("unchecked") 475 private static ClassLoader getContextClassLoader() { 476 if (System.getSecurityManager() == null) { 477 return Thread.currentThread().getContextClassLoader(); 478 } else { 479 return (ClassLoader) java.security.AccessController.doPrivileged( 480 new java.security.PrivilegedAction() { 481 public java.lang.Object run() { 482 return Thread.currentThread().getContextClassLoader(); 483 } 484 }); 485 } 486 } 487 488 @SuppressWarnings("unchecked") 489 private static ClassLoader getClassClassLoader(final Class c) { 490 if (System.getSecurityManager() == null) { 491 return c.getClassLoader(); 492 } else { 493 return (ClassLoader) java.security.AccessController.doPrivileged( 494 new java.security.PrivilegedAction() { 495 public java.lang.Object run() { 496 return c.getClassLoader(); 497 } 498 }); 499 } 500 } 501 502 private static ClassLoader getSystemClassLoader() { 503 if (System.getSecurityManager() == null) { 504 return ClassLoader.getSystemClassLoader(); 505 } else { 506 return (ClassLoader) java.security.AccessController.doPrivileged( 507 new java.security.PrivilegedAction() { 508 public java.lang.Object run() { 509 return ClassLoader.getSystemClassLoader(); 510 } 511 }); 512 } 513 } 514 515 // TODO: to be removed - SPEC change required 516 // ServiceLoaderUtil.firstByServiceLoaderDeprecated should be used instead. 517 @Deprecated 518 static String firstByServiceLoaderDeprecated(Class spiClass, ClassLoader classLoader) throws JAXBException { 519 final String jaxbContextFQCN = spiClass.getName(); 520 521 logger.fine("Searching META-INF/services"); 522 523 // search META-INF services next 524 BufferedReader r = null; 525 final String resource = new StringBuilder().append("META-INF/services/").append(jaxbContextFQCN).toString(); 526 try { 527 final InputStream resourceStream = 528 (classLoader == null) ? 529 ClassLoader.getSystemResourceAsStream(resource) : 530 classLoader.getResourceAsStream(resource); 531 532 if (resourceStream != null) { 533 r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8")); 534 String factoryClassName = r.readLine(); 535 if (factoryClassName != null) { 536 factoryClassName = factoryClassName.trim(); 537 } 538 r.close(); 539 logger.log(Level.FINE, "Configured factorty class:{0}", factoryClassName); 540 return factoryClassName; 541 } else { 542 logger.log(Level.FINE, "Unable to load:{0}", resource); 543 return null; 544 } 545 } catch (UnsupportedEncodingException e) { 546 // should never happen 547 throw new JAXBException(e); 548 } catch (IOException e) { 549 throw new JAXBException(e); 550 } finally { 551 try { 552 if (r != null) { 553 r.close(); 554 } 555 } catch (IOException ex) { 556 logger.log(Level.SEVERE, "Unable to close resource: " + resource, ex); 557 } 558 } 559 } 560 561 }