KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > util > ResourceLoader


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9  */

10 package org.mmbase.util;
11
12 // general
13
import java.io.*;
14 import java.util.*;
15 import java.util.regex.Pattern JavaDoc;
16 import java.net.*;
17
18 // used for resolving in servlet-environment
19
import javax.servlet.ServletContext JavaDoc;
20 import javax.servlet.http.HttpServletRequest JavaDoc;
21
22
23 // used for resolving in MMBase database
24
import org.mmbase.bridge.*;
25 import org.mmbase.bridge.util.Queries;
26 import org.mmbase.storage.search.implementation.*;
27 import org.mmbase.storage.search.*;
28
29 // XML stuff
30
import org.w3c.dom.Document JavaDoc;
31 import org.w3c.dom.DocumentType JavaDoc;
32 import org.xml.sax.InputSource JavaDoc;
33 import javax.xml.transform.*;
34 import javax.xml.transform.Transformer JavaDoc;
35 import javax.xml.parsers.DocumentBuilder JavaDoc;
36 import javax.xml.transform.stream.StreamResult JavaDoc;
37 import javax.xml.transform.dom.DOMSource JavaDoc;
38
39 // used for Unicode Escaping when editing property files
40
import org.mmbase.util.transformers.*;
41
42 import org.mmbase.util.logging.Logger;
43 import org.mmbase.util.logging.Logging;
44
45
46 /**
47  * MMBase resource loader, for loading config-files and those kind of things. It knows about MMBase config file locations.
48  *
49  * I read <a HREF="http://www.javaworld.com/javaqa/2003-08/02-qa-0822-urls.html">http://www.javaworld.com/javaqa/2003-08/02-qa-0822-urls.html</a>.
50  *
51  * Programmers should do something like this if they need a configuration file:
52 <pre>
53 InputStream configStream = ResourceLoader.getConfigurationRoot().getResourceAsStream("modules/myconfiguration.xml");
54 </pre>
55 or
56 <pre>
57 InputSource config = ResourceLoader.getConfigurationRoot().getInputSource("modules/myconfiguration.xml");
58 </pre>
59 of if you need a list of all resources:
60 <pre>
61 ResourceLoader builderLoader = new ResourceLoader("builders");
62 List list = builderLoader.getResourcePaths(ResourceLoader.XML_PATTERN, true)
63 </pre>
64
65 When you want to place a configuration file then you have several options, wich are in order of preference:
66 <ol>
67   <li>Place it as on object in 'resources' builder (if such a builder is present)</li>
68   <li>Place it in the directory identified by the 'mmbase.config' setting (A system property or web.xml setting).</li>
69   <li>Place it in the directory WEB-INF/config. If this is a real directory (you are not in a war), then the resource will also be returned by {@link #getFiles}.</li>
70   <li>
71   Place it in the class-loader path of your app-server, below the 'org.mmbase.config' package.
72   For tomcat this boils down to the following list (Taken from <a HREF="http://jakarta.apache.org/tomcat/tomcat-5.0-doc/class-loader-howto.html">tomcat 5 class-loader howto</a>)
73    <ol>
74     <li>Bootstrap classes of your JVM</li>
75     <li>System class loader classses</li>
76     <li>/WEB-INF/classes of your web application. If this is a real directory (you are not in a war), then the resource will also be returned by {@link #getFiles}.</li>
77     <li>/WEB-INF/lib/*.jar of your web application</li>
78     <li>$CATALINA_HOME/common/classes</li>
79      <li>$CATALINA_HOME/common/endorsed/*.jar</li>
80     <li>$CATALINA_HOME/common/lib/*.jar</li>
81     <li>$CATALINA_BASE/shared/classes</li>
82     <li>$CATALINA_BASE/shared/lib/*.jar</li>
83   </ol>
84   </li>
85 </ol>
86  * <p>
87  * Resources which do not reside in the MMBase configuration repository, can also be handled. Those can be resolved relatively to the web root, using {@link #getWebRoot()}.
88  * </p>
89  *
90  * <p>Resources can programmaticly created or changed by the use of {@link #createResourceAsStream}, or something like {@link #getWriter}.</p>
91  *
92  * <p>If you want to check beforehand if a resource can be changed, then something like <code>resourceLoader.getResource().openConnection().getDoOutput()</code> can be used.</p>
93  * <p>That is also valid if you want to check for existance. <code>resourceLoader.getResource().openConnection().getDoInput()</code>.</p>
94  * <p>If you want to remove a resource, you must write <code>null</code> to all URL's returned by {@link #findResources} (Do for every URL:<code>url.openConnection().getOutputStream().write(null);</code>)</p>
95  * <h3>Encodings</h3>
96  * <p>ResourceLoader is well aware of encodings. You can open XML's as Reader, and this will be done using the encoding specified in the XML itself. When saving an XML using a Writer, this will also be done using the encoding specified in the XML.</p>
97  * <p>For property-files, the java-unicode-escaping is undone on loading, and applied on saving, so there is no need to think of that.</p>
98  * @author Michiel Meeuwissen
99  * @since MMBase-1.8
100  * @version $Id: ResourceLoader.java,v 1.39 2006/07/17 17:26:13 michiel Exp $
101  */

