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