View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.io;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.lang.reflect.Method;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.net.URLStreamHandler;
28  import java.util.Map;
29  
30  import org.apache.commons.configuration2.ex.ConfigurationException;
31  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.commons.vfs2.FileContent;
35  import org.apache.commons.vfs2.FileName;
36  import org.apache.commons.vfs2.FileObject;
37  import org.apache.commons.vfs2.FileSystemConfigBuilder;
38  import org.apache.commons.vfs2.FileSystemException;
39  import org.apache.commons.vfs2.FileSystemManager;
40  import org.apache.commons.vfs2.FileSystemOptions;
41  import org.apache.commons.vfs2.VFS;
42  import org.apache.commons.vfs2.provider.UriParser;
43  
44  /**
45   * FileSystem that uses <a href="https://commons.apache.org/proper/commons-vfs/">Apache Commons VFS</a>.
46   *
47   * @since 1.7
48   */
49  public class VFSFileSystem extends DefaultFileSystem {
50  
51      /**
52       * Stream handler required to create URL.
53       */
54      private static final class VFSURLStreamHandler extends URLStreamHandler {
55  
56          @Override
57          protected URLConnection openConnection(final URL url) throws IOException {
58              throw new IOException("VFS URLs can only be used with VFS APIs");
59          }
60      }
61  
62      /** The logger. */
63      private final Log log = LogFactory.getLog(getClass());
64  
65      public VFSFileSystem() {
66          // empty
67      }
68  
69      @Override
70      public String getBasePath(final String path) {
71          if (UriParser.extractScheme(path) == null) {
72              return super.getBasePath(path);
73          }
74          try {
75              final FileName parent = resolveURI(path).getParent();
76              return parent != null ? parent.getURI() : null;
77          } catch (final FileSystemException fse) {
78              fse.printStackTrace();
79              return null;
80          }
81      }
82  
83      @Override
84      public String getFileName(final String path) {
85          if (UriParser.extractScheme(path) == null) {
86              return super.getFileName(path);
87          }
88          try {
89              return resolveURI(path).getBaseName();
90          } catch (final FileSystemException fse) {
91              fse.printStackTrace();
92              return null;
93          }
94      }
95  
96      @Override
97      public InputStream getInputStream(final URL url) throws ConfigurationException {
98          final FileObject file;
99          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 }