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],"<","<"),">",">")); 464 buf.append(" "); 465 buf.append("</TD><TD ALIGN=right>"); 466 buf.append(item.length()); 467 buf.append(" bytes </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