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.util.Map;
020import java.util.TreeMap;
021import java.util.stream.Stream;
022
023import org.apache.commons.vfs2.FileName;
024import org.apache.commons.vfs2.FileObject;
025import org.apache.commons.vfs2.FileSystem;
026import org.apache.commons.vfs2.FileSystemConfigBuilder;
027import org.apache.commons.vfs2.FileSystemException;
028import org.apache.commons.vfs2.FileSystemOptions;
029import org.apache.commons.vfs2.provider.local.GenericFileNameParser;
030
031/**
032 * A partial {@link FileProvider} implementation. Takes care of managing the file systems created by the provider.
033 */
034public abstract class AbstractFileProvider extends AbstractVfsContainer implements FileProvider {
035
036    private static final AbstractFileSystem[] EMPTY_ABSTRACT_FILE_SYSTEMS = {};
037
038    /**
039     * The cached file systems.
040     * <p>
041     * This is a mapping from {@link FileSystemKey} (root URI and options) to {@link FileSystem}.
042     * </p>
043     */
044    private final Map<FileSystemKey, FileSystem> fileSystemMap = new TreeMap<>(); // @GuardedBy("self")
045
046    private FileNameParser fileNameParser;
047
048    /**
049     * Constructs a new instance for subclasses.
050     */
051    public AbstractFileProvider() {
052        fileNameParser = GenericFileNameParser.getInstance();
053    }
054
055    /**
056     * Adds a file system to those cached by this provider.
057     * <p>
058     * The file system may implement {@link VfsComponent}, in which case it is initialized.
059     * </p>
060     *
061     * @param key The root file of the file system, part of the cache key.
062     * @param fs the file system to add.
063     * @throws FileSystemException if any error occurs.
064     */
065    protected void addFileSystem(final Comparable<?> key, final FileSystem fs) throws FileSystemException {
066        // Add to the container and initialize
067        addComponent(fs);
068        final FileSystemKey treeKey = new FileSystemKey(key, fs.getFileSystemOptions());
069        ((AbstractFileSystem) fs).setCacheKey(treeKey);
070        synchronized (fileSystemMap) {
071            fileSystemMap.put(treeKey, fs);
072        }
073    }
074
075    /**
076     * Closes the file systems created by this provider.
077     */
078    @Override
079    public void close() {
080        synchronized (fileSystemMap) {
081            fileSystemMap.clear();
082        }
083        super.close();
084    }
085
086    /**
087     * Closes the FileSystem.
088     *
089     * @param fileSystem The FileSystem to close.
090     */
091    public void closeFileSystem(final FileSystem fileSystem) {
092        final AbstractFileSystem fs = (AbstractFileSystem) fileSystem;
093
094        final FileSystemKey key = fs.getCacheKey();
095        if (key != null) {
096            synchronized (fileSystemMap) {
097                fileSystemMap.remove(key);
098            }
099        }
100
101        removeComponent(fs);
102        fs.close();
103    }
104
105    /**
106     * Creates a layered file system. This method throws a 'not supported' exception.
107     *
108     * @param scheme The protocol to use to access the file.
109     * @param file a FileObject.
110     * @param fileSystemOptions Options to the file system.
111     * @return A FileObject associated with the new FileSystem.
112     * @throws FileSystemException if an error occurs.
113     */
114    @Override
115    public FileObject createFileSystem(final String scheme, final FileObject file,
116        final FileSystemOptions fileSystemOptions) throws FileSystemException {
117        // Can't create a layered file system
118        throw new FileSystemException("vfs.provider/not-layered-fs.error", scheme);
119    }
120
121    /**
122     * Locates a cached file system.
123     *
124     * @param key The root file of the file system, part of the cache key.
125     * @param fileSystemOptions file system options the file system instance must have, may be null.
126     * @return The file system instance, or null if it is not cached.
127     */
128    protected FileSystem findFileSystem(final Comparable<?> key, final FileSystemOptions fileSystemOptions) {
129        synchronized (fileSystemMap) {
130            return fileSystemMap.get(new FileSystemKey(key, fileSystemOptions));
131        }
132    }
133
134    /**
135     * Frees unused resources.
136     */
137    public void freeUnusedResources() {
138        final AbstractFileSystem[] abstractFileSystems;
139        synchronized (fileSystemMap) {
140            // create snapshot under lock
141            abstractFileSystems = fileSystemMap.values().toArray(EMPTY_ABSTRACT_FILE_SYSTEMS);
142        }
143
144        // process snapshot outside lock
145        Stream.of(abstractFileSystems).filter(AbstractFileSystem::isReleaseable)
146                                      .forEach(AbstractFileSystem::closeCommunicationLink);
147    }
148
149    /**
150     * Gets the FileSystemConfigBuilder.
151     *
152     * @return the FileSystemConfigBuilder.
153     */
154    @Override
155    public FileSystemConfigBuilder getConfigBuilder() {
156        return null;
157    }
158
159    /**
160     * Gets the file name parser.
161     *
162     * @return the file name parser.
163     */
164    protected FileNameParser getFileNameParser() {
165        return fileNameParser;
166    }
167
168    /**
169     * Parses an absolute URI.
170     *
171     * @param base The base file - if null the {@code uri} needs to be absolute
172     * @param uri The URI to parse.
173     * @return The FileName.
174     * @throws FileSystemException if an error occurs.
175     */
176    @Override
177    public FileName parseUri(final FileName base, final String uri) throws FileSystemException {
178        if (getFileNameParser() != null) {
179            return getFileNameParser().parseUri(getContext(), base, uri);
180        }
181        throw new FileSystemException("vfs.provider/filename-parser-missing.error");
182    }
183
184    /**
185     * Sets the file name parser.
186     *
187     * @param parser a file name parser.
188     */
189    protected void setFileNameParser(final FileNameParser parser) {
190        this.fileNameParser = parser;
191    }
192}