001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.impl;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.util.ArrayList;
024import java.util.Enumeration;
025
026import javax.xml.parsers.DocumentBuilder;
027import javax.xml.parsers.DocumentBuilderFactory;
028import javax.xml.parsers.ParserConfigurationException;
029
030import org.apache.commons.vfs2.FileSystemException;
031import org.apache.commons.vfs2.VfsLog;
032import org.apache.commons.vfs2.operations.FileOperationProvider;
033import org.apache.commons.vfs2.provider.FileProvider;
034import org.apache.commons.vfs2.util.Messages;
035import org.w3c.dom.Element;
036import org.w3c.dom.NodeList;
037
038/**
039 * A {@link org.apache.commons.vfs2.FileSystemManager} that configures itself
040 * from an XML (Default: providers.xml) configuration file.<br>
041 * Certain providers are only loaded and available if the dependent library is in your
042 * classpath. You have to configure your debugging facility to log "debug" messages to see
043 * if a provider was skipped due to "unresolved externals".
044 */
045public class StandardFileSystemManager
046    extends DefaultFileSystemManager
047{
048    private static final String CONFIG_RESOURCE = "providers.xml";
049    private static final String PLUGIN_CONFIG_RESOURCE = "META-INF/vfs-providers.xml";
050
051    private URL configUri;
052    private ClassLoader classLoader;
053
054    /**
055     * Sets the configuration file for this manager.
056     * @param configUri The URI for this manager.
057     */
058    public void setConfiguration(final String configUri)
059    {
060        try
061        {
062            setConfiguration(new URL(configUri));
063        }
064        catch (final MalformedURLException e)
065        {
066            getLogger().warn(e.getLocalizedMessage(), e);
067        }
068    }
069
070    /**
071     * Sets the configuration file for this manager.
072     * @param configUri The URI forthis manager.
073     */
074    public void setConfiguration(final URL configUri)
075    {
076        this.configUri = configUri;
077    }
078
079    /**
080     * Sets the ClassLoader to use to load the providers.  Default is to
081     * use the ClassLoader that loaded this class.
082     * @param classLoader The ClassLoader.
083     */
084    public void setClassLoader(final ClassLoader classLoader)
085    {
086        this.classLoader = classLoader;
087    }
088
089    /**
090     * Initializes this manager.  Adds the providers and replicator.
091     * @throws FileSystemException if an error occurs.
092     */
093    @Override
094    public void init() throws FileSystemException
095    {
096        // Set the replicator and temporary file store (use the same component)
097        final DefaultFileReplicator replicator = createDefaultFileReplicator();
098        setReplicator(new PrivilegedFileReplicator(replicator));
099        setTemporaryFileStore(replicator);
100
101        /* replaced by findClassLoader
102        if (classLoader == null)
103        {
104            // Use default classloader
105            classLoader = getClass().getClassLoader();
106        }
107        */
108
109        if (configUri == null)
110        {
111            // Use default config
112            final URL url = getClass().getResource(CONFIG_RESOURCE);
113            if (url == null)
114            {
115                throw new FileSystemException("vfs.impl/find-config-file.error", CONFIG_RESOURCE);
116            }
117            configUri = url;
118        }
119
120        // Configure
121        configure(configUri);
122
123        // Configure Plugins
124        configurePlugins();
125
126        // Initialise super-class
127        super.init();
128    }
129
130    /**
131     * Scans the classpath to find any droped plugin.<br />
132     * The plugin-description has to be in /META-INF/vfs-providers.xml
133     * @throws FileSystemException if an error occurs.
134     */
135    protected void configurePlugins() throws FileSystemException
136    {
137        final ClassLoader cl = findClassLoader();
138
139        Enumeration<URL> enumResources;
140        try
141        {
142            enumResources = cl.getResources(PLUGIN_CONFIG_RESOURCE);
143        }
144        catch (final IOException e)
145        {
146            throw new FileSystemException(e);
147        }
148
149        while (enumResources.hasMoreElements())
150        {
151            final URL url = enumResources.nextElement();
152            configure(url);
153        }
154    }
155
156    private ClassLoader findClassLoader()
157    {
158        if (classLoader != null)
159        {
160            return classLoader;
161        }
162
163        ClassLoader cl = Thread.currentThread().getContextClassLoader();
164        if (cl == null)
165        {
166            cl = getClass().getClassLoader();
167        }
168
169        return cl;
170    }
171
172    protected DefaultFileReplicator createDefaultFileReplicator()
173    {
174        return new DefaultFileReplicator();
175    }
176
177    /**
178     * Configures this manager from an XML configuration file.
179     * @param configUri The URI of the configuration.
180     * @throws FileSystemException if an error occus.
181     */
182    private void configure(final URL configUri) throws FileSystemException
183    {
184        InputStream configStream = null;
185        try
186        {
187            // Load up the config
188            // TODO - validate
189            final DocumentBuilder builder = createDocumentBuilder();
190            configStream = configUri.openStream();
191            final Element config = builder.parse(configStream).getDocumentElement();
192
193            configure(config);
194        }
195        catch (final Exception e)
196        {
197            throw new FileSystemException("vfs.impl/load-config.error", configUri.toString(), e);
198        }
199        finally
200        {
201            if (configStream != null)
202            {
203                try
204                {
205                    configStream.close();
206                }
207                catch (final IOException e)
208                {
209                    getLogger().warn(e.getLocalizedMessage(), e);
210                }
211            }
212        }
213    }
214
215    /**
216     * Configures this manager from an XML configuration file.
217     * @param configUri The URI of the configuration.
218     * @param configStream An InputStream containing the configuration.
219     * @throws FileSystemException if an error occurs.
220     */
221    @SuppressWarnings("unused")
222    private void configure(final String configUri, final InputStream configStream)
223            throws FileSystemException
224    {
225        try
226        {
227            // Load up the config
228            // TODO - validate
229            final DocumentBuilder builder = createDocumentBuilder();
230            final Element config = builder.parse(configStream).getDocumentElement();
231
232            configure(config);
233
234        }
235        catch (final Exception e)
236        {
237            throw new FileSystemException("vfs.impl/load-config.error", configUri, e);
238        }
239    }
240
241    /**
242     * Configure and create a DocumentBuilder
243     * @return A DocumentBuilder for the configuration.
244     * @throws ParserConfigurationException if an error occurs.
245     */
246    private DocumentBuilder createDocumentBuilder() throws ParserConfigurationException
247    {
248        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
249        factory.setIgnoringElementContentWhitespace(true);
250        factory.setIgnoringComments(true);
251        factory.setExpandEntityReferences(true);
252        return  factory.newDocumentBuilder();
253    }
254
255    /**
256     * Configures this manager from an parsed XML configuration file
257     * @param config The configuration Element.
258     * @throws FileSystemException if an error occurs.
259     */
260    private void configure(final Element config) throws FileSystemException
261    {
262        // Add the providers
263        final NodeList providers = config.getElementsByTagName("provider");
264        final int count = providers.getLength();
265        for (int i = 0; i < count; i++)
266        {
267            final Element provider = (Element) providers.item(i);
268            addProvider(provider, false);
269        }
270
271        // Add the operation providers
272        final NodeList operationProviders = config.getElementsByTagName("operationProvider");
273        for (int i = 0; i < operationProviders.getLength(); i++)
274        {
275            final Element operationProvider = (Element) operationProviders.item(i);
276            addOperationProvider(operationProvider);
277        }
278
279        // Add the default provider
280        final NodeList defProviders = config.getElementsByTagName("default-provider");
281        if (defProviders.getLength() > 0)
282        {
283            final Element provider = (Element) defProviders.item(0);
284            addProvider(provider, true);
285        }
286
287        // Add the mime-type maps
288        final NodeList mimeTypes = config.getElementsByTagName("mime-type-map");
289        for (int i = 0; i < mimeTypes.getLength(); i++)
290        {
291            final Element map = (Element) mimeTypes.item(i);
292            addMimeTypeMap(map);
293        }
294
295        // Add the extension maps
296        final NodeList extensions = config.getElementsByTagName("extension-map");
297        for (int i = 0; i < extensions.getLength(); i++)
298        {
299            final Element map = (Element) extensions.item(i);
300            addExtensionMap(map);
301        }
302    }
303
304    /**
305     * Adds an extension map.
306     * @param map containing the Elements.
307     */
308    private void addExtensionMap(final Element map)
309    {
310        final String extension = map.getAttribute("extension");
311        final String scheme = map.getAttribute("scheme");
312        if (scheme != null && scheme.length() > 0)
313        {
314            addExtensionMap(extension, scheme);
315        }
316    }
317
318    /**
319     * Adds a mime-type map.
320     * @param map containing the Elements.
321     */
322    private void addMimeTypeMap(final Element map)
323    {
324        final String mimeType = map.getAttribute("mime-type");
325        final String scheme = map.getAttribute("scheme");
326        addMimeTypeMap(mimeType, scheme);
327    }
328
329    /**
330     * Adds a provider from a provider definition.
331     * @param providerDef the provider definition
332     * @param isDefault true if the default should be used.
333     * @throws FileSystemException if an error occurs.
334     */
335    private void addProvider(final Element providerDef, final boolean isDefault)
336        throws FileSystemException
337    {
338        final String classname = providerDef.getAttribute("class-name");
339
340        // Make sure all required schemes are available
341        final String[] requiredSchemes = getRequiredSchemes(providerDef);
342        for (final String requiredScheme : requiredSchemes)
343        {
344            if (!hasProvider(requiredScheme))
345            {
346                final String msg = Messages.getString("vfs.impl/skipping-provider-scheme.debug",
347                    classname, requiredScheme);
348                VfsLog.debug(getLogger(), getLogger(), msg);
349                return;
350            }
351        }
352
353        // Make sure all required classes are in classpath
354        final String[] requiredClasses = getRequiredClasses(providerDef);
355        for (final String requiredClass : requiredClasses)
356        {
357            if (!findClass(requiredClass))
358            {
359                final String msg = Messages.getString("vfs.impl/skipping-provider.debug",
360                    classname, requiredClass);
361                VfsLog.debug(getLogger(), getLogger(), msg);
362                return;
363            }
364        }
365
366        // Create and register the provider
367        final FileProvider provider = (FileProvider) createInstance(classname);
368        final String[] schemas = getSchemas(providerDef);
369        if (schemas.length > 0)
370        {
371            addProvider(schemas, provider);
372        }
373
374        // Set as default, if required
375        if (isDefault)
376        {
377            setDefaultProvider(provider);
378        }
379    }
380
381    /**
382     * Adds a operationProvider from a operationProvider definition.
383     */
384    private void addOperationProvider(final Element providerDef) throws FileSystemException
385    {
386        final String classname = providerDef.getAttribute("class-name");
387
388        // Attach only to available schemas
389        final String[] schemas = getSchemas(providerDef);
390        for (final String schema : schemas)
391        {
392            if (hasProvider(schema))
393            {
394                final FileOperationProvider operationProvider = (FileOperationProvider) createInstance(classname);
395                addOperationProvider(schema, operationProvider);
396            }
397        }
398    }
399
400    /**
401     * Tests if a class is available.
402     */
403    private boolean findClass(final String className)
404    {
405        try
406        {
407            findClassLoader().loadClass(className);
408            return true;
409        }
410        catch (final ClassNotFoundException e)
411        {
412            return false;
413        }
414    }
415
416    /**
417     * Extracts the required classes from a provider definition.
418     */
419    private String[] getRequiredClasses(final Element providerDef)
420    {
421        final ArrayList<String> classes = new ArrayList<String>();
422        final NodeList deps = providerDef.getElementsByTagName("if-available");
423        final int count = deps.getLength();
424        for (int i = 0; i < count; i++)
425        {
426            final Element dep = (Element) deps.item(i);
427            final String className = dep.getAttribute("class-name");
428            if (className != null && className.length() > 0)
429            {
430                classes.add(className);
431            }
432        }
433        return classes.toArray(new String[classes.size()]);
434    }
435
436    /**
437     * Extracts the required schemes from a provider definition.
438     */
439    private String[] getRequiredSchemes(final Element providerDef)
440    {
441        final ArrayList<String> schemes = new ArrayList<String>();
442        final NodeList deps = providerDef.getElementsByTagName("if-available");
443        final int count = deps.getLength();
444        for (int i = 0; i < count; i++)
445        {
446            final Element dep = (Element) deps.item(i);
447            final String scheme = dep.getAttribute("scheme");
448            if (scheme != null && scheme.length() > 0)
449            {
450                schemes.add(scheme);
451            }
452        }
453        return schemes.toArray(new String[schemes.size()]);
454    }
455
456    /**
457     * Extracts the schema names from a provider definition.
458     */
459    private String[] getSchemas(final Element provider)
460    {
461        final ArrayList<String> schemas = new ArrayList<String>();
462        final NodeList schemaElements = provider.getElementsByTagName("scheme");
463        final int count = schemaElements.getLength();
464        for (int i = 0; i < count; i++)
465        {
466            final Element scheme = (Element) schemaElements.item(i);
467            schemas.add(scheme.getAttribute("name"));
468        }
469        return schemas.toArray(new String[schemas.size()]);
470    }
471
472    /**
473     * Creates a provider.
474     */
475    private Object createInstance(final String className)
476        throws FileSystemException
477    {
478        try
479        {
480            final Class<?> clazz = findClassLoader().loadClass(className);
481            return clazz.newInstance();
482        }
483        catch (final Exception e)
484        {
485            throw new FileSystemException("vfs.impl/create-provider.error", className, e);
486        }
487    }
488}