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