View Javadoc

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