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.impl;
018
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.commons.vfs2.Capability;
024import org.apache.commons.vfs2.FileName;
025import org.apache.commons.vfs2.FileObject;
026import org.apache.commons.vfs2.FileSystemException;
027import org.apache.commons.vfs2.FileSystemOptions;
028import org.apache.commons.vfs2.FileType;
029import org.apache.commons.vfs2.NameScope;
030import org.apache.commons.vfs2.provider.AbstractFileName;
031import org.apache.commons.vfs2.provider.AbstractFileSystem;
032import org.apache.commons.vfs2.provider.DelegateFileObject;
033
034/**
035 * A logical file system, made up of set of junctions, or links, to files from other file systems.
036 * <p>
037 * TODO - Handle nested junctions.
038 * </p>
039 */
040public class VirtualFileSystem extends AbstractFileSystem {
041
042    private final Map<FileName, FileObject> junctions = new HashMap<>();
043
044    /**
045     * Constructs a new instance.
046     *
047     * @param rootFileName The root file name of this file system.
048     * @param fileSystemOptions Options to build this file system.
049     */
050    public VirtualFileSystem(final AbstractFileName rootFileName, final FileSystemOptions fileSystemOptions) {
051        super(rootFileName, null, fileSystemOptions);
052    }
053
054    /**
055     * Adds the capabilities of this file system.
056     */
057    @Override
058    protected void addCapabilities(final Collection<Capability> caps) {
059        // TODO - this isn't really true
060        caps.add(Capability.ATTRIBUTES);
061        caps.add(Capability.CREATE);
062        caps.add(Capability.DELETE);
063        caps.add(Capability.GET_TYPE);
064        caps.add(Capability.JUNCTIONS);
065        caps.add(Capability.GET_LAST_MODIFIED);
066        caps.add(Capability.SET_LAST_MODIFIED_FILE);
067        caps.add(Capability.SET_LAST_MODIFIED_FOLDER);
068        caps.add(Capability.LIST_CHILDREN);
069        caps.add(Capability.READ_CONTENT);
070        caps.add(Capability.SIGNING);
071        caps.add(Capability.WRITE_CONTENT);
072        caps.add(Capability.APPEND_CONTENT);
073    }
074
075    /**
076     * Adds a junction to this file system.
077     *
078     * @param junctionPoint The location of the junction.
079     * @param targetFile The target file to base the junction on.
080     * @throws FileSystemException if an error occurs.
081     */
082    @Override
083    public void addJunction(final String junctionPoint, final FileObject targetFile) throws FileSystemException {
084        final FileName junctionName = getFileSystemManager().resolveName(getRootName(), junctionPoint);
085
086        // Check for nested junction - these are not supported yet
087        if (getJunctionForFile(junctionName) != null) {
088            throw new FileSystemException("vfs.impl/nested-junction.error", junctionName);
089        }
090
091        try {
092            // Add to junction table
093            junctions.put(junctionName, targetFile);
094
095            // Attach to file
096            final DelegateFileObject junctionFile = (DelegateFileObject) getFileFromCache(junctionName);
097            if (junctionFile != null) {
098                junctionFile.setFile(targetFile);
099            }
100
101            // Create ancestors of junction point
102            FileName childName = junctionName;
103            boolean done = false;
104            for (AbstractFileName parentName = (AbstractFileName) childName.getParent(); !done
105                    && parentName != null; childName = parentName, parentName = (AbstractFileName) parentName
106                            .getParent()) {
107                DelegateFileObject file = (DelegateFileObject) getFileFromCache(parentName);
108                if (file == null) {
109                    file = new DelegateFileObject(parentName, this, null);
110                    putFileToCache(file);
111                } else {
112                    done = file.exists();
113                }
114
115                // As this is the parent of our junction it has to be a folder
116                file.attachChild(childName, FileType.FOLDER);
117            }
118
119            // TODO - attach all cached children of the junction point to their real file
120        } catch (final Exception e) {
121            throw new FileSystemException("vfs.impl/create-junction.error", junctionName, e);
122        }
123    }
124
125    @Override
126    public void close() {
127        super.close();
128        junctions.clear();
129    }
130
131    /**
132     * Creates a file object. This method is called only if the requested file is not cached.
133     */
134    @Override
135    protected FileObject createFile(final AbstractFileName name) throws Exception {
136        // Find the file that the name points to
137        final FileName junctionPoint = getJunctionForFile(name);
138        final FileObject file;
139        if (junctionPoint != null) {
140            // Resolve the real file
141            final FileObject junctionFile = junctions.get(junctionPoint);
142            final String relName = junctionPoint.getRelativeName(name);
143            file = junctionFile.resolveFile(relName, NameScope.DESCENDENT_OR_SELF);
144        } else {
145            file = null;
146        }
147
148        // Return a wrapper around the file
149        return new DelegateFileObject(name, this, file);
150    }
151
152    /**
153     * Locates the junction point for the junction containing the given file.
154     *
155     * @param name The FileName.
156     * @return the FileName where the junction occurs.
157     */
158    private FileName getJunctionForFile(final FileName name) {
159        if (junctions.containsKey(name)) {
160            // The name points to the junction point directly
161            return name;
162        }
163        // Find matching junction
164        return junctions.keySet().stream().filter(fileName -> fileName.isDescendent(name)).findFirst().orElse(null);
165    }
166
167    /**
168     * Removes a junction from this file system.
169     *
170     * @param junctionPoint The junction to remove.
171     * @throws FileSystemException if an error occurs.
172     */
173    @Override
174    public void removeJunction(final String junctionPoint) throws FileSystemException {
175        final FileName junctionName = getFileSystemManager().resolveName(getRootName(), junctionPoint);
176        junctions.remove(junctionName);
177
178        // TODO - remove from parents of junction point
179        // TODO - detach all cached children of the junction point from their real file
180    }
181}