    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}