View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.impl;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Enumeration;
25  import java.util.Objects;
26  
27  import javax.xml.parsers.DocumentBuilder;
28  import javax.xml.parsers.DocumentBuilderFactory;
29  import javax.xml.parsers.ParserConfigurationException;
30  
31  import org.apache.commons.lang3.ArrayUtils;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.commons.vfs2.FileSystemException;
34  import org.apache.commons.vfs2.VfsLog;
35  import org.apache.commons.vfs2.operations.FileOperationProvider;
36  import org.apache.commons.vfs2.provider.FileProvider;
37  import org.apache.commons.vfs2.util.Messages;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.NodeList;
40  
41  /**
42   * A {@link org.apache.commons.vfs2.FileSystemManager} that configures itself from an XML (Default: providers.xml)
43   * configuration file.
44   * <p>
45   * Certain providers are only loaded and available if the dependent library is in your classpath. You have to configure
46   * your debugging facility to log "debug" messages to see if a provider was skipped due to "unresolved externals".
47   * </p>
48   */
49  public class StandardFileSystemManager extends DefaultFileSystemManager {
50      private static final String CONFIG_RESOURCE = "providers.xml";
51      private static final String PLUGIN_CONFIG_RESOURCE = "META-INF/vfs-providers.xml";
52  
53      private URL configUri;
54      private ClassLoader classLoader;
55  
56      /**
57       * Sets the configuration file for this manager.
58       *
59       * @param configUri The URI for this manager.
60       */
61      public void setConfiguration(final String configUri) {
62          try {
63              setConfiguration(new URL(configUri));
64          } catch (final MalformedURLException e) {
65              getLogger().warn(e.getLocalizedMessage(), e);
66          }
67      }
68  
69      /**
70       * Sets the configuration file for this manager.
71       *
72       * @param configUri The URI forthis manager.
73       */
74      public void setConfiguration(final URL configUri) {
75          this.configUri = configUri;
76      }
77  
78      /**
79       * Sets the ClassLoader to use to load the providers. Default is to use the ClassLoader that loaded this class.
80       *
81       * @param classLoader The ClassLoader.
82       */
83      public void setClassLoader(final ClassLoader classLoader) {
84          this.classLoader = classLoader;
85      }
86  
87      /**
88       * Initializes this manager. Adds the providers and replicator.
89       *
90       * @throws FileSystemException if an error occurs.
91       */
92      @Override
93      public void init() throws FileSystemException {
94          // Set the replicator and temporary file store (use the same component)
95          final DefaultFileReplicator replicator = createDefaultFileReplicator();
96          setReplicator(new PrivilegedFileReplicator(replicator));
97          setTemporaryFileStore(replicator);
98  
99          if (configUri == null) {
100             // Use default config
101             final URL url = getClass().getResource(CONFIG_RESOURCE);
102             FileSystemException.requireNonNull(url, "vfs.impl/find-config-file.error", CONFIG_RESOURCE);
103             configUri = url;
104         }
105 
106         configure(configUri);
107         configurePlugins();
108 
109         // Initialize super-class
110         super.init();
111     }
112 
113     /**
114      * Scans the classpath to find any droped plugin.
115      * <p>
116      * The plugin-description has to be in {@code /META-INF/vfs-providers.xml}.
117      * </p>
118      *
119      * @throws FileSystemException if an error occurs.
120      */
121     protected void configurePlugins() throws FileSystemException {
122         final Enumeration<URL> enumResources;
123         try {
124             enumResources = enumerateResources(PLUGIN_CONFIG_RESOURCE);
125         } catch (final IOException e) {
126             throw new FileSystemException(e);
127         }
128 
129         while (enumResources.hasMoreElements()) {
130             configure(enumResources.nextElement());
131         }
132     }
133 
134     /**
135      * Returns a class loader or null since some Java implementation is null for the bootstrap class loader.
136      *
137      * @return A class loader or null since some Java implementation is null for the bootstrap class loader.
138      */
139     private ClassLoader findClassLoader() {
140         if (classLoader != null) {
141             return classLoader;
142         }
143         final ClassLoader cl = Thread.currentThread().getContextClassLoader();
144         if (cl != null) {
145             return cl;
146         }
147         return getValidClassLoader(getClass());
148     }
149 
150     protected DefaultFileReplicator createDefaultFileReplicator() {
151         return new DefaultFileReplicator();
152     }
153 
154     /**
155      * Configures this manager from an XML configuration file.
156      *
157      * @param configUri The URI of the configuration.
158      * @throws FileSystemException if an error occus.
159      */
160     private void configure(final URL configUri) throws FileSystemException {
161         InputStream configStream = null;
162         try {
163             // Load up the config
164             // TODO - validate
165             final DocumentBuilder builder = createDocumentBuilder();
166             configStream = configUri.openStream();
167             final Element config = builder.parse(configStream).getDocumentElement();
168 
169             configure(config);
170         } catch (final Exception e) {
171             throw new FileSystemException("vfs.impl/load-config.error", configUri.toString(), e);
172         } finally {
173             if (configStream != null) {
174                 try {
175                     configStream.close();
176                 } catch (final IOException e) {
177                     getLogger().warn(e.getLocalizedMessage(), e);
178                 }
179             }
180         }
181     }
182 
183     /**
184      * Configures this manager from an XML configuration file.
185      *
186      * @param configUri The URI of the configuration.
187      * @param configStream An InputStream containing the configuration.
188      * @throws FileSystemException if an error occurs.
189      */
190     @SuppressWarnings("unused")
191     private void configure(final String configUri, final InputStream configStream) throws FileSystemException {
192         try {
193             // Load up the config
194             // TODO - validate
195             configure(createDocumentBuilder().parse(configStream).getDocumentElement());
196 
197         } catch (final Exception e) {
198             throw new FileSystemException("vfs.impl/load-config.error", configUri, e);
199         }
200     }
201 
202     /**
203      * Configure and create a DocumentBuilder
204      *
205      * @return A DocumentBuilder for the configuration.
206      * @throws ParserConfigurationException if an error occurs.
207      */
208     private DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
209         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
210         factory.setIgnoringElementContentWhitespace(true);
211         factory.setIgnoringComments(true);
212         factory.setExpandEntityReferences(true);
213         return factory.newDocumentBuilder();
214     }
215 
216     /**
217      * Configures this manager from an parsed XML configuration file
218      *
219      * @param config The configuration Element.
220      * @throws FileSystemException if an error occurs.
221      */
222     private void configure(final Element config) throws FileSystemException {
223         // Add the providers
224         final NodeList providers = config.getElementsByTagName("provider");
225         final int count = providers.getLength();
226         for (int i = 0; i < count; i++) {
227             final Element provider = (Element) providers.item(i);
228             addProvider(provider, false);
229         }
230 
231         // Add the operation providers
232         final NodeList operationProviders = config.getElementsByTagName("operationProvider");
233         for (int i = 0; i < operationProviders.getLength(); i++) {
234             final Element operationProvider = (Element) operationProviders.item(i);
235             addOperationProvider(operationProvider);
236         }
237 
238         // Add the default provider
239         final NodeList defProviders = config.getElementsByTagName("default-provider");
240         if (defProviders.getLength() > 0) {
241             final Element provider = (Element) defProviders.item(0);
242             addProvider(provider, true);
243         }
244 
245         // Add the mime-type maps
246         final NodeList mimeTypes = config.getElementsByTagName("mime-type-map");
247         for (int i = 0; i < mimeTypes.getLength(); i++) {
248             final Element map = (Element) mimeTypes.item(i);
249             addMimeTypeMap(map);
250         }
251 
252         // Add the extension maps
253         final NodeList extensions = config.getElementsByTagName("extension-map");
254         for (int i = 0; i < extensions.getLength(); i++) {
255             final Element map = (Element) extensions.item(i);
256             addExtensionMap(map);
257         }
258     }
259 
260     /**
261      * Adds an extension map.
262      *
263      * @param map containing the Elements.
264      */
265     private void addExtensionMap(final Element map) {
266         final String extension = map.getAttribute("extension");
267         final String scheme = map.getAttribute("scheme");
268         if (!StringUtils.isEmpty(scheme)) {
269             addExtensionMap(extension, scheme);
270         }
271     }
272 
273     /**
274      * Adds a mime-type map.
275      *
276      * @param map containing the Elements.
277      */
278     private void addMimeTypeMap(final Element map) {
279         final String mimeType = map.getAttribute("mime-type");
280         final String scheme = map.getAttribute("scheme");
281         addMimeTypeMap(mimeType, scheme);
282     }
283 
284     /**
285      * Adds a provider from a provider definition.
286      *
287      * @param providerDef the provider definition
288      * @param isDefault true if the default should be used.
289      * @throws FileSystemException if an error occurs.
290      */
291     private void addProvider(final Element providerDef, final boolean isDefault) throws FileSystemException {
292         final String classname = providerDef.getAttribute("class-name");
293 
294         // Make sure all required schemes are available
295         final String[] requiredSchemes = getRequiredSchemes(providerDef);
296         for (final String requiredScheme : requiredSchemes) {
297             if (!hasProvider(requiredScheme)) {
298                 final String msg = Messages.getString("vfs.impl/skipping-provider-scheme.debug", classname,
299                         requiredScheme);
300                 VfsLog.debug(getLogger(), getLogger(), msg);
301                 return;
302             }
303         }
304 
305         // Make sure all required classes are in classpath
306         final String[] requiredClasses = getRequiredClasses(providerDef);
307         for (final String requiredClass : requiredClasses) {
308             if (!findClass(requiredClass)) {
309                 final String msg = Messages.getString("vfs.impl/skipping-provider.debug", classname, requiredClass);
310                 VfsLog.debug(getLogger(), getLogger(), msg);
311                 return;
312             }
313         }
314 
315         // Create and register the provider
316         final FileProvider/../../org/apache/commons/vfs2/provider/FileProvider.html#FileProvider">FileProvider provider = (FileProvider) createInstance(classname);
317         final String[] schemas = getSchemas(providerDef);
318         if (schemas.length > 0) {
319             addProvider(schemas, provider);
320         }
321 
322         // Set as default, if required
323         if (isDefault) {
324             setDefaultProvider(provider);
325         }
326     }
327 
328     /**
329      * Adds a operationProvider from a operationProvider definition.
330      */
331     private void addOperationProvider(final Element providerDef) throws FileSystemException {
332         final String classname = providerDef.getAttribute("class-name");
333 
334         // Attach only to available schemas
335         final String[] schemas = getSchemas(providerDef);
336         for (final String schema : schemas) {
337             if (hasProvider(schema)) {
338                 final FileOperationProvidercommons/vfs2/operations/FileOperationProvider.html#FileOperationProvider">FileOperationProvider operationProvider = (FileOperationProvider) createInstance(classname);
339                 addOperationProvider(schema, operationProvider);
340             }
341         }
342     }
343 
344     /**
345      * Tests if a class is available.
346      */
347     private boolean findClass(final String className) {
348         try {
349             loadClass(className);
350             return true;
351         } catch (final ClassNotFoundException e) {
352             return false;
353         }
354     }
355 
356     /**
357      * Extracts the required classes from a provider definition.
358      */
359     private String[] getRequiredClasses(final Element providerDef) {
360         final ArrayList<String> classes = new ArrayList<>();
361         final NodeList deps = providerDef.getElementsByTagName("if-available");
362         final int count = deps.getLength();
363         for (int i = 0; i < count; i++) {
364             final Element dep = (Element) deps.item(i);
365             final String className = dep.getAttribute("class-name");
366             if (!StringUtils.isEmpty(className)) {
367                 classes.add(className);
368             }
369         }
370         return classes.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
371     }
372 
373     /**
374      * Extracts the required schemes from a provider definition.
375      */
376     private String[] getRequiredSchemes(final Element providerDef) {
377         final ArrayList<String> schemes = new ArrayList<>();
378         final NodeList deps = providerDef.getElementsByTagName("if-available");
379         final int count = deps.getLength();
380         for (int i = 0; i < count; i++) {
381             final Element dep = (Element) deps.item(i);
382             final String scheme = dep.getAttribute("scheme");
383             if (!StringUtils.isEmpty(scheme)) {
384                 schemes.add(scheme);
385             }
386         }
387         return schemes.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
388     }
389 
390     /**
391      * Extracts the schema names from a provider definition.
392      */
393     private String[] getSchemas(final Element provider) {
394         final ArrayList<String> schemas = new ArrayList<>();
395         final NodeList schemaElements = provider.getElementsByTagName("scheme");
396         final int count = schemaElements.getLength();
397         for (int i = 0; i < count; i++) {
398             final Element scheme = (Element) schemaElements.item(i);
399             schemas.add(scheme.getAttribute("name"));
400         }
401         return schemas.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
402     }
403 
404     private ClassLoader getValidClassLoader(final Class<?> clazz) {
405         return validateClassLoader(clazz.getClassLoader(), clazz);
406     }
407 
408     private ClassLoader validateClassLoader(final ClassLoader clazzLoader, final Class<?> clazz) {
409         return Objects.requireNonNull(clazzLoader, "The class loader for " + clazz
410                 + " is null; some Java implementions use null for the bootstrap class loader.");
411     }
412 
413     /**
414      * Creates a provider.
415      */
416     private Object createInstance(final String className) throws FileSystemException {
417         try {
418             return loadClass(className).newInstance();
419         } catch (final Exception e) {
420             throw new FileSystemException("vfs.impl/create-provider.error", className, e);
421         }
422     }
423 
424     /**
425      * Load a class from different class loaders.
426      *
427      * @throws ClassNotFoundException if last {@code loadClass} failed.
428      * @see #findClassLoader()
429      */
430     private Class<?> loadClass(final String className) throws ClassNotFoundException {
431         try {
432             return findClassLoader().loadClass(className);
433         } catch (final ClassNotFoundException e) {
434             return getValidClassLoader(getClass()).loadClass(className);
435         }
436     }
437 
438     /**
439      * Enumerates resources from different class loaders.
440      *
441      * @throws IOException if {@code getResource} failed.
442      * @see #findClassLoader()
443      */
444     private Enumeration<URL> enumerateResources(final String name) throws IOException {
445         Enumeration<URL> enumeration = findClassLoader().getResources(name);
446         if (enumeration == null || !enumeration.hasMoreElements()) {
447             enumeration = getValidClassLoader(getClass()).getResources(name);
448         }
449         return enumeration;
450     }
451 
452 }