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    *      http://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.vfs2.provider;
18  
19  import java.io.File;
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.concurrent.atomic.AtomicInteger;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.commons.vfs2.CacheStrategy;
33  import org.apache.commons.vfs2.Capability;
34  import org.apache.commons.vfs2.FileListener;
35  import org.apache.commons.vfs2.FileName;
36  import org.apache.commons.vfs2.FileObject;
37  import org.apache.commons.vfs2.FileSelector;
38  import org.apache.commons.vfs2.FileSystem;
39  import org.apache.commons.vfs2.FileSystemConfigBuilder;
40  import org.apache.commons.vfs2.FileSystemException;
41  import org.apache.commons.vfs2.FileSystemManager;
42  import org.apache.commons.vfs2.FileSystemOptions;
43  import org.apache.commons.vfs2.FilesCache;
44  import org.apache.commons.vfs2.VfsLog;
45  import org.apache.commons.vfs2.cache.OnCallRefreshFileObject;
46  import org.apache.commons.vfs2.events.AbstractFileChangeEvent;
47  import org.apache.commons.vfs2.events.ChangedEvent;
48  import org.apache.commons.vfs2.events.CreateEvent;
49  import org.apache.commons.vfs2.events.DeleteEvent;
50  import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
51  import org.apache.commons.vfs2.util.FileObjectUtils;
52  import org.apache.commons.vfs2.util.Messages;
53  
54  /**
55   * A partial {@link org.apache.commons.vfs2.FileSystem} implementation.
56   */
57  public abstract class AbstractFileSystem extends AbstractVfsComponent implements FileSystem {
58  
59      private static final FileListener[] EMPTY_FILE_LISTENER_ARRAY = {};
60  
61      private static final Log LOG = LogFactory.getLog(AbstractFileSystem.class);
62  
63      /**
64       * The "root" of the file system. This is always "/" so it isn't always the "real" root.
65       */
66      private final FileName rootName;
67  
68      /**
69       * The root URI of the file system. The base path specified as a file system option when the file system was
70       * created.
71       */
72      private final String rootURI;
73  
74      private final Collection<Capability> capabilities = new HashSet<>();
75  
76      private final FileObject parentLayer;
77  
78      /**
79       * Map from FileName to an ArrayList of listeners for that file.
80       */
81      private final Map<FileName, ArrayList<FileListener>> listenerMap = new HashMap<>();
82  
83      /**
84       * FileSystemOptions used for configuration
85       */
86      private final FileSystemOptions fileSystemOptions;
87  
88      /**
89       * How many fileObjects are handed out
90       */
91      private final AtomicLong useCount = new AtomicLong();
92  
93      private FileSystemKey cacheKey;
94  
95      /**
96       * open streams counter for this file system
97       */
98      private final AtomicInteger openStreams = new AtomicInteger();
99  
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 }