1 /*
   2  * Copyright (c) 2004, 2016, 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.soap;
  27 
  28 import java.io.*;
  29 import java.nio.charset.StandardCharsets;
  30 import java.nio.file.Files;
  31 import java.nio.file.Path;
  32 import java.nio.file.Paths;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.Properties;
  36 import java.util.logging.Level;
  37 import java.util.logging.Logger;
  38 
  39 
  40 class FactoryFinder {
  41 
  42     private static final Logger logger = Logger.getLogger("javax.xml.soap");
  43 
  44     private static final ServiceLoaderUtil.ExceptionHandler<SOAPException> EXCEPTION_HANDLER =
  45             new ServiceLoaderUtil.ExceptionHandler<SOAPException>() {
  46                 @Override
  47                 public SOAPException createException(Throwable throwable, String message) {
  48                     return new SOAPException(message, throwable);
  49                 }
  50             };
  51 
  52     /**
  53      * Finds the implementation {@code Class} object for the given
  54      * factory type.  If it fails and {@code tryFallback} is {@code true}
  55      * finds the {@code Class} object for the given default class name.
  56      * The arguments supplied must be used in order
  57      * Note the default class name may be needed even if fallback
  58      * is not to be attempted in order to check if requested type is fallback.
  59      * <P>
  60      * This method is package private so that this code can be shared.
  61      *
  62      * @return the {@code Class} object of the specified message factory;
  63      *         may not be {@code null}
  64      *
  65      * @param factoryClass          factory abstract class or interface to be found
  66      * @param deprecatedFactoryId   deprecated name of a factory; it is used for types
  67      *                              where class name is different from a name
  68      *                              being searched (in previous spec).
  69      * @param defaultClassName      the implementation class name, which is
  70      *                              to be used only if nothing else
  71      *                              is found; {@code null} to indicate
  72      *                              that there is no default class name
  73      * @param tryFallback           whether to try the default class as a
  74      *                              fallback
  75      * @exception SOAPException if there is a SOAP error
  76      */
  77     @SuppressWarnings("unchecked")
  78     static <T> T find(Class<T> factoryClass,
  79                       String defaultClassName,
  80                       boolean tryFallback, String deprecatedFactoryId) throws SOAPException {
  81 
  82         ClassLoader tccl = ServiceLoaderUtil.contextClassLoader(EXCEPTION_HANDLER);
  83         String factoryId = factoryClass.getName();
  84 
  85         // Use the system property first
  86         String className = fromSystemProperty(factoryId, deprecatedFactoryId);
  87         if (className != null) {
  88             Object result = newInstance(className, defaultClassName, tccl);
  89             if (result != null) {
  90                 return (T) result;
  91             }
  92         }
  93 
  94         // try to read from $java.home/lib/jaxm.properties
  95         className = fromJDKProperties(factoryId, deprecatedFactoryId);
  96         if (className != null) {
  97             Object result = newInstance(className, defaultClassName, tccl);
  98             if (result != null) {
  99                 return (T) result;
 100             }
 101         }
 102 
 103         // standard services: java.util.ServiceLoader
 104         T factory = ServiceLoaderUtil.firstByServiceLoader(
 105                 factoryClass,
 106                 logger,
 107                 EXCEPTION_HANDLER);
 108         if (factory != null) {
 109             return factory;
 110         }
 111 
 112         // try to find services in CLASSPATH
 113         className = fromMetaInfServices(deprecatedFactoryId, tccl);
 114         if (className != null) {
 115             logger.log(Level.WARNING,
 116                     "Using deprecated META-INF/services mechanism with non-standard property: {0}. " +
 117                             "Property {1} should be used instead.",
 118                     new Object[]{deprecatedFactoryId, factoryId});
 119             Object result = newInstance(className, defaultClassName, tccl);
 120             if (result != null) {
 121                 return (T) result;
 122             }
 123         }
 124 
 125         // If not found and fallback should not be tried, return a null result.
 126         if (!tryFallback)
 127             return null;
 128 
 129         // We didn't find the class through the usual means so try the default
 130         // (built in) factory if specified.
 131         if (defaultClassName == null) {
 132             throw new SOAPException(
 133                     "Provider for " + factoryId + " cannot be found", null);
 134         }
 135         return (T) newInstance(defaultClassName, defaultClassName, tccl);
 136     }
 137 
 138     // in most cases there is no deprecated factory id
 139     static <T> T find(Class<T> factoryClass,
 140                       String defaultClassName,
 141                       boolean tryFallback) throws SOAPException {
 142         return find(factoryClass, defaultClassName, tryFallback, null);
 143     }
 144 
 145     private static Object newInstance(String className, String defaultClassName, ClassLoader tccl) throws SOAPException {
 146         return ServiceLoaderUtil.newInstance(
 147                 className,
 148                 defaultClassName,
 149                 tccl,
 150                 EXCEPTION_HANDLER);
 151     }
 152 
 153     // used only for deprecatedFactoryId;
 154     // proper factoryId searched by java.util.ServiceLoader
 155     private static String fromMetaInfServices(String deprecatedFactoryId, ClassLoader tccl) {
 156         String serviceId = "META-INF/services/" + deprecatedFactoryId;
 157         logger.log(Level.FINE, "Checking deprecated {0} resource", serviceId);
 158 
 159         try (InputStream is =
 160                      tccl == null ?
 161                              ClassLoader.getSystemResourceAsStream(serviceId)
 162                              :
 163                              tccl.getResourceAsStream(serviceId)) {
 164 
 165             if (is != null) {
 166                 String factoryClassName;
 167                 try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
 168                      BufferedReader rd = new BufferedReader(isr)) {
 169                     factoryClassName = rd.readLine();
 170                 }
 171 
 172                 logFound(factoryClassName);
 173                 if (factoryClassName != null && !"".equals(factoryClassName)) {
 174                     return factoryClassName;
 175                 }
 176             }
 177 
 178         } catch (IOException e) {
 179             // keep original behavior
 180         }
 181         return null;
 182     }
 183 
 184     private static String fromJDKProperties(String factoryId, String deprecatedFactoryId) {
 185         Path path = null;
 186         try {
 187             String JAVA_HOME = getSystemProperty("java.home");
 188             path = Paths.get(JAVA_HOME, "conf", "jaxm.properties");
 189             logger.log(Level.FINE, "Checking configuration in {0}", path);
 190 
 191             // to ensure backwards compatibility
 192             if (!Files.exists(path)) {
 193                 path = Paths.get(JAVA_HOME, "lib", "jaxm.properties");
 194             }
 195 
 196             logger.log(Level.FINE, "Checking configuration in {0}", path);
 197             if (Files.exists(path)) {
 198                 Properties props = new Properties();
 199                 try (InputStream inputStream = Files.newInputStream(path)) {
 200                     props.load(inputStream);
 201                 }
 202 
 203                 // standard property
 204                 logger.log(Level.FINE, "Checking property {0}", factoryId);
 205                 String factoryClassName = props.getProperty(factoryId);
 206                 logFound(factoryClassName);
 207                 if (factoryClassName != null) {
 208                     return factoryClassName;
 209                 }
 210 
 211                 // deprecated property
 212                 if (deprecatedFactoryId != null) {
 213                     logger.log(Level.FINE, "Checking deprecated property {0}", deprecatedFactoryId);
 214                     factoryClassName = props.getProperty(deprecatedFactoryId);
 215                     logFound(factoryClassName);
 216                     if (factoryClassName != null) {
 217                         logger.log(Level.WARNING,
 218                                 "Using non-standard property: {0}. Property {1} should be used instead.",
 219                                 new Object[]{deprecatedFactoryId, factoryId});
 220                         return factoryClassName;
 221                     }
 222                 }
 223             }
 224         } catch (Exception ignored) {
 225             logger.log(Level.SEVERE, "Error reading SAAJ configuration from ["  + path +
 226                     "] file. Check it is accessible and has correct format.", ignored);
 227         }
 228         return null;
 229     }
 230 
 231     private static String fromSystemProperty(String factoryId, String deprecatedFactoryId) {
 232         String systemProp = getSystemProperty(factoryId);
 233         if (systemProp != null) {
 234             return systemProp;
 235         }
 236         if (deprecatedFactoryId != null) {
 237             systemProp = getSystemProperty(deprecatedFactoryId);
 238             if (systemProp != null) {
 239                 logger.log(Level.WARNING,
 240                         "Using non-standard property: {0}. Property {1} should be used instead.",
 241                         new Object[] {deprecatedFactoryId, factoryId});
 242                 return systemProp;
 243             }
 244         }
 245         return null;
 246     }
 247 
 248     private static String getSystemProperty(String property) {
 249         logger.log(Level.FINE, "Checking system property {0}", property);
 250         String value = AccessController.doPrivileged(
 251                 (PrivilegedAction<String>) () -> System.getProperty(property));
 252         logFound(value);
 253         return value;
 254     }
 255 
 256     private static void logFound(String value) {
 257         if (value != null) {
 258             logger.log(Level.FINE, "  found {0}", value);
 259         } else {
 260             logger.log(Level.FINE, "  not found");
 261         }
 262     }
 263 
 264 }