102 public class ResourceLoader extends ClassLoader JavaDoc {
103
104     private static final Logger log = Logging.getLoggerInstance(ResourceLoader.class);
105
106     /**
107      * Protocol prefix used by URL objects in this class.
108      */

109     protected static final String JavaDoc PROTOCOL = "mm";
110
111     /**
112      * Used for files, and servlet resources.
113      */

114     protected static final String JavaDoc RESOURCE_ROOT = "/WEB-INF/config";
115
116     /**
117      * Used when getting resources with normal class-loader.
118      */

119     protected static final String JavaDoc CLASSLOADER_ROOT = "/org/mmbase/config";
120
121     /**
122      * Protocol prefix used by URL objects in this class.
123      */

124     public static final URL NODE_URL_CONTEXT;
125
126     static {
127         URL temp = null;
128         try {
129             temp = new URL("http", "localhost", "/node/");
130         } catch (MalformedURLException mfue) {
131             assert false : mfue;
132         }
133         NODE_URL_CONTEXT = temp;
134     }
135
136
137     /**
138      * Used when using getResourcePaths for normal class-loaders.
139      */

140     protected static final String JavaDoc INDEX = "INDEX";
141
142     private static ResourceLoader configRoot = null;
143     private static ResourceLoader webRoot = null;
144     private static ResourceLoader systemRoot = null;
145     private static ServletContext JavaDoc servletContext = null;
146
147
148
149     // these should perhaps be configurable:
150
public static final String JavaDoc RESOURCENAME_FIELD = "name";
151     public static final String JavaDoc TYPE_FIELD = "type";
152     public static final String JavaDoc FILENAME_FIELD = "filename";
153     public static final String JavaDoc HANDLE_FIELD = "handle";
154     public static final String JavaDoc LASTMODIFIED_FIELD = "lastmodified";
155     public static final String JavaDoc DEFAULT_CONTEXT = "admin";
156
157     public static final int TYPE_CONFIG = 0;
158     public static final int TYPE_WEB = 1;
159
160
161
162     // This should perhaps be a member (too) to allow for better authorisation support.
163
static NodeManager resourceBuilder = null;
164
165
166     /**
167      * The URLStreamHandler for 'mm' URL's.
168      */

169
170     private final MMURLStreamHandler mmStreamHandler = new MMURLStreamHandler();
171
172     /**
173      * Creates a new URL object, which is used to load resources. First a normal java.net.URL is
174      * instantiated, if that fails, we check for the 'mmbase' protocol. If so, a URL is instantiated
175      * with a URLStreamHandler which can handle that.
176      *
177      * If that too fails, it should actually already be a MalformedURLException, but we try
178      * supposing it is some existing file and return a file: URL. If no such file, only then a
179      * MalformedURLException is thrown.
180      */

181     protected URL newURL(final String JavaDoc url) throws MalformedURLException {
182         // Try already installed protocols first:
183
try {
184             return new URL (url);
185         } catch (MalformedURLException ignore) {
186             // Ignore: try our own handler next.
187
}
188
189         final int firstColon = url.indexOf (':');
190         if (firstColon <= 0) {
191             if (new File(url).exists()) return new URL("file:" + url); // try it as a simply file
192
throw new MalformedURLException ("No protocol specified: " + url);
193         } else {
194
195             final String JavaDoc protocol = url.substring (0, firstColon);
196             if (protocol.equals(PROTOCOL)) {
197                 return new URL (null/* no context */, url, mmStreamHandler);
198             } else {
199                 if (new File(url).exists()) return new URL("file:" + url);
200                 throw new MalformedURLException ("Unknown protocol: " + protocol);
201             }
202         }
203     }
204
205
206     private List /* <ResolverFactory> */ roots;
207
208
209     static {
210         // make sure it works a bit before servlet-startup.
211
init(null);
212     }
213
214
215
216     /**
217      * Initializes the Resourceloader using a servlet-context (makes resolving relatively to WEB-INF/config possible).
218      * @param sc The ServletContext used for determining the mmbase configuration directory. Or <code>null</code>.
219      */

220     public static synchronized void init(ServletContext JavaDoc sc) {
221         servletContext = sc;
222         // reset both roots, they will be redetermined using servletContext.
223
configRoot = null;
224         webRoot = null;
225     }
226
227     /**
228      * Sets the MMBase builder which must be used for resource.
229      * The builder must have an URL and a HANDLE field.
230      * This method can be called only once.
231      * @param b An MMObjectBuilder (this may be <code>null</code> if no such builder available)
232      * @throws RuntimeException if builder was set already.
233      */

234     public static void setResourceBuilder(NodeManager b) {
235         if (ResourceWatcher.resourceWatchers == null) {
236             throw new RuntimeException JavaDoc("A resource builder was set already: " + resourceBuilder);
237         }
238         resourceBuilder = b;
239         // must be informed to existing ResourceWatchers.
240
ResourceWatcher.setResourceBuilder(); // this will also set ResourceWatcher.resourceWatchers to null.
241
log.info("The resources builder '" + b.getName() + "' is available. (user: " + b.getCloud().getUser() + ")");
242     }
243
244
245     /**
246      * Utility method to return the name part of a resource-name (removed directory and 'extension').
247      * Used e.g. when loading builders in MMBase.
248      */

249     public static String JavaDoc getName(String JavaDoc path) {
250         //avoid NullPointerException in util method
251
if (path == null){
252             return null;
253         }
254         int i = path.lastIndexOf('/');
255         path = path.substring(i + 1);
256
257         i = path.lastIndexOf('.');
258         if (i > 0) {
259             path = path.substring(0, i);
260         }
261         return path;
262     }
263
264     /**
265      * Utility method to return the 'directory' part of a resource-name.
266      * Used e.g. when loading builders in MMBase.
267      */

268     public static String JavaDoc getDirectory(String JavaDoc path) {
269         //avoid NullPointerException in util method
270
if (path == null){
271             return null;
272         }
273         int i = path.lastIndexOf('/');
274         if (i > 0) {
275             path = path.substring(0, i);
276         } else {
277             path = "";
278         }
279         return path;
280     }
281
282     /**
283      * Utility method to return the name of the directory of a resource-name. This does not include
284      * any /-chars any more.
285      * @since MMBase-1.8.2
286      */

287     public static String JavaDoc getDirectoryName(String JavaDoc path) {
288         if (path == null){
289             return null;
290         }
291         if (path.length() > 0 && path.charAt(path.length() - 1) == '/') path = path.substring(0, path.length() - 1);
292         String JavaDoc dir = getDirectory(path);
293         int i = path.lastIndexOf('/');
294         path = path.substring(i + 1);
295         if (path.length() > 0 && path.charAt(0) == '/') path = path.substring(1);
296         return path;
297     }
298
299     /**
300      * Singleton that returns the ResourceLoader for loading mmbase configuration
301      */

302     public static synchronized ResourceLoader getConfigurationRoot() {
303         if (configRoot == null) {
304
305             configRoot = new ResourceLoader();
306
307             //adds a resource that can load from nodes
308
configRoot.roots.add(configRoot.new NodeURLStreamHandler(TYPE_CONFIG));
309
310             // mmbase.config settings
311
String JavaDoc configPath = null;
312             if (servletContext != null) {
313                 configPath = servletContext.getInitParameter("mmbase.config");
314                 log.debug("found the mmbase config path parameter using the mmbase.config servlet context parameter");
315             }
316             if (configPath == null) {
317                 configPath = System.getProperty("mmbase.config");
318                 if (configPath != null) {
319                     log.debug("found the mmbase.config path parameter using the mmbase.config system property");
320                 }
321             } else if (System.getProperty("mmbase.config") != null){
322                 //if the configPath at this point was not null and the mmbase.config system property is defined
323
//this deserves a warning message since the setting is masked
324
log.warn("mmbase.config system property is masked by mmbase.config servlet context parameter");
325             }
326
327             if (configPath != null) {
328                 if (servletContext != null) {
329                     // take into account that configpath can start at webrootdir
330
if (configPath.startsWith("$WEBROOT")) {
331                         configPath = servletContext.getRealPath(configPath.substring(8));
332                     }
333                 }
334                 log.debug("Adding " + configPath);
335                 configRoot.roots.add(configRoot.new FileURLStreamHandler(new File(configPath), true));
336             }
337
338             if (servletContext != null) {
339                 String JavaDoc s = servletContext.getRealPath(RESOURCE_ROOT);
340                 if (s != null) {
341                     configRoot.roots.add(configRoot.new FileURLStreamHandler(new File(s), true));
342                 } else {
343                     configRoot.roots.add(configRoot.new ServletResourceURLStreamHandler(RESOURCE_ROOT));
344                 }
345             }
346
347             if (servletContext != null) {
348                 String JavaDoc s = servletContext.getRealPath("/WEB-INF/classes" + CLASSLOADER_ROOT); // prefer opening as a files.
349
if (s != null) {
350                     configRoot.roots.add(configRoot.new FileURLStreamHandler(new File(s), false));
351                 }
352             }
353
354             configRoot.roots.add(configRoot.new ClassLoaderURLStreamHandler(CLASSLOADER_ROOT));
355
356             //last fall back: fully qualified class-name
357
configRoot.roots.add(configRoot.new ClassLoaderURLStreamHandler("/"));
358
359         }
360         return configRoot;
361     }
362
363
364     /**
365      * Singleton that returns the ResourceLoader for loading from the file-system, relative from
366      * current working directory and falling back to the file system roots. This can be used in
367      * command-line tools and such. In a servlet environment you should use either
368      * {@link #getConfigurationRoot} or {@link #getWebRoot}.
369      */

370     public static synchronized ResourceLoader getSystemRoot() {
371         if (systemRoot == null) {
372             systemRoot = new ResourceLoader();
373             systemRoot.roots.add(systemRoot.new FileURLStreamHandler(new File(System.getProperty("user.dir")), true));
374             File[] roots = File.listRoots();
375             for (int i = 0; i <roots.length; i++) {
376                 systemRoot.roots.add(systemRoot.new FileURLStreamHandler(roots[i], true));
377             }
378
379         }
380         return systemRoot;
381     }
382
383     /**
384      * Singleton that returns the ResourceLoader for witch the base path is the web root
385      */

386     public static synchronized ResourceLoader getWebRoot() {
387         if (webRoot == null) {
388             webRoot = new ResourceLoader();
389
390             //webRoot.roots.add(webRoot.new NodeURLStreamHandler(Resource.TYPE_WEB));
391

392
393             String JavaDoc htmlRoot = null;
394             if (servletContext != null) {
395                 htmlRoot = servletContext.getInitParameter("mmbase.htmlroot");
396             }
397
398             if (htmlRoot == null) {
399                 htmlRoot = System.getProperty("mmbase.htmlroot");
400             }
401             if (htmlRoot != null) {
402                 webRoot.roots.add(webRoot.new FileURLStreamHandler(new File(htmlRoot), true));
403             }
404
405             if (servletContext != null) {
406                 String JavaDoc s = servletContext.getRealPath("/");
407                 if (s != null) {
408                     webRoot.roots.add(webRoot.new FileURLStreamHandler(new File(s), true));
409                 }
410                 webRoot.roots.add(webRoot.new ServletResourceURLStreamHandler("/"));
411             }
412         }
413
414         return webRoot;
415     }
416
417     /**
418      * Returns the resource loader associated with the directory of the given request
419      */

420     public static synchronized ResourceLoader getWebDirectory(HttpServletRequest JavaDoc request) {
421         return ResourceLoader.getWebRoot().getChildResourceLoader(request.getServletPath()).getParentResourceLoader();
422     }
423
424
425     /**
426      * The URL relative to which this class-loader resolves. Cannot be <code>null</code>.
427      */

428     private URL context;
429
430
431     /**
432      * Child resourceloaders have a parent.
433      */

434     private ResourceLoader parent = null;
435
436     /**
437      * This constructor instantiates a new root resource-loader. This constructor is protected (so you may use it in an extension), but normally use:
438      * {@link #getConfigurationRoot} or {@link #getWebRoot}.
439      */

440     protected ResourceLoader() {
441         super();
442         roots = new ArrayList();
443         try {
444             context = newURL(PROTOCOL + ":/");
445         } catch (MalformedURLException mue) {
446             throw new RuntimeException JavaDoc(mue);
447         }
448     }
449
450
451
452     /**
453      * Instantiates a ResourceLoader for a 'sub directory' of given ResourceLoader. Used by {@link #getChildResourceLoader}.
454      */

455     protected ResourceLoader(final ResourceLoader cl, final String JavaDoc context) {
456         super(ResourceLoader.class.getClassLoader());
457         this.context = cl.findResource(context + "/");
458         roots = new ArrayList();
459         Iterator i = cl.roots.iterator();
460         // hmm, don't like this code, but don't know how else to copy the inner object.
461
while (i.hasNext()) {
462             Object JavaDoc o = i.next();
463             if (o instanceof FileURLStreamHandler) {
464                 roots.add(new FileURLStreamHandler((FileURLStreamHandler) o));
465             } else if (o instanceof NodeURLStreamHandler) {
466                 roots.add(new NodeURLStreamHandler((NodeURLStreamHandler) o));
467             } else if (o instanceof ServletResourceURLStreamHandler) {
468                 roots.add(new ServletResourceURLStreamHandler((ServletResourceURLStreamHandler) o));
469             } else if (o instanceof ClassLoaderURLStreamHandler) {
470                 roots.add(new ClassLoaderURLStreamHandler((ClassLoaderURLStreamHandler) o));
471             } else {
472                 assert false;
473             }
474         }
475         parent = cl;
476     }
477
478
479
480     /**
481      * If name starts with '/' or 'mm:/' the 'parent' resourceloader is used.
482      *
483      * Otherwise the name is resolved relatively. (For the root ResourceLoader that is the same as starting with /)
484      *
485      * {@inheritDoc}
486      */

487     protected URL findResource(final String JavaDoc name) {
488         try {
489             if (name.startsWith("/")) {
490                 return newURL(PROTOCOL + ":" + name);
491             } else if (name.startsWith(PROTOCOL + ":")) {
492                 return newURL(name);
493             } else {
494                 return new URL(context, name);
495             }
496         } catch (MalformedURLException mfue) {
497             log.info(mfue + Logging.stackTrace(mfue));
498             return null;
499         }
500     }
501
502
503     /**
504      * {@inheritDoc}
505      * @see #getResourceList
506      */

507     protected Enumeration findResources(final String JavaDoc name) throws IOException {
508         final Iterator i = roots.iterator();
509         return new Enumeration() {
510                 private Enumeration current = null;
511                 private Enumeration next;
512                 {
513                     current = getNext();
514                     next = getNext();
515                 }
516
517                 protected Enumeration getNext() {
518                     if (i.hasNext()) {
519                         try {
520                             PathURLStreamHandler ush = (PathURLStreamHandler) i.next();
521                             Enumeration e = ush.getResources(name);
522                             return e;
523                         } catch (IOException io) {
524                             log.warn(io);
525                             return current;
526                         }
527                     } else {
528                         return current;
529                     }
530                 }
531
532                 public boolean hasMoreElements() {
533                     return current.hasMoreElements() || next.hasMoreElements();
534                 }
535                 public Object JavaDoc nextElement() {
536                     if (! current.hasMoreElements()) {
537                         current = next;
538                         next = getNext();
539                     }
540                     return current.nextElement();
541                 }
542
543             };
544     }
545
546     /**
547      * Returns a List, containing all URL's which may represent the
548      * given resource. This can be used to show what resource whould be loaded and what resource
549      * whould be masked, or one can also simply somehow 'merge' all these resources.
550      */

551     public List getResourceList(final String JavaDoc name) {
552         try {
553             return Collections.list(getResources(name));
554         } catch (IOException io) {
555             log.warn(io);
556             return Collections.EMPTY_LIST;
557         }
558     }
559
560
561     /**
562      * Can be used as an argument for {@link #getResourcePaths(Pattern, boolean)}. MMBase works mainly
563      * with xml configuration files, so this comes in handy.
564      */

565     public static final Pattern JavaDoc XML_PATTERN = Pattern.compile(".*\\.xml$");
566
567     /**
568      * Returns the 'context' for the ResourceLoader (an URL).
569      */

570     public URL getContext() {
571         return context;
572     }
573
574
575     /**
576      * Returns the 'parent' ResourceLoader. Or <code>null</code> if this ClassLoader has no
577      * parent. You can create a ResourceLoader with a parent by {@link
578      * #getChildResourceLoader(String)}.
579      */

580     public ResourceLoader getParentResourceLoader() {
581         return parent;
582     }
583
584     /**
585      *
586      *
587      * @param context a context relative to the current resource loader
588      * @return a new 'child' ResourceLoader or the parent ResourceLoader if the context is ".."
589      * @see #ResourceLoader(ResourceLoader, String)
590      */

591     public ResourceLoader getChildResourceLoader(final String JavaDoc context) {
592         if (context.equals("..")) { // should be made a bit smarter, (also recognizing "../..", "/" and those kind of things).
593
return getParentResourceLoader();
594         }
595         String JavaDoc [] dirs = context.split("/");
596         ResourceLoader rl = this;
597         for (int i = 0; i < dirs.length; i++) {
598             rl = new ResourceLoader(rl, dirs[i]);
599         }
600         return rl;
601
602     }
603
604
605     /**
606      * Returns a set of 'sub resources' (read: 'files in the same directory'), which can succesfully be loaded by the ResourceLoader.
607      *
608      * @param pattern A Regular expression pattern to which the file-name must match, or <code>null</code> if no restrictions apply
609      * @param recursive If true, then also subdirectories are searched.
610      * @return A Set of Strings which can be successfully loaded with the resourceloader.
611      */

612     public Set getResourcePaths(final Pattern JavaDoc pattern, final boolean recursive) {
613         return getResourcePaths(pattern, recursive, false);
614     }
615
616     /**
617      * Returns a set of context strings which can be used to instantiated new ResourceLoaders (resource loaders for directories)
618      * (see {@link #getChildResourceLoader(String)}).
619      * @param pattern A Regular expression pattern to which the file-name must match, or <code>null</code> if no restrictions apply
620      * @param recursive If true, then also subdirectories are searched.
621      */

622     public Set getChildContexts(final Pattern JavaDoc pattern, final boolean recursive) {
623         return getResourcePaths(pattern, recursive, true);
624     }
625
626     /**
627      * Used by {@link #getResourcePaths(Pattern, boolean)} and {@link #getChildContexts(Pattern, boolean)}
628      * @param pattern A Regular expression pattern to which the file-name must match, or <code>null</code> if no restrictions apply
629      * @param recursive If true, then also subdirectories are searched.
630      * @param directories getResourceContext supplies <code>true</code> getResourcePaths supplies <code>false</code>
631      */

632     protected Set getResourcePaths(final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
633         Set results = new TreeSet(); // a set with fixed iteration order
634
Iterator i = roots.iterator();
635         while (i.hasNext()) {
636             PathURLStreamHandler cf = (PathURLStreamHandler) i.next();
637             cf.getPaths(results, pattern, recursive, directories);
638         }
639         return results;
640     }
641
642
643
644     /**
645      * If you want to change a resource, or create one, then this method can be used. Specify the
646      * desired resource-name and you get an OutputStream back, to which you must write.
647      *
648      * This is a shortcut to <code>findResource(name).openConnection().getOutputStream()</code>
649      *
650      * If the given resource already existed, it will be overwritten, or shadowed, if it was not
651      * writeable.
652      *
653      * @throws IOException If the Resource for some reason could not be created.
654      */

655     public OutputStream createResourceAsStream(String JavaDoc name) throws IOException {
656         if (name == null || name.equals("")) {
657             throw new IOException("You cannot create a resource with an empty name");
658         }
659         URL resource = findResource(name);
660         URLConnection connection = resource.openConnection();
661         return connection.getOutputStream();
662     }
663
664     /**
665      * Returns the givens resource as an InputSource (XML streams). ResourceLoader is often used for
666      * XML.
667      * The System ID is set, otherwise you could as wel do new InputSource(r.getResourceAsStream());
668      * @param name The name of the resource to be loaded
669      * @return The InputSource if succesfull, <code>null</code> otherwise.
670      */

671     public InputSource JavaDoc getInputSource(final String JavaDoc name) throws IOException {
672         return getInputSource(findResource(name));
673     }
674
675     /**
676      * Static version of {@link #getInputSource(String)}, can e.g. be used in combination with {@link #getResourceList(String)}
677      */

678     public static InputSource JavaDoc getInputSource(final URL url) throws IOException {
679         try {
680             InputStream stream = url.openStream();
681             if (stream == null) return null;
682             InputSource JavaDoc is = new InputSource JavaDoc(stream);
683             //is.setCharacterStream(new InputStreamReader(stream));
684
is.setSystemId(url.toExternalForm());
685             return is;
686         } catch (MalformedURLException mfue) {
687             log.info(mfue);
688             return null;
689         }
690     }
691
692     /**
693      * Returns the givens resource as a Document (parsed XML). This can come in handly, because most
694      * configuration in in XML.
695      *
696      * @param name The name of the resource to be loaded
697      * @return The Document if succesful, <code>null</code> if there is no such resource.
698      * @throws SAXException If the resource does not present parseable XML.
699      * @throws IOException
700      */

701     public Document JavaDoc getDocument(String JavaDoc name) throws org.xml.sax.SAXException JavaDoc, IOException {
702         return getDocument(name, true, null);
703     }
704
705     /**
706      * Returns the givens resource as a Document (parsed XML). This can come in handly, because most
707      * configuration in in XML.
708      *
709      * @param name The name of the resource to be loaded
710      * @param validation If <code>true</code>, validate the xml. By dtd if one of the first lines starts with &lt;!DOCTYPE, by XSD otherwise
711      * @param baseClass If validation is <code>true</code>, the base class to serach for the validating xsd or dtd
712      * @return The Document if succesful, <code>null</code> if there is no such resource.
713      * @throws SAXException If the resource does not present parseable XML.
714      * @throws IOException
715      */

716     public Document JavaDoc getDocument(String JavaDoc name, boolean validation, Class JavaDoc baseClass) throws org.xml.sax.SAXException JavaDoc, IOException {
717         return getDocument(getResource(name), validation, baseClass);
718     }
719
720
721     /**
722      * Static version of {@link #getDocument(String, boolean, Class)}, can e.g. be used in combination with {@link #getResourceList(String)}
723      */

724     public static Document JavaDoc getDocument(URL url, boolean validation, Class JavaDoc baseClass) throws org.xml.sax.SAXException JavaDoc, IOException {
725         boolean xsd = validation;
726         if (validation) {
727             // determin whether this XML perhaps must be validated by DTD (specified 'DOCTYPE')
728
Reader r = new InputStreamReader(url.openStream());
729             if (r == null) return null;
730             BufferedReader reader = new BufferedReader(r);
731             String JavaDoc line = reader.readLine();
732             int lineNumber = 0;
733             while (lineNumber < 2 && line != null) {
734                 if (line.startsWith("<!DOCTYPE")) {
735                     log.debug("Using DTD to validate '" + url + "'");
736                     xsd = false;
737                 }
738                 line = reader.readLine();
739                 lineNumber++;
740             }
741             reader.close();
742         }
743
744         InputSource JavaDoc source = getInputSource(url);
745         if (source == null) return null;
746         XMLEntityResolver resolver = new XMLEntityResolver(validation, baseClass);
747         DocumentBuilder JavaDoc dbuilder = org.mmbase.util.xml.DocumentReader.getDocumentBuilder(validation, xsd,
748                                                                                          null/* default error handler */, resolver);
749         if(dbuilder == null) throw new RuntimeException JavaDoc("failure retrieving document builder");
750         if (log.isDebugEnabled()) log.debug("Reading " + source.getSystemId());
751         try {
752             Document JavaDoc doc = dbuilder.parse(source);
753             return doc;
754         } catch (IOException ioe) { // dtd or so not found?
755
if (validation) {
756                 log.error(ioe);
757                 // try without validating too.
758
return getDocument(url, false, baseClass);
759             } else {
760                 throw ioe;
761             }
762         }
763         
764     }
765
766     /**
767      * Creates a resource with given name for given Source.
768      *
769      * @see #createResourceAsStream(String)
770      * @param docType Document which must be used, or <code>null</code> if none.
771      */

772     public void storeSource(String JavaDoc name, Source JavaDoc source, DocumentType JavaDoc docType) throws IOException {
773         try {
774             log.service("Storing source " + name + " for " + this);
775             OutputStream stream = createResourceAsStream(name);
776             StreamResult JavaDoc streamResult = new StreamResult JavaDoc(stream);
777             TransformerFactory tf = TransformerFactory.newInstance();
778             Transformer JavaDoc serializer = tf.newTransformer();
779             serializer.setOutputProperty(OutputKeys.INDENT, "yes");
780             if (docType != null) {
781                 serializer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, docType.getPublicId());
782                 serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, docType.getSystemId());
783             }
784             // Indenting not very nice in all xslt-engines, but well, its better then depending
785
// on a real xslt or lots of code.
786
serializer.transform(source, streamResult);
787             stream.close();
788         } catch (final TransformerException te) {
789             IOException io = new IOException(te.getMessage());
790             io.initCause(te);
791             throw io;
792         }
793     }
794
795     /**
796      * Creates a resource for a given Document.
797      * @param name Name of the resource.
798      * @param doc The xml document which must be stored.
799      * @see #createResourceAsStream(String)
800      */

