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