Home | History | Annotate | Download | only in resource
      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     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         //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.release();
     87     }
     88 
     89     /* ------------------------------------------------------------ */
     90     @Override
     91     protected 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 = (JarEntry) 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                 if (_directory && !_urlString.endsWith("/"))
    216                 {
    217                     _urlString+="/";
    218                     try
    219                     {
    220                         _url=new URL(_urlString);
    221                     }
    222                     catch(MalformedURLException ex)
    223                     {
    224                         LOG.warn(ex);
    225                     }
    226                 }
    227             }
    228         }
    229 
    230         _exists= ( _directory || _entry!=null);
    231         return _exists;
    232     }
    233 
    234 
    235     /* ------------------------------------------------------------ */
    236     /**
    237      * Returns true if the represented resource is a container/directory.
    238      * If the resource is not a file, resources ending with "/" are
    239      * considered directories.
    240      */
    241     @Override
    242     public boolean isDirectory()
    243     {
    244         return _urlString.endsWith("/") || exists() && _directory;
    245     }
    246 
    247     /* ------------------------------------------------------------ */
    248     /**
    249      * Returns the last modified time
    250      */
    251     @Override
    252     public long lastModified()
    253     {
    254         if (checkConnection() && _file!=null)
    255         {
    256             if (exists() && _entry!=null)
    257                 return _entry.getTime();
    258             return _file.lastModified();
    259         }
    260         return -1;
    261     }
    262 
    263     /* ------------------------------------------------------------ */
    264     @Override
    265     public synchronized String[] list()
    266     {
    267         if (isDirectory() && _list==null)
    268         {
    269             List<String> list = null;
    270             try
    271             {
    272                 list = listEntries();
    273             }
    274             catch (Exception e)
    275             {
    276                 //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
    277                 //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
    278                 //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in
    279                 //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
    280                 //by other code.
    281                 //So, do one retry to drop a connection and get a fresh JarFile
    282                 LOG.warn("Retrying list:"+e);
    283                 LOG.debug(e);
    284                 release();
    285                 list = listEntries();
    286             }
    287 
    288             if (list != null)
    289             {
    290                 _list=new String[list.size()];
    291                 list.toArray(_list);
    292             }
    293         }
    294         return _list;
    295     }
    296 
    297 
    298     /* ------------------------------------------------------------ */
    299     private List<String> listEntries ()
    300     {
    301         checkConnection();
    302 
    303         ArrayList<String> list = new ArrayList<String>(32);
    304         JarFile jarFile=_jarFile;
    305         if(jarFile==null)
    306         {
    307             try
    308             {
    309                 JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
    310                 jc.setUseCaches(getUseCaches());
    311                 jarFile=jc.getJarFile();
    312             }
    313             catch(Exception e)
    314             {
    315 
    316                 e.printStackTrace();
    317                  LOG.ignore(e);
    318             }
    319         }
    320 
    321         Enumeration<JarEntry> e=jarFile.entries();
    322         String dir=_urlString.substring(_urlString.indexOf("!/")+2);
    323         while(e.hasMoreElements())
    324         {
    325             JarEntry entry = e.nextElement();
    326             String name=entry.getName().replace('\\','/');
    327             if(!name.startsWith(dir) || name.length()==dir.length())
    328             {
    329                 continue;
    330             }
    331             String listName=name.substring(dir.length());
    332             int dash=listName.indexOf('/');
    333             if (dash>=0)
    334             {
    335                 //when listing jar:file urls, you get back one
    336                 //entry for the dir itself, which we ignore
    337                 if (dash==0 && listName.length()==1)
    338                     continue;
    339                 //when listing jar:file urls, all files and
    340                 //subdirs have a leading /, which we remove
    341                 if (dash==0)
    342                     listName=listName.substring(dash+1, listName.length());
    343                 else
    344                     listName=listName.substring(0,dash+1);
    345 
    346                 if (list.contains(listName))
    347                     continue;
    348             }
    349 
    350             list.add(listName);
    351         }
    352 
    353         return list;
    354     }
    355 
    356 
    357 
    358 
    359 
    360     /* ------------------------------------------------------------ */
    361     /**
    362      * Return the length of the resource
    363      */
    364     @Override
    365     public long length()
    366     {
    367         if (isDirectory())
    368             return -1;
    369 
    370         if (_entry!=null)
    371             return _entry.getSize();
    372 
    373         return -1;
    374     }
    375 
    376     /* ------------------------------------------------------------ */
    377     /** Encode according to this resource type.
    378      * File URIs are not encoded.
    379      * @param uri URI to encode.
    380      * @return The uri unchanged.
    381      */
    382     @Override
    383     public String encode(String uri)
    384     {
    385         return uri;
    386     }
    387 
    388 
    389     /**
    390      * Take a Resource that possibly might use URLConnection caching
    391      * and turn it into one that doesn't.
    392      * @param resource
    393      * @return the non-caching resource
    394      */
    395     public static Resource getNonCachingResource (Resource resource)
    396     {
    397         if (!(resource instanceof JarFileResource))
    398             return resource;
    399 
    400         JarFileResource oldResource = (JarFileResource)resource;
    401 
    402         JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
    403         return newResource;
    404 
    405     }
    406 
    407     /**
    408      * Check if this jar:file: resource is contained in the
    409      * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
    410      * @param resource
    411      * @return true if resource is contained in the named resource
    412      * @throws MalformedURLException
    413      */
    414     @Override
    415     public boolean isContainedIn (Resource resource)
    416     throws MalformedURLException
    417     {
    418         String string = _urlString;
    419         int index = string.indexOf("!/");
    420         if (index > 0)
    421             string = string.substring(0,index);
    422         if (string.startsWith("jar:"))
    423             string = string.substring(4);
    424         URL url = new URL(string);
    425         return url.sameFile(resource.getURL());
    426     }
    427 }
    428 
    429 
    430 
    431 
    432 
    433 
    434 
    435 
    436