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}