    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 *     https://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                throw new ConfigurationException("Cannot access content of %s", file.getName().getFriendlyURI());
    111            }
    112            return content.getInputStream();
    113        } catch (final FileSystemException e) {
    114            throw new ConfigurationException(e, "Unable to access %s", url);
    115        }
    116    }
    117
    118    private FileSystemManager getManager() throws FileSystemException {
    119        return VFS.getManager();
    120    }
    121
    122    private FileSystemOptions getOptions(final String scheme) {
    123        if (scheme == null) {
    124            return null;
    125        }
    126        final FileSystemOptions opts = new FileSystemOptions();
    127        final FileSystemConfigBuilder builder;
    128        try {
    129            builder = getManager().getFileSystemConfigBuilder(scheme);
    130        } catch (final Exception ex) {
    131            return null;
    132        }
    133        final FileOptionsProvider provider = getFileOptionsProvider();
    134        if (provider != null) {
    135            final Map<String, Object> map = provider.getOptions();
    136            if (map == null) {
    137                return null;
    138            }
    139            int count = 0;
    140            for (final Map.Entry<String, Object> entry : map.entrySet()) {
    141                try {
    142                    String key = entry.getKey();
    143                    if (FileOptionsProvider.CURRENT_USER.equals(key)) {
    144                        key = "creatorName";
    145                    }
    146                    setProperty(builder, opts, key, entry.getValue());
    147                    ++count;
    148                } catch (final Exception ex) {
    149                    // Ignore an incorrect property.
    150                    continue;
    151                }
    152            }
    153            if (count > 0) {
    154                return opts;
    155            }
    156        }
    157        return null;
    158
    159    }
    160
    161    @Override
    162    public OutputStream getOutputStream(final URL url) throws ConfigurationException {
    163        try {
    164            final FileSystemOptions opts = getOptions(url.getProtocol());
    165            final FileObject file = getManager().resolveFile(url.toString(), opts);
    166            // throw an exception if the target URL is a directory
    167            if (file == null || file.isFolder()) {
    168                throw new ConfigurationException("Cannot save a configuration to a directory");
    169            }
    170            final FileContent content = file.getContent();
    171
    172            if (content == null) {
    173                throw new ConfigurationException("Cannot access content of %s", url);
    174            }
    175            return content.getOutputStream();
    176        } catch (final FileSystemException e) {
    177            throw new ConfigurationException(e, "Unable to access ", url);
    178        }
    179    }
    180
    181    @Override
    182    public String getPath(final File file, final URL url, final String basePath, final String fileName) {
    183        if (file != null) {
    184            return super.getPath(file, url, basePath, fileName);
    185        }
    186        try {
    187            if (url != null) {
    188                final FileName name = resolveURI(url.toString());
    189                if (name != null) {
    190                    return name.toString();
    191                }
    192            }
    193            if (UriParser.extractScheme(fileName) != null) {
    194                return fileName;
    195            }
    196            if (basePath != null) {
    197                final FileName base = resolveURI(basePath);
    198                return getManager().resolveName(base, fileName).getURI();
    199            }
    200            final FileName name = resolveURI(fileName);
    201            final FileName base = name.getParent();
    202            return getManager().resolveName(base, name.getBaseName()).getURI();
    203        } catch (final FileSystemException fse) {
    204            fse.printStackTrace();
    205            return null;
    206        }
    207    }
    208
    209    @Override
    210    public URL getURL(final String basePath, final String file) throws MalformedURLException {
    211        if (basePath != null && UriParser.extractScheme(basePath) == null || basePath == null && UriParser.extractScheme(file) == null) {
    212            return super.getURL(basePath, file);
    213        }
    214        try {
    215            final FileName path;
    216            if (basePath != null && UriParser.extractScheme(file) == null) {
    217                final FileName base = resolveURI(basePath);
    218                path = getManager().resolveName(base, file);
    219            } else {
    220                path = resolveURI(file);
    221            }
    222
    223            final URLStreamHandler handler = new VFSURLStreamHandler();
    224            return new URL(null, path.getURI(), handler);
    225        } catch (final FileSystemException e) {
    226            throw new ConfigurationRuntimeException(e, "Could not parse basePath: %s and fileName: %s", basePath, file);
    227        }
    228    }
    229
    230    @Override
    231    public URL locateFromURL(final String basePath, final String fileName) {
    232        final String fileScheme = UriParser.extractScheme(fileName);
    233        // Use DefaultFileSystem if basePath and fileName don't have a scheme.
    234        if ((basePath == null || UriParser.extractScheme(basePath) == null) && fileScheme == null) {
    235            return super.locateFromURL(basePath, fileName);
    236        }
    237        try {
    238            final FileObject file;
    239            // Only use the base path if the file name doesn't have a scheme.
    240            if (basePath != null && fileScheme == null) {
    241                final String scheme = UriParser.extractScheme(basePath);
    242                final FileSystemOptions opts = getOptions(scheme);
    243                FileObject base = getManager().resolveFile(basePath, opts);
    244                if (base.isFile()) {
    245                    base = base.getParent();
    246                }
    247
    248                file = getManager().resolveFile(base, fileName);
    249            } else {
    250                final FileSystemOptions opts = getOptions(fileScheme);
    251                file = getManager().resolveFile(fileName, opts);
    252            }
    253
    254            if (!file.exists()) {
    255                return null;
    256            }
    257            final FileName path = file.getName();
    258            final URLStreamHandler handler = new VFSURLStreamHandler();
    259            return new URL(null, path.getURI(), handler);
    260        } catch (final FileSystemException | MalformedURLException fse) {
    261            return null;
    262        }
    263    }
    264
    265    private FileName resolveURI(final String path) throws FileSystemException {
    266        return getManager().resolveURI(path);
    267    }
    268
    269    private void setProperty(final FileSystemConfigBuilder builder, final FileSystemOptions options, final String key, final Object value) {
    270        final String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
    271        final Class<?>[] paramTypes = new Class<?>[2];
    272        paramTypes[0] = FileSystemOptions.class;
    273        paramTypes[1] = value.getClass();
    274        try {
    275            final Method method = builder.getClass().getMethod(methodName, paramTypes);
    276            final Object[] params = new Object[2];
    277            params[0] = options;
    278            params[1] = value;
    279            method.invoke(builder, params);
    280        } catch (final Exception ex) {
    281            log.warn("Cannot access property '" + key + "'! Ignoring.", ex);
    282        }
    283    }
    284}