801     public void storeDocument(String JavaDoc name, Document JavaDoc doc) throws IOException {
802         storeSource(name, new DOMSource JavaDoc(doc), doc.getDoctype());
803     }
804
805     /**
806      * Returns a reader for a given resource. This performs the tricky task of finding the encoding.
807      * Resources are actually InputStreams (byte arrays), but often they are quite text-oriented
808      * (like e.g. XML's or property-files), so this method may be useful.
809      * A resource is supposed to be a property-file if it's name ends in ".properties", it is
810      * supposed to be XML if it's content starts with &lt;?xml.
811      * @see #getResourceAsStream(String)
812      */

813     public Reader getReader(String JavaDoc name) throws IOException {
814         try {
815             InputStream is = getResourceAsStream(name);
816             if (is == null) return null;
817             if (name.endsWith(".properties")) {
818                 // todo \ u escapes must be escaped to decent Character's.
819
return new TransformingReader(new InputStreamReader(is, "UTF-8"), new InverseCharTransformer(new UnicodeEscaper()));
820             }
821             byte b[] = new byte[100];
822             if (is.markSupported()) {
823                 is.mark(101);
824             }
825             try {
826                 is.read(b, 0, 100);
827                 if (is.markSupported()) {
828                     is.reset();
829                 } else {
830                     is = getResourceAsStream(name);
831                 }
832             } catch (IOException ioe) {
833                 is = getResourceAsStream(name);
834             }
835
836
837             String JavaDoc encoding = GenericResponseWrapper.getXMLEncoding(b);
838             if (encoding != null) {
839                 return new InputStreamReader(is, encoding);
840             }
841
842             // all other things, default to UTF-8
843
return new InputStreamReader(is, "UTF-8");
844         } catch (UnsupportedEncodingException uee) {
845             // could not happen
846
return null;
847         }
848     }
849
850     /**
851      * Returns a writer for a given resource, so that you can overwrite or create it. This performs
852      * the tricky task of serializing to the right encoding. It supports the same tricks as {@link
853      * #getReader}, but then inversed.
854      * @see #getReader(String)
855      * @see #createResourceAsStream(String)
856      */

