1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 2003-2018 Oracle and/or its affiliates. All rights reserved.
5  *
6  * The contents of this file are subject to the terms of either the GNU
7  * General Public License Version 2 only ("GPL") or the Common Development
8  * and Distribution License("CDDL") (collectively, the "License").  You
9  * may not use this file except in compliance with the License.  You can
10  * obtain a copy of the License at
11  * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12  * or LICENSE.txt.  See the License for the specific
13  * language governing permissions and limitations under the License.
14  *
15  * When distributing the software, include this License Header Notice in each
16  * file and include the License file at LICENSE.txt.
17  *
18  * GPL Classpath Exception:
19  * Oracle designates this particular file as subject to the "Classpath"
20  * exception as provided by Oracle in the GPL Version 2 section of the License
21  * file that accompanied this code.
22  *
23  * Modifications:
24  * If applicable, add the following below the License Header, with the fields
25  * enclosed by brackets [] replaced by your own identifying information:
26  * "Portions Copyright [year] [name of copyright owner]"
27  *
28  * Contributor(s):
29  * If you wish your version of this file to be governed by only the CDDL or
30  * only the GPL Version 2, indicate your decision by adding "[Contributor]
31  * elects to include this software in this distribution under the [CDDL or GPL
32  * Version 2] license."  If you don't indicate a single choice of license, a
33  * recipient has the option to distribute your version of this file under
34  * either the CDDL, the GPL Version 2 or to extend the choice of license to
35  * its licensees as provided above.  However, if you add GPL Version 2 code
36  * and therefore, elected the GPL Version 2 license, then the option applies
37  * only if the new code is made subject to such option by the copyright
38  * holder.
39  */

40
41 package javax.xml.bind;
42
43 import java.io.BufferedReader;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.lang.reflect.InvocationTargetException;
48 import java.lang.reflect.Method;
49 import java.net.URL;
50 import java.security.AccessController;
51 import java.security.PrivilegedActionException;
52 import java.security.PrivilegedExceptionAction;
53 import java.util.Map;
54 import java.util.Properties;
55 import java.util.StringTokenizer;
56 import java.util.logging.ConsoleHandler;
57 import java.util.logging.Level;
58 import java.util.logging.Logger;
59
60
61 /**
62  * This class is package private and therefore is not exposed as part of the
63  * JAXB API.
64  *
65  * This code is designed to implement the JAXB 1.0 spec pluggability feature
66  *
67  * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
68  * @see JAXBContext
69  */

