2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.util.resource;
22 import java.io.IOException;
23 import java.net.JarURLConnection;
24 import java.net.MalformedURLException;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.List;
29 import java.util.jar.JarEntry;
30 import java.util.jar.JarFile;
32 import org.eclipse.jetty.util.log.Log;
33 import org.eclipse.jetty.util.log.Logger;
35 /* ------------------------------------------------------------ */
36 class JarFileResource extends JarResource
38 private static final Logger LOG = Log.getLogger(JarFileResource.class);
39 private JarFile _jarFile;
41 private String[] _list;
42 private JarEntry _entry;
43 private boolean _directory;
44 private String _jarUrl;
46 private boolean _exists;
48 /* -------------------------------------------------------- */
49 protected JarFileResource(URL url)
54 /* ------------------------------------------------------------ */
55 protected JarFileResource(URL url, boolean useCaches)
57 super(url, useCaches);
61 /* ------------------------------------------------------------ */
63 public synchronized void close()
68 //if the jvm is not doing url caching, then the JarFiles will not be cached either,
69 //and so they are safe to close
72 if ( _jarFile != null )
76 LOG.debug("Closing JarFile "+_jarFile.getName());
79 catch ( IOException ioe )
89 /* ------------------------------------------------------------ */
91 protected synchronized boolean checkConnection()
95 super.checkConnection();
99 if (_jarConnection==null)
107 return _jarFile!=null;
111 /* ------------------------------------------------------------ */
113 protected synchronized void newConnection()
116 super.newConnection();
123 int sep = _urlString.indexOf("!/");
124 _jarUrl=_urlString.substring(0,sep+2);
125 _path=_urlString.substring(sep+2);
126 if (_path.length()==0)
128 _jarFile=_jarConnection.getJarFile();
129 _file=new File(_jarFile.getName());
133 /* ------------------------------------------------------------ */
135 * Returns true if the represented resource exists.
138 public boolean exists()
143 if (_urlString.endsWith("!/"))
146 String file_url=_urlString.substring(4,_urlString.length()-2);
147 try{return newResource(file_url).exists();}
148 catch(Exception e) {LOG.ignore(e); return false;}
151 boolean check=checkConnection();
153 // Is this a root URL?
154 if (_jarUrl!=null && _path==null)
156 // Then if it exists it is a directory
162 // Can we find a file for it?
163 JarFile jarFile=null;
169 // No - so lets look if the root entry exists.
172 JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
173 c.setUseCaches(getUseCaches());
174 jarFile=c.getJarFile();
182 // Do we need to look more closely?
183 if (jarFile!=null && _entry==null && !_directory)
185 // OK - we have a JarFile, lets look at the entries for our path
186 Enumeration<JarEntry> e=jarFile.entries();
187 while(e.hasMoreElements())
189 JarEntry entry = e.nextElement();
190 String name=entry.getName().replace('\\','/');
192 // Do we have a match
193 if (name.equals(_path))
196 // Is the match a directory
197 _directory=_path.endsWith("/");
200 else if (_path.endsWith("/"))
202 if (name.startsWith(_path))
208 else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
217 _exists= ( _directory || _entry!=null);
222 /* ------------------------------------------------------------ */
224 * Returns true if the represented resource is a container/directory.
225 * If the resource is not a file, resources ending with "/" are
226 * considered directories.
229 public boolean isDirectory()
231 return _urlString.endsWith("/") || exists() && _directory;
234 /* ------------------------------------------------------------ */
236 * Returns the last modified time
239 public long lastModified()
241 if (checkConnection() && _file!=null)
243 if (exists() && _entry!=null)
244 return _entry.getTime();
245 return _file.lastModified();
250 /* ------------------------------------------------------------ */
252 public synchronized String[] list()
254 if (isDirectory() && _list==null)
256 List<String> list = null;
259 list = listEntries();
263 //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
264 //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
265 //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in
266 //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
268 //So, do one retry to drop a connection and get a fresh JarFile
269 LOG.warn("Retrying list:"+e);
272 list = listEntries();
277 _list=new String[list.size()];
285 /* ------------------------------------------------------------ */
286 private List<String> listEntries ()
290 ArrayList<String> list = new ArrayList<String>(32);
291 JarFile jarFile=_jarFile;
296 JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
297 jc.setUseCaches(getUseCaches());
298 jarFile=jc.getJarFile();
307 throw new IllegalStateException();
310 Enumeration<JarEntry> e=jarFile.entries();
311 String dir=_urlString.substring(_urlString.indexOf("!/")+2);
312 while(e.hasMoreElements())
314 JarEntry entry = e.nextElement();
315 String name=entry.getName().replace('\\','/');
316 if(!name.startsWith(dir) || name.length()==dir.length())
320 String listName=name.substring(dir.length());
321 int dash=listName.indexOf('/');
324 //when listing jar:file urls, you get back one
325 //entry for the dir itself, which we ignore
326 if (dash==0 && listName.length()==1)
328 //when listing jar:file urls, all files and
329 //subdirs have a leading /, which we remove
331 listName=listName.substring(dash+1, listName.length());
333 listName=listName.substring(0,dash+1);
335 if (list.contains(listName))
349 /* ------------------------------------------------------------ */
351 * Return the length of the resource
360 return _entry.getSize();
367 * Take a Resource that possibly might use URLConnection caching
368 * and turn it into one that doesn't.
370 * @return the non-caching resource
372 public static Resource getNonCachingResource (Resource resource)
374 if (!(resource instanceof JarFileResource))
377 JarFileResource oldResource = (JarFileResource)resource;
379 JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
385 * Check if this jar:file: resource is contained in the
386 * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
388 * @return true if resource is contained in the named resource
389 * @throws MalformedURLException
392 public boolean isContainedIn (Resource resource)
393 throws MalformedURLException
395 String string = _urlString;
396 int index = string.indexOf("!/");
398 string = string.substring(0,index);
399 if (string.startsWith("jar:"))
400 string = string.substring(4);
401 URL url = new URL(string);
402 return url.sameFile(resource.getURL());