View Javadoc

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