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