001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util.xml;
011:
012: import javax.xml.transform.*;
013: import javax.xml.transform.stream.*;
014: import java.io.*;
015: import java.net.*;
016: import java.util.*;
017:
018: import org.mmbase.util.SizeMeasurable;
019: import org.mmbase.util.ResourceLoader;
020: import org.mmbase.util.logging.Logger;
021: import org.mmbase.util.logging.Logging;
022:
023: /**
024: * This URIResolver can be used to resolve URI's, also in TransformerFactory's.
025: *
026: * It has knowledge of a kind of path (as used by shells). Every entry
027: * of this path is labeled with a 'prefix'.
028: *
029: * This path always has at least (and on default) two entries:
030:
031: <ol>
032: <li> Current working directory (prefix: none or 'file:')</li>
033: <li> MMBase configuration directory (prefix: 'mm:') </li>
034: </ol>
035:
036: * Optionially you can add other dirs between these two.
037: *
038: * When you start searching in the current working dir, and the URI
039: * does not point to an existing file, it starts searching downwards in
040: * this list, until it finds a file that does exist.
041: *
042: * @author Michiel Meeuwissen.
043: * @since MMBase-1.6
044: * @version $Id: URIResolver.java,v 1.29 2008/02/12 17:21:22 michiel Exp $
045: */
046:
047: public class URIResolver implements javax.xml.transform.URIResolver,
048: SizeMeasurable, Serializable {
049:
050: private static final long serialVersionUID = 1L; // increase this if object serialization changes (which we shouldn't do!)
051: private static final Logger log = Logging
052: .getLoggerInstance(URIResolver.class);
053:
054: private EntryList dirs; // prefix -> URL pairs
055: private int hashCode;
056:
057: /**
058: * This constructor does not create an actual object that can be
059: * used. Only the hashCode is filled. This is because I liked it
060: * possible a URIResolver to be equal to a File. But 'equals' must
061: * be symmetric, and only a File can be equal to a File. It seemed
062: * stupid to extend URIResolver from File, only for this. If you
063: * want to compare a File to to an URIResolver (in Maps), you
064: * could wrap the file in such an empty URIResolver, and avoid all
065: * further overhead.
066: *
067: * @param c The directory for which this URIResolver must (not) be created.
068: * @param overhead A boolean. It is ignored. It serves only to distinct this constructor from the other one.
069: * @see org.mmbase.cache.xslt.FactoryCache
070: */
071:
072: public URIResolver(URL c, boolean overhead) {
073: hashCode = c.hashCode();
074: }
075:
076: /**
077: * Create an URIResolver for a certain directory.
078: * @param c The directory for which this URIResolver must be created.
079: */
080:
081: public URIResolver(URL c) {
082: this (c, null);
083: }
084:
085: /**
086: * @since MMBase-1.8
087: */
088: private static URL toURL(File f) {
089: try {
090: return f.toURL();
091: } catch (Exception e) {
092: return null;
093: }
094: }
095:
096: /**
097: * @deprecated
098: */
099: public URIResolver(File f) {
100: this (toURL(f), null);
101: }
102:
103: /**
104: * Create an URIResolver without support for a certain directory. (Will be taken the first root).
105: */
106: public URIResolver() {
107: this ((URL) null, null);
108: }
109:
110: /**
111: * @deprecated
112: */
113: public URIResolver(File f, EntryList extraDirs) {
114: this (toURL(f), extraDirs);
115: }
116:
117: /**
118: * Besides the current working directory you can also supply an
119: * ordered list of URIResolver.Entry's. First in this list are the
120: * directories which must be checked first, in case no prefix is
121: * given.
122: * @param c 'Current working dir'
123: * @param extraDirs A EntryList, containing 'extra' dirs with
124: * prefixes. If not specified or null, there will still be one
125: * 'extra dir' available, namely the MMBase configuration
126: * directory (with prefix mm:)
127: */
128: public URIResolver(URL c, EntryList extraDirs) {
129: if (log.isDebugEnabled())
130: log.debug("Creating URI Resolver for " + c);
131: URL cwd;
132: if (c == null) {
133: File[] roots = File.listRoots();
134: if (roots != null && roots.length > 0) {
135: try {
136: cwd = roots[0].toURL();
137: } catch (Exception e) {
138: cwd = null;
139: }
140: } else {
141: log
142: .warn("No filesystem root available, trying with 'null'");
143: cwd = null;
144: // will this result in anything useful?
145: // well, I don't think we will use mmbase on root-less systems anyway?
146: }
147: } else {
148: cwd = c;
149: }
150: dirs = new EntryList();
151: dirs.add(new Entry("", cwd));
152: if (extraDirs != null) {
153: dirs.addAll(extraDirs);
154: }
155: dirs
156: .add(new Entry("mm:", ResourceLoader
157: .getConfigurationRoot()));
158: // URIResolvers cannot be changed, the hashCode can already be calculated and stored.
159:
160: if (extraDirs == null || extraDirs.size() == 0) { // only mmbase config, and root cannot change
161: if (log.isDebugEnabled())
162: log.debug("getting hashCode " + cwd.hashCode());
163: hashCode = cwd.hashCode();
164: // if only the cwd is set, then you alternatively use the cwd has hashCode is this way.
165: // it this way in these case it is easy to avoid constructing an URIResolver at all.
166: } else {
167: hashCode = dirs.hashCode(); // see also javadoc of List
168: }
169: }
170:
171: /**
172: * Returns the working directory which was supplied in the
173: * constructor.
174: *
175: */
176: public URL getCwd() {
177: return dirs.get(0).getDir();
178: }
179:
180: /**
181: * Creates a 'path' string, which is a list of directories. Mainly usefull for debugging, of course.
182: *
183: * @return A String which could be used as a shell's path.
184: */
185: public String getPath() {
186: StringBuffer result = new StringBuffer();
187: Iterator<Entry> i = dirs.iterator();
188: while (i.hasNext()) {
189: Entry entry = i.next();
190: result.append(File.pathSeparatorChar);
191: result.append(entry.getDir().toString());
192: }
193: return result.toString();
194: }
195:
196: /**
197: * Creates a List of strings, every entry is a directory prefixed with its 'prefix'. Handy during debugging.
198: *
199: * @return A List with prefix:path Strings.
200: */
201: public List<String> getPrefixPath() {
202: List<String> result = new ArrayList<String>();
203: Iterator<Entry> i = dirs.iterator();
204: while (i.hasNext()) {
205: Entry entry = i.next();
206: result.add(entry.getPrefix() + entry.getDir().toString());
207: }
208: return result;
209: }
210:
211: /**
212: * @deprecated
213: */
214: public File resolveToFile(String href) {
215: return resolveToFile(href, null);
216: }
217:
218: /**
219: * @deprecated
220: */
221: public File resolveToFile(String href, String base) {
222: try {
223: return new File(resolveToURL(href, base).getFile());
224: } catch (Exception e) {
225: return null;
226: }
227: }
228:
229: public URL resolveToURL(final String href, final String base)
230: throws TransformerException {
231: if (log.isDebugEnabled()) {
232: log.debug("Using resolver " + this + " to resolve href: "
233: + href + " base: " + base);
234: }
235: try {
236: URL baseURL;
237: if (base == null // 'base' is often 'null', but happily, this object knows about cwd itself.
238: || base
239: .endsWith("javax.xml.transform.stream.StreamSource")) {
240: baseURL = getCwd();
241: } else {
242: baseURL = resolveToURL(base, null); // resolve URIResolver's prefixes like mm:, ew: in base.
243: log.debug("Resolved '" + base + "' to " + baseURL);
244: }
245:
246: URL path = null;
247: { // check all known prefixes
248: Iterator<Entry> i = dirs.iterator();
249: while (i.hasNext()) {
250: Entry entry = i.next();
251: String pref = entry.getPrefix();
252: if (!"".equals(pref) && href.startsWith(pref)) { //explicitely stated!
253: path = entry.getPath(href.substring(entry
254: .getPrefixLength()));
255: if (log.isTraceEnabled()) {
256: log.trace("href matches " + entry
257: + " returning " + path);
258: }
259: break;
260: }
261: try {
262: URL u = entry.getPath(href);
263: if (log.isTraceEnabled()) {
264: log.trace("Trying " + u + " "
265: + u.getClass());
266: }
267: // getDoInput does not work for every connection.
268: if (u.openConnection().getInputStream() != null) {
269: log.trace("Ok, breaking");
270: path = u;
271: break;
272: }
273: } catch (MalformedURLException mfe) {
274: log.debug(mfe);
275: // ignore, this might be because of a prefix, which is not yet tried.
276: } catch (java.io.IOException io) {
277: log.debug(io);
278: // ignore, try next one.
279: }
280: }
281: }
282:
283: // still not found!
284: if (path == null) {
285: if (href.startsWith("file:")) { // don't know excactly why this is good.
286: path = new URL(baseURL, href.substring(5));
287: } else {
288: log.debug("" + baseURL + " " + href);
289: path = new URL(baseURL, href);
290: }
291: try {
292: if (path.openConnection().getInputStream() == null) {
293: path = null;
294: }
295: } catch (Exception e) {
296: path = null;
297: }
298:
299: }
300: if (log.isDebugEnabled()) {
301: log.debug("Returning " + path);
302: }
303: return path;
304:
305: } catch (Exception e) {
306: throw new TransformerException(e);
307: }
308: }
309:
310: /**
311: * Implementation of the resolve method of javax.xml.transform.URIResolver.
312: *
313: * @see javax.xml.transform.URIResolver
314: **/
315:
316: public Source resolve(String href, String base)
317: throws TransformerException {
318: try {
319: URL u = resolveToURL(href, base);
320: if (u == null)
321: return null;
322: Source source = new StreamSource(u.openStream());
323: source.setSystemId(u.toString());
324: return source;
325: } catch (Exception e) {
326: throw new TransformerException(e);
327: }
328: }
329:
330: /**
331: * URIResolver can be used as a key in Maps (Caches).
332: */
333: public int hashCode() {
334: return hashCode;
335: }
336:
337: /**
338: * URIResolver can be used as a key in Maps (Caches).
339: */
340: public boolean equals(Object o) {
341: if (o != null && (o instanceof URIResolver)) {
342: URIResolver res = (URIResolver) o;
343: return (dirs == null ? (res.dirs == null || res.dirs.size() == 1)
344: : dirs.equals(res.dirs));
345: // See java javadoc, lists compare every element, files equal if point to same file
346: // extraDirs == null?
347: // -> created with first constructor.
348: }
349: return false;
350: }
351:
352: public int getByteSize() {
353: return getByteSize(new org.mmbase.util.SizeOf());
354: }
355:
356: public int getByteSize(org.mmbase.util.SizeOf sizeof) {
357: return sizeof.sizeof(dirs);
358: }
359:
360: public String toString() {
361: return getPrefixPath().toString();
362: }
363:
364: /**
365: * This is a list of prefix/directory pairs which is used in the constructor of URIResolver.
366: */
367:
368: static public class EntryList extends ArrayList<Entry> implements
369: Serializable {
370: private static final long serialVersionUID = 1L;
371:
372: public EntryList() {
373: }
374:
375: /**
376: * Adds an prefix/dir entry to the List.
377: * @return The list again, so you can easily 'chain' a few.
378: * @throws IllegalArgumentException if d is not a directory.
379: * @deprecated
380: */
381: public EntryList add(String p, File d) {
382: try {
383: add(new Entry(p, d.toURI().toURL()));
384: return this ;
385: } catch (Exception e) {
386: return this ;
387: }
388: }
389:
390: public EntryList add(String p, URL u) {
391: try {
392: add(new Entry(p, u));
393: return this ;
394: } catch (Exception e) {
395: return this ;
396: }
397: }
398:
399: /**
400: * @since MMBase-1.8.2
401: */
402: public EntryList add(String p, ClassLoader cl) {
403: try {
404: add(new Entry(p, cl));
405: return this ;
406: } catch (Exception e) {
407: return this ;
408: }
409: }
410:
411: }
412:
413: /**
414: * Objects of this type connect a prefix (must normally end in :)
415: * with a File (which must be a Directory). A List of this type
416: * (EntryList) can be fed to the constructor of URIResolver.
417: *
418: */
419:
420: static class Entry implements java.io.Serializable {
421: private static final long serialVersionUID = 2L;
422: private String prefix;
423: private URL dir;
424: private ClassLoader classLoader;
425: private int prefixLength;
426:
427: Entry(String p, URL u) {
428: prefix = p;
429: dir = u;
430: classLoader = null;
431: prefixLength = prefix.length(); // avoid calculating it again.
432: }
433:
434: Entry(String p, ClassLoader cl) {
435: prefix = p;
436: dir = null;
437: classLoader = cl;
438: prefixLength = prefix.length(); // avoid calculating it again.
439: }
440:
441: private void writeObject(java.io.ObjectOutputStream out)
442: throws IOException {
443: try {
444: out.writeUTF(prefix);
445: if (dir != null && dir.getProtocol().equals("mm")) {
446: out.writeObject("mm");
447: } else {
448: out.writeObject(dir);
449: }
450: } catch (Throwable t) {
451: log.warn(t.getMessage(), t);
452: }
453: }
454:
455: private void readObject(java.io.ObjectInputStream in)
456: throws IOException, ClassNotFoundException {
457: try {
458: prefix = in.readUTF();
459: Object o = in.readObject();
460: if ("mm:".equals(prefix)) {
461: classLoader = ResourceLoader.getConfigurationRoot();
462: dir = null;
463: } else {
464: if ("mm".equals(o)) {
465: classLoader = ResourceLoader
466: .getConfigurationRoot();
467: dir = null;
468: } else {
469: dir = (URL) o;
470: classLoader = null;
471: }
472: }
473: } catch (Throwable t) {
474: log.warn(t.getMessage(), t);
475: }
476: prefixLength = prefix.length();
477: }
478:
479: String getPrefix() {
480: return prefix;
481: }
482:
483: URL getDir() {
484: if (dir != null) {
485: return dir;
486: } else {
487: return classLoader.getResource("");
488: }
489: }
490:
491: /**
492: * Uses this entry to resolve a href
493: * @since MMBase-1.8.2
494: */
495: URL getPath(String href) throws MalformedURLException {
496: if (dir != null) {
497: return new URL(dir, href);
498: } else {
499: return classLoader.getResource(href);
500: }
501: }
502:
503: int getPrefixLength() {
504: return prefixLength;
505: }
506:
507: public String toString() {
508: return prefix
509: + ":"
510: + (dir != null ? dir.toString() : classLoader
511: .toString());
512: }
513:
514: public boolean equals(Object o) {
515: if (o instanceof File) {
516: return dir != null && dir.equals(o);
517: } else if (o instanceof Entry) {
518: Entry e = (Entry) o;
519: return dir != null ? dir.equals(e.dir) : classLoader
520: .equals(e.classLoader);
521: } else {
522: return false;
523: }
524: }
525:
526: public int hashCode() {
527: if (dir != null) {
528: return dir.hashCode();
529: } else {
530: return classLoader.hashCode();
531: }
532: }
533:
534: }
535:
536: /**
537: * For testing only
538: * @since MMBase-1.8
539: */
540: public static void main(String argv[]) throws Exception {
541:
542: URIResolver resolver = new URIResolver(new URL(
543: "file:///home/mmbase/head/mmbase/edit/wizard/data"));
544: System.out.println("Resolving with " + resolver);
545: String href, base;
546:
547: href = "xsl/list.xsl";
548: base = null;
549: System.out.println("href: " + href + " base: " + base + " --> "
550: + resolver.resolveToURL(href, base));
551: href = "prompts.xsl";
552: base = "file:///home/mmbase/head/mmbase/edit/wizard/data/xsl/base.xsl";
553: System.out.println("href: " + href + " base: " + base + " --> "
554: + resolver.resolveToURL(href, base));
555:
556: FileOutputStream fos = new FileOutputStream(
557: "/tmp/uriresolver.ser");
558: ObjectOutputStream oos = new ObjectOutputStream(fos);
559: oos.writeObject(resolver);
560: oos.close();
561:
562: FileInputStream fis = new FileInputStream(
563: "/tmp/uriresolver.ser");
564: ObjectInputStream ois = new ObjectInputStream(fis);
565: URIResolver resolver2 = (URIResolver) ois.readObject();
566: ois.close();
567:
568: System.out.println("r "
569: + resolver2.resolveToURL("mm:hoi", null).getProtocol());
570:
571: href = "xsl/list.xsl";
572: base = null;
573: System.out.println("href: " + href + " base: " + base + " --> "
574: + resolver2.resolveToURL(href, base));
575: href = "prompts.xsl";
576: base = "file:///home/mmbase/head/mmbase/edit/wizard/data/xsl/base.xsl";
577: System.out.println("href: " + href + " base: " + base + " --> "
578: + resolver2.resolveToURL(href, base));
579:
580: }
581:
582: }
|