1/*
2 * Copyright (c) 2004, 2017, 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
26package javax.xml.soap;
27
28import java.io.*;
29import java.nio.charset.StandardCharsets;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.security.AccessController;
34import java.security.PrivilegedAction;
35import java.util.Properties;
36import java.util.logging.Level;
37import java.util.logging.Logger;
38
39
40class 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(final String property) {
249        logger.log(Level.FINE, "Checking system property {0}", property);
250        String value = AccessController.doPrivileged(new PrivilegedAction<String>() {
251            @Override
252            public String run() {
253                return System.getProperty(property);
254            }
255        });
256        logFound(value);
257        return value;
258    }
259
260    private static void logFound(String value) {
261        if (value != null) {
262            logger.log(Level.FINE, "  found {0}", value);
263        } else {
264            logger.log(Level.FINE, "  not found");
265        }
266    }
267
268}
269