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.tar;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.nio.file.Files;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.zip.GZIPInputStream;
28  
29  import org.apache.commons.compress.archivers.ArchiveEntry;
30  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
31  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.commons.vfs2.Capability;
35  import org.apache.commons.vfs2.FileName;
36  import org.apache.commons.vfs2.FileNotFoundException;
37  import org.apache.commons.vfs2.FileObject;
38  import org.apache.commons.vfs2.FileSystemException;
39  import org.apache.commons.vfs2.FileSystemOptions;
40  import org.apache.commons.vfs2.Selectors;
41  import org.apache.commons.vfs2.VfsLog;
42  import org.apache.commons.vfs2.provider.AbstractFileName;
43  import org.apache.commons.vfs2.provider.AbstractFileSystem;
44  import org.apache.commons.vfs2.provider.UriParser;
45  import org.apache.commons.vfs2.provider.bzip2.Bzip2FileObject;
46  
47  /**
48   * A read-only file system for Tar files.
49   */
50  public class TarFileSystem extends AbstractFileSystem {
51      private static final Log LOG = LogFactory.getLog(TarFileSystem.class);
52  
53      private static final char[] ENC = {'!'};
54  
55      private final File file;
56  
57      private TarArchiveInputStream tarFile;
58  
59      /**
60       * Cache doesn't need to be synchronized since it is read-only.
61       */
62      private final Map<FileName, FileObject> cache = new HashMap<>();
63  
64      /**
65       * Constructs a new instance.
66       *
67       * @param rootName The root file name of this file system.
68       * @param parentLayer The parent layer of this file system.
69       * @param fileSystemOptions Options to build this file system.
70       * @throws FileSystemException if a file system error occurs.
71       */
72      protected TarFileSystem(final AbstractFileName rootName, final FileObject parentLayer,
73          final FileSystemOptions fileSystemOptions) throws FileSystemException {
74          super(rootName, parentLayer, fileSystemOptions);
75  
76          // Make a local copy of the file
77          file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF);
78  
79          // Open the Tar file
80          if (!file.exists()) {
81              // Don't need to do anything
82              tarFile = null;
83          }
84  
85          // tarFile = createTarFile(this.file);
86      }
87  
88      /**
89       * Returns the capabilities of this file system.
90       */
91      @Override
92      protected void addCapabilities(final Collection<Capability> caps) {
93          caps.addAll(TarFileProvider.capabilities);
94      }
95  
96      /**
97       * Creates a file object.
98       */
99      @Override
100     protected FileObject createFile(final AbstractFileName name) throws FileSystemException {
101         // This is only called for files which do not exist in the Tar file
102         return new TarFileObject(name, null, this, false);
103     }
104 
105     /**
106      * Creates a new TarArchiveInputStream.
107      *
108      * @param file the file.
109      * @return a new TarArchiveInputStream.
110      * @throws FileSystemException if a file system error occurs.
111      */
112     protected TarArchiveInputStream createTarFile(final File file) throws FileSystemException {
113         try {
114             if ("tgz".equalsIgnoreCase(getRootName().getScheme())) {
115                 return new TarArchiveInputStream(new GZIPInputStream(Files.newInputStream(file.toPath())));
116             }
117             if ("tbz2".equalsIgnoreCase(getRootName().getScheme())) {
118                 return new TarArchiveInputStream(
119                     Bzip2FileObject.wrapInputStream(file.getAbsolutePath(), Files.newInputStream(file.toPath())));
120             }
121             return new TarArchiveInputStream(Files.newInputStream(file.toPath()));
122         } catch (final IOException ioe) {
123             throw new FileSystemException("vfs.provider.tar/open-tar-file.error", file, ioe);
124         }
125     }
126 
127     /**
128      * Creates a new TarFileObject.
129      *
130      * @param fileName the file name.
131      * @param entry the archive entry.
132      * @return a new TarFileObject.
133      */
134     protected TarFileObject createTarFileObject(final AbstractFileName fileName, final TarArchiveEntry entry) {
135         return new TarFileObject(fileName, entry, this, true);
136     }
137 
138     @Override
139     protected void doCloseCommunicationLink() {
140         // Release the tar file
141         try {
142             if (tarFile != null) {
143                 tarFile.close();
144                 tarFile = null;
145             }
146         } catch (final IOException e) {
147             // getLogger().warn("vfs.provider.tar/close-tar-file.error :" + file, e);
148             VfsLog.warn(getLogger(), LOG, "vfs.provider.tar/close-tar-file.error :" + file, e);
149         }
150     }
151 
152     /**
153      * Returns a cached file.
154      */
155     @Override
156     protected FileObject getFileFromCache(final FileName name) {
157         return cache.get(name);
158     }
159 
160     /**
161      * Returns the input stream for the given entry.
162      *
163      * @param entry The entry to seek.
164      * @return the input stream for the given entry.
165      * @throws FileSystemException If an I/O error occurs.
166      */
167     public InputStream getInputStream(final TarArchiveEntry entry) throws FileSystemException {
168         Objects.requireNonNull(entry, "entry");
169         resetTarFile();
170         try {
171             ArchiveEntry next;
172             while ((next = tarFile.getNextEntry()) != null) {
173                 if (next.equals(entry)) {
174                     return tarFile;
175                 }
176             }
177             throw new FileNotFoundException(entry.toString());
178         } catch (final IOException e) {
179             throw new FileSystemException(e);
180         }
181     }
182 
183     /**
184      * Gets the TarArchiveInputStream.
185      *
186      * @return the TarArchiveInputStream.
187      * @throws FileSystemException if a file system error occurs.
188      */
189     protected TarArchiveInputStream getTarFile() throws FileSystemException {
190         if (tarFile == null && file.exists()) {
191             recreateTarFile();
192         }
193         return tarFile;
194     }
195 
196     @Override
197     public void init() throws FileSystemException {
198         super.init();
199 
200         // Build the index
201         try {
202             TarArchiveEntry entry;
203             while ((entry = getTarFile().getNextTarEntry()) != null) {
204                 final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(),
205                     UriParser.encode(entry.getName(), ENC));
206 
207                 // Create the file
208                 TarFileObject fileObj;
209                 if (entry.isDirectory() && getFileFromCache(name) != null) {
210                     fileObj = (TarFileObject) getFileFromCache(name);
211                     fileObj.setTarEntry(entry);
212                     continue;
213                 }
214 
215                 fileObj = createTarFileObject(name, entry);
216                 putFileToCache(fileObj);
217 
218                 // Make sure all ancestors exist
219                 // TODO - create these on demand
220                 TarFileObject parent = null;
221                 for (AbstractFileName parentName = (AbstractFileName) name
222                     .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName
223                         .getParent()) {
224                     // Locate the parent
225                     parent = (TarFileObject) getFileFromCache(parentName);
226                     if (parent == null) {
227                         parent = createTarFileObject(parentName, null);
228                         putFileToCache(parent);
229                     }
230 
231                     // Attach child to parent
232                     parent.attachChild(fileObj.getName());
233                 }
234             }
235         } catch (final IOException e) {
236             throw new FileSystemException(e);
237         } finally {
238             closeCommunicationLink();
239         }
240     }
241 
242     /**
243      * Adds a file object to the cache.
244      */
245     @Override
246     protected void putFileToCache(final FileObject file) {
247         cache.put(file.getName(), file);
248     }
249 
250     /**
251      * will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() {
252      * closeCommunicationLink(); }
253      */
254     private void recreateTarFile() throws FileSystemException {
255         if (tarFile != null) {
256             try {
257                 tarFile.close();
258             } catch (final IOException e) {
259                 throw new FileSystemException("vfs.provider.tar/close-tar-file.error", file, e);
260             }
261             tarFile = null;
262         }
263         tarFile = createTarFile(file);
264     }
265 
266     /**
267      * remove a cached file.
268      */
269     @Override
270     protected void removeFileFromCache(final FileName name) {
271         cache.remove(name);
272     }
273 
274     /**
275      * Resets the tar file.
276      *
277      * @throws FileSystemException if a file system error occurs.
278      */
279     protected void resetTarFile() throws FileSystemException {
280         // Reading specific entries requires skipping through the tar file from the beginning
281         // Not especially elegant, but we don't have the ability to seek to specific positions
282         // with an input stream.
283         if (file.exists()) {
284             recreateTarFile();
285         }
286     }
287 }