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      /** Only provided for Serializable subclasses. */
59      AbstractFileSystem() {
60          this(null, null, null);
61      }
62  
63      private static final FileListenerer">FileListener[] EMPTY_FILE_LISTENER_ARRAY = new FileListener[0];
64  
65      private static final Log LOG = LogFactory.getLog(AbstractFileSystem.class);
66  
67      /**
68       * The "root" of the file system. This is always "/" so it isn't always the "real" root.
69       */
70      private final FileName rootName;
71  
72      /**
73       * The root URI of the file system. The base path specified as a file system option when the file system was
74       * created.
75       */
76      private final String rootURI;
77  
78      private final Collection<Capability> capabilities = new HashSet<>();
79  
80      private final FileObject parentLayer;
81  
82      /**
83       * Map from FileName to an ArrayList of listeners for that file.
84       */
85      private final Map<FileName, ArrayList<FileListener>> listenerMap = new HashMap<>();
86  
87      /**
88       * FileSystemOptions used for configuration
89       */
90      private final FileSystemOptions fileSystemOptions;
91  
92      /**
93       * How many fileObjects are handed out
94       */
95      private final AtomicLong useCount = new AtomicLong();
96  
97      private FileSystemKey cacheKey;
98  
99      /**
100      * open streams counter for this file system
101      */
102     private final AtomicInteger openStreams = new AtomicInteger(0);
103 
104     protected AbstractFileSystem(final FileName rootFileName, final FileObject parentLayer,
105         final FileSystemOptions fileSystemOptions) {
106         this.parentLayer = parentLayer;
107         this.rootName = rootFileName;
108         this.fileSystemOptions = fileSystemOptions;
109         final FileSystemConfigBuilder builder = DefaultFileSystemConfigBuilder.getInstance();
110         String uri = builder.getRootURI(fileSystemOptions);
111         if (uri == null) {
112             uri = rootFileName != null ? rootFileName.getURI() : null;
113         }
114         this.rootURI = uri;
115     }
116 
117     /**
118      * Initializes this component.
119      *
120      * @throws FileSystemException if an error occurs.
121      */
122     @Override
123     public void init() throws FileSystemException {
124         addCapabilities(capabilities);
125     }
126 
127     /**
128      * Closes this component.
129      */
130     @Override
131     public void close() {
132         closeCommunicationLink();
133     }
134 
135     /**
136      * Closes the underlying link used to access the files.
137      */
138     public void closeCommunicationLink() {
139         synchronized (this) {
140             doCloseCommunicationLink();
141         }
142     }
143 
144     /**
145      * Closes the underlying link used to access the files.
146      */
147     protected void doCloseCommunicationLink() {
148         // default is noop.
149     }
150 
151     /**
152      * Creates a file object.
153      * <p>
154      * This method is called only if the requested file is not cached.
155      * </p>
156      *
157      * @param name name referencing the new file.
158      * @return new created FileObject.
159      * @throws Exception might throw an Exception, which is then wrapped in FileSystemException.
160      */
161     protected abstract FileObject createFile(final AbstractFileName name) throws Exception;
162 
163     /**
164      * Adds the capabilities of this file system.
165      *
166      * @param caps collections of Capabilities, can be immutable.
167      */
168     protected abstract void addCapabilities(Collection<Capability> caps);
169 
170     /**
171      * Returns the name of the root of this file system.
172      *
173      * @return the root FileName.
174      */
175     @Override
176     public FileName getRootName() {
177         return rootName;
178     }
179 
180     /**
181      * Returns the root URI specified for this file System.
182      *
183      * @return The root URI used in this file system.
184      * @since 2.0
185      */
186     @Override
187     public String getRootURI() {
188         return rootURI;
189     }
190 
191     /**
192      * Adds a file object to the cache.
193      *
194      * @param file the file to add.
195      */
196     protected void putFileToCache(final FileObject file) {
197         getFilesCache().putFile(file);
198     }
199 
200     private FilesCache getFilesCache() {
201         final FilesCache filesCache = getContext().getFileSystemManager().getFilesCache();
202         if (filesCache == null) {
203             throw new RuntimeException(Messages.getString("vfs.provider/files-cache-missing.error"));
204         }
205 
206         return filesCache;
207     }
208 
209     /**
210      * Returns a cached file.
211      *
212      * @param name name to search for.
213      * @return file object or null if not found.
214      */
215     protected FileObject getFileFromCache(final FileName name) {
216         return getFilesCache().getFile(this, name);
217     }
218 
219     /**
220      * Removes a cached file.
221      *
222      * @param name The file name to remove.
223      */
224     protected void removeFileFromCache(final FileName name) {
225         getFilesCache().removeFile(this, name);
226     }
227 
228     /**
229      * Determines if this file system has a particular capability.
230      *
231      * @param capability the Capability to check for.
232      * @return true if the FileSystem has the Capability, false otherwise.
233      */
234     @Override
235     public boolean hasCapability(final Capability capability) {
236         return capabilities.contains(capability);
237     }
238 
239     /**
240      * Retrieves the attribute with the specified name. The default implementation simply throws an exception.
241      *
242      * @param attrName The name of the attribute.
243      * @return the Object associated with the attribute or null if no object is.
244      * @throws FileSystemException if an error occurs.
245      */
246     @Override
247     public Object getAttribute(final String attrName) throws FileSystemException {
248         throw new FileSystemException("vfs.provider/get-attribute-not-supported.error");
249     }
250 
251     /**
252      * Sets the attribute with the specified name. The default implementation simply throws an exception.
253      *
254      * @param attrName the attribute name.
255      * @param value The object to associate with the attribute.
256      * @throws FileSystemException if an error occurs.
257      */
258     @Override
259     public void setAttribute(final String attrName, final Object value) throws FileSystemException {
260         throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
261     }
262 
263     /**
264      * Returns the parent layer if this is a layered file system.
265      *
266      * @return The FileObject for the parent layer.
267      * @throws FileSystemException if an error occurs.
268      */
269     @Override
270     public FileObject getParentLayer() throws FileSystemException {
271         return parentLayer;
272     }
273 
274     /**
275      * Returns the root file of this file system.
276      *
277      * @return The root FileObject of the FileSystem
278      * @throws FileSystemException if an error occurs.
279      */
280     @Override
281     public FileObject getRoot() throws FileSystemException {
282         return resolveFile(rootName);
283     }
284 
285     /**
286      * Finds a file in this file system.
287      *
288      * @param nameStr The name of the file to resolve.
289      * @return The located FileObject or null if none could be located.
290      * @throws FileSystemException if an error occurs.
291      */
292     @Override
293     public FileObject resolveFile(final String nameStr) throws FileSystemException {
294         // Resolve the name, and create the file
295         return resolveFile(getFileSystemManager().resolveName(rootName, nameStr));
296     }
297 
298     /**
299      * Finds a file in this file system.
300      *
301      * @param name The name of the file to locate.
302      * @return The located FileObject or null if none could be located.
303      * @throws FileSystemException if an error occurs.
304      */
305     @Override
306     public FileObject resolveFile(final FileName name) throws FileSystemException {
307         return resolveFile(name, true);
308     }
309 
310     private synchronized FileObject resolveFile(final FileName name, final boolean useCache)
311             throws FileSystemException {
312         if (!rootName.getRootURI().equals(name.getRootURI())) {
313             throw new FileSystemException("vfs.provider/mismatched-fs-for-name.error", name, rootName,
314                     name.getRootURI());
315         }
316 
317         // imario@apache.org ==> use getFileFromCache
318         FileObject file;
319         if (useCache) {
320             file = getFileFromCache(name);
321         } else {
322             file = null;
323         }
324 
325         if (file == null) {
326             try {
327                 file = createFile((AbstractFileName) name);
328             } catch (final Exception e) {
329                 throw new FileSystemException("vfs.provider/resolve-file.error", name, e);
330             }
331 
332             file = decorateFileObject(file);
333 
334             // imario@apache.org ==> use putFileToCache
335             if (useCache) {
336                 putFileToCache(file);
337             }
338         }
339 
340         /*
341           resync the file information if requested
342          */
343         if (getFileSystemManager().getCacheStrategy().equals(CacheStrategy.ON_RESOLVE)) {
344             file.refresh();
345         }
346         return file;
347     }
348 
349     protected FileObject./org/apache/commons/vfs2/FileObject.html#FileObject">FileObject decorateFileObject(FileObject file) throws FileSystemException {
350         if (getFileSystemManager().getCacheStrategy().equals(CacheStrategy.ON_CALL)) {
351             file = new OnCallRefreshFileObject(file);
352         }
353 
354         if (getFileSystemManager().getFileObjectDecoratorConst() != null) {
355             try {
356                 file = (FileObject) getFileSystemManager().getFileObjectDecoratorConst().newInstance(file);
357             } catch (final InstantiationException | IllegalAccessException | 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 (!FileObjectUtils.exists(file)) {
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      * Returns the FileSystemOptions used to instantiate this file system.
389      *
390      * @return the FileSystemOptions.
391      */
392     @Override
393     public FileSystemOptions getFileSystemOptions() {
394         return fileSystemOptions;
395     }
396 
397     /**
398      * Returns the FileSystemManager used to instantiate this file system.
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             final ArrayList<FileListener> listeners = listenerMap.computeIfAbsent(file.getName(), k -> new ArrayList<>());
462             listeners.add(listener);
463         }
464     }
465 
466     /**
467      * Removes a listener from a file in this file system.
468      *
469      * @param file The FileObject to be monitored.
470      * @param listener The FileListener
471      */
472     @Override
473     public void removeListener(final FileObject file, final FileListener listener) {
474         synchronized (listenerMap) {
475             final ArrayList<?> listeners = listenerMap.get(file.getName());
476             if (listeners != null) {
477                 listeners.remove(listener);
478                 if (listeners.isEmpty()) {
479                     listenerMap.remove(file.getName());
480                 }
481             }
482         }
483     }
484 
485     /**
486      * Fires a file create event.
487      *
488      * @param file The FileObject that was created.
489      */
490     public void fireFileCreated(final FileObject file) {
491         fireEvent(new CreateEvent(file));
492     }
493 
494     /**
495      * Fires a file delete event.
496      *
497      * @param file The FileObject that was deleted.
498      */
499     public void fireFileDeleted(final FileObject file) {
500         fireEvent(new DeleteEvent(file));
501     }
502 
503     /**
504      * Fires a file changed event.
505      * <p>
506      * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
507      * </p>
508      *
509      * @param file The FileObject that changed.
510      */
511     public void fireFileChanged(final FileObject file) {
512         fireEvent(new ChangedEvent(file));
513     }
514 
515     /**
516      * Returns true if no file is using this FileSystem.
517      *
518      * @return true if no file is using this FileSystem.
519      */
520     public boolean isReleaseable() {
521         return useCount.get() < 1;
522     }
523 
524     void freeResources() {
525         // default is noop.
526     }
527 
528     /**
529      * Fires an event.
530      */
531     private void fireEvent(final AbstractFileChangeEvent event) {
532         FileListener[] fileListeners = null;
533         final FileObject fileObject = event.getFileObject();
534 
535         synchronized (listenerMap) {
536             final ArrayList<?> listeners = listenerMap.get(fileObject.getName());
537             if (listeners != null) {
538                 fileListeners = listeners.toArray(EMPTY_FILE_LISTENER_ARRAY);
539             }
540         }
541 
542         if (fileListeners != null) {
543             for (final FileListener fileListener : fileListeners) {
544                 try {
545                     event.notify(fileListener);
546                 } catch (final Exception e) {
547                     final String message = Messages.getString("vfs.provider/notify-listener.warn", fileObject);
548                     // getLogger().warn(message, e);
549                     VfsLog.warn(getLogger(), LOG, message, e);
550                 }
551             }
552         }
553     }
554 
555     void fileObjectHanded(final FileObject fileObject) {
556         useCount.incrementAndGet();
557     }
558 
559     void fileObjectDestroyed(final FileObject fileObject) {
560         useCount.decrementAndGet();
561     }
562 
563     void setCacheKey(final FileSystemKey cacheKey) {
564         this.cacheKey = cacheKey;
565     }
566 
567     FileSystemKey getCacheKey() {
568         return this.cacheKey;
569     }
570 
571     void streamOpened() {
572         openStreams.incrementAndGet();
573     }
574 
575     void streamClosed() {
576         if (openStreams.decrementAndGet() == 0) {
577             notifyAllStreamsClosed();
578         }
579     }
580 
581     /**
582      * Called after all file-objects closed their streams.
583      */
584     protected void notifyAllStreamsClosed() {
585         // default is noop.
586     }
587 
588     /**
589      * Checks if this file system has open streams.
590      *
591      * @return true if the FileSystem has open streams.
592      */
593     public boolean isOpen() {
594         return openStreams.get() > 0;
595     }
596 }