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.impl;
18  
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.apache.commons.vfs2.Capability;
24  import org.apache.commons.vfs2.FileName;
25  import org.apache.commons.vfs2.FileObject;
26  import org.apache.commons.vfs2.FileSystemException;
27  import org.apache.commons.vfs2.FileSystemOptions;
28  import org.apache.commons.vfs2.FileType;
29  import org.apache.commons.vfs2.NameScope;
30  import org.apache.commons.vfs2.provider.AbstractFileName;
31  import org.apache.commons.vfs2.provider.AbstractFileSystem;
32  import org.apache.commons.vfs2.provider.DelegateFileObject;
33  
34  /**
35   * A logical file system, made up of set of junctions, or links, to files from other file systems.
36   * <p>
37   * TODO - Handle nested junctions.
38   * </p>
39   */
40  public class VirtualFileSystem extends AbstractFileSystem {
41  
42      private final Map<FileName, FileObject> junctions = new HashMap<>();
43  
44      /**
45       * Constructs a new instance.
46       *
47       * @param rootFileName The root file name of this file system.
48       * @param fileSystemOptions Options to build this file system.
49       */
50      public VirtualFileSystem(final AbstractFileName rootFileName, final FileSystemOptions fileSystemOptions) {
51          super(rootFileName, null, fileSystemOptions);
52      }
53  
54      /**
55       * Adds the capabilities of this file system.
56       */
57      @Override
58      protected void addCapabilities(final Collection<Capability> caps) {
59          // TODO - this isn't really true
60          caps.add(Capability.ATTRIBUTES);
61          caps.add(Capability.CREATE);
62          caps.add(Capability.DELETE);
63          caps.add(Capability.GET_TYPE);
64          caps.add(Capability.JUNCTIONS);
65          caps.add(Capability.GET_LAST_MODIFIED);
66          caps.add(Capability.SET_LAST_MODIFIED_FILE);
67          caps.add(Capability.SET_LAST_MODIFIED_FOLDER);
68          caps.add(Capability.LIST_CHILDREN);
69          caps.add(Capability.READ_CONTENT);
70          caps.add(Capability.SIGNING);
71          caps.add(Capability.WRITE_CONTENT);
72          caps.add(Capability.APPEND_CONTENT);
73      }
74  
75      /**
76       * Adds a junction to this file system.
77       *
78       * @param junctionPoint The location of the junction.
79       * @param targetFile The target file to base the junction on.
80       * @throws FileSystemException if an error occurs.
81       */
82      @Override
83      public void addJunction(final String junctionPoint, final FileObject targetFile) throws FileSystemException {
84          final FileName junctionName = getFileSystemManager().resolveName(getRootName(), junctionPoint);
85  
86          // Check for nested junction - these are not supported yet
87          if (getJunctionForFile(junctionName) != null) {
88              throw new FileSystemException("vfs.impl/nested-junction.error", junctionName);
89          }
90  
91          try {
92              // Add to junction table
93              junctions.put(junctionName, targetFile);
94  
95              // Attach to file
96              final DelegateFileObject junctionFile = (DelegateFileObject) getFileFromCache(junctionName);
97              if (junctionFile != null) {
98                  junctionFile.setFile(targetFile);
99              }
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 }