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 final 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}