857     public Writer getWriter(String JavaDoc name) throws IOException {
858         OutputStream os = createResourceAsStream(name);
859         try {
860             if (os == null) return null;
861             if (name.endsWith(".properties")) {
862                 // performs \ u escaping.
863
return new TransformingWriter(new OutputStreamWriter(os, "UTF-8"), new UnicodeEscaper());
864             }
865         } catch (UnsupportedEncodingException uee) {
866             log.error("uee " + uee);
867         }
868         return new EncodingDetectingOutputStreamWriter(os);
869     }
870
871     /**
872      * Returns an abstract URL for a resource with given name, <code>findResource(name).toString()</code> would give an 'external' form.
873      */

874     public String JavaDoc toInternalForm(String JavaDoc name) {
875         return toInternalForm(findResource(name));
876
877     }
878
879     public static String JavaDoc toInternalForm(URL u) {
880         return u.getProtocol() + ":" + u.getPath();
881     }
882
883     /**
884      * Used by {@link ResourceWatcher}. And by some deprecated code that wants to produce File objects.
885      * @return A List of all files associated with the resource.
886      */

887     public List/*<File>*/ getFiles(String JavaDoc name) {
888
889
890         List result = new ArrayList();
891         Iterator i = roots.iterator();
892         while (i.hasNext()) {
893             Object JavaDoc o = i.next();
894             if (o instanceof FileURLStreamHandler) {
895                 result.add(((FileURLStreamHandler) o).getFile(name));
896             }
897         }
898         return result;
899
900     }
901
902
903     /**
904      * @return A Node associated with the resource.
905      * Used by {@link ResourceWatcher}.
906      */

907     Node getResourceNode(String JavaDoc name) {
908         Iterator i = roots.iterator();
909         while (i.hasNext()) {
910             Object JavaDoc o = i.next();
911             if (o instanceof NodeURLStreamHandler) {
912                 return ((NodeConnection) (((PathURLStreamHandler) o).openConnection(name))).getResourceNode();
913             }
914         }
915         return null;
916     }
917
918     /**
919      * Logs warning if 'newer' resources are shadowed by older ones.
920      */

921     void checkShadowedNewerResources(String JavaDoc name) {
922         long lastModified = -1;
923         URL usedUrl = null;
924
925         Iterator i = roots.iterator();
926         while (i.hasNext()) {
927             PathURLStreamHandler cf = (PathURLStreamHandler) i.next();
928             URLConnection con = cf.openConnection(name);
929             if (con.getDoInput()) {
930                 long lm = con.getLastModified();
931                 if (lm > 0 && usedUrl != null && lastModified > 0 && lm > lastModified) {
932                     log.warn("File " + con.getURL() + " is newer (" + new Date(lm) + ") then " + usedUrl + "(" + new Date(lastModified) + ") but shadowed by it");
933                     log.debug("Checked because " + Logging.stackTrace(15));
934                 }
935                 if (usedUrl == null && lm > 0) {
936                     usedUrl = con.getURL();
937                     lastModified = lm;
938                 }
939             }
940         }
941     }
942
943     /**
944      * Determine wether File f is shadowed.
945      * @param name Check for resource with this name
946      * @param file The file to check for this resource.
947      * @return The URL for the shadowing resource, or <code>null</code> if not shadowed.
948      * @throws IllegalArgumentException if <code>file</code> is not a file associated with the resource with given name.
949      */

