24 */ |
24 */ |
25 |
25 |
26 package javax.xml.soap; |
26 package javax.xml.soap; |
27 |
27 |
28 import java.io.*; |
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; |
29 import java.util.Properties; |
35 import java.util.Properties; |
|
36 import java.util.logging.Level; |
|
37 import java.util.logging.Logger; |
30 |
38 |
31 |
39 |
32 class FactoryFinder { |
40 class FactoryFinder { |
33 |
41 |
34 /** |
42 private static final Logger logger = Logger.getLogger("javax.xml.soap"); |
35 * Creates an instance of the specified class using the specified |
43 |
36 * {@code ClassLoader} object. |
44 private static final ServiceLoaderUtil.ExceptionHandler<SOAPException> EXCEPTION_HANDLER = |
37 * |
45 new ServiceLoaderUtil.ExceptionHandler<SOAPException>() { |
38 * @exception SOAPException if the given class could not be found |
46 @Override |
39 * or could not be instantiated |
47 public SOAPException createException(Throwable throwable, String message) { |
40 */ |
48 return new SOAPException(message, throwable); |
41 private static Object newInstance(String className, |
49 } |
42 ClassLoader classLoader) |
50 }; |
43 throws SOAPException |
|
44 { |
|
45 try { |
|
46 Class spiClass = safeLoadClass(className, classLoader); |
|
47 return spiClass.newInstance(); |
|
48 |
|
49 } catch (ClassNotFoundException x) { |
|
50 throw new SOAPException("Provider " + className + " not found", x); |
|
51 } catch (Exception x) { |
|
52 throw new SOAPException("Provider " + className + " could not be instantiated: " + x, x); |
|
53 } |
|
54 } |
|
55 |
51 |
56 /** |
52 /** |
57 * Finds the implementation {@code Class} object for the given |
53 * Finds the implementation {@code Class} object for the given |
58 * factory name, or null if that fails. |
54 * factory type. If it fails and {@code tryFallback} is {@code true} |
59 * <P> |
55 * finds the {@code Class} object for the given default class name. |
60 * This method is package private so that this code can be shared. |
56 * The arguments supplied must be used in order |
61 * |
57 * Note the default class name may be needed even if fallback |
62 * @return the {@code Class} object of the specified message factory; |
58 * is not to be attempted in order to check if requested type is fallback. |
63 * or {@code null} |
|
64 * |
|
65 * @param factoryId the name of the factory to find, which is |
|
66 * a system property |
|
67 * @exception SOAPException if there is a SOAP error |
|
68 */ |
|
69 static Object find(String factoryId) |
|
70 throws SOAPException |
|
71 { |
|
72 return find(factoryId, null, false); |
|
73 } |
|
74 |
|
75 /** |
|
76 * Finds the implementation {@code Class} object for the given |
|
77 * factory name, or if that fails, finds the {@code Class} object |
|
78 * for the given fallback class name. The arguments supplied must be |
|
79 * used in order. If using the first argument is successful, the second |
|
80 * one will not be used. |
|
81 * <P> |
|
82 * This method is package private so that this code can be shared. |
|
83 * |
|
84 * @return the {@code Class} object of the specified message factory; |
|
85 * may be {@code null} |
|
86 * |
|
87 * @param factoryId the name of the factory to find, which is |
|
88 * a system property |
|
89 * @param fallbackClassName the implementation class name, which is |
|
90 * to be used only if nothing else |
|
91 * is found; {@code null} to indicate that |
|
92 * there is no fallback class name |
|
93 * @exception SOAPException if there is a SOAP error |
|
94 */ |
|
95 static Object find(String factoryId, String fallbackClassName) |
|
96 throws SOAPException |
|
97 { |
|
98 return find(factoryId, fallbackClassName, true); |
|
99 } |
|
100 |
|
101 /** |
|
102 * Finds the implementation {@code Class} object for the given |
|
103 * factory name, or if that fails, finds the {@code Class} object |
|
104 * for the given default class name, but only if {@code tryFallback} |
|
105 * is {@code true}. The arguments supplied must be used in order |
|
106 * If using the first argument is successful, the second one will not |
|
107 * be used. Note the default class name may be needed even if fallback |
|
108 * is not to be attempted, so certain error conditions can be handled. |
|
109 * <P> |
59 * <P> |
110 * This method is package private so that this code can be shared. |
60 * This method is package private so that this code can be shared. |
111 * |
61 * |
112 * @return the {@code Class} object of the specified message factory; |
62 * @return the {@code Class} object of the specified message factory; |
113 * may not be {@code null} |
63 * may not be {@code null} |
114 * |
64 * |
115 * @param factoryId the name of the factory to find, which is |
65 * @param factoryClass factory abstract class or interface to be found |
116 * a system property |
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). |
117 * @param defaultClassName the implementation class name, which is |
69 * @param defaultClassName the implementation class name, which is |
118 * to be used only if nothing else |
70 * to be used only if nothing else |
119 * is found; {@code null} to indicate |
71 * is found; {@code null} to indicate |
120 * that there is no default class name |
72 * that there is no default class name |
121 * @param tryFallback whether to try the default class as a |
73 * @param tryFallback whether to try the default class as a |
122 * fallback |
74 * fallback |
123 * @exception SOAPException if there is a SOAP error |
75 * @exception SOAPException if there is a SOAP error |
124 */ |
76 */ |
125 static Object find(String factoryId, String defaultClassName, |
77 @SuppressWarnings("unchecked") |
126 boolean tryFallback) throws SOAPException { |
78 static <T> T find(Class<T> factoryClass, |
127 ClassLoader classLoader; |
79 String defaultClassName, |
128 try { |
80 boolean tryFallback, String deprecatedFactoryId) throws SOAPException { |
129 classLoader = Thread.currentThread().getContextClassLoader(); |
81 |
130 } catch (Exception x) { |
82 ClassLoader tccl = ServiceLoaderUtil.contextClassLoader(EXCEPTION_HANDLER); |
131 throw new SOAPException(x.toString(), x); |
83 String factoryId = factoryClass.getName(); |
132 } |
|
133 |
84 |
134 // Use the system property first |
85 // Use the system property first |
135 try { |
86 String className = fromSystemProperty(factoryId, deprecatedFactoryId); |
136 String systemProp = |
87 if (className != null) { |
137 System.getProperty( factoryId ); |
88 Object result = newInstance(className, defaultClassName, tccl); |
138 if( systemProp!=null) { |
89 if (result != null) { |
139 return newInstance(systemProp, classLoader); |
90 return (T) result; |
140 } |
91 } |
141 } catch (SecurityException se) { |
|
142 } |
92 } |
143 |
93 |
144 // try to read from $java.home/lib/jaxm.properties |
94 // try to read from $java.home/lib/jaxm.properties |
145 try { |
95 className = fromJDKProperties(factoryId, deprecatedFactoryId); |
146 String javah=System.getProperty( "java.home" ); |
96 if (className != null) { |
147 String configFile = javah + File.separator + |
97 Object result = newInstance(className, defaultClassName, tccl); |
148 "lib" + File.separator + "jaxm.properties"; |
98 if (result != null) { |
149 File f=new File( configFile ); |
99 return (T) result; |
150 if( f.exists()) { |
100 } |
151 Properties props=new Properties(); |
101 } |
152 props.load( new FileInputStream(f)); |
102 |
153 String factoryClassName = props.getProperty(factoryId); |
103 // standard services: java.util.ServiceLoader |
154 return newInstance(factoryClassName, classLoader); |
104 T factory = ServiceLoaderUtil.firstByServiceLoader( |
155 } |
105 factoryClass, |
156 } catch(Exception ex ) { |
106 logger, |
157 } |
107 EXCEPTION_HANDLER); |
158 |
108 if (factory != null) { |
159 String serviceId = "META-INF/services/" + factoryId; |
109 return factory; |
|
110 } |
|
111 |
160 // try to find services in CLASSPATH |
112 // try to find services in CLASSPATH |
161 try { |
113 className = fromMetaInfServices(deprecatedFactoryId, tccl); |
162 InputStream is=null; |
114 if (className != null) { |
163 if (classLoader == null) { |
115 logger.log(Level.WARNING, |
164 is=ClassLoader.getSystemResourceAsStream(serviceId); |
116 "Using deprecated META-INF/services mechanism with non-standard property: {0}. " + |
165 } else { |
117 "Property {1} should be used instead.", |
166 is=classLoader.getResourceAsStream(serviceId); |
118 new Object[]{deprecatedFactoryId, factoryId}); |
167 } |
119 Object result = newInstance(className, defaultClassName, tccl); |
168 |
120 if (result != null) { |
169 if( is!=null ) { |
121 return (T) result; |
170 BufferedReader rd = |
122 } |
171 new BufferedReader(new InputStreamReader(is, "UTF-8")); |
|
172 |
|
173 String factoryClassName = rd.readLine(); |
|
174 rd.close(); |
|
175 |
|
176 if (factoryClassName != null && |
|
177 ! "".equals(factoryClassName)) { |
|
178 return newInstance(factoryClassName, classLoader); |
|
179 } |
|
180 } |
|
181 } catch( Exception ex ) { |
|
182 } |
123 } |
183 |
124 |
184 // If not found and fallback should not be tried, return a null result. |
125 // If not found and fallback should not be tried, return a null result. |
185 if (!tryFallback) |
126 if (!tryFallback) |
186 return null; |
127 return null; |
189 // (built in) factory if specified. |
130 // (built in) factory if specified. |
190 if (defaultClassName == null) { |
131 if (defaultClassName == null) { |
191 throw new SOAPException( |
132 throw new SOAPException( |
192 "Provider for " + factoryId + " cannot be found", null); |
133 "Provider for " + factoryId + " cannot be found", null); |
193 } |
134 } |
194 return newInstance(defaultClassName, classLoader); |
135 return (T) newInstance(defaultClassName, defaultClassName, tccl); |
195 } |
136 } |
196 |
137 |
197 /** |
138 // in most cases there is no deprecated factory id |
198 * Loads the class, provided that the calling thread has an access to the |
139 static <T> T find(Class<T> factoryClass, |
199 * class being loaded. If this is the specified default factory class and it |
140 String defaultClassName, |
200 * is restricted by package.access we get a SecurityException and can do a |
141 boolean tryFallback) throws SOAPException { |
201 * Class.forName() on it so it will be loaded by the bootstrap class loader. |
142 return find(factoryClass, defaultClassName, tryFallback, null); |
202 */ |
143 } |
203 private static Class safeLoadClass(String className, |
144 |
204 ClassLoader classLoader) |
145 private static Object newInstance(String className, String defaultClassName, ClassLoader tccl) throws SOAPException { |
205 throws ClassNotFoundException { |
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; |
206 try { |
186 try { |
207 // make sure that the current thread has an access to the package of the given name. |
187 String JAVA_HOME = System.getProperty("java.home"); |
208 SecurityManager s = System.getSecurityManager(); |
188 path = Paths.get(JAVA_HOME, "conf", "jaxm.properties"); |
209 if (s != null) { |
189 logger.log(Level.FINE, "Checking configuration in {0}", path); |
210 int i = className.lastIndexOf('.'); |
190 |
211 if (i != -1) { |
191 // to ensure backwards compatibility |
212 s.checkPackageAccess(className.substring(0, i)); |
192 if (!Files.exists(path)) { |
213 } |
193 path = Paths.get(JAVA_HOME, "lib", "jaxm.properties"); |
214 } |
194 } |
215 |
195 |
216 if (classLoader == null) |
196 logger.log(Level.FINE, "Checking configuration in {0}", path); |
217 return Class.forName(className); |
197 if (Files.exists(path)) { |
218 else |
198 Properties props = new Properties(); |
219 return classLoader.loadClass(className); |
199 try (InputStream inputStream = Files.newInputStream(path)) { |
220 } catch (SecurityException se) { |
200 props.load(inputStream); |
221 // (only) default implementation can be loaded |
201 } |
222 // using bootstrap class loader: |
202 |
223 if (isDefaultImplementation(className)) |
203 // standard property |
224 return Class.forName(className); |
204 logger.log(Level.FINE, "Checking property {0}", factoryId); |
225 |
205 String factoryClassName = props.getProperty(factoryId); |
226 throw se; |
206 logFound(factoryClassName); |
227 } |
207 if (factoryClassName != null) { |
228 } |
208 return factoryClassName; |
229 |
209 } |
230 private static boolean isDefaultImplementation(String className) { |
210 |
231 return MessageFactory.DEFAULT_MESSAGE_FACTORY.equals(className) || |
211 // deprecated property |
232 SOAPFactory.DEFAULT_SOAP_FACTORY.equals(className) || |
212 if (deprecatedFactoryId != null) { |
233 SOAPConnectionFactory.DEFAULT_SOAP_CONNECTION_FACTORY.equals(className) || |
213 logger.log(Level.FINE, "Checking deprecated property {0}", deprecatedFactoryId); |
234 SAAJMetaFactory.DEFAULT_META_FACTORY_CLASS.equals(className); |
214 factoryClassName = props.getProperty(deprecatedFactoryId); |
235 } |
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 |
236 } |
264 } |