70 class ContextFinder {
71
72     /**
73      * When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
74      * However, rt.jar cannot have META-INF/services/javax.xml.bind.JAXBContext
75      * because if it has, it will take precedence over any file that applications have
76      * in their jar files.
77      *
78      * <p>
79      * When the user bundles his own JAXB implementation, we'd like to use it, and we
80      * want the platform default to be used only when there's no other JAXB provider.
81      *
82      * <p>
83      * For this reason, we have to hard-code the class name into the API.
84      */

85     private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
86
87     // previous value of JAXBContext.JAXB_CONTEXT_FACTORY, using also this to ensure backwards compatibility
88     private static final String JAXB_CONTEXT_FACTORY_DEPRECATED = "javax.xml.bind.context.factory";
89
90     private static final Logger logger;
91
92     static {
93         logger = Logger.getLogger("javax.xml.bind");
94         try {
95             if (AccessController.doPrivileged(new GetPropertyAction("jaxb.debug")) != null) {
96                 // disconnect the logger from a bigger framework (if any)
97                 // and take the matters into our own hands
98                 logger.setUseParentHandlers(false);
99                 logger.setLevel(Level.ALL);
100                 ConsoleHandler handler = new ConsoleHandler();
101                 handler.setLevel(Level.ALL);
102                 logger.addHandler(handler);
103             } else {
104                 // don't change the setting of this logger
105                 // to honor what other frameworks
106                 // have done on configurations.
107             }
108         } catch (Throwable t) {
109             // just to be extra safe. in particular System.getProperty may throw
110             // SecurityException.
111         }
112     }
113
114     private static ServiceLoaderUtil.ExceptionHandler<JAXBException> EXCEPTION_HANDLER =
115             new ServiceLoaderUtil.ExceptionHandler<JAXBException>() {
116                 @Override
117                 public JAXBException createException(Throwable throwable, String message) {
118                     return new JAXBException(message, throwable);
119                 }
120             };
121
122     /**
123      * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
124      * throw the wrapped exception. Otherwise returns exception to be wrapped for further processing.
125      */

126     private static Throwable handleInvocationTargetException(InvocationTargetException x) throws JAXBException {
127         Throwable t = x.getTargetException();
128         if (t != null) {
129             if (t instanceof JAXBException)
130                 // one of our exceptions, just re-throw
131                 throw (JAXBException) t;
132             if (t instanceof RuntimeException)
133                 // avoid wrapping exceptions unnecessarily
134                 throw (RuntimeException) t;
135             if (t instanceof Error)
136                 throw (Error) t;
137             return t;
138         }
139         return x;
140     }
141
142
143     /**
144      * Determine if two types (JAXBContext in this case) will generate a ClassCastException.
145      *
146      * For example, (targetType)originalType
147      *
148      * @param originalType
149      *          The Class object of the type being cast
150      * @param targetType
151      *          The Class object of the type that is being cast to
152      * @return JAXBException to be thrown.
153      */

154     private static JAXBException handleClassCastException(Class originalType, Class targetType) {
155         final URL targetTypeURL = which(targetType);
156
157         return new JAXBException(Messages.format(Messages.ILLEGAL_CAST,
158                 // we don't care where the impl class is, we want to know where JAXBContext lives in the impl
159                 // class' ClassLoader
160                 getClassClassLoader(originalType).getResource("javax/xml/bind/JAXBContext.class"),
161                 targetTypeURL));
162     }
163
164     /**
165      * Create an instance of a class using the specified ClassLoader
166      */

167     static JAXBContext newInstance(String contextPath,
168                                    Class[] contextPathClasses,
169                                    String className,
170                                    ClassLoader classLoader,
171                                    Map properties) throws JAXBException {
172
173         try {
174             Class spFactory = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader);
175             return newInstance(contextPath, contextPathClasses, spFactory, classLoader, properties);
176         } catch (ClassNotFoundException x) {
177             throw new JAXBException(Messages.format(Messages.DEFAULT_PROVIDER_NOT_FOUND), x);
178
179         } catch (RuntimeException | JAXBException x) {
180             // avoid wrapping RuntimeException to JAXBException,
181             // because it indicates a bug in this code.
182             // JAXBException re-thrown as is
183             throw x;
184         } catch (Exception x) {
185             // can't catch JAXBException because the method is hidden behind
186             // reflection.  Root element collisions detected in the call to
187             // createContext() are reported as JAXBExceptions - just re-throw it
188             // some other type of exception - just wrap it
189             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, className, x), x);
190         }
191     }
192
193     static JAXBContext newInstance(String contextPath,
194                                    Class[] contextPathClasses,
195                                    Class spFactory,
196                                    ClassLoader classLoader,
197                                    Map properties) throws JAXBException {
198
199         try {
200
201             ModuleUtil.delegateAddOpensToImplModule(contextPathClasses, spFactory);
202
203             /*
204              * javax.xml.bind.context.factory points to a class which has a
205              * static method called 'createContext' that
206              * returns a javax.xml.JAXBContext.
207              */

208
209             Object context = null;
210
211             // first check the method that takes Map as the third parameter.
212             // this is added in 2.0.
213             try {
214                 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class, Map.class);
215                 // any failure in invoking this method would be considered fatal
216                 Object obj = instantiateProviderIfNecessary(spFactory);
217                 context = m.invoke(obj, contextPath, classLoader, properties);
218             } catch (NoSuchMethodException ignored) {
219                 // it's not an error for the provider not to have this method.
220             }
221
222             if (context == null) {
223                 // try the old method that doesn't take properties. compatible with 1.0.
224                 // it is an error for an implementation not to have both forms of the createContext method.
225                 Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class);
226                 Object obj = instantiateProviderIfNecessary(spFactory);
227                 // any failure in invoking this method would be considered fatal
228                 context = m.invoke(obj, contextPath, classLoader);
229             }
230
231             if (!(context instanceof JAXBContext)) {
232                 // the cast would fail, so generate an exception with a nice message
233                 throw handleClassCastException(context.getClass(), JAXBContext.class);
234             }
235
236             return (JAXBContext) context;
237         } catch (InvocationTargetException x) {
238             // throw if it is exception not to be wrapped
239             // otherwise, wrap with a JAXBException
240             Throwable e = handleInvocationTargetException(x);
241             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, e), e);
242
243         } catch (Exception x) {
244             // can't catch JAXBException because the method is hidden behind
245             // reflection.  Root element collisions detected in the call to
246             // createContext() are reported as JAXBExceptions - just re-throw it
247             // some other type of exception - just wrap it
248             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, x), x);
249         }
250     }
251
252     private static Object instantiateProviderIfNecessary(final Class<?> implClass) throws JAXBException {
253         try {
254             if (JAXBContextFactory.class.isAssignableFrom(implClass)) {
255                 return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
256                     @Override
257                     public Object run() throws Exception {
258                         return implClass.newInstance();
259                     }
260                 });
261             }
262             return null;
263         } catch (PrivilegedActionException x) {
264             Throwable e = (x.getCause() == null) ? x : x.getCause();
265             throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, implClass, e), e);
266         }
267     }
268
269     /**
270      * Create an instance of a class using the thread context ClassLoader
271      */

