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