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