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.configuration2.io;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.lang.reflect.Method;
024import java.net.MalformedURLException;
025import java.net.URL;
026import java.net.URLConnection;
027import java.net.URLStreamHandler;
028import java.util.Map;
029
030import org.apache.commons.configuration2.ex.ConfigurationException;
031import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.commons.vfs2.FileContent;
035import org.apache.commons.vfs2.FileName;
036import org.apache.commons.vfs2.FileObject;
037import org.apache.commons.vfs2.FileSystemConfigBuilder;
038import org.apache.commons.vfs2.FileSystemException;
039import org.apache.commons.vfs2.FileSystemManager;
040import org.apache.commons.vfs2.FileSystemOptions;
041import org.apache.commons.vfs2.FileType;
042import org.apache.commons.vfs2.VFS;
043import org.apache.commons.vfs2.provider.UriParser;
044
045/**
046 * FileSystem that uses Commons VFS
047 * @since 1.7
048 * @author <a
049 * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
050 */
051public class VFSFileSystem extends DefaultFileSystem
052{
053    /** The logger. */
054    private final Log log = LogFactory.getLog(getClass());
055
056    public VFSFileSystem()
057    {
058    }
059
060    @Override
061    public InputStream getInputStream(final URL url) throws ConfigurationException
062    {
063        FileObject file;
064        try
065        {
066            final FileSystemOptions opts = getOptions(url.getProtocol());
067            file = (opts == null) ? VFS.getManager().resolveFile(url.toString())
068                    : VFS.getManager().resolveFile(url.toString(), opts);
069            if (file.getType() != FileType.FILE)
070            {
071                throw new ConfigurationException("Cannot load a configuration from a directory");
072            }
073            final FileContent content = file.getContent();
074            if (content == null)
075            {
076                final String msg = "Cannot access content of " + file.getName().getFriendlyURI();
077                throw new ConfigurationException(msg);
078            }
079            return content.getInputStream();
080        }
081        catch (final FileSystemException fse)
082        {
083            final String msg = "Unable to access " + url.toString();
084            throw new ConfigurationException(msg, fse);
085        }
086    }
087
088    @Override
089    public OutputStream getOutputStream(final URL url) throws ConfigurationException
090    {
091        try
092        {
093            final FileSystemOptions opts = getOptions(url.getProtocol());
094            final FileSystemManager fsManager = VFS.getManager();
095            final FileObject file = (opts == null) ? fsManager.resolveFile(url.toString())
096                    : fsManager.resolveFile(url.toString(), opts);
097            // throw an exception if the target URL is a directory
098            if (file == null || file.getType() == FileType.FOLDER)
099            {
100                throw new ConfigurationException("Cannot save a configuration to a directory");
101            }
102            final FileContent content = file.getContent();
103
104            if (content == null)
105            {
106                throw new ConfigurationException("Cannot access content of " + url);
107            }
108            return content.getOutputStream();
109        }
110        catch (final FileSystemException fse)
111        {
112            throw new ConfigurationException("Unable to access " + url, fse);
113        }
114    }
115
116    @Override
117    public String getPath(final File file, final URL url, final String basePath, final String fileName)
118    {
119        if (file != null)
120        {
121            return super.getPath(file, url, basePath, fileName);
122        }
123        try
124        {
125            final FileSystemManager fsManager = VFS.getManager();
126            if (url != null)
127            {
128                final FileName name = fsManager.resolveURI(url.toString());
129                if (name != null)
130                {
131                    return name.toString();
132                }
133            }
134
135            if (UriParser.extractScheme(fileName) != null)
136            {
137                return fileName;
138            }
139            else if (basePath != null)
140            {
141                final FileName base = fsManager.resolveURI(basePath);
142                return fsManager.resolveName(base, fileName).getURI();
143            }
144            else
145            {
146                final FileName name = fsManager.resolveURI(fileName);
147                final FileName base = name.getParent();
148                return fsManager.resolveName(base, name.getBaseName()).getURI();
149            }
150        }
151        catch (final FileSystemException fse)
152        {
153            fse.printStackTrace();
154            return null;
155        }
156    }
157
158    @Override
159    public String getBasePath(final String path)
160    {
161        if (UriParser.extractScheme(path) == null)
162        {
163            return super.getBasePath(path);
164        }
165        try
166        {
167            final FileSystemManager fsManager = VFS.getManager();
168            final FileName name = fsManager.resolveURI(path);
169            return name.getParent().getURI();
170        }
171        catch (final FileSystemException fse)
172        {
173            fse.printStackTrace();
174            return null;
175        }
176    }
177
178    @Override
179    public String getFileName(final String path)
180    {
181        if (UriParser.extractScheme(path) == null)
182        {
183            return super.getFileName(path);
184        }
185        try
186        {
187            final FileSystemManager fsManager = VFS.getManager();
188            final FileName name = fsManager.resolveURI(path);
189            return name.getBaseName();
190        }
191        catch (final FileSystemException fse)
192        {
193            fse.printStackTrace();
194            return null;
195        }
196    }
197
198    @Override
199    public URL getURL(final String basePath, final String file) throws MalformedURLException
200    {
201        if ((basePath != null && UriParser.extractScheme(basePath) == null)
202            || (basePath == null && UriParser.extractScheme(file) == null))
203        {
204            return super.getURL(basePath, file);
205        }
206        try
207        {
208            final FileSystemManager fsManager = VFS.getManager();
209
210            FileName path;
211            if (basePath != null && UriParser.extractScheme(file) == null)
212            {
213                final FileName base = fsManager.resolveURI(basePath);
214                path = fsManager.resolveName(base, file);
215            }
216            else
217            {
218                path = fsManager.resolveURI(file);
219            }
220
221            final URLStreamHandler handler = new VFSURLStreamHandler(path);
222            return new URL(null, path.getURI(), handler);
223        }
224        catch (final FileSystemException fse)
225        {
226            throw new ConfigurationRuntimeException("Could not parse basePath: " + basePath
227                + " and fileName: " + file, fse);
228        }
229    }
230
231    @Override
232    public URL locateFromURL(final String basePath, final String fileName)
233    {
234        final String fileScheme = UriParser.extractScheme(fileName);
235
236        // Use DefaultFileSystem if basePath and fileName don't have a scheme.
237        if ((basePath == null || UriParser.extractScheme(basePath) == null) && fileScheme == null)
238        {
239            return super.locateFromURL(basePath, fileName);
240        }
241        try
242        {
243            final FileSystemManager fsManager = VFS.getManager();
244
245            FileObject file;
246            // Only use the base path if the file name doesn't have a scheme.
247            if (basePath != null && fileScheme == null)
248            {
249                final String scheme = UriParser.extractScheme(basePath);
250                final FileSystemOptions opts = (scheme != null) ? getOptions(scheme) : null;
251                FileObject base = (opts == null) ? fsManager.resolveFile(basePath)
252                        : fsManager.resolveFile(basePath, opts);
253                if (base.getType() == FileType.FILE)
254                {
255                    base = base.getParent();
256                }
257
258                file = fsManager.resolveFile(base, fileName);
259            }
260            else
261            {
262                final FileSystemOptions opts = (fileScheme != null) ? getOptions(fileScheme) : null;
263                file = (opts == null) ? fsManager.resolveFile(fileName)
264                        : fsManager.resolveFile(fileName, opts);
265            }
266
267            if (!file.exists())
268            {
269                return null;
270            }
271            final FileName path = file.getName();
272            final URLStreamHandler handler = new VFSURLStreamHandler(path);
273            return new URL(null, path.getURI(), handler);
274        }
275        catch (final FileSystemException fse)
276        {
277            return null;
278        }
279        catch (final MalformedURLException ex)
280        {
281            return null;
282        }
283    }
284
285    private FileSystemOptions getOptions(final String scheme)
286    {
287        final FileSystemOptions opts = new FileSystemOptions();
288        FileSystemConfigBuilder builder;
289        try
290        {
291            builder = VFS.getManager().getFileSystemConfigBuilder(scheme);
292        }
293        catch (final Exception ex)
294        {
295            return null;
296        }
297        final FileOptionsProvider provider = getFileOptionsProvider();
298        if (provider != null)
299        {
300            final Map<String, Object> map = provider.getOptions();
301            if (map == null)
302            {
303                return null;
304            }
305            int count = 0;
306            for (final Map.Entry<String, Object> entry : map.entrySet())
307            {
308                try
309                {
310                    String key = entry.getKey();
311                    if (FileOptionsProvider.CURRENT_USER.equals(key))
312                    {
313                        key = "creatorName";
314                    }
315                    setProperty(builder, opts, key, entry.getValue());
316                    ++count;
317                }
318                catch (final Exception ex)
319                {
320                    // Ignore an incorrect property.
321                    continue;
322                }
323            }
324            if (count > 0)
325            {
326                return opts;
327            }
328        }
329        return null;
330
331    }
332
333    private void setProperty(final FileSystemConfigBuilder builder, final FileSystemOptions options,
334                             final String key, final Object value)
335    {
336        final String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
337        final Class<?>[] paramTypes = new Class<?>[2];
338        paramTypes[0] = FileSystemOptions.class;
339        paramTypes[1] = value.getClass();
340
341        try
342        {
343            final Method method = builder.getClass().getMethod(methodName, paramTypes);
344            final Object[] params = new Object[2];
345            params[0] = options;
346            params[1] = value;
347            method.invoke(builder, params);
348        }
349        catch (final Exception ex)
350        {
351            log.warn("Cannot access property '" + key + "'! Ignoring.", ex);
352        }
353
354    }
355
356    /**
357     * Stream handler required to create URL.
358     */
359    private static class VFSURLStreamHandler extends URLStreamHandler
360    {
361        /** The Protocol used */
362        private final String protocol;
363
364        public VFSURLStreamHandler(final FileName file)
365        {
366            this.protocol = file.getScheme();
367        }
368
369        @Override
370        protected URLConnection openConnection(final URL url) throws IOException
371        {
372            throw new IOException("VFS URLs can only be used with VFS APIs");
373        }
374    }
375}