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