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      */
setDefaultUseCaches(boolean useCaches)51     public static void setDefaultUseCaches (boolean useCaches)
52     {
53         __defaultUseCaches=useCaches;
54     }
55 
getDefaultUseCaches()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      */
newResource(URL url)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      */
newResource(URL url, boolean useCaches)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      */
newResource(String resource)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      */
newResource(String resource, boolean useCaches)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      */
newSystemResource(String resource)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      */
newClassPathResource(String resource)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      */
newClassPathResource(String name,boolean useCaches,boolean checkParents)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     /* ------------------------------------------------------------ */
finalize()273     protected void finalize()
274     {
275         release();
276     }
277 
278     /* ------------------------------------------------------------ */
279     /** Release any resources held by the resource.
280      */
release()281     public abstract void release();
282 
283 
284     /* ------------------------------------------------------------ */
285     /**
286      * Returns true if the respresened resource exists.
287      */
exists()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      */
isDirectory()297     public abstract boolean isDirectory();
298 
299     /* ------------------------------------------------------------ */
300     /**
301      * Returns the last modified time
302      */
lastModified()303     public abstract long lastModified();
304 
305 
306     /* ------------------------------------------------------------ */
307     /**
308      * Return the length of the resource
309      */
length()310     public abstract long length();
311 
312 
313     /* ------------------------------------------------------------ */
314     /**
315      * Returns an URL representing the given resource
316      */
getURL()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      */
getFile()325     public abstract File getFile()
326         throws IOException;
327 
328 
329     /* ------------------------------------------------------------ */
330     /**
331      * Returns the name of the resource
332      */
getName()333     public abstract String getName();
334 
335 
336     /* ------------------------------------------------------------ */
337     /**
338      * Returns an input stream to the resource
339      */
getInputStream()340     public abstract InputStream getInputStream()
341         throws java.io.IOException;
342 
343     /* ------------------------------------------------------------ */
344     /**
345      * Returns an output stream to the resource
346      */
getOutputStream()347     public abstract OutputStream getOutputStream()
348         throws java.io.IOException, SecurityException;
349 
350     /* ------------------------------------------------------------ */
351     /**
352      * Deletes the given resource
353      */
delete()354     public abstract boolean delete()
355         throws SecurityException;
356 
357     /* ------------------------------------------------------------ */
358     /**
359      * Rename the given resource
360      */
renameTo( Resource dest)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      */
list()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      */
addPath(String path)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      */
encode(String uri)388     public String encode(String uri)
389     {
390         return URIUtil.encodePath(uri);
391     }
392 
393     /* ------------------------------------------------------------ */
getAssociate()394     public Object getAssociate()
395     {
396         return _associate;
397     }
398 
399     /* ------------------------------------------------------------ */
setAssociate(Object o)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      */
getAlias()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      */
getListHTML(String base, boolean parent)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      */
writeTo(OutputStream out,long start,long count)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 }
501