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