View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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      @Override
62      public synchronized void close()
63      {
64          _exists=false;
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                      if (LOG.isDebugEnabled())
77                          LOG.debug("Closing JarFile "+_jarFile.getName());
78                      _jarFile.close();
79                  }
80                  catch ( IOException ioe )
81                  {
82                      LOG.ignore(ioe);
83                  }
84              }
85          }
86          _jarFile=null;
87          super.close();
88      }
89      
90      /* ------------------------------------------------------------ */
91      @Override
92      protected synchronized boolean checkConnection()
93      {
94          try
95          {
96              super.checkConnection();
97          }
98          finally
99          {
100             if (_jarConnection==null)
101             {
102                 _entry=null;
103                 _file=null;
104                 _jarFile=null;
105                 _list=null;
106             }
107         }
108         return _jarFile!=null;
109     }
110 
111 
112     /* ------------------------------------------------------------ */
113     @Override
114     protected synchronized void newConnection()
115         throws IOException
116     {
117         super.newConnection();
118         
119         _entry=null;
120         _file=null;
121         _jarFile=null;
122         _list=null;
123         
124         int sep = _urlString.indexOf("!/");
125         _jarUrl=_urlString.substring(0,sep+2);
126         _path=_urlString.substring(sep+2);
127         if (_path.length()==0)
128             _path=null;   
129         _jarFile=_jarConnection.getJarFile();
130         _file=new File(_jarFile.getName());
131     }
132     
133     
134     /* ------------------------------------------------------------ */
135     /**
136      * Returns true if the represented resource exists.
137      */
138     @Override
139 
140     public boolean exists()
141     {
142         if (_exists)
143             return true;
144 
145         if (_urlString.endsWith("!/"))
146         {
147             String file_url=_urlString.substring(4,_urlString.length()-2);
148             try{return newResource(file_url).exists();}
149             catch(Exception e) {LOG.ignore(e); return false;}
150         }
151         
152         boolean check=checkConnection();
153         
154         // Is this a root URL?
155         if (_jarUrl!=null && _path==null)
156         {
157             // Then if it exists it is a directory
158             _directory=check;
159             return true;
160         }
161         else 
162         {
163             // Can we find a file for it?
164             boolean close_jar_file= false;
165             JarFile jar_file=null;
166             if (check)
167                 // Yes
168                 jar_file=_jarFile;
169             else
170             {
171                 // No - so lets look if the root entry exists.
172                 try
173                 {
174                     JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
175                     c.setUseCaches(getUseCaches());
176                     jar_file=c.getJarFile();
177                     close_jar_file = !getUseCaches();
178                 }
179                 catch(Exception e)
180                 {
181                     LOG.ignore(e);
182                 }
183             }
184 
185             // Do we need to look more closely?
186             if (jar_file!=null && _entry==null && !_directory)
187             {
188                 // OK - we have a JarFile, lets look for the entry
189                 JarEntry entry = jar_file.getJarEntry(_path);
190                 if (entry == null) 
191                 {
192                     // the entry does not exist
193                     _exists = false;
194                 } 
195                 else if (entry.isDirectory()) 
196                 {
197                     _directory = true;
198                     _entry = entry;
199                 } 
200                 else 
201                 {
202                     // Let's confirm is a file
203                     JarEntry directory = jar_file.getJarEntry(_path + '/');
204                     if (directory != null) 
205                     {
206                         _directory = true;
207                         _entry = directory;
208                     } 
209                     else 
210                     {
211                         // OK is a file
212                       _directory = false;
213                       _entry = entry;
214                     }
215                 }
216             }
217 
218             if(close_jar_file && jar_file!=null) 
219             {
220                 try 
221                 {
222                     jar_file.close();
223                 } 
224                 catch (IOException ioe) 
225                 {
226                     LOG.ignore(ioe);
227                 }
228             }
229         }
230         
231         _exists= ( _directory || _entry!=null);
232         return _exists;
233     }
234 
235     
236     /* ------------------------------------------------------------ */
237     /**
238      * Returns true if the represented resource is a container/directory.
239      * If the resource is not a file, resources ending with "/" are
240      * considered directories.
241      */
242     @Override
243     public boolean isDirectory()
244     {
245         return _urlString.endsWith("/") || exists() && _directory;
246     }
247     
248     /* ------------------------------------------------------------ */
249     /**
250      * Returns the last modified time
251      */
252     @Override
253     public long lastModified()
254     {
255         if (checkConnection() && _file!=null)
256         {
257             if (exists() && _entry!=null)
258                 return _entry.getTime();
259             return _file.lastModified();
260         }
261         return -1;
262     }
263 
264     /* ------------------------------------------------------------ */
265     @Override
266     public synchronized String[] list()
267     {
268         if (isDirectory() && _list==null)
269         {
270             List<String> list = null;
271             try
272             {
273                 list = listEntries();
274             }
275             catch (Exception e)
276             {
277                 //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
278                 //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
279                 //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in 
280                 //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
281                 //by other code.
282                 //So, do one retry to drop a connection and get a fresh JarFile
283                 LOG.warn("Retrying list:"+e);
284                 LOG.debug(e);
285                 close();
286                 list = listEntries();
287             }
288 
289             if (list != null)
290             {
291                 _list=new String[list.size()];
292                 list.toArray(_list);
293             }  
294         }
295         return _list;
296     }
297     
298     
299     /* ------------------------------------------------------------ */
300     private List<String> listEntries ()
301     {
302         checkConnection();
303         
304         ArrayList<String> list = new ArrayList<String>(32);
305         JarFile jarFile=_jarFile;
306         if(jarFile==null)
307         {
308             try
309             {
310                 JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
311                 jc.setUseCaches(getUseCaches());
312                 jarFile=jc.getJarFile();
313             }
314             catch(Exception e)
315             {
316 
317                 e.printStackTrace();
318                  LOG.ignore(e);
319             }
320                 if(jarFile==null)
321                     throw new IllegalStateException();
322         }
323         
324         Enumeration<JarEntry> e=jarFile.entries();
325         String dir=_urlString.substring(_urlString.indexOf("!/")+2);
326         while(e.hasMoreElements())
327         {
328             JarEntry entry = e.nextElement();               
329             String name=entry.getName().replace('\\','/');               
330             if(!name.startsWith(dir) || name.length()==dir.length())
331             {
332                 continue;
333             }
334             String listName=name.substring(dir.length());               
335             int dash=listName.indexOf('/');
336             if (dash>=0)
337             {
338                 //when listing jar:file urls, you get back one
339                 //entry for the dir itself, which we ignore
340                 if (dash==0 && listName.length()==1)
341                     continue;
342                 //when listing jar:file urls, all files and
343                 //subdirs have a leading /, which we remove
344                 if (dash==0)
345                     listName=listName.substring(dash+1, listName.length());
346                 else
347                     listName=listName.substring(0,dash+1);
348                 
349                 if (list.contains(listName))
350                     continue;
351             }
352             
353             list.add(listName);
354         }
355         
356         return list;
357     }
358     
359     
360     
361     
362     
363     /* ------------------------------------------------------------ */
364     /**
365      * Return the length of the resource
366      */
367     @Override
368     public long length()
369     {
370         if (isDirectory())
371             return -1;
372 
373         if (_entry!=null)
374             return _entry.getSize();
375         
376         return -1;
377     }
378 
379     
380     /**
381      * Take a Resource that possibly might use URLConnection caching
382      * and turn it into one that doesn't.
383      * @param resource the JarFileResource to obtain without URLConnection caching. 
384      * @return the non-caching resource
385      */
386     public static Resource getNonCachingResource (Resource resource)
387     {
388         if (!(resource instanceof JarFileResource))
389             return resource;
390         
391         JarFileResource oldResource = (JarFileResource)resource;
392         
393         JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
394         return newResource;
395         
396     }
397     
398     /**
399      * Check if this jar:file: resource is contained in the
400      * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
401      * @param resource the resource to test for
402      * @return true if resource is contained in the named resource
403      * @throws MalformedURLException if unable to process is contained due to invalid URL format
404      */
405     @Override
406     public boolean isContainedIn (Resource resource) 
407     throws MalformedURLException
408     {
409         String string = _urlString;
410         int index = string.indexOf("!/");
411         if (index > 0)
412             string = string.substring(0,index);
413         if (string.startsWith("jar:"))
414             string = string.substring(4);
415         URL url = new URL(string);
416         return url.sameFile(resource.getURL());     
417     }
418 }