272     static JAXBContext newInstance(Class[] classes, Map properties, String className) throws JAXBException {
273
274         Class spi;
275         try {
276             spi = ServiceLoaderUtil.safeLoadClass(className, PLATFORM_DEFAULT_FACTORY_CLASS, getContextClassLoader());
277         } catch (ClassNotFoundException e) {
278             throw new JAXBException(Messages.format(Messages.DEFAULT_PROVIDER_NOT_FOUND), e);
279         }
280
281         if (logger.isLoggable(Level.FINE)) {
282             // extra check to avoid costly which operation if not logged
283             logger.log(Level.FINE, "loaded {0} from {1}"new Object[]{className, which(spi)});
284         }
285
286         return newInstance(classes, properties, spi);
287     }
288
289     static JAXBContext newInstance(Class[] classes,
290                                    Map properties,
291                                    Class spFactory) throws JAXBException {
292         try {
293             ModuleUtil.delegateAddOpensToImplModule(classes,  spFactory);
294
295             Method m = spFactory.getMethod("createContext", Class[].class, Map.class);
296             Object obj = instantiateProviderIfNecessary(spFactory);
297             Object context = m.invoke(obj, classes, properties);
298             if (!(context instanceof JAXBContext)) {
299                 // the cast would fail, so generate an exception with a nice message
300                 throw handleClassCastException(context.getClass(), JAXBContext.class);
301             }
302             return (JAXBContext) context;
303
304         } catch (NoSuchMethodException | IllegalAccessException e) {
305             throw new JAXBException(e);
306         } catch (InvocationTargetException e) {
307             // throw if it is exception not to be wrapped
308             // otherwise, wrap with a JAXBException
309             Throwable x = handleInvocationTargetException(e);
310
311             throw new JAXBException(x);
312         }
313     }
314
315     static JAXBContext find(String factoryId,
316                             String contextPath,
317                             ClassLoader classLoader,
318                             Map properties) throws JAXBException {
319
320         if (contextPath == null || contextPath.isEmpty()) {
321             // no context is specified
322             throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
323         }
324
325         //ModuleUtil is mr-jar class, scans context path for jaxb classes on jdk9 and higher
326         Class[] contextPathClasses = ModuleUtil.getClassesFromContextPath(contextPath, classLoader);
327
328         //first try with classloader#getResource
329         String factoryClassName = jaxbProperties(contextPath, classLoader, factoryId);
330         if (factoryClassName == null && contextPathClasses != null) {
331             //try with class#getResource
332             factoryClassName = jaxbProperties(contextPathClasses, factoryId);
333         }
334
335         if (factoryClassName != null) {
336             return newInstance(contextPath, contextPathClasses, factoryClassName, classLoader, properties);
337         }
338
339
340         String factoryName = classNameFromSystemProperties();
341         if (factoryName != nullreturn newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
342
343         JAXBContextFactory obj = ServiceLoaderUtil.firstByServiceLoader(
344                 JAXBContextFactory.class, logger, EXCEPTION_HANDLER);
345
346         if (obj != null) {
347             ModuleUtil.delegateAddOpensToImplModule(contextPathClasses, obj.getClass());
348             return obj.createContext(contextPath, classLoader, properties);
349         }
350
351         // to ensure backwards compatibility
352         factoryName = firstByServiceLoaderDeprecated(JAXBContext.class, classLoader);
353         if (factoryName != nullreturn newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
354
355         Class ctxFactory = (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader(
356                 "javax.xml.bind.JAXBContext", logger);
357
358         if (ctxFactory != null) {
359             return newInstance(contextPath, contextPathClasses, ctxFactory, classLoader, properties);
360         }
361
362         // else no provider found
363         logger.fine("Trying to create the platform default provider");
364         return newInstance(contextPath, contextPathClasses, PLATFORM_DEFAULT_FACTORY_CLASS, classLoader, properties);
365     }
366
367     static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JAXBException {
368
369         // search for jaxb.properties in the class loader of each class first
370         logger.fine("Searching jaxb.properties");
371         for (final Class c : classes) {
372             // this classloader is used only to load jaxb.properties, so doing this should be safe.
373             // this is possible for primitives, arrays, and classes that are
374             // loaded by poorly implemented ClassLoaders
375             if (c.getPackage() == nullcontinue;
376
377             // TODO: do we want to optimize away searching the same package?  org.Foo, org.Bar, com.Baz
378             // classes from the same package might come from different class loades, so it might be a bad idea
379             // TODO: it's easier to look things up from the class
380             // c.getResourceAsStream("jaxb.properties");
381
382             URL jaxbPropertiesUrl = getResourceUrl(c, "jaxb.properties");
383
384             if (jaxbPropertiesUrl != null) {
385
386                 String factoryClassName =
387                         classNameFromPackageProperties(
388                                 jaxbPropertiesUrl,
389                                 JAXBContext.JAXB_CONTEXT_FACTORY, JAXB_CONTEXT_FACTORY_DEPRECATED);
390
391                 return newInstance(classes, properties, factoryClassName);
392             }
393
394         }
395
396         String factoryClassName = classNameFromSystemProperties();
397         if (factoryClassName != nullreturn newInstance(classes, properties, factoryClassName);
398
399         JAXBContextFactory factory =
400                 ServiceLoaderUtil.firstByServiceLoader(JAXBContextFactory.class, logger, EXCEPTION_HANDLER);
401
402         if (factory != null) {
403             ModuleUtil.delegateAddOpensToImplModule(classes, factory.getClass());
404             return factory.createContext(classes, properties);
405         }
406
407         // to ensure backwards compatibility
408         String className = firstByServiceLoaderDeprecated(JAXBContext.class, getContextClassLoader());
409         if (className != nullreturn newInstance(classes, properties, className);
410
411         logger.fine("Trying to create the platform default provider");
412         Class ctxFactoryClass =
413                 (Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("javax.xml.bind.JAXBContext", logger);
414
415         if (ctxFactoryClass != null) {
416             return newInstance(classes, properties, ctxFactoryClass);
417         }
418
419         // else no provider found
420         logger.fine("Trying to create the platform default provider");
421         return newInstance(classes, properties, PLATFORM_DEFAULT_FACTORY_CLASS);
422     }
423
424
425     /**
426      * first factoryId should be the preferred one,
427      * more of those can be provided to support backwards compatibility
428      */

429     private static String classNameFromPackageProperties(URL packagePropertiesUrl,
430                                                          String ... factoryIds) throws JAXBException {
431
432         logger.log(Level.FINE, "Trying to locate {0}", packagePropertiesUrl.toString());
433         Properties props = loadJAXBProperties(packagePropertiesUrl);
434         for(String factoryId : factoryIds) {
435             if (props.containsKey(factoryId)) {
436                 return props.getProperty(factoryId);
437             }
438         }
439         //Factory key not found
440         String propertiesUrl = packagePropertiesUrl.toExternalForm();
441         String packageName = propertiesUrl.substring(0, propertiesUrl.indexOf("/jaxb.properties"));
442         throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryIds[0]));
443     }
444
445     private static String classNameFromSystemProperties() throws JAXBException {
446
447         String factoryClassName = getSystemProperty(JAXBContext.JAXB_CONTEXT_FACTORY);
448         if (factoryClassName != null) {
449             return factoryClassName;
450         }
451         // leave this here to assure compatibility
452         factoryClassName = getDeprecatedSystemProperty(JAXB_CONTEXT_FACTORY_DEPRECATED);
453         if (factoryClassName != null) {
454             return factoryClassName;
455         }
456         // leave this here to assure compatibility
457         factoryClassName = getDeprecatedSystemProperty(JAXBContext.class.getName());
458         if (factoryClassName != null) {
459             return factoryClassName;
460         }
461         return null;
462     }
463
464     private static String getDeprecatedSystemProperty(String property) {
465         String value = getSystemProperty(property);
466         if (value != null) {
467             logger.log(Level.WARNING, "Using non-standard property: {0}. Property {1} should be used instead.",
468                     new Object[] {property, JAXBContext.JAXB_CONTEXT_FACTORY});
469         }
470         return value;
471     }
472
473     private static String getSystemProperty(String property) {
474         logger.log(Level.FINE, "Checking system property {0}", property);
475         String value = AccessController.doPrivileged(new GetPropertyAction(property));
476         if (value != null) {
477             logger.log(Level.FINE, "  found {0}", value);
478         } else {
479             logger.log(Level.FINE, "  not found");
480         }
481         return value;
482     }
483
484     private static Properties loadJAXBProperties(URL url) throws JAXBException {
485
486         try {
487             Properties props;
488             logger.log(Level.FINE, "loading props from {0}", url);
489             props = new Properties();
490             InputStream is = url.openStream();
491             props.load(is);
492             is.close();
493             return props;
494         } catch (IOException ioe) {
495             logger.log(Level.FINE, "Unable to load " + url.toString(), ioe);
496             throw new JAXBException(ioe.toString(), ioe);
497         }
498     }
499
500     /**
501      * If run on JPMS package containing resource must be open unconditionally.
502      *
503      * @param classLoader classloader to load resource with
504      * @param resourceName qualified name of the resource
505      * @return resource url if found
506      */

507     private static URL getResourceUrl(ClassLoader classLoader, String resourceName) {
508         URL url;
509         if (classLoader == null)
510             url = ClassLoader.getSystemResource(resourceName);
511         else
512             url = classLoader.getResource(resourceName);
513         return url;
514     }
515
516     private static URL getResourceUrl(Class<?> clazz, String resourceName) {
517         return clazz.getResource(resourceName);
518     }
519
520
521     /**
522      * Search the given ClassLoader for an instance of the specified class and
523      * return a string representation of the URL that points to the resource.
524      *
525      * @param clazz
526      *          The class to search for
527      * @param loader
528      *          The ClassLoader to search.  If this parameter is null, then the
529      *          system class loader will be searched
530      * @return
531      *          the URL for the class or null if it wasn't found
532      */

533     static URL which(Class clazz, ClassLoader loader) {
534
535         String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
536
537         if (loader == null) {
538             loader = getSystemClassLoader();
539         }
540
541         return loader.getResource(classnameAsResource);
542     }
543
544     /**
545      * Get the URL for the Class from it's ClassLoader.
546      *
547      * Convenience method for {@link #which(Class, ClassLoader)}.
548      *
549      * Equivalent to calling: which(clazz, clazz.getClassLoader())
550      *
551      * @param clazz
552      *          The class to search for
553      * @return
554      *          the URL for the class or null if it wasn't found
555      */

556     static URL which(Class clazz) {
557         return which(clazz, getClassClassLoader(clazz));
558     }
559
560     @SuppressWarnings("unchecked")
561     private static ClassLoader getContextClassLoader() {
562         if (System.getSecurityManager() == null) {
563             return Thread.currentThread().getContextClassLoader();
564         } else {
565             return (ClassLoader) java.security.AccessController.doPrivileged(
566                     new java.security.PrivilegedAction() {
567                         @Override
568                         public java.lang.Object run() {
569                             return Thread.currentThread().getContextClassLoader();
570                         }
571                     });
572         }
573     }
574
575     @SuppressWarnings("unchecked")
576     private static ClassLoader getClassClassLoader(final Class c) {
577         if (System.getSecurityManager() == null) {
578             return c.getClassLoader();
579         } else {
580             return (ClassLoader) java.security.AccessController.doPrivileged(
581                     new java.security.PrivilegedAction() {
582                         @Override
583                         public java.lang.Object run() {
584                             return c.getClassLoader();
585                         }
586                     });
587         }
588     }
589
590     private static ClassLoader getSystemClassLoader() {
591         if (System.getSecurityManager() == null) {
592             return ClassLoader.getSystemClassLoader();
593         } else {
594             return (ClassLoader) java.security.AccessController.doPrivileged(
595                     new java.security.PrivilegedAction() {
596                         @Override
597                         public java.lang.Object run() {
598                             return ClassLoader.getSystemClassLoader();
599                         }
600                     });
601         }
602     }
603
604     // ServiceLoaderUtil.firstByServiceLoaderDeprecated should be used instead.
605     @Deprecated
606     static String firstByServiceLoaderDeprecated(Class spiClass,
607                                                  ClassLoader classLoader) throws JAXBException {
608
609         final String jaxbContextFQCN = spiClass.getName();
610
611         logger.fine("Searching META-INF/services");
612
613         // search META-INF services next
614         BufferedReader r = null;
615         final String resource = "META-INF/services/" + jaxbContextFQCN;
616         try {
617             final InputStream resourceStream =
618                     (classLoader == null) ?
619                             ClassLoader.getSystemResourceAsStream(resource) :
620                             classLoader.getResourceAsStream(resource);
621
622             if (resourceStream != null) {
623                 r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
624                 String factoryClassName = r.readLine();
625                 if (factoryClassName != null) {
626                     factoryClassName = factoryClassName.trim();
627                 }
628                 r.close();
629                 logger.log(Level.FINE, "Configured factorty class:{0}", factoryClassName);
630                 return factoryClassName;
631             } else {
632                 logger.log(Level.FINE, "Unable to load:{0}", resource);
633                 return null;
634             }
635         } catch (IOException e) {
636             throw new JAXBException(e);
637         } finally {
638             try {
639                 if (r != null) {
640                     r.close();
641                 }
642             } catch (IOException ex) {
643                 logger.log(Level.SEVERE, "Unable to close resource: " + resource, ex);
644             }
645         }
646     }
647
648     private static String jaxbProperties(String contextPath, ClassLoader classLoader, String factoryId) throws JAXBException {
649         String[] packages = contextPath.split(":");
650
651         for (String pkg : packages) {
652             String pkgUrl = pkg.replace('.', '/');
653             URL jaxbPropertiesUrl = getResourceUrl(classLoader, pkgUrl + "/jaxb.properties");
654             if (jaxbPropertiesUrl != null) {
655                 return classNameFromPackageProperties(jaxbPropertiesUrl,
656                                                       factoryId, JAXB_CONTEXT_FACTORY_DEPRECATED);
657             }
658         }
659         return null;
660     }
661
662     private static String jaxbProperties(Class[] classesFromContextPath, String factoryId) throws JAXBException {
663         for (Class c : classesFromContextPath) {
664             URL jaxbPropertiesUrl = getResourceUrl(c, "jaxb.properties");
665             if (jaxbPropertiesUrl != null) {
666                 return classNameFromPackageProperties(jaxbPropertiesUrl, factoryId, JAXB_CONTEXT_FACTORY_DEPRECATED);
667             }
668         }
669         return null;
670     }
671
672 }
673