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.vfs2.impl; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.MalformedURLException; 022import java.net.URL; 023import java.util.ArrayList; 024import java.util.Enumeration; 025 026import javax.xml.parsers.DocumentBuilder; 027import javax.xml.parsers.DocumentBuilderFactory; 028import javax.xml.parsers.ParserConfigurationException; 029 030import org.apache.commons.vfs2.FileSystemException; 031import org.apache.commons.vfs2.VfsLog; 032import org.apache.commons.vfs2.operations.FileOperationProvider; 033import org.apache.commons.vfs2.provider.FileProvider; 034import org.apache.commons.vfs2.util.Messages; 035import org.w3c.dom.Element; 036import org.w3c.dom.NodeList; 037 038/** 039 * A {@link org.apache.commons.vfs2.FileSystemManager} that configures itself from an XML (Default: providers.xml) 040 * configuration file. 041 * <p> 042 * Certain providers are only loaded and available if the dependent library is in your classpath. You have to configure 043 * your debugging facility to log "debug" messages to see if a provider was skipped due to "unresolved externals". 044 */ 045public class StandardFileSystemManager extends DefaultFileSystemManager { 046 private static final String CONFIG_RESOURCE = "providers.xml"; 047 private static final String PLUGIN_CONFIG_RESOURCE = "META-INF/vfs-providers.xml"; 048 049 private URL configUri; 050 private ClassLoader classLoader; 051 052 /** 053 * Sets the configuration file for this manager. 054 * 055 * @param configUri The URI for this manager. 056 */ 057 public void setConfiguration(final String configUri) { 058 try { 059 setConfiguration(new URL(configUri)); 060 } catch (final MalformedURLException e) { 061 getLogger().warn(e.getLocalizedMessage(), e); 062 } 063 } 064 065 /** 066 * Sets the configuration file for this manager. 067 * 068 * @param configUri The URI forthis manager. 069 */ 070 public void setConfiguration(final URL configUri) { 071 this.configUri = configUri; 072 } 073 074 /** 075 * Sets the ClassLoader to use to load the providers. Default is to use the ClassLoader that loaded this class. 076 * 077 * @param classLoader The ClassLoader. 078 */ 079 public void setClassLoader(final ClassLoader classLoader) { 080 this.classLoader = classLoader; 081 } 082 083 /** 084 * Initializes this manager. Adds the providers and replicator. 085 * 086 * @throws FileSystemException if an error occurs. 087 */ 088 @Override 089 public void init() throws FileSystemException { 090 // Set the replicator and temporary file store (use the same component) 091 final DefaultFileReplicator replicator = createDefaultFileReplicator(); 092 setReplicator(new PrivilegedFileReplicator(replicator)); 093 setTemporaryFileStore(replicator); 094 095 if (configUri == null) { 096 // Use default config 097 final URL url = getClass().getResource(CONFIG_RESOURCE); 098 if (url == null) { 099 throw new FileSystemException("vfs.impl/find-config-file.error", CONFIG_RESOURCE); 100 } 101 configUri = url; 102 } 103 104 configure(configUri); 105 configurePlugins(); 106 107 // Initialise super-class 108 super.init(); 109 } 110 111 /** 112 * Scans the classpath to find any droped plugin. 113 * <p> 114 * The plugin-description has to be in {@code /META-INF/vfs-providers.xml}. 115 * 116 * @throws FileSystemException if an error occurs. 117 */ 118 protected void configurePlugins() throws FileSystemException { 119 Enumeration<URL> enumResources; 120 try { 121 enumResources = loadResources(PLUGIN_CONFIG_RESOURCE); 122 } catch (final IOException e) { 123 throw new FileSystemException(e); 124 } 125 126 while (enumResources.hasMoreElements()) { 127 final URL url = enumResources.nextElement(); 128 configure(url); 129 } 130 } 131 132 private ClassLoader findClassLoader() { 133 if (classLoader != null) { 134 return classLoader; 135 } 136 137 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 138 if (cl == null) { 139 cl = getClass().getClassLoader(); 140 } 141 142 return cl; 143 } 144 145 protected DefaultFileReplicator createDefaultFileReplicator() { 146 return new DefaultFileReplicator(); 147 } 148 149 /** 150 * Configures this manager from an XML configuration file. 151 * 152 * @param configUri The URI of the configuration. 153 * @throws FileSystemException if an error occus. 154 */ 155 private void configure(final URL configUri) throws FileSystemException { 156 InputStream configStream = null; 157 try { 158 // Load up the config 159 // TODO - validate 160 final DocumentBuilder builder = createDocumentBuilder(); 161 configStream = configUri.openStream(); 162 final Element config = builder.parse(configStream).getDocumentElement(); 163 164 configure(config); 165 } catch (final Exception e) { 166 throw new FileSystemException("vfs.impl/load-config.error", configUri.toString(), e); 167 } finally { 168 if (configStream != null) { 169 try { 170 configStream.close(); 171 } catch (final IOException e) { 172 getLogger().warn(e.getLocalizedMessage(), e); 173 } 174 } 175 } 176 } 177 178 /** 179 * Configures this manager from an XML configuration file. 180 * 181 * @param configUri The URI of the configuration. 182 * @param configStream An InputStream containing the configuration. 183 * @throws FileSystemException if an error occurs. 184 */ 185 @SuppressWarnings("unused") 186 private void configure(final String configUri, final InputStream configStream) throws FileSystemException { 187 try { 188 // Load up the config 189 // TODO - validate 190 final DocumentBuilder builder = createDocumentBuilder(); 191 final Element config = builder.parse(configStream).getDocumentElement(); 192 193 configure(config); 194 195 } catch (final Exception e) { 196 throw new FileSystemException("vfs.impl/load-config.error", configUri, e); 197 } 198 } 199 200 /** 201 * Configure and create a DocumentBuilder 202 * 203 * @return A DocumentBuilder for the configuration. 204 * @throws ParserConfigurationException if an error occurs. 205 */ 206 private DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { 207 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 208 factory.setIgnoringElementContentWhitespace(true); 209 factory.setIgnoringComments(true); 210 factory.setExpandEntityReferences(true); 211 return factory.newDocumentBuilder(); 212 } 213 214 /** 215 * Configures this manager from an parsed XML configuration file 216 * 217 * @param config The configuration Element. 218 * @throws FileSystemException if an error occurs. 219 */ 220 private void configure(final Element config) throws FileSystemException { 221 // Add the providers 222 final NodeList providers = config.getElementsByTagName("provider"); 223 final int count = providers.getLength(); 224 for (int i = 0; i < count; i++) { 225 final Element provider = (Element) providers.item(i); 226 addProvider(provider, false); 227 } 228 229 // Add the operation providers 230 final NodeList operationProviders = config.getElementsByTagName("operationProvider"); 231 for (int i = 0; i < operationProviders.getLength(); i++) { 232 final Element operationProvider = (Element) operationProviders.item(i); 233 addOperationProvider(operationProvider); 234 } 235 236 // Add the default provider 237 final NodeList defProviders = config.getElementsByTagName("default-provider"); 238 if (defProviders.getLength() > 0) { 239 final Element provider = (Element) defProviders.item(0); 240 addProvider(provider, true); 241 } 242 243 // Add the mime-type maps 244 final NodeList mimeTypes = config.getElementsByTagName("mime-type-map"); 245 for (int i = 0; i < mimeTypes.getLength(); i++) { 246 final Element map = (Element) mimeTypes.item(i); 247 addMimeTypeMap(map); 248 } 249 250 // Add the extension maps 251 final NodeList extensions = config.getElementsByTagName("extension-map"); 252 for (int i = 0; i < extensions.getLength(); i++) { 253 final Element map = (Element) extensions.item(i); 254 addExtensionMap(map); 255 } 256 } 257 258 /** 259 * Adds an extension map. 260 * 261 * @param map containing the Elements. 262 */ 263 private void addExtensionMap(final Element map) { 264 final String extension = map.getAttribute("extension"); 265 final String scheme = map.getAttribute("scheme"); 266 if (scheme != null && scheme.length() > 0) { 267 addExtensionMap(extension, scheme); 268 } 269 } 270 271 /** 272 * Adds a mime-type map. 273 * 274 * @param map containing the Elements. 275 */ 276 private void addMimeTypeMap(final Element map) { 277 final String mimeType = map.getAttribute("mime-type"); 278 final String scheme = map.getAttribute("scheme"); 279 addMimeTypeMap(mimeType, scheme); 280 } 281 282 /** 283 * Adds a provider from a provider definition. 284 * 285 * @param providerDef the provider definition 286 * @param isDefault true if the default should be used. 287 * @throws FileSystemException if an error occurs. 288 */ 289 private void addProvider(final Element providerDef, final boolean isDefault) throws FileSystemException { 290 final String classname = providerDef.getAttribute("class-name"); 291 292 // Make sure all required schemes are available 293 final String[] requiredSchemes = getRequiredSchemes(providerDef); 294 for (final String requiredScheme : requiredSchemes) { 295 if (!hasProvider(requiredScheme)) { 296 final String msg = Messages.getString("vfs.impl/skipping-provider-scheme.debug", classname, 297 requiredScheme); 298 VfsLog.debug(getLogger(), getLogger(), msg); 299 return; 300 } 301 } 302 303 // Make sure all required classes are in classpath 304 final String[] requiredClasses = getRequiredClasses(providerDef); 305 for (final String requiredClass : requiredClasses) { 306 if (!findClass(requiredClass)) { 307 final String msg = Messages.getString("vfs.impl/skipping-provider.debug", classname, requiredClass); 308 VfsLog.debug(getLogger(), getLogger(), msg); 309 return; 310 } 311 } 312 313 // Create and register the provider 314 final FileProvider provider = (FileProvider) createInstance(classname); 315 final String[] schemas = getSchemas(providerDef); 316 if (schemas.length > 0) { 317 addProvider(schemas, provider); 318 } 319 320 // Set as default, if required 321 if (isDefault) { 322 setDefaultProvider(provider); 323 } 324 } 325 326 /** 327 * Adds a operationProvider from a operationProvider definition. 328 */ 329 private void addOperationProvider(final Element providerDef) throws FileSystemException { 330 final String classname = providerDef.getAttribute("class-name"); 331 332 // Attach only to available schemas 333 final String[] schemas = getSchemas(providerDef); 334 for (final String schema : schemas) { 335 if (hasProvider(schema)) { 336 final FileOperationProvider operationProvider = (FileOperationProvider) createInstance(classname); 337 addOperationProvider(schema, operationProvider); 338 } 339 } 340 } 341 342 /** 343 * Tests if a class is available. 344 */ 345 private boolean findClass(final String className) { 346 try { 347 loadClass(className); 348 return true; 349 } catch (final ClassNotFoundException e) { 350 return false; 351 } 352 } 353 354 /** 355 * Extracts the required classes from a provider definition. 356 */ 357 private String[] getRequiredClasses(final Element providerDef) { 358 final ArrayList<String> classes = new ArrayList<>(); 359 final NodeList deps = providerDef.getElementsByTagName("if-available"); 360 final int count = deps.getLength(); 361 for (int i = 0; i < count; i++) { 362 final Element dep = (Element) deps.item(i); 363 final String className = dep.getAttribute("class-name"); 364 if (className != null && className.length() > 0) { 365 classes.add(className); 366 } 367 } 368 return classes.toArray(new String[classes.size()]); 369 } 370 371 /** 372 * Extracts the required schemes from a provider definition. 373 */ 374 private String[] getRequiredSchemes(final Element providerDef) { 375 final ArrayList<String> schemes = new ArrayList<>(); 376 final NodeList deps = providerDef.getElementsByTagName("if-available"); 377 final int count = deps.getLength(); 378 for (int i = 0; i < count; i++) { 379 final Element dep = (Element) deps.item(i); 380 final String scheme = dep.getAttribute("scheme"); 381 if (scheme != null && scheme.length() > 0) { 382 schemes.add(scheme); 383 } 384 } 385 return schemes.toArray(new String[schemes.size()]); 386 } 387 388 /** 389 * Extracts the schema names from a provider definition. 390 */ 391 private String[] getSchemas(final Element provider) { 392 final ArrayList<String> schemas = new ArrayList<>(); 393 final NodeList schemaElements = provider.getElementsByTagName("scheme"); 394 final int count = schemaElements.getLength(); 395 for (int i = 0; i < count; i++) { 396 final Element scheme = (Element) schemaElements.item(i); 397 schemas.add(scheme.getAttribute("name")); 398 } 399 return schemas.toArray(new String[schemas.size()]); 400 } 401 402 /** 403 * Creates a provider. 404 */ 405 private Object createInstance(final String className) throws FileSystemException { 406 try { 407 final Class<?> clazz = loadClass(className); 408 return clazz.newInstance(); 409 } catch (final Exception e) { 410 throw new FileSystemException("vfs.impl/create-provider.error", className, e); 411 } 412 } 413 414 /** 415 * Load a class from different class loaders. 416 * 417 * @throws ClassNotFoundException if last {@code loadClass} failed. 418 * @see #findClassLoader() 419 */ 420 private Class<?> loadClass(final String className) throws ClassNotFoundException { 421 try { 422 return findClassLoader().loadClass(className); 423 } catch (final ClassNotFoundException e) { 424 return getClass().getClassLoader().loadClass(className); 425 } 426 } 427 428 /** 429 * Resolve resources from different class loaders. 430 * 431 * @throws IOException if {@code getResource} failed. 432 * @see #findClassLoader() 433 */ 434 private Enumeration<URL> loadResources(final String name) throws IOException { 435 Enumeration<URL> res = findClassLoader().getResources(name); 436 if (res == null || !res.hasMoreElements()) { 437 res = getClass().getClassLoader().getResources(name); 438 } 439 return res; 440 } 441}