    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.VFS;
    042import org.apache.commons.vfs2.provider.UriParser;
    043
    044/**
    045 * FileSystem that uses <a href="https://commons.apache.org/proper/commons-vfs/">Apache Commons VFS</a>.
    046 *
    047 * @since 1.7
    048 */
    049public class VFSFileSystem extends DefaultFileSystem {
    050
    051    /**
    052     * Stream handler required to create URL.
    053     */
    054    private static final class VFSURLStreamHandler extends URLStreamHandler {
    055
    056        @Override
    057        protected URLConnection openConnection(final URL url) throws IOException {
    058            throw new IOException("VFS URLs can only be used with VFS APIs");
    059        }
    060    }
    061
    062    /** The logger. */
    063    private final Log log = LogFactory.getLog(getClass());
    064
    065    public VFSFileSystem() {
    066        // empty
    067    }
    068
    069    @Override
    070    public String getBasePath(final String path) {
    071        if (UriParser.extractScheme(path) == null) {
    072            return super.getBasePath(path);
    073        }
    074        try {
    075            final FileName parent = resolveURI(path).getParent();
    076            return parent != null ? parent.getURI() : null;
    077        } catch (final FileSystemException fse) {
    078            fse.printStackTrace();
    079            return null;
    080        }
    081    }
    082
    083    @Override
    084    public String getFileName(final String path) {
    085        if (UriParser.extractScheme(path) == null) {
    086            return super.getFileName(path);
    087        }
    088        try {
    089            return resolveURI(path).getBaseName();
    090        } catch (final FileSystemException fse) {
    091            fse.printStackTrace();
    092            return null;
    093        }
    094    }
    095
    096    @Override
    097    public InputStream getInputStream(final URL url) throws ConfigurationException {
    098        final FileObject file;
    099        try {
    100            final FileSystemOptions opts = getOptions(url.getProtocol());
    101            file = getManager().resolveFile(url.toString(), opts);
    102            if (!file.exists()) {
    103                throw new ConfigurationException("File not found");
    104            }
    105            if (!file.isFile()) {
    106                throw new ConfigurationException("Cannot load a configuration from a directory");
    107            }
    108            final FileContent content = file.getContent();
    109            if (content == null) {
    110                final String msg = "Cannot access content of " + file.getName().getFriendlyURI();
    111                throw new ConfigurationException(msg);
    112            }
    113            return content.getInputStream();
    114        } catch (final FileSystemException fse) {
    115            final String msg = "Unable to access " + url.toString();
    116            throw new ConfigurationException(msg, fse);
    117        }
    118    }
    119
    120    private FileSystemManager getManager() throws FileSystemException {
    121        return VFS.getManager();
    122    }
    123
    124    private FileSystemOptions getOptions(final String scheme) {
    125        if (scheme == null) {
    126            return null;
    127        }
    128        final FileSystemOptions opts = new FileSystemOptions();
    129        final FileSystemConfigBuilder builder;
    130        try {
    131            builder = getManager().getFileSystemConfigBuilder(scheme);
    132        } catch (final Exception ex) {
    133            return null;
    134        }
    135        final FileOptionsProvider provider = getFileOptionsProvider();
    136        if (provider != null) {
    137            final Map<String, Object> map = provider.getOptions();
    138            if (map == null) {
    139                return null;
    140            }
    141            int count = 0;
    142            for (final Map.Entry<String, Object> entry : map.entrySet()) {
    143                try {
    144                    String key = entry.getKey();
    145                    if (FileOptionsProvider.CURRENT_USER.equals(key)) {
    146                        key = "creatorName";
    147                    }
    148                    setProperty(builder, opts, key, entry.getValue());
    149                    ++count;
    150                } catch (final Exception ex) {
    151                    // Ignore an incorrect property.
    152                    continue;
    153                }
    154            }
    155            if (count > 0) {
    156                return opts;
    157            }
    158        }
    159        return null;
    160
    161    }
    162
    163    @Override
    164    public OutputStream getOutputStream(final URL url) throws ConfigurationException {
    165        try {
    166            final FileSystemOptions opts = getOptions(url.getProtocol());
    167            final FileObject file = getManager().resolveFile(url.toString(), opts);
    168            // throw an exception if the target URL is a directory
    169            if (file == null || file.isFolder()) {
    170                throw new ConfigurationException("Cannot save a configuration to a directory");
    171            }
    172            final FileContent content = file.getContent();
    173
    174            if (content == null) {
    175                throw new ConfigurationException("Cannot access content of " + url);
    176            }
    177            return content.getOutputStream();
    178        } catch (final FileSystemException fse) {
    179            throw new ConfigurationException("Unable to access " + url, fse);
    180        }
    181    }
    182
    183    @Override
    184    public String getPath(final File file, final URL url, final String basePath, final String fileName) {
    185        if (file != null) {
    186            return super.getPath(file, url, basePath, fileName);
    187        }
    188        try {
    189            if (url != null) {
    190                final FileName name = resolveURI(url.toString());
    191                if (name != null) {
    192                    return name.toString();
    193                }
    194            }
    195            if (UriParser.extractScheme(fileName) != null) {
    196                return fileName;
    197            }
    198            if (basePath != null) {
    199                final FileName base = resolveURI(basePath);
    200                return getManager().resolveName(base, fileName).getURI();
    201            }
    202            final FileName name = resolveURI(fileName);
    203            final FileName base = name.getParent();
    204            return getManager().resolveName(base, name.getBaseName()).getURI();
    205        } catch (final FileSystemException fse) {
    206            fse.printStackTrace();
    207            return null;
    208        }
    209    }
    210
    211    @Override
    212    public URL getURL(final String basePath, final String file) throws MalformedURLException {
    213        if (basePath != null && UriParser.extractScheme(basePath) == null || basePath == null && UriParser.extractScheme(file) == null) {
    214            return super.getURL(basePath, file);
    215        }
    216        try {
    217            final FileName path;
    218            if (basePath != null && UriParser.extractScheme(file) == null) {
    219                final FileName base = resolveURI(basePath);
    220                path = getManager().resolveName(base, file);
    221            } else {
    222                path = resolveURI(file);
    223            }
    224
    225            final URLStreamHandler handler = new VFSURLStreamHandler();
    226            return new URL(null, path.getURI(), handler);
    227        } catch (final FileSystemException fse) {
    228            throw new ConfigurationRuntimeException("Could not parse basePath: " + basePath + " and fileName: " + file, fse);
    229        }
    230    }
    231
    232    @Override
    233    public URL locateFromURL(final String basePath, final String fileName) {
    234        final String fileScheme = UriParser.extractScheme(fileName);
    235        // Use DefaultFileSystem if basePath and fileName don't have a scheme.
    236        if ((basePath == null || UriParser.extractScheme(basePath) == null) && fileScheme == null) {
    237            return super.locateFromURL(basePath, fileName);
    238        }
    239        try {
    240            final FileObject file;
    241            // Only use the base path if the file name doesn't have a scheme.
    242            if (basePath != null && fileScheme == null) {
    243                final String scheme = UriParser.extractScheme(basePath);
    244                final FileSystemOptions opts = getOptions(scheme);
    245                FileObject base = getManager().resolveFile(basePath, opts);
    246                if (base.isFile()) {
    247                    base = base.getParent();
    248                }
    249
    250                file = getManager().resolveFile(base, fileName);
    251            } else {
    252                final FileSystemOptions opts = getOptions(fileScheme);
    253                file = getManager().resolveFile(fileName, opts);
    254            }
    255
    256            if (!file.exists()) {
    257                return null;
    258            }
    259            final FileName path = file.getName();
    260            final URLStreamHandler handler = new VFSURLStreamHandler();
    261            return new URL(null, path.getURI(), handler);
    262        } catch (final FileSystemException | MalformedURLException fse) {
    263            return null;
    264        }
    265    }
    266
    267    private FileName resolveURI(final String path) throws FileSystemException {
    268        return getManager().resolveURI(path);
    269    }
    270
    271    private void setProperty(final FileSystemConfigBuilder builder, final FileSystemOptions options, final String key, final Object value) {
    272        final String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
    273        final Class<?>[] paramTypes = new Class<?>[2];
    274        paramTypes[0] = FileSystemOptions.class;
    275        paramTypes[1] = value.getClass();
    276        try {
    277            final Method method = builder.getClass().getMethod(methodName, paramTypes);
    278            final Object[] params = new Object[2];
    279            params[0] = options;
    280            params[1] = value;
    281            method.invoke(builder, params);
    282        } catch (final Exception ex) {
    283            log.warn("Cannot access property '" + key + "'! Ignoring.", ex);
    284        }
    285    }
    286}