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}