View Javadoc
1   /*
2    * Copyright 2007 The Codehaus Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.codehaus.plexus.archiver.zip;
17  
18  import java.io.Closeable;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  import java.nio.charset.Charset;
25  import java.nio.charset.StandardCharsets;
26  import java.util.Enumeration;
27  import java.util.Iterator;
28  import java.util.jar.JarFile;
29  
30  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
31  import org.apache.commons.compress.archivers.zip.ZipFile;
32  import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
33  import org.codehaus.plexus.components.io.resources.AbstractPlexusIoArchiveResourceCollection;
34  import org.codehaus.plexus.components.io.resources.EncodingSupported;
35  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
36  import org.codehaus.plexus.components.io.resources.PlexusIoURLResource;
37  
38  // TODO: there were two components for same name!
39  // @Named( "zip" )
40  public class PlexusIoZipFileResourceCollection extends AbstractPlexusIoArchiveResourceCollection
41          implements EncodingSupported {
42  
43      private Charset charset = StandardCharsets.UTF_8;
44  
45      public PlexusIoZipFileResourceCollection() {}
46  
47      @Override
48      public boolean isConcurrentAccessSupported() {
49          // Maybe we could support concurrent some time in the future
50          return false;
51      }
52  
53      @Override
54      protected Iterator<PlexusIoResource> getEntries() throws IOException {
55          final File f = getFile();
56          if (f == null) {
57              throw new IOException("The zip file has not been set.");
58          }
59          final URLClassLoader urlClassLoader =
60                  new URLClassLoader(new URL[] {f.toURI().toURL()}, null) {
61  
62                      @Override
63                      public URL getResource(String name) {
64                          return findResource(name);
65                      }
66                  };
67  
68          final URL url = new URL("jar:" + f.toURI().toURL() + "!/");
69          final JarFile jarFile = new JarFile(f);
70          final ZipFile zipFile = ZipFile.builder()
71                  .setFile(f)
72                  .setCharset(charset != null ? charset : StandardCharsets.UTF_8)
73                  .get();
74          final Enumeration<ZipArchiveEntry> en = zipFile.getEntriesInPhysicalOrder();
75          return new ZipFileResourceIterator(en, url, jarFile, zipFile, urlClassLoader);
76      }
77  
78      private static class ZipFileResourceIterator implements Iterator<PlexusIoResource>, Closeable {
79  
80          private class ZipFileResource extends PlexusIoURLResource {
81              private final JarFile jarFile;
82  
83              private ZipFileResource(JarFile jarFile, ZipArchiveEntry entry) {
84                  super(
85                          entry.getName(),
86                          entry.getTime() == -1 ? PlexusIoResource.UNKNOWN_MODIFICATION_DATE : entry.getTime(),
87                          entry.isDirectory() ? PlexusIoResource.UNKNOWN_RESOURCE_SIZE : entry.getSize(),
88                          !entry.isDirectory(),
89                          entry.isDirectory(),
90                          true);
91  
92                  this.jarFile = jarFile;
93              }
94  
95              @Override
96              public InputStream getContents() throws IOException {
97                  // Uses the JarFile to get the input stream for the entry.
98                  // The super method will do the same, why overriding it?
99                  // But it will create new JarFile for every entry
100                 // and that could be very expensive in some cases (when the JAR is signed).
101                 // Enabling the URLConnection cache would solve the problem
102                 // but the cache is global and shared during the build so
103                 // if the AJR file changed during the causes another problem
104                 // (see plexus-io#2).
105                 // Using local JarFile instance solves the two issues -
106                 // JarFile is initialized once so there is no performance penalty
107                 // and it is local so if the file changed during the build
108                 // would not be a problem.
109                 // And we know the URL returned by getURL is part of the JAR
110                 // because that is how we constructed it.
111                 return jarFile.getInputStream(jarFile.getEntry(getName()));
112             }
113 
114             @Override
115             public URL getURL() throws IOException {
116                 String spec = getName();
117                 if (spec.startsWith("/")) {
118                     // Code path for PLXCOMP-170. Note that urlClassloader does not seem to produce correct
119                     // urls for this. Which again means files loaded via this path cannot have file names
120                     // requiring url encoding
121                     spec = "./" + spec;
122                     return new URL(url, spec);
123                 }
124                 return urlClassLoader.getResource(spec);
125             }
126         }
127 
128         private class ZipFileSymlinkResource extends ZipFileResource implements SymlinkDestinationSupplier {
129 
130             private final ZipArchiveEntry entry;
131 
132             private ZipFileSymlinkResource(JarFile jarFile, ZipArchiveEntry entry) {
133                 super(jarFile, entry);
134 
135                 this.entry = entry;
136             }
137 
138             @Override
139             public String getSymlinkDestination() throws IOException {
140                 return zipFile.getUnixSymlink(entry);
141             }
142 
143             @Override
144             public boolean isSymbolicLink() {
145                 return true;
146             }
147         }
148 
149         private final Enumeration<ZipArchiveEntry> en;
150 
151         private final URL url;
152 
153         private final JarFile jarFile;
154 
155         private final ZipFile zipFile;
156 
157         private final URLClassLoader urlClassLoader;
158 
159         public ZipFileResourceIterator(
160                 Enumeration<ZipArchiveEntry> en,
161                 URL url,
162                 JarFile jarFile,
163                 ZipFile zipFile,
164                 URLClassLoader urlClassLoader) {
165             this.en = en;
166             this.url = url;
167             this.jarFile = jarFile;
168             this.zipFile = zipFile;
169             this.urlClassLoader = urlClassLoader;
170         }
171 
172         @Override
173         public boolean hasNext() {
174             return en.hasMoreElements();
175         }
176 
177         @Override
178         public PlexusIoResource next() {
179             final ZipArchiveEntry entry = en.nextElement();
180             return entry.isUnixSymlink()
181                     ? new ZipFileSymlinkResource(jarFile, entry)
182                     : new ZipFileResource(jarFile, entry);
183         }
184 
185         @Override
186         public void remove() {
187             throw new UnsupportedOperationException("Removing isn't implemented.");
188         }
189 
190         @Override
191         public void close() throws IOException {
192             try {
193                 urlClassLoader.close();
194             } finally {
195                 try {
196                     zipFile.close();
197                 } finally {
198                     jarFile.close();
199                 }
200             }
201         }
202     }
203 
204     @Override
205     public void setEncoding(Charset charset) {
206         this.charset = charset;
207     }
208 }