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}