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