View Javadoc
1   package org.codehaus.classworlds.uberjar.protocol.jar;
2   
3   /*
4    $Id: JarUrlConnection.java 78 2004-07-01 13:59:13Z jvanzyl $
5   
6    Copyright 2002 (C) The Werken Company. All Rights Reserved.
7   
8    Redistribution and use of this software and associated documentation
9    ("Software"), with or without modification, are permitted provided
10   that the following conditions are met:
11  
12   1. Redistributions of source code must retain copyright
13      statements and notices. Redistributions must also contain a
14      copy of this document.
15  
16   2. Redistributions in binary form must reproduce the
17      above copyright notice, this list of conditions and the
18      following disclaimer in the documentation and/or other
19      materials provided with the distribution.
20  
21   3. The name "classworlds" must not be used to endorse or promote
22      products derived from this Software without prior written
23      permission of The Werken Company. For written permission,
24      please contact bob@werken.com.
25  
26   4. Products derived from this Software may not be called "classworlds"
27      nor may "classworlds" appear in their names without prior written
28      permission of The Werken Company. "classworlds" is a registered
29      trademark of The Werken Company.
30  
31   5. Due credit should be given to The Werken Company.
32      (http://classworlds.werken.com/).
33  
34   THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
35   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
36   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
37   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
38   THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
39   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
43   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
45   OF THE POSSIBILITY OF SUCH DAMAGE.
46  
47   */
48  
49  import java.io.IOException;
50  import java.io.InputStream;
51  import java.net.JarURLConnection;
52  import java.net.MalformedURLException;
53  import java.net.URL;
54  import java.net.URLDecoder;
55  import java.util.ArrayList;
56  import java.util.List;
57  import java.util.StringTokenizer;
58  import java.util.jar.JarEntry;
59  import java.util.jar.JarFile;
60  import java.util.jar.JarInputStream;
61  import java.util.zip.ZipEntry;
62  
63  /**
64   * This is copied from Classwords 1.1 org.codehaus.classworlds.uberjar.protocol.jar.JarURLConnection
65   * so that an additional dependency does not need to be added to plugins. The formatting is left as is to reduce
66   * the diff.
67   * <p>
68   * The setupPathedInputStream() method has been modified to improve the speed of resource lookups. It now
69   * uses a ZipEntry to get random access to entries in the JAR.
70   * <p>
71   * This change removes the ability for this connection class to load resources from JARs nested inside the outer
72   * JAR. This is not used in atlassian-plugin because the inner JAR loading is handled by
73   * {@link com.atlassian.plugin.classloader.PluginClassLoader}.
74   */
75  public class NonLockingJarUrlConnection
76          extends JarURLConnection {
77      // ----------------------------------------------------------------------
78      //     Instance members
79      // ----------------------------------------------------------------------
80  
81      /**
82       * Base resource.
83       */
84      private URL baseResource;
85  
86      /**
87       * Additional nested segments.
88       */
89      private String[] segments;
90  
91      /**
92       * Terminal input-stream.
93       */
94      private InputStream in;
95  
96      // ----------------------------------------------------------------------
97      //     Constructors
98      // ----------------------------------------------------------------------
99  
100     /**
101      * Construct.
102      *
103      * @param url Target URL of the connections.
104      * @throws java.io.IOException If an error occurs while attempting to initialize
105      *                             the connection.
106      */
107     NonLockingJarUrlConnection(URL url)
108             throws IOException {
109         super(url = normaliseURL(url));
110 
111         String baseText = url.getPath();
112 
113         int bangLoc = baseText.indexOf("!");
114 
115         String baseResourceText = baseText.substring(0, bangLoc);
116 
117         String extraText = "";
118 
119         if (bangLoc <= (baseText.length() - 2)
120                 &&
121                 baseText.charAt(bangLoc + 1) == '/') {
122             if (bangLoc + 2 == baseText.length()) {
123                 extraText = "";
124             } else {
125                 extraText = baseText.substring(bangLoc + 1);
126             }
127         } else {
128             throw new MalformedURLException("No !/ in url: " + url.toExternalForm());
129         }
130 
131 
132         List segments = new ArrayList();
133 
134         StringTokenizer tokens = new StringTokenizer(extraText, "!");
135 
136         while (tokens.hasMoreTokens()) {
137             segments.add(tokens.nextToken());
138         }
139 
140         this.segments = (String[]) segments.toArray(new String[segments.size()]);
141 
142         this.baseResource = new URL(baseResourceText);
143     }
144 
145     protected static URL normaliseURL(URL url) throws MalformedURLException {
146         String text = normalizeUrlPath(url.toString());
147 
148         if (!text.startsWith("jar:")) {
149             text = "jar:" + text;
150         }
151 
152         if (text.indexOf('!') < 0) {
153             text = text + "!/";
154         }
155 
156         return new URL(text);
157     }
158 
159     // ----------------------------------------------------------------------
160     //     Instance methods
161     // ----------------------------------------------------------------------
162 
163     /**
164      * Retrieve the nesting path segments.
165      *
166      * @return The segments.
167      */
168     protected String[] getSegments() {
169         return this.segments;
170     }
171 
172     /**
173      * Retrieve the base resource <code>URL</code>.
174      *
175      * @return The base resource url.
176      */
177     protected URL getBaseResource() {
178         return this.baseResource;
179     }
180 
181     /**
182      * @see java.net.URLConnection
183      */
184     public void connect()
185             throws IOException {
186         if (this.segments.length == 0) {
187             setupBaseResourceInputStream();
188         } else {
189             setupPathedInputStream();
190         }
191     }
192 
193     /**
194      * Setup the <code>InputStream</code> purely from the base resource.
195      *
196      * @throws java.io.IOException If an I/O error occurs.
197      */
198     protected void setupBaseResourceInputStream()
199             throws IOException {
200         this.in = getBaseResource().openStream();
201     }
202 
203     /**
204      * Setup the <code>InputStream</code> for URL with nested segments.
205      *
206      * @throws java.io.IOException If an I/O error occurs.
207      */
208     protected void setupPathedInputStream()
209             throws IOException {
210         final JarFile jar = getJarFile();
211         String entryName = segments[0].substring(1); // remove leading slash
212         final ZipEntry zipEntry = jar.getEntry(entryName);
213 
214         if (zipEntry == null) {
215             throw new IOException("Unable to locate entry: " + entryName + ", in JAR file: " + jar.getName());
216         }
217 
218         final InputStream delegate = jar.getInputStream(zipEntry);
219         this.in = new InputStream() {
220 
221             public int read() throws IOException {
222                 return delegate.read();
223             }
224 
225             public int read(byte b[]) throws IOException {
226                 return delegate.read(b);
227             }
228 
229             public int read(byte b[], int off, int len) throws IOException {
230                 return delegate.read(b, off, len);
231             }
232 
233             public long skip(long n) throws IOException {
234                 return delegate.skip(n);
235             }
236 
237             public int available() throws IOException {
238                 return delegate.available();
239             }
240 
241             public void close() throws IOException {
242                 // close the stream and the plugin JAR file
243                 delegate.close();
244                 jar.close();
245             }
246 
247             public synchronized void mark(int readlimit) {
248                 delegate.mark(readlimit);
249             }
250 
251             public synchronized void reset() throws IOException {
252                 delegate.reset();
253             }
254 
255             public boolean markSupported() {
256                 return delegate.markSupported();
257             }
258         };
259     }
260 
261     /**
262      * Retrieve the <code>InputStream</code> for the nesting
263      * segment relative to a base <code>InputStream</code>.
264      *
265      * @param baseIn  The base input-stream.
266      * @param segment The nesting segment path.
267      * @return The input-stream to the segment.
268      * @throws java.io.IOException If an I/O error occurs.
269      */
270     protected InputStream getSegmentInputStream(InputStream baseIn,
271                                                 String segment)
272             throws IOException {
273         JarInputStream jarIn = new JarInputStream(baseIn);
274         JarEntry entry = null;
275 
276         while (jarIn.available() != 0) {
277             entry = jarIn.getNextJarEntry();
278 
279             if (entry == null) {
280                 break;
281             }
282 
283             if (("/" + entry.getName()).equals(segment)) {
284                 return jarIn;
285             }
286         }
287 
288         throw new IOException("unable to locate segment: " + segment);
289     }
290 
291     /**
292      * @see java.net.URLConnection
293      */
294     public InputStream getInputStream()
295             throws IOException {
296         if (this.in == null) {
297             connect();
298         }
299         return this.in;
300     }
301 
302     /**
303      * @return JarFile
304      * @throws java.io.IOException
305      * @see java.net.JarURLConnection#getJarFile()
306      */
307     public JarFile getJarFile() throws IOException {
308         String url = baseResource.toExternalForm();
309 
310         if (url.startsWith("file:")) {
311             url = url.substring(5);
312         }
313 
314         return new JarFile(URLDecoder.decode(url, "UTF-8"));
315     }
316 
317     private static String normalizeUrlPath(String name) {
318         if (name.startsWith("/")) {
319             name = name.substring(1);
320         }
321         int i = name.indexOf("/..");
322         if (i > 0) {
323             int j = name.lastIndexOf("/", i - 1);
324             name = name.substring(0, j) + name.substring(i + 3);
325         }
326 
327         return name;
328     }
329 }