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.provider.zip;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.nio.charset.Charset;
22  import java.nio.charset.StandardCharsets;
23  import java.util.Collection;
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.zip.ZipEntry;
28  import java.util.zip.ZipFile;
29  
30  import org.apache.commons.io.IOUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.commons.vfs2.Capability;
34  import org.apache.commons.vfs2.FileName;
35  import org.apache.commons.vfs2.FileObject;
36  import org.apache.commons.vfs2.FileSystemException;
37  import org.apache.commons.vfs2.FileSystemOptions;
38  import org.apache.commons.vfs2.Selectors;
39  import org.apache.commons.vfs2.VfsLog;
40  import org.apache.commons.vfs2.provider.AbstractFileName;
41  import org.apache.commons.vfs2.provider.AbstractFileSystem;
42  import org.apache.commons.vfs2.provider.UriParser;
43  
44  /**
45   * A read-only file system for ZIP and JAR files.
46   */
47  public class ZipFileSystem extends AbstractFileSystem {
48  
49      private static final char[] ENC = {'!'};
50  
51      private static final Log LOG = LogFactory.getLog(ZipFileSystem.class);
52  
53      private final File file;
54      private final Charset charset;
55      private ZipFile zipFile;
56  
57      /**
58       * Cache doesn't need to be synchronized since it is read-only.
59       */
60      private final Map<FileName, FileObject> cache = new HashMap<>();
61  
62      /**
63       * Constructs a new instance.
64       *
65       * @param rootFileName The root file name of this file system.
66       * @param parentLayer The parent layer of this file system.
67       * @param fileSystemOptions Options to build this file system.
68       * @throws FileSystemException If the parent layer does not exist, or on error replicating the file.
69       */
70      public ZipFileSystem(final AbstractFileName rootFileName, final FileObject parentLayer, final FileSystemOptions fileSystemOptions)
71          throws FileSystemException {
72          super(rootFileName, parentLayer, fileSystemOptions);
73  
74          // Make a local copy of the file
75          file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF);
76          charset = ZipFileSystemConfigBuilder.getInstance().getCharset(fileSystemOptions);
77  
78          // Open the Zip file
79          if (!file.exists()) {
80              // Don't need to do anything
81              zipFile = null;
82          }
83      }
84  
85      /**
86       * Returns the capabilities of this file system.
87       */
88      @Override
89      protected void addCapabilities(final Collection<Capability> caps) {
90          caps.addAll(ZipFileProvider.capabilities);
91      }
92  
93      /**
94       * Creates a file object.
95       */
96      @Override
97      protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
98          // This is only called for files which do not exist in the Zip file
99          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 }