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