View Javadoc

    1   // ========================================================================
    2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14  package org.mortbay.resource;
    15
    16  import java.io.File;
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.io.OutputStream;
    20  import java.io.Serializable;
    21  import java.net.MalformedURLException;
    22  import java.net.URL;
    23  import java.net.URLConnection;
    24  import java.text.DateFormat;
    25  import java.util.Arrays;
    26  import java.util.Date;
    27
    28  import org.mortbay.log.Log;
    29  import org.mortbay.util.IO;
    30  import org.mortbay.util.Loader;
    31  import org.mortbay.util.StringUtil;
    32  import org.mortbay.util.URIUtil;
    33
    34
    35  /* ------------------------------------------------------------ */
    36  /** Abstract resource class.
    37   *
    38   * @author Nuno Pregui�a
    39   * @author Greg Wilkins (gregw)
    40   */
    41  public abstract class Resource implements Serializable
    42  {
    43      public static boolean __defaultUseCaches = true;
    44      Object _associate;
    45
    46      /**
    47       * Change the default setting for url connection caches.
    48       * Subsequent URLConnections will use this default.
    49       * @param useCaches
    50       */
    51      public static void setDefaultUseCaches (boolean useCaches)
    52      {
    53          __defaultUseCaches=useCaches;
    54      }
    55
    56      public static boolean getDefaultUseCaches ()
    57      {
    58          return __defaultUseCaches;
    59      }
    60
    61      /* ------------------------------------------------------------ */
    62      /** Construct a resource from a url.
    63       * @param url A URL.
    64       * @return A Resource object.
    65       */
    66      public static Resource newResource(URL url)
    67          throws IOException
    68      {
    69          return newResource(url, __defaultUseCaches);
    70      }
    71
    72      /* ------------------------------------------------------------ */
    73      /**
    74       * Construct a resource from a url.
    75       * @param url the url for which to make the resource
    76       * @param useCaches true enables URLConnection caching if applicable to the type of resource
    77       * @return
    78       */
    79      public static Resource newResource(URL url, boolean useCaches)
    80      {
    81          if (url==null)
    82              return null;
    83
    84          String url_string=url.toExternalForm();
    85          if( url_string.startsWith( "file:"))
    86          {
    87              try
    88              {
    89                  FileResource fileResource= new FileResource(url);
    90                  return fileResource;
    91              }
    92              catch(Exception e)
    93              {
    94                  Log.debug(Log.EXCEPTION,e);
    95                  return new BadResource(url,e.toString());
    96              }
    97          }
    98          else if( url_string.startsWith( "jar:file:"))
    99          {
    100             return new JarFileResource(url, useCaches);
    101         }
    102         else if( url_string.startsWith( "jar:"))
    103         {
    104             return new JarResource(url, useCaches);
    105         }
    106
    107         return new URLResource(url,null,useCaches);
    108     }
    109
    110
    111
    112     /* ------------------------------------------------------------ */
    113     /** Construct a resource from a string.
    114      * @param resource A URL or filename.
    115      * @return A Resource object.
    116      */
    117     public static Resource newResource(String resource)
    118         throws MalformedURLException, IOException
    119     {
    120         return newResource(resource, __defaultUseCaches);
    121     }
    122
    123     /* ------------------------------------------------------------ */
    124     /** Construct a resource from a string.
    125      * @param resource A URL or filename.
    126      * @param useCaches controls URLConnection caching
    127      * @return A Resource object.
    128      */
    129     public static Resource newResource (String resource, boolean useCaches)
    130     throws MalformedURLException, IOException
    131     {
    132         URL url=null;
    133         try
    134         {
    135             // Try to format as a URL?
    136             url = new URL(resource);
    137         }
    138         catch(MalformedURLException e)
    139         {
    140             if(!resource.startsWith("ftp:") &&
    141                !resource.startsWith("file:") &&
    142                !resource.startsWith("jar:"))
    143             {
    144                 try
    145                 {
    146                     // It's a file.
    147                     if (resource.startsWith("./"))
    148                         resource=resource.substring(2);
    149
    150                     File file=new File(resource).getCanonicalFile();
    151                     url=new URL(URIUtil.encodePath(file.toURL().toString()));
    152
    153                     URLConnection connection=url.openConnection();
    154                     connection.setUseCaches(useCaches);
    155                     FileResource fileResource= new FileResource(url,connection,file);
    156                     return fileResource;
    157                 }
    158                 catch(Exception e2)
    159                 {
    160                     Log.debug(Log.EXCEPTION,e2);
    161                     throw e;
    162                 }
    163             }
    164             else
    165             {
    166                 Log.warn("Bad Resource: "+resource);
    167                 throw e;
    168             }
    169         }
    170
    171         // Make sure that any special characters stripped really are ignorable.
    172         String nurl=url.toString();
    173         if (nurl.length()>0 &&  nurl.charAt(nurl.length()-1)!=resource.charAt(resource.length()-1))
    174         {
    175             if ((nurl.charAt(nurl.length()-1)!='/' ||
    176                  nurl.charAt(nurl.length()-2)!=resource.charAt(resource.length()-1))
    177                 &&
    178                 (resource.charAt(resource.length()-1)!='/' ||
    179                  resource.charAt(resource.length()-2)!=nurl.charAt(nurl.length()-1)
    180                  ))
    181             {
    182                 return new BadResource(url,"Trailing special characters stripped by URL in "+resource);
    183             }
    184         }
    185         return newResource(url);
    186     }
    187
    188     /* ------------------------------------------------------------ */
    189     /** Construct a system resource from a string.
    190      * The resource is tried as classloader resource before being
    191      * treated as a normal resource.
    192      */
    193     public static Resource newSystemResource(String resource)
    194         throws IOException
    195     {
    196         URL url=null;
    197         // Try to format as a URL?
    198         ClassLoader
    199             loader=Thread.currentThread().getContextClassLoader();
    200         if (loader!=null)
    201         {
    202             url=loader.getResource(resource);
    203             if (url==null && resource.startsWith("/"))
    204                 url=loader.getResource(resource.substring(1));
    205         }
    206         if (url==null)
    207         {
    208             loader=Resource.class.getClassLoader();
    209             if (loader!=null)
    210             {
    211                 url=loader.getResource(resource);
    212                 if (url==null && resource.startsWith("/"))
    213                     url=loader.getResource(resource.substring(1));
    214             }
    215         }
    216
    217         if (url==null)
    218         {
    219             url=ClassLoader.getSystemResource(resource);
    220             if (url==null && resource.startsWith("/"))
    221                 url=loader.getResource(resource.substring(1));
    222         }
    223
    224         if (url==null)
    225             return null;
    226
    227         return newResource(url);
    228     }
    229
    230     /* ------------------------------------------------------------ */
    231     /** Find a classpath resource.
    232      */
    233     public static Resource newClassPathResource(String resource)
    234     {
    235         return newClassPathResource(resource,true,false);
    236     }
    237
    238     /* ------------------------------------------------------------ */
    239     /** Find a classpath resource.
    240      * The {@java.lang.Class#getResource} method is used to lookup the resource. If it is not
    241      * found, then the {@link Loader#getResource(Class, String, boolean)} method is used.
    242      * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
    243      * Unlike {@link #getSystemResource} this method does not check for normal resources.
    244      * @param name The relative name of the resouce
    245      * @param useCaches True if URL caches are to be used.
    246      * @param checkParents True if forced searching of parent classloaders is performed to work around
    247      * loaders with inverted priorities
    248      * @return Resource or null
    249      */
    250     public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
    251     {
    252         URL url=Resource.class.getResource(name);
    253
    254         if (url==null)
    255         {
    256             try
    257             {
    258                 url=Loader.getResource(Resource.class,name,checkParents);
    259             }
    260             catch(ClassNotFoundException e)
    261             {
    262                 url=ClassLoader.getSystemResource(name);
    263             }
    264         }
    265         if (url==null)
    266             return null;
    267         return newResource(url,useCaches);
    268     }
    269
    270
    271
    272     /* ------------------------------------------------------------ */
    273     protected void finalize()
    274     {
    275         release();
    276     }
    277
    278     /* ------------------------------------------------------------ */
    279     /** Release any resources held by the resource.
    280      */
    281     public abstract void release();
    282
    283
    284     /* ------------------------------------------------------------ */
    285     /**
    286      * Returns true if the respresened resource exists.
    287      */
    288     public abstract boolean exists();
    289
    290
    291     /* ------------------------------------------------------------ */
    292     /**
    293      * Returns true if the respresenetd resource is a container/directory.
    294      * If the resource is not a file, resources ending with "/" are
    295      * considered directories.
    296      */
    297     public abstract boolean isDirectory();
    298
    299     /* ------------------------------------------------------------ */
    300     /**
    301      * Returns the last modified time
    302      */
    303     public abstract long lastModified();
    304
    305
    306     /* ------------------------------------------------------------ */
    307     /**
    308      * Return the length of the resource
    309      */
    310     public abstract long length();
    311
    312
    313     /* ------------------------------------------------------------ */
    314     /**
    315      * Returns an URL representing the given resource
    316      */
    317     public abstract URL getURL();
    318
    319
    320     /* ------------------------------------------------------------ */
    321     /**
    322      * Returns an File representing the given resource or NULL if this
    323      * is not possible.
    324      */
    325     public abstract File getFile()
    326         throws IOException;
    327
    328
    329     /* ------------------------------------------------------------ */
    330     /**
    331      * Returns the name of the resource
    332      */
    333     public abstract String getName();
    334
    335
    336     /* ------------------------------------------------------------ */
    337     /**
    338      * Returns an input stream to the resource
    339      */
    340     public abstract InputStream getInputStream()
    341         throws java.io.IOException;
    342
    343     /* ------------------------------------------------------------ */
    344     /**
    345      * Returns an output stream to the resource
    346      */
    347     public abstract OutputStream getOutputStream()
    348         throws java.io.IOException, SecurityException;
    349
    350     /* ------------------------------------------------------------ */
    351     /**
    352      * Deletes the given resource
    353      */
    354     public abstract boolean delete()
    355         throws SecurityException;
    356
    357     /* ------------------------------------------------------------ */
    358     /**
    359      * Rename the given resource
    360      */
    361     public abstract boolean renameTo( Resource dest)
    362         throws SecurityException;
    363
    364     /* ------------------------------------------------------------ */
    365     /**
    366      * Returns a list of resource names contained in the given resource
    367      * The resource names are not URL encoded.
    368      */
    369     public abstract String[] list();
    370
    371     /* ------------------------------------------------------------ */
    372     /**
    373      * Returns the resource contained inside the current resource with the
    374      * given name.
    375      * @param path The path segment to add, which should be encoded by the
    376      * encode method.
    377      */
    378     public abstract Resource addPath(String path)
    379         throws IOException,MalformedURLException;
    380
    381
    382     /* ------------------------------------------------------------ */
    383     /** Encode according to this resource type.
    384      * The default implementation calls URI.encodePath(uri)
    385      * @param uri
    386      * @return String encoded for this resource type.
    387      */
    388     public String encode(String uri)
    389     {
    390         return URIUtil.encodePath(uri);
    391     }
    392
    393     /* ------------------------------------------------------------ */
    394     public Object getAssociate()
    395     {
    396         return _associate;
    397     }
    398
    399     /* ------------------------------------------------------------ */
    400     public void setAssociate(Object o)
    401     {
    402         _associate=o;
    403     }
    404
    405     /* ------------------------------------------------------------ */
    406     /**
    407      * @return The canonical Alias of this resource or null if none.
    408      */
    409     public URL getAlias()
    410     {
    411         return null;
    412     }
    413
    414     /* ------------------------------------------------------------ */
    415     /** Get the resource list as a HTML directory listing.
    416      * @param base The base URL
    417      * @param parent True if the parent directory should be included
    418      * @return String of HTML
    419      */
    420     public String getListHTML(String base,
    421                               boolean parent)
    422         throws IOException
    423     {
    424         if (!isDirectory())
    425             return null;
    426
    427         String[] ls = list();
    428         if (ls==null)
    429             return null;
    430         Arrays.sort(ls);
    431
    432         String decodedBase = URIUtil.decodePath(base);
    433         String title = "Directory: "+decodedBase;
    434
    435         StringBuffer buf=new StringBuffer(4096);
    436         buf.append("<HTML><HEAD><TITLE>");
    437         buf.append(title);
    438         buf.append("</TITLE></HEAD><BODY>\n<H1>");
    439         buf.append(title);
    440         buf.append("</H1><TABLE BORDER=0>");
    441
    442         if (parent)
    443         {
    444             buf.append("<TR><TD><A HREF=");
    445             buf.append(URIUtil.addPaths(base,"../"));
    446             buf.append(">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
    447         }
    448
    449         DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
    450                                                        DateFormat.MEDIUM);
    451         for (int i=0 ; i< ls.length ; i++)
    452         {
    453             String encoded=URIUtil.encodePath(ls[i]);
    454             Resource item = addPath(ls[i]);
    455
    456             buf.append("<TR><TD><A HREF=\"");
    457             String path=URIUtil.addPaths(base,encoded);
    458
    459             if (item.isDirectory() && !path.endsWith("/"))
    460                 path=URIUtil.addPaths(path,URIUtil.SLASH);
    461             buf.append(path);
    462             buf.append("\">");
    463             buf.append(StringUtil.replace(StringUtil.replace(ls[i],"<","&lt;"),">","&gt;"));
    464             buf.append("&nbsp;");
    465             buf.append("</TD><TD ALIGN=right>");
    466             buf.append(item.length());
    467             buf.append(" bytes&nbsp;</TD><TD>");
    468             buf.append(dfmt.format(new Date(item.lastModified())));
    469             buf.append("</TD></TR>\n");
    470         }
    471         buf.append("</TABLE>\n");
    472 	buf.append("</BODY></HTML>\n");
    473
    474         return buf.toString();
    475     }
    476
    477     /* ------------------------------------------------------------ */
    478     /**
    479      * @param out
    480      * @param start First byte to write
    481      * @param count Bytes to write or -1 for all of them.
    482      */
    483     public void writeTo(OutputStream out,long start,long count)
    484         throws IOException
    485     {
    486         InputStream in = getInputStream();
    487         try
    488         {
    489             in.skip(start);
    490             if (count<0)
    491                 IO.copy(in,out);
    492             else
    493                 IO.copy(in,out,count);
    494         }
    495         finally
    496         {
    497             in.close();
    498         }
    499     }
    500 }