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.provider.zip; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.charset.Charset; 022import java.nio.charset.StandardCharsets; 023import java.util.Collection; 024import java.util.Enumeration; 025import java.util.HashMap; 026import java.util.Map; 027import java.util.zip.ZipEntry; 028import java.util.zip.ZipFile; 029 030import org.apache.commons.io.IOUtils; 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.apache.commons.vfs2.Capability; 034import org.apache.commons.vfs2.FileName; 035import org.apache.commons.vfs2.FileObject; 036import org.apache.commons.vfs2.FileSystemException; 037import org.apache.commons.vfs2.FileSystemOptions; 038import org.apache.commons.vfs2.Selectors; 039import org.apache.commons.vfs2.VfsLog; 040import org.apache.commons.vfs2.provider.AbstractFileName; 041import org.apache.commons.vfs2.provider.AbstractFileSystem; 042import org.apache.commons.vfs2.provider.UriParser; 043 044/** 045 * A read-only file system for ZIP and JAR files. 046 */ 047public class ZipFileSystem extends AbstractFileSystem { 048 049 private static final char[] ENC = {'!'}; 050 051 private static final Log LOG = LogFactory.getLog(ZipFileSystem.class); 052 053 private final File file; 054 private final Charset charset; 055 private ZipFile zipFile; 056 057 /** 058 * Cache doesn't need to be synchronized since it is read-only. 059 */ 060 private final Map<FileName, FileObject> cache = new HashMap<>(); 061 062 /** 063 * Constructs a new instance. 064 * 065 * @param rootFileName The root file name of this file system. 066 * @param parentLayer The parent layer of this file system. 067 * @param fileSystemOptions Options to build this file system. 068 * @throws FileSystemException If the parent layer does not exist, or on error replicating the file. 069 */ 070 public ZipFileSystem(final AbstractFileName rootFileName, final FileObject parentLayer, final FileSystemOptions fileSystemOptions) 071 throws FileSystemException { 072 super(rootFileName, parentLayer, fileSystemOptions); 073 074 // Make a local copy of the file 075 file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF); 076 charset = ZipFileSystemConfigBuilder.getInstance().getCharset(fileSystemOptions); 077 078 // Open the Zip file 079 if (!file.exists()) { 080 // Don't need to do anything 081 zipFile = null; 082 } 083 } 084 085 /** 086 * Returns the capabilities of this file system. 087 */ 088 @Override 089 protected void addCapabilities(final Collection<Capability> caps) { 090 caps.addAll(ZipFileProvider.capabilities); 091 } 092 093 /** 094 * Creates a file object. 095 */ 096 @Override 097 protected FileObject createFile(final AbstractFileName name) throws FileSystemException { 098 // This is only called for files which do not exist in the Zip file 099 return new ZipFileObject(name, null, this, false); 100 } 101 102 /** 103 * Creates a Zip file. 104 * 105 * @param file the underlying file. 106 * @return a Zip file. 107 * @throws FileSystemException if a file system error occurs. 108 */ 109 protected ZipFile createZipFile(final File file) throws FileSystemException { 110 try { 111 return new ZipFile(file, charset); 112 } catch (final IOException ioe) { 113 throw new FileSystemException("vfs.provider.zip/open-zip-file.error", file, ioe); 114 } 115 } 116 117 /** 118 * Creates a new Zip file object. 119 * 120 * @param fileName the underlying file. 121 * @param entry the Zip entry. 122 * @return a new ZipFileObject. 123 * @throws FileSystemException if a file system error occurs. 124 */ 125 protected ZipFileObject createZipFileObject(final AbstractFileName fileName, final ZipEntry entry) throws FileSystemException { 126 return new ZipFileObject(fileName, entry, this, true); 127 } 128 129 @Override 130 protected void doCloseCommunicationLink() { 131 // Release the zip file 132 try { 133 IOUtils.close(zipFile); 134 zipFile = null; 135 } catch (final IOException e) { 136 // getLogger().warn("vfs.provider.zip/close-zip-file.error :" + file, e); 137 VfsLog.warn(getLogger(), LOG, "vfs.provider.zip/close-zip-file.error :" + file, e); 138 } 139 } 140 141 /** 142 * Gets the Charset, defaults to {@link StandardCharsets#UTF_8}, the value used in {@link ZipFile}. 143 * 144 * @return the Charset. 145 */ 146 protected Charset getCharset() { 147 return charset; 148 } 149 150 /** 151 * Gets a cached file. 152 */ 153 @Override 154 protected FileObject getFileFromCache(final FileName name) { 155 return cache.get(name); 156 } 157 158 /** 159 * Gets the zip file. 160 * 161 * @return the zip file. 162 * @throws FileSystemException if a file system error occurs. 163 */ 164 protected ZipFile getZipFile() throws FileSystemException { 165 if (zipFile == null && file.exists()) { 166 zipFile = createZipFile(file); 167 } 168 return zipFile; 169 } 170 171 @Override 172 public void init() throws FileSystemException { 173 super.init(); 174 175 try { 176 // Build the index 177 final Enumeration<? extends ZipEntry> entries = getZipFile().entries(); 178 while (entries.hasMoreElements()) { 179 final ZipEntry entry = entries.nextElement(); 180 final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(), 181 UriParser.encode(entry.getName(), ENC)); 182 183 // Create the file 184 ZipFileObject fileObj; 185 if (entry.isDirectory() && getFileFromCache(name) != null) { 186 fileObj = (ZipFileObject) getFileFromCache(name); 187 fileObj.setZipEntry(entry); 188 continue; 189 } 190 191 fileObj = createZipFileObject(name, entry); 192 putFileToCache(fileObj); 193 194 // Make sure all ancestors exist 195 // TODO - create these on demand 196 ZipFileObject parent; 197 for (AbstractFileName parentName = (AbstractFileName) name 198 .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName 199 .getParent()) { 200 // Locate the parent 201 parent = (ZipFileObject) getFileFromCache(parentName); 202 if (parent == null) { 203 parent = createZipFileObject(parentName, null); 204 putFileToCache(parent); 205 } 206 207 // Attach child to parent 208 parent.attachChild(fileObj.getName()); 209 } 210 } 211 } finally { 212 closeCommunicationLink(); 213 } 214 } 215 216 /** 217 * Adds a file object to the cache. 218 */ 219 @Override 220 protected void putFileToCache(final FileObject file) { 221 cache.put(file.getName(), file); 222 } 223 224 /** 225 * remove a cached file. 226 */ 227 @Override 228 protected void removeFileFromCache(final FileName name) { 229 cache.remove(name); 230 } 231 232 @Override 233 public String toString() { 234 return super.toString() + " for " + file; 235 } 236 237 /* 238 will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() { 239 closeCommunicationLink(); } 240 */ 241}