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