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