950     URL shadowed(File f, String JavaDoc name) {
951         Iterator i = roots.iterator();
952         while (i.hasNext()) {
953             PathURLStreamHandler cf = (PathURLStreamHandler) i.next();
954             if (cf instanceof NodeURLStreamHandler) {
955                 URLConnection con = cf.openConnection(name);
956                 if (con.getDoInput()) {
957                     return con.getURL();
958                 }
959             } else if (cf instanceof FileURLStreamHandler) {
960                 FileConnection con = (FileConnection) cf.openConnection(name);
961                 File file = con.getFile();
962                 if (file.equals(f)) {
963                     return null; // ok, not shadowed.
964
} else {
965                     if (file.exists()) {
966                         try {
967                             return file.toURL(); // f is shadowed!
968
} catch (MalformedURLException mfue) {
969                             assert false : mfue;
970                         }
971                     }
972                 }
973             }
974         }
975         // did not find f as a file for this resource
976
throw new IllegalArgumentException JavaDoc("File " + f + " is not a file for resource " + name);
977     }
978
979
980
981     /**
982      * @see java.lang.Object#toString()
983      */

984     public String JavaDoc toString() {
985         return "" + context.getPath() + " resolving in " + roots;
986     }
987
988     /**
989      * @see java.lang.Object#equals(java.lang.Object)
990      */

991     public boolean equals(Object JavaDoc o) {
992         if(this == o) return true;
993         // if this is a 'root' loader, then the only equal object should be the object itself!
994
if (parent == null) return false;
995         if (o instanceof ResourceLoader) {
996             ResourceLoader rl = (ResourceLoader) o;
997             return rl.parent == parent && rl.context.sameFile(context);
998         } else {
999             return false;
1000        }
1001    }
1002
1003    /**
1004     * @see java.lang.Object#hashCode()
1005     */

1006    public int hashCode() {
1007        int result = 0;
1008        result = HashCodeUtil.hashCode(result, parent);
1009        result = HashCodeUtil.hashCode(result, context);
1010        return result;
1011    }
1012
1013    /**
1014     * ================================================================================
1015     * INNER CLASSES, all private, protected
1016     * ================================================================================
1017     */

1018
1019
1020    /**
1021     * Extension URLStreamHandler, used for the 'sub' Handlers, entries of 'roots' in ResourceLoader are of this type.
1022     */

1023    protected abstract class PathURLStreamHandler extends URLStreamHandler {
1024        /**
1025         * We need an openConnection by name only, and public.
1026         */

1027        abstract public URLConnection openConnection(String JavaDoc name);
1028
1029        /**
1030         * When a URL has been created, in {@link #openConnection(String)}, this method can make a 'name' of it again.
1031         */

1032        abstract protected String JavaDoc getName(URL u);
1033
1034        /**
1035         * Returns a List of URL's with the same name. Defaults to one URL.
1036         */

1037        Enumeration getResources(final String JavaDoc name) throws IOException {
1038            return new Enumeration() {
1039                    private boolean hasMore = true;
1040                    public boolean hasMoreElements() { return hasMore; };
1041                    public Object JavaDoc nextElement() {
1042                        hasMore = false;
1043                        return openConnection(name).getURL();
1044                    }
1045
1046                };
1047        }
1048
1049        protected URLConnection openConnection(URL u) throws IOException {
1050            return openConnection(getName(u));
1051        }
1052
1053        abstract Set getPaths(Set results, Pattern JavaDoc pattern, boolean recursive, boolean directories);
1054    }
1055
1056
1057    protected class FileURLStreamHandler extends PathURLStreamHandler {
1058        private File fileRoot;
1059        private boolean writeable;
1060        FileURLStreamHandler(File root, boolean w) {
1061            fileRoot = root;
1062            writeable = w;
1063
1064        }
1065        FileURLStreamHandler(FileURLStreamHandler f) {
1066            fileRoot = f.fileRoot;
1067            writeable = f.writeable;
1068        }
1069
1070        public File getFile(String JavaDoc name) {
1071            if (name != null && name.startsWith("file:")) {
1072                try {
1073                    return new File(new URI(name)); // hff, how cumbersome, to translate an URL to a File
1074
} catch (URISyntaxException use) {
1075                    log.warn(use);
1076                }
1077            }
1078            String JavaDoc fileName = fileRoot + ResourceLoader.this.context.getPath() + (name == null ? "" : name);
1079            if (! File.separator.equals("/")) { // windows compatibility
1080
fileName = fileName.replace('/', File.separator.charAt(0)); // er
1081
}
1082            return new File(fileName);
1083        }
1084        public String JavaDoc getName(URL u) {
1085            int l = (fileRoot + ResourceLoader.this.context.getPath()).length();
1086            String JavaDoc path = u.getPath();
1087            return l < path.length() ? path.substring(l) : path;
1088        }
1089        public URLConnection openConnection(String JavaDoc name) {
1090            URL u;
1091            try {
1092                if (name.startsWith("file:")) {
1093                    u = new URL(null, name, this);
1094                } else {
1095                    u = new URL(null, "file:" + getFile(name), this);
1096                }
1097            } catch (MalformedURLException mfue) {
1098                throw new AssertionError JavaDoc(mfue.getMessage());
1099            }
1100            return new FileConnection(u, getFile(name), writeable);
1101        }
1102        public Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
1103            return getPaths(results, pattern, recursive ? "" : null, directories);
1104        }
1105        private Set getPaths(final Set results, final Pattern JavaDoc pattern, final String JavaDoc recursive, final boolean directories) {
1106            FilenameFilter filter = new FilenameFilter() {
1107                    public boolean accept(File dir, String JavaDoc name) {
1108                        File f = new File(dir, name);
1109                        return pattern == null || (f.isDirectory() && recursive != null) || pattern.matcher(f.toString()).matches();
1110                    }
1111                };
1112            File f = getFile(recursive);
1113
1114            if (f.isDirectory()) { // should always be true
1115
File [] files = f.listFiles(filter);
1116                if (files == null) return results;
1117                for (int j = 0; j < files.length; j++) {
1118                    if (files[j].getName().equals("")) continue;
1119                    if (recursive != null && files[j].isDirectory()) {
1120                        getPaths(results, pattern, recursive + files[j].getName() + "/", directories);
1121                    }
1122                    if (files[j].canRead() && (directories == files[j].isDirectory())) {
1123                        results.add((recursive == null ? "" : recursive) + files[j].getName());
1124                    }
1125
1126                }
1127            }
1128
1129            return results;
1130        }
1131        public String JavaDoc toString() {
1132            return fileRoot.toString();
1133        }
1134
1135
1136    }
1137
1138
1139    /**
1140     * A URLConnection for connecting to a File. Of course SUN ships an implementation as well
1141     * (File.getURL), but Sun's implementation sucks. You can't use it for writing a file, and
1142     * getDoInput always gives true, even if the file does not even exist. This version supports
1143     * checking by <code>getDoInput()</code> (read rights) and <code>getDoOutput()</code> (write
1144     * rights) and deleting by <code>getOutputStream().write(null)</code>
1145     */

1146    private class FileConnection extends URLConnection {
1147        private File file;
1148        private boolean writeable;
1149        FileConnection(URL u, File f, boolean w) {
1150            super(u);
1151            this.file = f;
1152            this.writeable = w;
1153        }
1154        public void connect() throws IOException {
1155            connected = true;
1156        }
1157
1158        public File getFile() {
1159            return file;
1160        }
1161
1162        public boolean getDoInput() {
1163            return file.canRead();
1164        }
1165        public boolean getDoOutput() {
1166            if (! writeable) return false;
1167            File f = file;
1168            while (f != null) {
1169                if (f.exists()) {
1170                    return f.canWrite();
1171                } else {
1172                    f = f.getParentFile();
1173                }
1174            }
1175            return false;
1176        }
1177
1178        public InputStream getInputStream() throws IOException {
1179            if (! connected) connect();
1180            return new FileInputStream(file);
1181        }
1182        public OutputStream getOutputStream() throws IOException {
1183            if (! connected) connect();
1184            if (! writeable) {
1185                throw new UnknownServiceException("This file-connection does not allow writing.");
1186            }
1187            // create directory if necessary.
1188
File parent = file.getParentFile();
1189            if (parent != null) {
1190                if (! parent.exists()) {
1191                    log.info("Creating subdirs for " + file);
1192                }
1193                parent.mkdirs();
1194                if (! parent.exists()) {
1195                    log.warn("Could not create directory for " + file + ": " + parent);
1196                }
1197            } else {
1198                log.warn("Parent of " + file + " is null ?!");
1199            }
1200            if (file.isDirectory()) {
1201                final File directory = file;
1202                return new OutputStream() {
1203                        public void write(byte[] b) throws IOException {
1204                            if (b == null) {
1205                                directory.delete();
1206                            } else {
1207                                super.write(b);
1208                            }
1209                        }
1210                        public void write(int b) throws IOException {
1211                            throw new UnsupportedOperationException JavaDoc("Cannot write bytes to a directory outputstream");
1212                        }
1213                    };
1214
1215            } else {
1216                return new FileOutputStream(file) {
1217                    public void write(byte[] b) throws IOException {
1218                        if (b == null) {
1219                            file.delete();
1220                        } else {
1221                            super.write(b);
1222                        }
1223                    }
1224                };
1225            }
1226        }
1227        public long getLastModified() {
1228            return file.lastModified();
1229        }
1230
1231        public String JavaDoc toString() {
1232            return "FileConnection " + file.toString();
1233        }
1234
1235    }
1236
1237
1238    /**
1239     * URLStreamHandler for NodeConnections.
1240     */

