View Javadoc

1   //
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.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util.resource;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.JarURLConnection;
24  import java.net.MalformedURLException;
25  import java.net.URL;
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;
31  
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.util.log.Logger;
34  
35  /* ------------------------------------------------------------ */
36  class JarFileResource extends JarResource
37  {
38      private static final Logger LOG = Log.getLogger(JarFileResource.class);
39      private JarFile _jarFile;
40      private File _file;
41      private String[] _list;
42      private JarEntry _entry;
43      private boolean _directory;
44      private String _jarUrl;
45      private String _path;
46      private boolean _exists;
47      
48      /* -------------------------------------------------------- */
49      protected JarFileResource(URL url)
50      {
51          super(url);
52      }
53      
54      /* ------------------------------------------------------------ */
55      protected JarFileResource(URL url, boolean useCaches)
56      {
57          super(url, useCaches);
58      }
59     
60  
61      /* ------------------------------------------------------------ */
62      @Override
63      public synchronized void close()
64      {
65          _list=null;
66          _entry=null;
67          _file=null;
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
70          if (!getUseCaches())
71          {
72              if ( _jarFile != null )
73              {
74                  try
75                  {
76                      LOG.debug("Closing JarFile "+_jarFile.getName());
77                      _jarFile.close();
78                  }
79                  catch ( IOException ioe )
80                  {
81                      LOG.ignore(ioe);
82                  }
83              }
84          }
85          _jarFile=null;
86          super.close();
87      }
88      
89      /* ------------------------------------------------------------ */
90      @Override
91      protected synchronized boolean checkConnection()
92      {
93          try
94          {
95              super.checkConnection();
96          }
97          finally
98          {
99              if (_jarConnection==null)
100             {
101                 _entry=null;
102                 _file=null;
103                 _jarFile=null;
104                 _list=null;
105             }
106         }
107         return _jarFile!=null;
108     }
109 
110 
111     /* ------------------------------------------------------------ */
112     @Override
113     protected synchronized void newConnection()
114         throws IOException
115     {
116         super.newConnection();
117         
118         _entry=null;
119         _file=null;
120         _jarFile=null;
121         _list=null;
122         
123         int sep = _urlString.indexOf("!/");
124         _jarUrl=_urlString.substring(0,sep+2);
125         _path=_urlString.substring(sep+2);
126         if (_path.length()==0)
127             _path=null;   
128         _jarFile=_jarConnection.getJarFile();
129         _file=new File(_jarFile.getName());
130     }
131     
132     
133     /* ------------------------------------------------------------ */
134     /**
135      * Returns true if the represented resource exists.
136      */
137     @Override
138     public boolean exists()
139     {
140         if (_exists)
141             return true;
142 
143         if (_urlString.endsWith("!/"))
144         {
145             
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;}
149         }
150         
151         boolean check=checkConnection();
152         
153         // Is this a root URL?
154         if (_jarUrl!=null && _path==null)
155         {
156             // Then if it exists it is a directory
157             _directory=check;
158             return true;
159         }
160         else 
161         {
162             // Can we find a file for it?
163             JarFile jarFile=null;
164             if (check)
165                 // Yes
166                 jarFile=_jarFile;
167             else
168             {
169                 // No - so lets look if the root entry exists.
170                 try
171                 {
172                     JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
173                     c.setUseCaches(getUseCaches());
174                     jarFile=c.getJarFile();
175                 }
176                 catch(Exception e)
177                 {
178                        LOG.ignore(e);
179                 }
180             }
181 
182             // Do we need to look more closely?
183             if (jarFile!=null && _entry==null && !_directory)
184             {
185                 // OK - we have a JarFile, lets look at the entries for our path
186                 Enumeration<JarEntry> e=jarFile.entries();
187                 while(e.hasMoreElements())
188                 {
189                     JarEntry entry = e.nextElement();
190                     String name=entry.getName().replace('\\','/');
191                     
192                     // Do we have a match
193                     if (name.equals(_path))
194                     {
195                         _entry=entry;
196                         // Is the match a directory
197                         _directory=_path.endsWith("/");
198                         break;
199                     }
200                     else if (_path.endsWith("/"))
201                     {
202                         if (name.startsWith(_path))
203                         {
204                             _directory=true;
205                             break;
206                         }
207                     }
208                     else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
209                     {
210                         _directory=true;
211                         break;
212                     }
213                 }
214             }
215         }    
216         
217         _exists= ( _directory || _entry!=null);
218         return _exists;
219     }
220 
221     
222     /* ------------------------------------------------------------ */
223     /**
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.
227      */
228     @Override
229     public boolean isDirectory()
230     {
231         return _urlString.endsWith("/") || exists() && _directory;
232     }
233     
234     /* ------------------------------------------------------------ */
235     /**
236      * Returns the last modified time
237      */
238     @Override
239     public long lastModified()
240     {
241         if (checkConnection() && _file!=null)
242         {
243             if (exists() && _entry!=null)
244                 return _entry.getTime();
245             return _file.lastModified();
246         }
247         return -1;
248     }
249 
250     /* ------------------------------------------------------------ */
251     @Override
252     public synchronized String[] list()
253     {
254         if (isDirectory() && _list==null)
255         {
256             List<String> list = null;
257             try
258             {
259                 list = listEntries();
260             }
261             catch (Exception e)
262             {
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
267                 //by other code.
268                 //So, do one retry to drop a connection and get a fresh JarFile
269                 LOG.warn("Retrying list:"+e);
270                 LOG.debug(e);
271                 release();
272                 list = listEntries();
273             }
274 
275             if (list != null)
276             {
277                 _list=new String[list.size()];
278                 list.toArray(_list);
279             }  
280         }
281         return _list;
282     }
283     
284     
285     /* ------------------------------------------------------------ */
286     private List<String> listEntries ()
287     {
288         checkConnection();
289         
290         ArrayList<String> list = new ArrayList<String>(32);
291         JarFile jarFile=_jarFile;
292         if(jarFile==null)
293         {
294             try
295             {
296                 JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
297                 jc.setUseCaches(getUseCaches());
298                 jarFile=jc.getJarFile();
299             }
300             catch(Exception e)
301             {
302 
303                 e.printStackTrace();
304                  LOG.ignore(e);
305             }
306                 if(jarFile==null)
307                     throw new IllegalStateException();
308         }
309         
310         Enumeration<JarEntry> e=jarFile.entries();
311         String dir=_urlString.substring(_urlString.indexOf("!/")+2);
312         while(e.hasMoreElements())
313         {
314             JarEntry entry = e.nextElement();               
315             String name=entry.getName().replace('\\','/');               
316             if(!name.startsWith(dir) || name.length()==dir.length())
317             {
318                 continue;
319             }
320             String listName=name.substring(dir.length());               
321             int dash=listName.indexOf('/');
322             if (dash>=0)
323             {
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)
327                     continue;
328                 //when listing jar:file urls, all files and
329                 //subdirs have a leading /, which we remove
330                 if (dash==0)
331                     listName=listName.substring(dash+1, listName.length());
332                 else
333                     listName=listName.substring(0,dash+1);
334                 
335                 if (list.contains(listName))
336                     continue;
337             }
338             
339             list.add(listName);
340         }
341         
342         return list;
343     }
344     
345     
346     
347     
348     
349     /* ------------------------------------------------------------ */
350     /**
351      * Return the length of the resource
352      */
353     @Override
354     public long length()
355     {
356         if (isDirectory())
357             return -1;
358 
359         if (_entry!=null)
360             return _entry.getSize();
361         
362         return -1;
363     }
364 
365     
366     /**
367      * Take a Resource that possibly might use URLConnection caching
368      * and turn it into one that doesn't.
369      * @param resource
370      * @return the non-caching resource
371      */
372     public static Resource getNonCachingResource (Resource resource)
373     {
374         if (!(resource instanceof JarFileResource))
375             return resource;
376         
377         JarFileResource oldResource = (JarFileResource)resource;
378         
379         JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
380         return newResource;
381         
382     }
383     
384     /**
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>
387      * @param resource
388      * @return true if resource is contained in the named resource
389      * @throws MalformedURLException
390      */
391     @Override
392     public boolean isContainedIn (Resource resource) 
393     throws MalformedURLException
394     {
395         String string = _urlString;
396         int index = string.indexOf("!/");
397         if (index > 0)
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());     
403     }
404 }
405 
406 
407 
408 
409 
410 
411 
412