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 = new ZipFile(f, charset != null ? charset.name() : "UTF8");
71          final Enumeration<ZipArchiveEntry> en = zipFile.getEntriesInPhysicalOrder();
72          return new ZipFileResourceIterator(en, url, jarFile, zipFile, urlClassLoader);
73      }
74  
75      private static class ZipFileResourceIterator implements Iterator<PlexusIoResource>, Closeable {
76  
77          private class ZipFileResource extends PlexusIoURLResource {
78              private final JarFile jarFile;
79  
80              private ZipFileResource(JarFile jarFile, ZipArchiveEntry entry) {
81                  super(
82                          entry.getName(),
83                          entry.getTime() == -1 ? PlexusIoResource.UNKNOWN_MODIFICATION_DATE : entry.getTime(),
84                          entry.isDirectory() ? PlexusIoResource.UNKNOWN_RESOURCE_SIZE : entry.getSize(),
85                          !entry.isDirectory(),
86                          entry.isDirectory(),
87                          true);
88  
89                  this.jarFile = jarFile;
90              }
91  
92              @Override
93              public InputStream getContents() throws IOException {
94                  // Uses the JarFile to get the input stream for the entry.
95                  // The super method will do the same, why overriding it?
96                  // But it will create new JarFile for every entry
97                  // and that could be very expensive in some cases (when the JAR is signed).
98                  // Enabling the URLConnection cache would solve the problem
99                  // but the cache is global and shared during the build so
100                 // if the AJR file changed during the causes another problem
101                 // (see plexus-io#2).
102                 // Using local JarFile instance solves the two issues -
103                 // JarFile is initialized once so there is no performance penalty
104                 // and it is local so if the file changed during the build
105                 // would not be a problem.
106                 // And we know the URL returned by getURL is part of the JAR
107                 // because that is how we constructed it.
108                 return jarFile.getInputStream(jarFile.getEntry(getName()));
109             }
110 
111             @Override
112             public URL getURL() throws IOException {
113                 String spec = getName();
114                 if (spec.startsWith("/")) {
115                     // Code path for PLXCOMP-170. Note that urlClassloader does not seem to produce correct
116                     // urls for this. Which again means files loaded via this path cannot have file names
117                     // requiring url encoding
118                     spec = "./" + spec;
119                     return new URL(url, spec);
120                 }
121                 return urlClassLoader.getResource(spec);
122             }
123         }
124 
125         private class ZipFileSymlinkResource extends ZipFileResource implements SymlinkDestinationSupplier {
126 
127             private final ZipArchiveEntry entry;
128 
129             private ZipFileSymlinkResource(JarFile jarFile, ZipArchiveEntry entry) {
130                 super(jarFile, entry);
131 
132                 this.entry = entry;
133             }
134 
135             @Override
136             public String getSymlinkDestination() throws IOException {
137                 return zipFile.getUnixSymlink(entry);
138             }
139 
140             @Override
141             public boolean isSymbolicLink() {
142                 return true;
143             }
144         }
145 
146         private final Enumeration<ZipArchiveEntry> en;
147 
148         private final URL url;
149 
150         private final JarFile jarFile;
151 
152         private final ZipFile zipFile;
153 
154         private final URLClassLoader urlClassLoader;
155 
156         public ZipFileResourceIterator(
157                 Enumeration<ZipArchiveEntry> en,
158                 URL url,
159                 JarFile jarFile,
160                 ZipFile zipFile,
161                 URLClassLoader urlClassLoader) {
162             this.en = en;
163             this.url = url;
164             this.jarFile = jarFile;
165             this.zipFile = zipFile;
166             this.urlClassLoader = urlClassLoader;
167         }
168 
169         @Override
170         public boolean hasNext() {
171             return en.hasMoreElements();
172         }
173 
174         @Override
175         public PlexusIoResource next() {
176             final ZipArchiveEntry entry = en.nextElement();
177             return entry.isUnixSymlink()
178                     ? new ZipFileSymlinkResource(jarFile, entry)
179                     : new ZipFileResource(jarFile, entry);
180         }
181 
182         @Override
183         public void remove() {
184             throw new UnsupportedOperationException("Removing isn't implemented.");
185         }
186 
187         @Override
188         public void close() throws IOException {
189             try {
190                 urlClassLoader.close();
191             } finally {
192                 try {
193                     zipFile.close();
194                 } finally {
195                     jarFile.close();
196                 }
197             }
198         }
199     }
200 
201     @Override
202     public void setEncoding(Charset charset) {
203         this.charset = charset;
204     }
205 }