1241    protected class NodeURLStreamHandler extends PathURLStreamHandler {
1242        private int type;
1243        NodeURLStreamHandler(int type) {
1244            this.type = type;
1245        }
1246        NodeURLStreamHandler(NodeURLStreamHandler nf) {
1247            this.type = nf.type;
1248        }
1249
1250        protected String JavaDoc getName(URL u) {
1251            return u.getPath().substring(NODE_URL_CONTEXT.getPath().length());
1252        }
1253        public URLConnection openConnection(String JavaDoc name) {
1254            URL u;
1255            try {
1256                u = new URL(NODE_URL_CONTEXT, name, this);
1257            } catch (MalformedURLException mfue) {
1258                throw new AssertionError JavaDoc(mfue.getMessage());
1259            }
1260            return new NodeConnection(u, name, type);
1261        }
1262        public Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
1263            if (ResourceLoader.resourceBuilder != null) {
1264                try {
1265                    NodeQuery query = ResourceLoader.resourceBuilder.createQuery();
1266                    Constraint typeConstraint = Queries.createConstraint(query, TYPE_FIELD, Queries.getOperator("="), new Integer JavaDoc(type));
1267                    Constraint nameConstraint = Queries.createConstraint(query, RESOURCENAME_FIELD, Queries.getOperator("LIKE"), ResourceLoader.this.context.getPath().substring(1) + "%");
1268
1269                    BasicCompositeConstraint constraint = new BasicCompositeConstraint(CompositeConstraint.LOGICAL_AND);
1270
1271                    constraint.addChild(typeConstraint).addChild(nameConstraint);
1272
1273
1274                    query.setConstraint(constraint);
1275                    Iterator i = resourceBuilder.getList(query).iterator();
1276                    while (i.hasNext()) {
1277                        Node node = (Node) i.next();
1278                        String JavaDoc url = node.getStringValue(RESOURCENAME_FIELD);
1279                        String JavaDoc subUrl = url.substring(ResourceLoader.this.context.getPath().length() - 1);
1280                        int pos = subUrl.indexOf('/');
1281
1282                        if (directories) {
1283                            if (pos < 0) continue; // not a directory
1284
do {
1285                                String JavaDoc u = subUrl.substring(0, pos);
1286                                if (pattern != null && ! pattern.matcher(u).matches()) {
1287                                    continue;
1288                                }
1289                                results.add(u);
1290                                pos = subUrl.indexOf('/', pos + 1);
1291                            } while (pos > 0 && recursive);
1292                        } else {
1293                            if (pos > 0 && ! recursive) continue;
1294                            if (pattern != null && ! pattern.matcher(subUrl).matches()) {
1295                                continue;
1296                            }
1297                            results.add(subUrl);
1298                        }
1299
1300                    }
1301                } catch (BridgeException sqe) {
1302                    log.warn(sqe);
1303                }
1304            }
1305            return results;
1306        }
1307        public String JavaDoc toString() {
1308            return "nodes of type " + type;
1309        }
1310
1311    }
1312
1313    /**
1314     * A URLConnection based on an MMBase node.
1315     * @see FileConnection
1316     */

1317    private class NodeConnection extends URLConnection {
1318        Node node;
1319        String JavaDoc name;
1320        int type;
1321        NodeConnection(URL url, String JavaDoc name, int t) {
1322            super(url);
1323            this.name = name;
1324            this.type = t;
1325        }
1326        public void connect() throws IOException {
1327            if (ResourceLoader.resourceBuilder == null) {
1328                throw new IOException("No resources builder available.");
1329            }
1330            connected = true;
1331        }
1332        /**
1333         * Gets the Node associated with this URL if there is one.
1334         * @return MMObjectNode or <code>null</code>
1335         */

1336        public Node getResourceNode() {
1337            if (node != null) return node;
1338            if (name.equals("")) return null;
1339            String JavaDoc realName = (ResourceLoader.this.context.getPath() + name).substring(1);
1340            if (ResourceLoader.resourceBuilder != null) {
1341                try {
1342                    NodeQuery query = resourceBuilder.createQuery();
1343                    Constraint constraint1 = Queries.createConstraint(query, RESOURCENAME_FIELD, Queries.getOperator("="), realName);
1344                    Constraint constraint2 = Queries.createConstraint(query, TYPE_FIELD, Queries.getOperator("="), new Integer JavaDoc(type));
1345
1346                    BasicCompositeConstraint constraint = new BasicCompositeConstraint(CompositeConstraint.LOGICAL_AND);
1347                    constraint.addChild(constraint1);
1348                    constraint.addChild(constraint2);
1349
1350                    query.setConstraint(constraint);
1351                    Iterator i = resourceBuilder.getList(query).iterator();
1352                    if (i.hasNext()) {
1353                        node = (Node) i.next();
1354                        return node;
1355                    }
1356                } catch (BridgeException sqe) {
1357                    log.warn(sqe);
1358                }
1359            }
1360            return null;
1361        }
1362
1363        public boolean getDoInput() {
1364            return getResourceNode() != null;
1365        }
1366
1367        public boolean getDoOutput() {
1368            getResourceNode();
1369            return
1370                (node != null && node.mayWrite()) ||
1371                (ResourceLoader.resourceBuilder != null && ResourceLoader.resourceBuilder.mayCreateNode());
1372        }
1373
1374        public InputStream getInputStream() throws IOException {
1375            getResourceNode();
1376            if (node != null) {
1377                return node.getInputStreamValue(HANDLE_FIELD);
1378            } else {
1379                throw new IOException("No such (node) resource for " + name);
1380            }
1381        }
1382        public OutputStream getOutputStream() throws IOException {
1383            if (getResourceNode() == null) {
1384                if (ResourceLoader.resourceBuilder == null) return null;
1385
1386                node = ResourceLoader.resourceBuilder.createNode();
1387                node.setContext(DEFAULT_CONTEXT);
1388                String JavaDoc resourceName = (ResourceLoader.this.context.getPath() + name).substring(1);
1389                node.setStringValue(RESOURCENAME_FIELD, resourceName);
1390                node.setIntValue(TYPE_FIELD, type);
1391                log.info("Creating node " + resourceName + " " + name + " " + type);
1392                node.commit();
1393            }
1394            return new OutputStream() {
1395                    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
1396                    public void close() throws IOException {
1397                        byte[] b = bytes.toByteArray();
1398                        node.setValue(HANDLE_FIELD, b);
1399                        String JavaDoc mimeType = URLConnection.guessContentTypeFromStream(new ByteArrayInputStream(b));
1400                        if (mimeType == null) {
1401                            URLConnection.guessContentTypeFromName(name);
1402                        }
1403                        node.setValue("mimetype", mimeType);
1404                        node.commit();
1405                    }
1406                    public void write(int b) {
1407                        bytes.write(b);
1408                    }
1409                    public void write(byte[] b) throws IOException {
1410                        if (b == null) {
1411                            node.delete();
1412                            node = null;
1413                        } else {
1414                            super.write(b);
1415                        }
1416                    }
1417                };
1418        }
1419        public long getLastModified() {
1420            getResourceNode();
1421            if (node != null) {
1422                Date lm = node.getDateValue(LASTMODIFIED_FIELD);
1423                if (lm != null) {
1424                    return lm.getTime();
1425                }
1426            }
1427            return -1;
1428        }
1429
1430        public String JavaDoc toString() {
1431            return "NodeConnection " + node;
1432        }
1433
1434    }
1435
1436    /**
1437     * If running in a servlet 2.3 environment the ServletResourceURLStreamHandler is not fully
1438     * functional. A warning about that is logged, but only once.
1439     */

1440    private static boolean warned23 = false;
1441
1442    /**
1443     * URLStreamHandler based on the servletContext object of ResourceLoader
1444 */

