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