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 }