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}