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