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