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.provider; 018 019import java.io.File; 020import java.lang.reflect.InvocationTargetException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Map; 026import java.util.concurrent.atomic.AtomicInteger; 027import java.util.concurrent.atomic.AtomicLong; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.apache.commons.vfs2.CacheStrategy; 032import org.apache.commons.vfs2.Capability; 033import org.apache.commons.vfs2.FileListener; 034import org.apache.commons.vfs2.FileName; 035import org.apache.commons.vfs2.FileObject; 036import org.apache.commons.vfs2.FileSelector; 037import org.apache.commons.vfs2.FileSystem; 038import org.apache.commons.vfs2.FileSystemConfigBuilder; 039import org.apache.commons.vfs2.FileSystemException; 040import org.apache.commons.vfs2.FileSystemManager; 041import org.apache.commons.vfs2.FileSystemOptions; 042import org.apache.commons.vfs2.FilesCache; 043import org.apache.commons.vfs2.VfsLog; 044import org.apache.commons.vfs2.cache.OnCallRefreshFileObject; 045import org.apache.commons.vfs2.events.AbstractFileChangeEvent; 046import org.apache.commons.vfs2.events.ChangedEvent; 047import org.apache.commons.vfs2.events.CreateEvent; 048import org.apache.commons.vfs2.events.DeleteEvent; 049import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder; 050import org.apache.commons.vfs2.util.Messages; 051 052/** 053 * A partial {@link org.apache.commons.vfs2.FileSystem} implementation. 054 */ 055public abstract class AbstractFileSystem extends AbstractVfsComponent implements FileSystem { 056 private static final Log LOG = LogFactory.getLog(AbstractFileSystem.class); 057 058 /** 059 * The "root" of the file system. This is always "/" so it isn't always the "real" root. 060 */ 061 private final FileName rootName; 062 063 /** 064 * The root URI of the file system. The base path specified as a file system option when the file system was 065 * created. 066 */ 067 private final String rootURI; 068 069 private final Collection<Capability> caps = new HashSet<>(); 070 071 private FileObject parentLayer; 072 073 /** 074 * Map from FileName to an ArrayList of listeners for that file. 075 */ 076 private final Map<FileName, ArrayList<FileListener>> listenerMap = new HashMap<>(); 077 078 /** 079 * FileSystemOptions used for configuration 080 */ 081 private final FileSystemOptions fileSystemOptions; 082 083 /** 084 * How many fileObjects are handed out 085 */ 086 private final AtomicLong useCount = new AtomicLong(0); 087 088 private FileSystemKey cacheKey; 089 090 /** 091 * open streams counter for this filesystem 092 */ 093 private final AtomicInteger openStreams = new AtomicInteger(0); 094 095 protected AbstractFileSystem(final FileName rootName, final FileObject parentLayer, 096 final FileSystemOptions fileSystemOptions) { 097 this.parentLayer = parentLayer; 098 this.rootName = rootName; 099 this.fileSystemOptions = fileSystemOptions; 100 final FileSystemConfigBuilder builder = DefaultFileSystemConfigBuilder.getInstance(); 101 String uri = builder.getRootURI(fileSystemOptions); 102 if (uri == null) { 103 uri = rootName.getURI(); 104 } 105 this.rootURI = uri; 106 } 107 108 /** 109 * Initializes this component. 110 * 111 * @throws FileSystemException if an error occurs. 112 */ 113 @Override 114 public void init() throws FileSystemException { 115 addCapabilities(caps); 116 } 117 118 /** 119 * Closes this component. 120 */ 121 @Override 122 public void close() { 123 closeCommunicationLink(); 124 125 parentLayer = null; 126 } 127 128 /** 129 * Close the underlying link used to access the files. 130 */ 131 public void closeCommunicationLink() { 132 synchronized (this) { 133 doCloseCommunicationLink(); 134 } 135 } 136 137 /** 138 * Close the underlying link used to access the files 139 */ 140 protected void doCloseCommunicationLink() { 141 } 142 143 /** 144 * Creates a file object. 145 * <p> 146 * This method is called only if the requested file is not cached. 147 * 148 * @param name name referencing the new file. 149 * @return new created FileObject. 150 * @throws Exception might throw an Exception, which is then wrapped in FileSystemException. 151 */ 152 protected abstract FileObject createFile(final AbstractFileName name) throws Exception; 153 154 /** 155 * Adds the capabilities of this file system. 156 * 157 * @param caps collections of Capabilities, can be immutable. 158 */ 159 protected abstract void addCapabilities(Collection<Capability> caps); 160 161 /** 162 * Returns the name of the root of this file system. 163 * 164 * @return the root FileName. 165 */ 166 @Override 167 public FileName getRootName() { 168 return rootName; 169 } 170 171 /** 172 * Returns the root URI specified for this file System. 173 * 174 * @return The root URI used in this file system. 175 * @since 2.0 176 */ 177 @Override 178 public String getRootURI() { 179 return rootURI; 180 } 181 182 /** 183 * Adds a file object to the cache. 184 * 185 * @param file the file to add. 186 */ 187 protected void putFileToCache(final FileObject file) { 188 getCache().putFile(file); 189 } 190 191 private FilesCache getCache() { 192 FilesCache files; 193 files = getContext().getFileSystemManager().getFilesCache(); 194 if (files == null) { 195 throw new RuntimeException(Messages.getString("vfs.provider/files-cache-missing.error")); 196 } 197 198 return files; 199 } 200 201 /** 202 * Returns a cached file. 203 * 204 * @param name name to search for. 205 * @return file object or null if not found. 206 */ 207 protected FileObject getFileFromCache(final FileName name) { 208 return getCache().getFile(this, name); 209 } 210 211 /** 212 * Remove a cached file. 213 * 214 * @param name The file name to remove. 215 */ 216 protected void removeFileFromCache(final FileName name) { 217 getCache().removeFile(this, name); 218 } 219 220 /** 221 * Determines if this file system has a particular capability. 222 * 223 * @param capability the Capability to check for. 224 * @return true if the FileSystem has the Capability, false otherwise. 225 */ 226 @Override 227 public boolean hasCapability(final Capability capability) { 228 return caps.contains(capability); 229 } 230 231 /** 232 * Retrieves the attribute with the specified name. The default implementation simply throws an exception. 233 * 234 * @param attrName The name of the attribute. 235 * @return the Object associated with the attribute or null if no object is. 236 * @throws FileSystemException if an error occurs. 237 */ 238 @Override 239 public Object getAttribute(final String attrName) throws FileSystemException { 240 throw new FileSystemException("vfs.provider/get-attribute-not-supported.error"); 241 } 242 243 /** 244 * Sets the attribute with the specified name. The default implementation simply throws an exception. 245 * 246 * @param attrName the attribute name. 247 * @param value The object to associate with the attribute. 248 * @throws FileSystemException if an error occurs. 249 */ 250 @Override 251 public void setAttribute(final String attrName, final Object value) throws FileSystemException { 252 throw new FileSystemException("vfs.provider/set-attribute-not-supported.error"); 253 } 254 255 /** 256 * Returns the parent layer if this is a layered file system. 257 * 258 * @return The FileObject for the parent layer. 259 * @throws FileSystemException if an error occurs. 260 */ 261 @Override 262 public FileObject getParentLayer() throws FileSystemException { 263 return parentLayer; 264 } 265 266 /** 267 * Returns the root file of this file system. 268 * 269 * @return The root FileObject of the FileSystem 270 * @throws FileSystemException if an error occurs. 271 */ 272 @Override 273 public FileObject getRoot() throws FileSystemException { 274 return resolveFile(rootName); 275 } 276 277 /** 278 * Finds a file in this file system. 279 * 280 * @param nameStr The name of the file to resolve. 281 * @return The located FileObject or null if none could be located. 282 * @throws FileSystemException if an error occurs. 283 */ 284 @Override 285 public FileObject resolveFile(final String nameStr) throws FileSystemException { 286 // Resolve the name, and create the file 287 final FileName name = getFileSystemManager().resolveName(rootName, nameStr); 288 return resolveFile(name); 289 } 290 291 /** 292 * Finds a file in this file system. 293 * 294 * @param name The name of the file to locate. 295 * @return The located FileObject or null if none could be located. 296 * @throws FileSystemException if an error occurs. 297 */ 298 @Override 299 public FileObject resolveFile(final FileName name) throws FileSystemException { 300 return resolveFile(name, true); 301 } 302 303 private synchronized FileObject resolveFile(final FileName name, final boolean useCache) 304 throws FileSystemException { 305 if (!rootName.getRootURI().equals(name.getRootURI())) { 306 throw new FileSystemException("vfs.provider/mismatched-fs-for-name.error", name, rootName, 307 name.getRootURI()); 308 } 309 310 // imario@apache.org ==> use getFileFromCache 311 FileObject file; 312 if (useCache) { 313 file = getFileFromCache(name); 314 } else { 315 file = null; 316 } 317 318 if (file == null) { 319 try { 320 file = createFile((AbstractFileName) name); 321 } catch (final Exception e) { 322 throw new FileSystemException("vfs.provider/resolve-file.error", name, e); 323 } 324 325 file = decorateFileObject(file); 326 327 // imario@apache.org ==> use putFileToCache 328 if (useCache) { 329 putFileToCache(file); 330 } 331 } 332 333 /** 334 * resync the file information if requested 335 */ 336 if (getFileSystemManager().getCacheStrategy().equals(CacheStrategy.ON_RESOLVE)) { 337 file.refresh(); 338 } 339 return file; 340 } 341 342 protected FileObject decorateFileObject(FileObject file) throws FileSystemException { 343 if (getFileSystemManager().getCacheStrategy().equals(CacheStrategy.ON_CALL)) { 344 file = new OnCallRefreshFileObject(file); 345 } 346 347 if (getFileSystemManager().getFileObjectDecoratorConst() != null) { 348 try { 349 file = (FileObject) getFileSystemManager().getFileObjectDecoratorConst() 350 .newInstance(new Object[] { file }); 351 } catch (final InstantiationException e) { 352 throw new FileSystemException("vfs.impl/invalid-decorator.error", 353 getFileSystemManager().getFileObjectDecorator().getName(), e); 354 } catch (final IllegalAccessException e) { 355 throw new FileSystemException("vfs.impl/invalid-decorator.error", 356 getFileSystemManager().getFileObjectDecorator().getName(), e); 357 } catch (final InvocationTargetException e) { 358 throw new FileSystemException("vfs.impl/invalid-decorator.error", 359 getFileSystemManager().getFileObjectDecorator().getName(), e); 360 } 361 } 362 363 return file; 364 } 365 366 /** 367 * Creates a temporary local copy of a file and its descendants. 368 * 369 * @param file The FileObject to replicate. 370 * @param selector The FileSelector. 371 * @return The replicated File. 372 * @throws FileSystemException if an error occurs. 373 */ 374 @Override 375 public File replicateFile(final FileObject file, final FileSelector selector) throws FileSystemException { 376 if (!file.exists()) { 377 throw new FileSystemException("vfs.provider/replicate-missing-file.error", file.getName()); 378 } 379 380 try { 381 return doReplicateFile(file, selector); 382 } catch (final Exception e) { 383 throw new FileSystemException("vfs.provider/replicate-file.error", file.getName(), e); 384 } 385 } 386 387 /** 388 * Return the FileSystemOptions used to instantiate this filesystem. 389 * 390 * @return the FileSystemOptions. 391 */ 392 @Override 393 public FileSystemOptions getFileSystemOptions() { 394 return fileSystemOptions; 395 } 396 397 /** 398 * Return the FileSystemManager used to instantiate this filesystem. 399 * 400 * @return the FileSystemManager. 401 */ 402 @Override 403 public FileSystemManager getFileSystemManager() { 404 return getContext().getFileSystemManager(); 405 } 406 407 /** 408 * Returns the accuracy of the last modification time. 409 * 410 * @return ms 0 perfectly accurate, {@literal >0} might be off by this value e.g. sftp 1000ms 411 */ 412 @Override 413 public double getLastModTimeAccuracy() { 414 return 0; 415 } 416 417 /** 418 * Creates a temporary local copy of a file and its descendants. 419 * 420 * @param file the start of the tree. 421 * @param selector selection what to do with childs. 422 * @return replicated root file. 423 * @throws Exception any Exception is wrapped as FileSystemException. 424 */ 425 protected File doReplicateFile(final FileObject file, final FileSelector selector) throws Exception { 426 return getContext().getReplicator().replicateFile(file, selector); 427 } 428 429 /** 430 * Adds a junction to this file system. 431 * 432 * @param junctionPoint The junction point. 433 * @param targetFile The target to add. 434 * @throws FileSystemException if an error occurs. 435 */ 436 @Override 437 public void addJunction(final String junctionPoint, final FileObject targetFile) throws FileSystemException { 438 throw new FileSystemException("vfs.provider/junctions-not-supported.error", rootName); 439 } 440 441 /** 442 * Removes a junction from this file system. 443 * 444 * @param junctionPoint The junction point. 445 * @throws FileSystemException if an error occurs 446 */ 447 @Override 448 public void removeJunction(final String junctionPoint) throws FileSystemException { 449 throw new FileSystemException("vfs.provider/junctions-not-supported.error", rootName); 450 } 451 452 /** 453 * Adds a listener on a file in this file system. 454 * 455 * @param file The FileObject to be monitored. 456 * @param listener The FileListener 457 */ 458 @Override 459 public void addListener(final FileObject file, final FileListener listener) { 460 synchronized (listenerMap) { 461 ArrayList<FileListener> listeners = listenerMap.get(file.getName()); 462 if (listeners == null) { 463 listeners = new ArrayList<>(); 464 listenerMap.put(file.getName(), listeners); 465 } 466 listeners.add(listener); 467 } 468 } 469 470 /** 471 * Removes a listener from a file in this file system. 472 * 473 * @param file The FileObject to be monitored. 474 * @param listener The FileListener 475 */ 476 @Override 477 public void removeListener(final FileObject file, final FileListener listener) { 478 synchronized (listenerMap) { 479 final ArrayList<?> listeners = listenerMap.get(file.getName()); 480 if (listeners != null) { 481 listeners.remove(listener); 482 if (listeners.isEmpty()) { 483 listenerMap.remove(file.getName()); 484 } 485 } 486 } 487 } 488 489 /** 490 * Fires a file create event. 491 * 492 * @param file The FileObject that was created. 493 */ 494 public void fireFileCreated(final FileObject file) { 495 fireEvent(new CreateEvent(file)); 496 } 497 498 /** 499 * Fires a file delete event. 500 * 501 * @param file The FileObject that was deleted. 502 */ 503 public void fireFileDeleted(final FileObject file) { 504 fireEvent(new DeleteEvent(file)); 505 } 506 507 /** 508 * Fires a file changed event. 509 * <p> 510 * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. 511 * 512 * @param file The FileObject that changed. 513 */ 514 public void fireFileChanged(final FileObject file) { 515 fireEvent(new ChangedEvent(file)); 516 } 517 518 /** 519 * Returns true if no file is using this filesystem. 520 * 521 * @return true if no file is using this FileSystem. 522 */ 523 public boolean isReleaseable() { 524 return useCount.get() < 1; 525 } 526 527 void freeResources() { 528 } 529 530 /** 531 * Fires an event. 532 */ 533 private void fireEvent(final AbstractFileChangeEvent event) { 534 FileListener[] fileListeners = null; 535 final FileObject file = event.getFile(); 536 537 synchronized (listenerMap) { 538 final ArrayList<?> listeners = listenerMap.get(file.getName()); 539 if (listeners != null) { 540 fileListeners = listeners.toArray(new FileListener[listeners.size()]); 541 } 542 } 543 544 if (fileListeners != null) { 545 for (final FileListener fileListener : fileListeners) { 546 try { 547 event.notify(fileListener); 548 } catch (final Exception e) { 549 final String message = Messages.getString("vfs.provider/notify-listener.warn", file); 550 // getLogger().warn(message, e); 551 VfsLog.warn(getLogger(), LOG, message, e); 552 } 553 } 554 } 555 } 556 557 void fileObjectHanded(final FileObject fileObject) { 558 useCount.incrementAndGet(); 559 } 560 561 void fileObjectDestroyed(final FileObject fileObject) { 562 useCount.decrementAndGet(); 563 } 564 565 void setCacheKey(final FileSystemKey cacheKey) { 566 this.cacheKey = cacheKey; 567 } 568 569 FileSystemKey getCacheKey() { 570 return this.cacheKey; 571 } 572 573 void streamOpened() { 574 openStreams.incrementAndGet(); 575 } 576 577 void streamClosed() { 578 if (openStreams.decrementAndGet() == 0) { 579 notifyAllStreamsClosed(); 580 } 581 } 582 583 /** 584 * will be called after all file-objects closed their streams. 585 */ 586 protected void notifyAllStreamsClosed() { 587 } 588 589 /** 590 * check if this filesystem has open streams. 591 * 592 * @return true if the FileSystem has open streams. 593 */ 594 public boolean isOpen() { 595 return openStreams.get() > 0; 596 } 597}