1445    protected class ServletResourceURLStreamHandler extends PathURLStreamHandler {
1446        private String JavaDoc root;
1447        ServletResourceURLStreamHandler(String JavaDoc r) {
1448            root = r;
1449        }
1450        ServletResourceURLStreamHandler(ServletResourceURLStreamHandler f) {
1451            root = f.root;
1452        }
1453
1454
1455        protected String JavaDoc getName(URL u) {
1456            return u.getPath().substring(root.length());
1457        }
1458        public URLConnection openConnection(String JavaDoc name) {
1459            try {
1460                URL u = ResourceLoader.servletContext.getResource(root + ResourceLoader.this.context.getPath() + name);
1461                if (u == null) return NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1462                return u.openConnection();
1463            } catch (IOException ioe) {
1464                return NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1465            }
1466        }
1467        public Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
1468            if (log.isDebugEnabled()) {
1469                log.debug("Getting " + (directories ? "directories" : "files") + " matching '" + pattern + "' in '" + root + "'");
1470            }
1471            return getPaths(results, pattern, recursive ? "" : null, directories);
1472        }
1473
1474        private Set getPaths(final Set results, final Pattern JavaDoc pattern, final String JavaDoc recursive, final boolean directories) {
1475            if (servletContext != null) {
1476                try {
1477                    String JavaDoc currentRoot = root + ResourceLoader.this.context.getPath();
1478                    String JavaDoc resourcePath = currentRoot + (recursive == null ? "" : recursive);
1479                    Collection c = servletContext.getResourcePaths(resourcePath);
1480                    if (c == null) return results;
1481                    Iterator j = c.iterator();
1482                    while (j.hasNext()) {
1483                        String JavaDoc res = (String JavaDoc) j.next();
1484                        if (res.equals(resourcePath + "/")) {
1485                            // I think this is a bug in Jetty (according to javadoc this should not happen, but it does!)
1486
continue;
1487                        }
1488
1489                        String JavaDoc newResourcePath = res.substring(currentRoot.length());
1490                        boolean isDir = newResourcePath.endsWith("/");
1491                        if (isDir) {
1492                            // subdirs
1493
if (recursive != null) {
1494                                getPaths(results, pattern, newResourcePath.substring(0, newResourcePath.length() - 1), directories);
1495                            }
1496                            if (newResourcePath.equals("/")) continue;
1497                        }
1498                        if ((pattern == null || pattern.matcher(newResourcePath).matches()) && (directories == isDir)) {
1499                            if (isDir) newResourcePath = newResourcePath.substring(0, newResourcePath.length() - 1) ;
1500                            results.add(newResourcePath);
1501                        }
1502                    }
1503                } catch (NoSuchMethodError JavaDoc nsme) {
1504                    if (! warned23) {
1505                        log.warn("Servet 2.3 feature not supported! " + nsme.getMessage());
1506                        warned23 = true;
1507                    }
1508                    // servletContext.getResourcePaths is only a servlet 2.3 feature.
1509

1510                    // old app-server (orion 1.5.4: java.lang.NoSuchMethodError: javax.servlet.ServletContext.getResourcePaths(Ljava/lang/String;)Ljava/util/Set;)
1511
// simply ignore, running on war will not work in such app-servers
1512
} catch (Throwable JavaDoc t) {
1513                    log.error(Logging.stackTrace(t));
1514                    // ignore
1515
}
1516            }
1517            return results;
1518        }
1519
1520        public String JavaDoc toString() {
1521            return "ServletResource " + root;
1522        }
1523    }
1524
1525
1526    protected class ClassLoaderURLStreamHandler extends PathURLStreamHandler {
1527        private String JavaDoc root;
1528
1529        // Some arrangment to remember wich subdirs were possible
1530
//private Set subDirs = new HashSet();
1531

1532        ClassLoaderURLStreamHandler(String JavaDoc r) {
1533            root = r;
1534        }
1535        ClassLoaderURLStreamHandler(ClassLoaderURLStreamHandler f) {
1536            root = f.root;
1537        }
1538
1539
1540        private ClassLoader JavaDoc getClassLoader() {
1541            ClassLoader JavaDoc cl = ResourceLoader.class.getClassLoader();
1542            if (cl == null) {
1543                // A system class.
1544
return ClassLoader.getSystemClassLoader();
1545            } else {
1546                return cl;
1547            }
1548        }
1549
1550        protected String JavaDoc getName(URL u) {
1551            return u.getPath().substring((root + ResourceLoader.this.context.getPath()).length());
1552        }
1553        private String JavaDoc getClassResourceName(final String JavaDoc name) throws MalformedURLException {
1554            String JavaDoc res = root + new URL(ResourceLoader.this.context, name).getPath();
1555            while (res.startsWith("/")) {
1556                res = res.substring(1);
1557            }
1558            if (log.isDebugEnabled()) {
1559                log.debug("Name " + name + " is resource " + res);
1560            }
1561            return res;
1562        }
1563
1564        Enumeration getResources(final String JavaDoc name) throws IOException {
1565            try {
1566                return getClassLoader().getResources(getClassResourceName(name));
1567            } catch (IOException ioe) {
1568                throw ioe;
1569            } catch (Throwable JavaDoc t) {
1570                log.warn(t);
1571                return Collections.enumeration(Collections.EMPTY_LIST);
1572            }
1573        }
1574        public URLConnection openConnection(String JavaDoc name) {
1575            try {
1576                URL u = getClassLoader().getResource(getClassResourceName(name));
1577                if (u == null) {
1578                    return NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1579                }
1580                //subDirs.add(ResourceLoader.getDirectory(name));
1581
return u.openConnection();
1582            } catch (IOException ioe) {
1583                return NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1584            }
1585        }
1586
1587        public Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
1588            return getPaths(results, pattern, recursive, directories, "", null);
1589        }
1590
1591        private Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories, String JavaDoc resourceDir, String JavaDoc searchUp) {
1592            try {
1593                List subDirs = new ArrayList();
1594                Enumeration e = getResources("".equals(resourceDir) ? INDEX : resourceDir + INDEX);
1595                if (searchUp != null && resourceDir.startsWith("..")) resourceDir = "";
1596                while (e.hasMoreElements()) {
1597                    URL u = (URL) e.nextElement();
1598                    InputStream inputStream = u.openStream();
1599                    if (inputStream != null) {
1600                        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
1601                        try {
1602                            while (true) {
1603                                String JavaDoc line = reader.readLine();
1604                                if (line == null) break;
1605                                if (line.startsWith("#")) continue; // support for comments
1606
line = line.trim();
1607
1608                                if (line.startsWith("./")) line = line.substring(2);
1609
1610                                if (searchUp != null) {
1611                                    if (line.startsWith(searchUp)) {
1612                                        line = line.substring(searchUp.length());
1613                                    } else {
1614                                        continue;
1615                                    }
1616                                }
1617
1618                                if (directories) {
1619                                    line = getDirectory(line);
1620                                }
1621                                if (line.equals("")) continue; // support for empty lines
1622

1623                                int firstSlash = line.indexOf('/');
1624                                if (firstSlash > 0 && firstSlash < line.length() && ! recursive) continue;
1625
1626                                if (pattern == null || pattern.matcher(line).matches()) {
1627                                    results.add("".equals(resourceDir) ? line : resourceDir + line);
1628                                }
1629                                if (line.endsWith("/")) {
1630                                    subDirs.add("".equals(resourceDir) ? line : resourceDir + line);
1631                                }
1632                            }
1633                        } catch (IOException ioe) {
1634                        } finally {
1635                            reader.close();
1636                        }
1637                    }
1638                }
1639                if (recursive) {
1640                    for (Iterator iter = subDirs.iterator(); iter.hasNext();) {
1641                        String JavaDoc dir = (String JavaDoc) iter.next();
1642                        String JavaDoc newDir = "".equals(resourceDir) ? dir : resourceDir + dir;
1643                        getPaths(results, pattern, recursive, directories, newDir, null);
1644                    }
1645                }
1646                if (searchUp == null) {
1647                    searchUp = ResourceLoader.getDirectoryName(ResourceLoader.this.context.getFile()) + '/';
1648                    ResourceLoader p = ResourceLoader.this.parent;
1649                    String JavaDoc rd = "../";
1650                    while (p != null) {
1651                        getPaths(results, pattern, recursive, directories, rd, searchUp);
1652                        searchUp = ResourceLoader.getDirectoryName(p.context.getFile()) + '/' + searchUp;
1653                        p = p.getParentResourceLoader();
1654                        rd = "../" + rd;
1655
1656                    }
1657                }
1658            } catch (IOException ioe) {
1659            }
1660            /*
1661            if (directories) {
1662                Iterator i = subDirs.iterator();
1663                while (i.hasNext()) {
1664                    String dir = (String) i.next();
1665                    if (pattern == null || pattern.matcher(dir).matches()) {
1666                        results.add(dir);
1667                    }
1668                }
1669            }
1670            */

1671            return results;
1672        }
1673
1674        public String JavaDoc toString() {
1675            return "ClassLoader " + root;
1676        }
1677    }
1678
1679
1680    private static String JavaDoc NOT_FOUND = "/localhost/NOTFOUND/";
1681
1682
1683    /**
1684     * URLStreamHandler for URL's which can do neither input, nor output. Such an URL can be
1685     * returned by other PathURLStreamHandlers too.
1686     */

