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.*;
52 import java.util.*;
53 import java.util.zip.ZipEntry;
54 import java.util.jar.JarEntry;
55 import java.util.jar.JarFile;
56 import java.util.jar.JarInputStream;
57
58 /**
59 * This is copied from Classwords 1.1 org.codehaus.classworlds.uberjar.protocol.jar.JarURLConnection
60 * so that an additional dependency does not need to be added to plugins. The formatting is left as is to reduce
61 * the diff.
62 * <p/>
63 * The setupPathedInputStream() method has been modified to improve the speed of resource lookups. It now
64 * uses a ZipEntry to get random access to entries in the JAR.
65 * <p/>
66 * This change removes the ability for this connection class to load resources from JARs nested inside the outer
67 * JAR. This is not used in atlassian-plugin because the inner JAR loading is handled by
68 * {@link com.atlassian.plugin.classloader.PluginClassLoader}.
69 */
70 public class NonLockingJarUrlConnection
71 extends JarURLConnection
72 {
73 // ----------------------------------------------------------------------
74 // Instance members
75 // ----------------------------------------------------------------------
76
77 /**
78 * Base resource.
79 */
80 private URL baseResource;
81
82 /**
83 * Additional nested segments.
84 */
85 private String[] segments;
86
87 /**
88 * Terminal input-stream.
89 */
90 private InputStream in;
91
92 // ----------------------------------------------------------------------
93 // Constructors
94 // ----------------------------------------------------------------------
95
96 /**
97 * Construct.
98 *
99 * @param url Target URL of the connections.
100 * @throws java.io.IOException If an error occurs while attempting to initialize
101 * the connection.
102 */
103 NonLockingJarUrlConnection( URL url )
104 throws IOException
105 {
106 super( url = normaliseURL( url ) );
107
108 String baseText = url.getPath();
109
110 int bangLoc = baseText.indexOf( "!" );
111
112 String baseResourceText = baseText.substring( 0, bangLoc );
113
114 String extraText = "";
115
116 if ( bangLoc <= ( baseText.length() - 2 )
117 &&
118 baseText.charAt( bangLoc + 1 ) == '/' )
119 {
120 if ( bangLoc + 2 == baseText.length() )
121 {
122 extraText = "";
123 }
124 else
125 {
126 extraText = baseText.substring( bangLoc + 1 );
127 }
128 }
129 else
130 {
131 throw new MalformedURLException( "No !/ in url: " + url.toExternalForm() );
132 }
133
134
135 List segments = new ArrayList();
136
137 StringTokenizer tokens = new StringTokenizer( extraText, "!" );
138
139 while ( tokens.hasMoreTokens() )
140 {
141 segments.add( tokens.nextToken() );
142 }
143
144 this.segments = (String[]) segments.toArray( new String[segments.size()] );
145
146 this.baseResource = new URL( baseResourceText );
147 }
148
149 protected static URL normaliseURL( URL url ) throws MalformedURLException
150 {
151 String text = normalizeUrlPath( url.toString() );
152
153 if ( !text.startsWith( "jar:" ) )
154 {
155 text = "jar:" + text;
156 }
157
158 if ( text.indexOf( '!' ) < 0 )
159 {
160 text = text + "!/";
161 }
162
163 return new URL( text );
164 }
165
166 // ----------------------------------------------------------------------
167 // Instance methods
168 // ----------------------------------------------------------------------
169
170 /**
171 * Retrieve the nesting path segments.
172 *
173 * @return The segments.
174 */
175 protected String[] getSegments()
176 {
177 return this.segments;
178 }
179
180 /**
181 * Retrieve the base resource <code>URL</code>.
182 *
183 * @return The base resource url.
184 */
185 protected URL getBaseResource()
186 {
187 return this.baseResource;
188 }
189
190 /**
191 * @see java.net.URLConnection
192 */
193 public void connect()
194 throws IOException
195 {
196 if ( this.segments.length == 0 )
197 {
198 setupBaseResourceInputStream();
199 }
200 else
201 {
202 setupPathedInputStream();
203 }
204 }
205
206 /**
207 * Setup the <code>InputStream</code> purely from the base resource.
208 *
209 * @throws java.io.IOException If an I/O error occurs.
210 */
211 protected void setupBaseResourceInputStream()
212 throws IOException
213 {
214 this.in = getBaseResource().openStream();
215 }
216
217 /**
218 * Setup the <code>InputStream</code> for URL with nested segments.
219 *
220 * @throws java.io.IOException If an I/O error occurs.
221 */
222 protected void setupPathedInputStream()
223 throws IOException
224 {
225 final JarFile jar = getJarFile();
226 String entryName = segments[0].substring(1); // remove leading slash
227 final ZipEntry zipEntry = jar.getEntry(entryName);
228
229 if (zipEntry == null)
230 {
231 throw new IOException("Unable to locate entry: " + entryName + ", in JAR file: " + jar.getName());
232 }
233
234 final InputStream delegate = jar.getInputStream(zipEntry);
235 this.in = new InputStream() {
236
237 public int read() throws IOException
238 {
239 return delegate.read();
240 }
241
242 public int read(byte b[]) throws IOException
243 {
244 return delegate.read(b);
245 }
246
247 public int read(byte b[], int off, int len) throws IOException
248 {
249 return delegate.read(b, off, len);
250 }
251
252 public long skip(long n) throws IOException
253 {
254 return delegate.skip(n);
255 }
256
257 public int available() throws IOException
258 {
259 return delegate.available();
260 }
261
262 public void close() throws IOException
263 {
264 // close the stream and the plugin JAR file
265 delegate.close();
266 jar.close();
267 }
268
269 public synchronized void mark(int readlimit)
270 {
271 delegate.mark(readlimit);
272 }
273
274 public synchronized void reset() throws IOException
275 {
276 delegate.reset();
277 }
278
279 public boolean markSupported()
280 {
281 return delegate.markSupported();
282 }
283 };
284 }
285
286 /**
287 * Retrieve the <code>InputStream</code> for the nesting
288 * segment relative to a base <code>InputStream</code>.
289 *
290 * @param baseIn The base input-stream.
291 * @param segment The nesting segment path.
292 * @return The input-stream to the segment.
293 * @throws java.io.IOException If an I/O error occurs.
294 */
295 protected InputStream getSegmentInputStream( InputStream baseIn,
296 String segment )
297 throws IOException
298 {
299 JarInputStream jarIn = new JarInputStream( baseIn );
300 JarEntry entry = null;
301
302 while ( jarIn.available() != 0 )
303 {
304 entry = jarIn.getNextJarEntry();
305
306 if ( entry == null )
307 {
308 break;
309 }
310
311 if ( ( "/" + entry.getName() ).equals( segment ) )
312 {
313 return jarIn;
314 }
315 }
316
317 throw new IOException( "unable to locate segment: " + segment );
318 }
319
320 /**
321 * @see java.net.URLConnection
322 */
323 public InputStream getInputStream()
324 throws IOException
325 {
326 if ( this.in == null )
327 {
328 connect();
329 }
330 return this.in;
331 }
332
333 /**
334 * @return JarFile
335 * @throws java.io.IOException
336 * @see java.net.JarURLConnection#getJarFile()
337 */
338 public JarFile getJarFile() throws IOException
339 {
340 String url = baseResource.toExternalForm();
341
342 if ( url.startsWith( "file:/" ) )
343 {
344 url = url.substring( 5 );
345 }
346
347 return new JarFile( URLDecoder.decode( url, "UTF-8" ) );
348 }
349
350 private static String normalizeUrlPath(String name) {
351 if (name.startsWith("/")) {
352 name = name.substring(1);
353 }
354 int i = name.indexOf("/..");
355 if (i > 0) {
356 int j = name.lastIndexOf("/", i - 1);
357 name = name.substring(0, j) + name.substring(i + 3);
358 }
359
360 return name;
361 }
362 }