001: // ========================================================================
002: // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014: package org.mortbay.resource;
015:
016: import java.io.File;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.io.OutputStream;
020: import java.io.Serializable;
021: import java.net.MalformedURLException;
022: import java.net.URL;
023: import java.net.URLConnection;
024: import java.text.DateFormat;
025: import java.util.Arrays;
026: import java.util.Date;
027:
028: import org.mortbay.log.Log;
029: import org.mortbay.util.IO;
030: import org.mortbay.util.Loader;
031: import org.mortbay.util.StringUtil;
032: import org.mortbay.util.URIUtil;
033:
034: /* ------------------------------------------------------------ */
035: /** Abstract resource class.
036: *
037: * @author Nuno Pregui�a
038: * @author Greg Wilkins (gregw)
039: */
040: public abstract class Resource implements Serializable {
041: public static boolean __defaultUseCaches = true;
042: Object _associate;
043:
044: /**
045: * Change the default setting for url connection caches.
046: * Subsequent URLConnections will use this default.
047: * @param useCaches
048: */
049: public static void setDefaultUseCaches(boolean useCaches) {
050: __defaultUseCaches = useCaches;
051: }
052:
053: public static boolean getDefaultUseCaches() {
054: return __defaultUseCaches;
055: }
056:
057: /* ------------------------------------------------------------ */
058: /** Construct a resource from a url.
059: * @param url A URL.
060: * @return A Resource object.
061: */
062: public static Resource newResource(URL url) throws IOException {
063: return newResource(url, __defaultUseCaches);
064: }
065:
066: /* ------------------------------------------------------------ */
067: /**
068: * Construct a resource from a url.
069: * @param url the url for which to make the resource
070: * @param useCaches true enables URLConnection caching if applicable to the type of resource
071: * @return
072: */
073: public static Resource newResource(URL url, boolean useCaches) {
074: if (url == null)
075: return null;
076:
077: String url_string = url.toExternalForm();
078: if (url_string.startsWith("file:")) {
079: try {
080: FileResource fileResource = new FileResource(url);
081: return fileResource;
082: } catch (Exception e) {
083: Log.debug(Log.EXCEPTION, e);
084: return new BadResource(url, e.toString());
085: }
086: } else if (url_string.startsWith("jar:file:")) {
087: return new JarFileResource(url, useCaches);
088: } else if (url_string.startsWith("jar:")) {
089: return new JarResource(url, useCaches);
090: }
091:
092: return new URLResource(url, null, useCaches);
093: }
094:
095: /* ------------------------------------------------------------ */
096: /** Construct a resource from a string.
097: * @param resource A URL or filename.
098: * @return A Resource object.
099: */
100: public static Resource newResource(String resource)
101: throws MalformedURLException, IOException {
102: return newResource(resource, __defaultUseCaches);
103: }
104:
105: /* ------------------------------------------------------------ */
106: /** Construct a resource from a string.
107: * @param resource A URL or filename.
108: * @param useCaches controls URLConnection caching
109: * @return A Resource object.
110: */
111: public static Resource newResource(String resource,
112: boolean useCaches) throws MalformedURLException,
113: IOException {
114: URL url = null;
115: try {
116: // Try to format as a URL?
117: url = new URL(resource);
118: } catch (MalformedURLException e) {
119: if (!resource.startsWith("ftp:")
120: && !resource.startsWith("file:")
121: && !resource.startsWith("jar:")) {
122: try {
123: // It's a file.
124: if (resource.startsWith("./"))
125: resource = resource.substring(2);
126:
127: File file = new File(resource).getCanonicalFile();
128: url = file.toURI().toURL();
129:
130: URLConnection connection = url.openConnection();
131: connection.setUseCaches(useCaches);
132: FileResource fileResource = new FileResource(url,
133: connection, file);
134: return fileResource;
135: } catch (Exception e2) {
136: Log.debug(Log.EXCEPTION, e2);
137: throw e;
138: }
139: } else {
140: Log.warn("Bad Resource: " + resource);
141: throw e;
142: }
143: }
144:
145: // Make sure that any special characters stripped really are ignorable.
146: String nurl = url.toString();
147: if (nurl.length() > 0
148: && nurl.charAt(nurl.length() - 1) != resource
149: .charAt(resource.length() - 1)) {
150: if ((nurl.charAt(nurl.length() - 1) != '/' || nurl
151: .charAt(nurl.length() - 2) != resource
152: .charAt(resource.length() - 1))
153: && (resource.charAt(resource.length() - 1) != '/' || resource
154: .charAt(resource.length() - 2) != nurl
155: .charAt(nurl.length() - 1))) {
156: return new BadResource(url,
157: "Trailing special characters stripped by URL in "
158: + resource);
159: }
160: }
161: return newResource(url);
162: }
163:
164: /* ------------------------------------------------------------ */
165: /** Construct a system resource from a string.
166: * The resource is tried as classloader resource before being
167: * treated as a normal resource.
168: */
169: public static Resource newSystemResource(String resource)
170: throws IOException {
171: URL url = null;
172: // Try to format as a URL?
173: ClassLoader loader = Thread.currentThread()
174: .getContextClassLoader();
175: if (loader != null) {
176: url = loader.getResource(resource);
177: if (url == null && resource.startsWith("/"))
178: url = loader.getResource(resource.substring(1));
179: }
180: if (url == null) {
181: loader = Resource.class.getClassLoader();
182: if (loader != null) {
183: url = loader.getResource(resource);
184: if (url == null && resource.startsWith("/"))
185: url = loader.getResource(resource.substring(1));
186: }
187: }
188:
189: if (url == null) {
190: url = ClassLoader.getSystemResource(resource);
191: if (url == null && resource.startsWith("/"))
192: url = loader.getResource(resource.substring(1));
193: }
194:
195: if (url == null)
196: return null;
197:
198: return newResource(url);
199: }
200:
201: /* ------------------------------------------------------------ */
202: /** Find a classpath resource.
203: */
204: public static Resource newClassPathResource(String resource) {
205: return newClassPathResource(resource, true, false);
206: }
207:
208: /* ------------------------------------------------------------ */
209: /** Find a classpath resource.
210: * The {@java.lang.Class#getResource} method is used to lookup the resource. If it is not
211: * found, then the {@link Loader#getResource(Class, String, boolean)} method is used.
212: * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
213: * Unlike {@link #getSystemResource} this method does not check for normal resources.
214: * @param name The relative name of the resouce
215: * @param useCaches True if URL caches are to be used.
216: * @param checkParents True if forced searching of parent classloaders is performed to work around
217: * loaders with inverted priorities
218: * @return Resource or null
219: */
220: public static Resource newClassPathResource(String name,
221: boolean useCaches, boolean checkParents) {
222: URL url = Resource.class.getResource(name);
223:
224: if (url == null) {
225: try {
226: url = Loader.getResource(Resource.class, name,
227: checkParents);
228: } catch (ClassNotFoundException e) {
229: url = ClassLoader.getSystemResource(name);
230: }
231: }
232: if (url == null)
233: return null;
234: return newResource(url, useCaches);
235: }
236:
237: /* ------------------------------------------------------------ */
238: protected void finalize() {
239: release();
240: }
241:
242: /* ------------------------------------------------------------ */
243: /** Release any resources held by the resource.
244: */
245: public abstract void release();
246:
247: /* ------------------------------------------------------------ */
248: /**
249: * Returns true if the respresened resource exists.
250: */
251: public abstract boolean exists();
252:
253: /* ------------------------------------------------------------ */
254: /**
255: * Returns true if the respresenetd resource is a container/directory.
256: * If the resource is not a file, resources ending with "/" are
257: * considered directories.
258: */
259: public abstract boolean isDirectory();
260:
261: /* ------------------------------------------------------------ */
262: /**
263: * Returns the last modified time
264: */
265: public abstract long lastModified();
266:
267: /* ------------------------------------------------------------ */
268: /**
269: * Return the length of the resource
270: */
271: public abstract long length();
272:
273: /* ------------------------------------------------------------ */
274: /**
275: * Returns an URL representing the given resource
276: */
277: public abstract URL getURL();
278:
279: /* ------------------------------------------------------------ */
280: /**
281: * Returns an File representing the given resource or NULL if this
282: * is not possible.
283: */
284: public abstract File getFile() throws IOException;
285:
286: /* ------------------------------------------------------------ */
287: /**
288: * Returns the name of the resource
289: */
290: public abstract String getName();
291:
292: /* ------------------------------------------------------------ */
293: /**
294: * Returns an input stream to the resource
295: */
296: public abstract InputStream getInputStream()
297: throws java.io.IOException;
298:
299: /* ------------------------------------------------------------ */
300: /**
301: * Returns an output stream to the resource
302: */
303: public abstract OutputStream getOutputStream()
304: throws java.io.IOException, SecurityException;
305:
306: /* ------------------------------------------------------------ */
307: /**
308: * Deletes the given resource
309: */
310: public abstract boolean delete() throws SecurityException;
311:
312: /* ------------------------------------------------------------ */
313: /**
314: * Rename the given resource
315: */
316: public abstract boolean renameTo(Resource dest)
317: throws SecurityException;
318:
319: /* ------------------------------------------------------------ */
320: /**
321: * Returns a list of resource names contained in the given resource
322: * The resource names are not URL encoded.
323: */
324: public abstract String[] list();
325:
326: /* ------------------------------------------------------------ */
327: /**
328: * Returns the resource contained inside the current resource with the
329: * given name.
330: * @param path The path segment to add, which should be encoded by the
331: * encode method.
332: */
333: public abstract Resource addPath(String path) throws IOException,
334: MalformedURLException;
335:
336: /* ------------------------------------------------------------ */
337: /** Encode according to this resource type.
338: * The default implementation calls URI.encodePath(uri)
339: * @param uri
340: * @return String encoded for this resource type.
341: */
342: public String encode(String uri) {
343: return URIUtil.encodePath(uri);
344: }
345:
346: /* ------------------------------------------------------------ */
347: public Object getAssociate() {
348: return _associate;
349: }
350:
351: /* ------------------------------------------------------------ */
352: public void setAssociate(Object o) {
353: _associate = o;
354: }
355:
356: /* ------------------------------------------------------------ */
357: /**
358: * @return The canonical Alias of this resource or null if none.
359: */
360: public URL getAlias() {
361: return null;
362: }
363:
364: /* ------------------------------------------------------------ */
365: /** Get the resource list as a HTML directory listing.
366: * @param base The base URL
367: * @param parent True if the parent directory should be included
368: * @return String of HTML
369: */
370: public String getListHTML(String base, boolean parent)
371: throws IOException {
372: if (!isDirectory())
373: return null;
374:
375: String[] ls = list();
376: if (ls == null)
377: return null;
378: Arrays.sort(ls);
379:
380: String title = "Directory: " + base;
381:
382: StringBuffer buf = new StringBuffer(4096);
383: buf.append("<HTML><HEAD><TITLE>");
384: buf.append(title);
385: buf.append("</TITLE></HEAD><BODY>\n<H1>");
386: buf.append(title);
387: buf.append("</H1><TABLE BORDER=0>");
388:
389: if (parent) {
390: buf.append("<TR><TD><A HREF=");
391: buf.append(URIUtil
392: .encodePath(URIUtil.addPaths(base, "../")));
393: buf
394: .append(">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
395: }
396:
397: DateFormat dfmt = DateFormat.getDateTimeInstance(
398: DateFormat.MEDIUM, DateFormat.MEDIUM);
399: for (int i = 0; i < ls.length; i++) {
400: String encoded = URIUtil.encodePath(ls[i]);
401: Resource item = addPath(encoded);
402:
403: buf.append("<TR><TD><A HREF=\"");
404: String path = URIUtil.addPaths(base, encoded);
405:
406: if (item.isDirectory() && !path.endsWith("/"))
407: path = URIUtil.addPaths(path, "/");
408: buf.append(path);
409: buf.append("\">");
410: buf.append(StringUtil.replace(StringUtil.replace(ls[i],
411: "<", "<"), ">", ">"));
412: buf.append(" ");
413: buf.append("</TD><TD ALIGN=right>");
414: buf.append(item.length());
415: buf.append(" bytes </TD><TD>");
416: buf.append(dfmt.format(new Date(item.lastModified())));
417: buf.append("</TD></TR>\n");
418: }
419: buf.append("</TABLE>\n");
420: buf.append("</BODY></HTML>\n");
421:
422: return buf.toString();
423: }
424:
425: /* ------------------------------------------------------------ */
426: /**
427: * @param out
428: * @param start First byte to write
429: * @param count Bytes to write or -1 for all of them.
430: */
431: public void writeTo(OutputStream out, long start, long count)
432: throws IOException {
433: InputStream in = getInputStream();
434: try {
435: in.skip(start);
436: if (count < 0)
437: IO.copy(in, out);
438: else
439: IO.copy(in, out, (int) count);
440: } finally {
441: in.close();
442: }
443: }
444: }
|