1687    private PathURLStreamHandler NOT_AVAILABLE_URLSTREAM_HANDLER = new PathURLStreamHandler() {
1688
1689            protected String JavaDoc getName(URL u) {
1690                String JavaDoc path = u.getPath();
1691                return path.substring("/NOTFOUND/".length());
1692            }
1693
1694            public URLConnection openConnection(String JavaDoc name) {
1695                URL u;
1696                try {
1697                    u = new URL(null, "http:/" + NOT_FOUND + name, this);
1698                } catch (MalformedURLException mfue) {
1699                    throw new AssertionError JavaDoc(mfue.getMessage());
1700                }
1701                return new NotAvailableConnection(u, name);
1702            }
1703
1704            public Set getPaths(final Set results, final Pattern JavaDoc pattern, final boolean recursive, final boolean directories) {
1705                return new HashSet();
1706            }
1707        };
1708
1709
1710
1711    /**
1712     * A connection which can neither do input, nor output.
1713     */

1714    private class NotAvailableConnection extends URLConnection {
1715
1716        private String JavaDoc name;
1717
1718        private NotAvailableConnection(URL u, String JavaDoc n) {
1719            super(u);
1720            name = n;
1721        }
1722        public void connect() throws IOException { throw new IOException("No such resource " + name); };
1723        public boolean getDoInput() { return false; }
1724        public boolean getDoOutput() { return false; }
1725        public InputStream getInputStream() throws IOException { connect(); return null;}
1726        public OutputStream getOutputStream() throws IOException { connect(); return null; }
1727        public String JavaDoc toString() {
1728            return "NOTAVAILABLECONNECTION " + name;
1729        }
1730    };
1731
1732
1733    /**
1734     * The MMURLStreamHandler is a StreamHandler for the protocol 'mm' (which is only for internal
1735     * use). It combines the Connection types implented here above.
1736     */

1737
1738    private class MMURLStreamHandler extends URLStreamHandler implements java.io.Serializable JavaDoc {
1739
1740        MMURLStreamHandler() {
1741            super();
1742        }
1743        protected URLConnection openConnection(URL u) throws IOException {
1744            return new MMURLConnection(u);
1745        }
1746        /**
1747         * ExternalForms are mainly used in entity-resolving and URL.toString()
1748         * {@inheritDoc}
1749         */

1750        protected String JavaDoc toExternalForm(URL u) {
1751            return new MMURLConnection(u).getInputConnection().getURL().toExternalForm();
1752        }
1753    }
1754
1755
1756    /**
1757     * Implements the logic for our MM protocol. This logic consists of iterating in <code>ResourceLoader.this.roots</code>.
1758     */

1759    private class MMURLConnection extends URLConnection {
1760
1761        URLConnection inputConnection = null;
1762        URLConnection outputConnection = null;
1763        String JavaDoc name;
1764
1765
1766        MMURLConnection(URL u) {
1767            super(u);
1768            name = url.getPath().substring(1);
1769            //log.debug("Connection to " + url + Logging.stackTrace(new Throwable()));
1770
if (! url.getProtocol().equals(PROTOCOL)) {
1771                throw new RuntimeException JavaDoc("Only supporting URL's with protocol " + PROTOCOL);
1772            }
1773        }
1774
1775        /**
1776         * {@inheritDoc}
1777         */

1778        public void connect() {
1779            connected = true;
1780        }
1781
1782        /**
1783         * Returns first possible connection which can be read.
1784         */

1785        protected URLConnection getInputConnection() {
1786            if (inputConnection != null) {
1787                return inputConnection;
1788            }
1789            Iterator i = ResourceLoader.this.roots.iterator();
1790            while(i.hasNext()) {
1791                PathURLStreamHandler cf = (PathURLStreamHandler) i.next();
1792                URLConnection c = cf.openConnection(name);
1793                if (c.getDoInput()) {
1794                    inputConnection = c;
1795                    break;
1796                }
1797            }
1798            if (inputConnection == null) {
1799                setDoInput(false);
1800                inputConnection = NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1801            } else {
1802                setDoInput(true);
1803            }
1804            connect();
1805            return inputConnection;
1806        }
1807
1808
1809
1810        /**
1811         * Returns <code>true</true> if you can successfully use getInputStream();
1812         */

1813        public boolean getDoInput() {
1814            return getInputConnection().getDoInput();
1815        }
1816
1817
1818        /**
1819         * {@inheritDoc}
1820         */

1821        public InputStream getInputStream() throws IOException {
1822            return getInputConnection().getInputStream();
1823        }
1824
1825        /**
1826         * Returns last URL which can be written, and which is still earlier the the first URL which can be read (or the same URL).
1827         * This ensures that when used for writing, it will then be the prefered one for reading.
1828         */

1829        protected URLConnection getOutputConnection() {
1830            if (outputConnection != null) {
1831                return outputConnection;
1832            }
1833
1834            // search connection which will be used for reading, and check if it can be used for writing
1835
ListIterator i = ResourceLoader.this.roots.listIterator();
1836            while (i.hasNext()) {
1837                PathURLStreamHandler cf = (PathURLStreamHandler) i.next();
1838                URLConnection c = cf.openConnection(name);
1839                if (c.getDoInput()) {
1840                    if(c.getDoOutput()) { // prefer the currently read one.
1841
outputConnection = c;
1842                    }
1843                    break;
1844                }
1845            }
1846            if (outputConnection == null) {
1847                // the URL used for reading, could not be written.
1848
// Now iterate backwards, and search one which can be.
1849
while (i.hasPrevious()) {
1850                    PathURLStreamHandler cf = (PathURLStreamHandler) i.previous();
1851                    URLConnection c = cf.openConnection(name);
1852                    if (c.getDoOutput()) {
1853                        outputConnection = c;
1854                        break;
1855                    }
1856                }
1857            }
1858
1859            if (outputConnection == null) {
1860                setDoOutput(false);
1861                outputConnection = NOT_AVAILABLE_URLSTREAM_HANDLER.openConnection(name);
1862            } else {
1863                setDoOutput(true);
1864            }
1865            connect();
1866            return outputConnection;
1867        }
1868
1869        /**
1870         * Returns <code>true</true> if you can successfully use getOutputStream();
1871         */

1872        public boolean getDoOutput() {
1873            return getOutputConnection().getDoOutput();
1874        }
1875        /**
1876         * {@inheritDoc}
1877         */

1878        public OutputStream getOutputStream() throws IOException {
1879            try {
1880                OutputStream os = getOutputConnection().getOutputStream();
1881                if (os == null) {
1882                    // Can find no place to store this resource. Giving up.
1883
throw new IOException("Cannot create an OutputStream for " + url + ", it can no way written, and no resource-node could be created)");
1884                } else {
1885                    return os;
1886                }
1887            } catch (Exception JavaDoc ioe) {
1888                IOException i = new IOException("Cannot create an OutputStream for " + url + " " + ioe.getMessage());
1889                i.initCause(ioe);
1890                throw i;
1891            }
1892        }
1893        /**
1894         * {@inheritDoc}
1895         */

1896        public long getLastModified() {
1897            return getInputConnection().getLastModified();
1898        }
1899
1900    }
1901
1902    // ================================================================================
1903
/**
1904     * For testing purposes only
1905     */

1906    public static void main(String JavaDoc[] argv) {
1907        ResourceLoader resourceLoader;
1908
1909        if (System.getProperty("mmbase.htmlroot") != null) {
1910            resourceLoader = getWebRoot();
1911        } else if (System.getProperty("mmbase.config") != null) {
1912            resourceLoader = getConfigurationRoot();
1913        } else {
1914            resourceLoader = getSystemRoot();
1915        }
1916        try {
1917            if (argv.length == 0) {
1918                System.err.println("useage: java [-Dmmbase.config=<config dir>|-Dmmbase.htmlroot=<some other dir>] " + ResourceLoader.class.getName() + " [<sub directory>] [<resource-name>|*|**]");
1919                return;
1920            }
1921            String JavaDoc arg = argv[0];
1922            if (argv.length > 1) {
1923                resourceLoader = getConfigurationRoot().getChildResourceLoader(argv[0]);
1924                arg = argv[1];
1925            }
1926            if (arg.equals("*") || arg.equals("**")) {
1927                Iterator i = resourceLoader.getResourcePaths(Pattern.compile(".*"), arg.equals("**")).iterator();
1928                while (i.hasNext()) {
1929                    System.out.println("" + i.next());
1930                }
1931            } else {
1932                InputStream resource = resourceLoader.getResourceAsStream(arg);
1933                if (resource == null) {
1934                    System.out.println("No such resource " + arg + " for " + resourceLoader.getResource(arg) + ". Creating now.");
1935                    PrintWriter writer = new PrintWriter(resourceLoader.getWriter(arg));
1936                    writer.println("TEST");
1937                    writer.close();
1938                    return;
1939                }
1940                System.out.println("-------------------- resolved " + resourceLoader.getResourceList(arg) + " with " + resourceLoader + ": ");
1941
1942                BufferedReader reader = new BufferedReader(new InputStreamReader(resource));
1943
1944                while(true) {
1945                    String JavaDoc line = reader.readLine();
1946                    if (line == null) break;
1947                    System.out.println(line);
1948                }
1949            }
1950
1951        } catch (Exception JavaDoc mfeu) {
1952            System.err.println(mfeu.getMessage() + Logging.stackTrace(mfeu));
1953        }
1954    }
1955
1956
1957}
1958
1959
Popular Tags