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