View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jelly;
17  
18  import java.io.File;
19  import java.io.InputStream;
20  import java.io.IOException;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.util.Hashtable;
24  import java.util.Iterator;
25  import java.util.Map;
26  
27  import org.apache.commons.jelly.parser.XMLParser;
28  import org.apache.commons.jelly.util.ClassLoaderUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  import org.xml.sax.InputSource;
33  import org.xml.sax.SAXException;
34  
35  /***
36    * <p><code>JellyContext</code> represents the Jelly context.</p>
37    *
38    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
39    * @version $Revision: 1.10 $
40    */
41  public class JellyContext {
42  
43      /*** The Log to which logging calls will be made. */
44      private static final Log log = LogFactory.getLog(JellyContext.class);
45  
46      /*** Default for inheritance of variables **/
47      private static final boolean DEFAULT_INHERIT = true;
48  
49      /*** Default for export of variables **/
50      private static final boolean DEFAULT_EXPORT = false;
51  
52      /*** String used to denote a script can't be parsed */
53      private static final String BAD_PARSE = "Could not parse Jelly script";
54  
55      /***
56       * The class loader to use for instantiating application objects.
57       * If not specified, the context class loader, or the class loader
58       * used to load this class itself, is used, based on the value of the
59       * <code>useContextClassLoader</code> variable.
60       */
61      protected ClassLoader classLoader;
62  
63      /***
64       * Do we want to use the Context ClassLoader when loading classes
65       * for instantiating new objects?  Default is <code>false</code>.
66       */
67      protected boolean useContextClassLoader = false;
68  
69      /*** The root URL context (where scripts are located from) */
70      private URL rootURL;
71  
72      /*** The current URL context (where relative scripts are located from) */
73      private URL currentURL;
74  
75      /*** Tag libraries found so far */
76      private Map taglibs = new Hashtable();
77  
78      /*** synchronized access to the variables in scope */
79      private Map variables = new Hashtable();
80  
81      /*** The parent context */
82      private JellyContext parent;
83  
84      /*** Do we inherit variables from parent context? */
85      private boolean inherit = JellyContext.DEFAULT_INHERIT;
86  
87      /*** Do we export our variables to parent context? */
88      private boolean export  = JellyContext.DEFAULT_EXPORT;
89  
90      /*** Should we export tag libraries to our parents context */
91      private boolean exportLibraries = true;
92  
93      /*** Should we cache Tag instances, per thread, to reduce object contruction overhead? */
94      private boolean cacheTags = false;
95  
96      /***
97       * Create a new context with the currentURL set to the rootURL
98       */
99      public JellyContext() {
100         this.currentURL = rootURL;
101         init();
102     }
103 
104     /***
105      * Create a new context with the given rootURL
106      * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
107      */
108     public JellyContext(URL rootURL) {
109         this( rootURL, rootURL );
110     }
111 
112     /***
113      * Create a new context with the given rootURL and currentURL
114      * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
115      * @param currentURL the root URL used in resolving relative resources
116      */
117     public JellyContext(URL rootURL, URL currentURL) {
118         this.rootURL = rootURL;
119         this.currentURL = currentURL;
120         init();
121     }
122 
123 
124     /***
125      * Create a new context with the given parent context.
126      * The parent's rootURL and currentURL are set on the child, and the parent's variables are
127      * available in the child context under the name <code>parentScope</code>.
128      *
129      * @param parent the parent context for the newly created context.
130      */
131     public JellyContext(JellyContext parent) {
132         this.parent = parent;
133         this.rootURL = parent.rootURL;
134         this.currentURL = parent.currentURL;
135         this.variables.put("parentScope", parent.variables);
136         this.cacheTags = parent.cacheTags;
137         init();
138     }
139 
140     /***
141      * Create a new context with the given parent context.
142      * The parent's rootURL are set on the child, and the parent's variables are
143      * available in the child context under the name <code>parentScope</code>.
144      *
145      * @param parentJellyContext the parent context for the newly created context.
146      * @param currentURL the root URL used in resolving relative resources
147      */
148     public JellyContext(JellyContext parentJellyContext, URL currentURL) {
149         this(parentJellyContext);
150         this.currentURL = currentURL;
151     }
152 
153     /***
154      * Create a new context with the given parent context.
155      * The parent's variables are available in the child context under the name <code>parentScope</code>.
156      *
157      * @param parentJellyContext the parent context for the newly created context.
158      * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
159      * @param currentURL the root URL used in resolving relative resources
160      */
161     public JellyContext(JellyContext parentJellyContext, URL rootURL, URL currentURL) {
162         this(parentJellyContext, currentURL);
163         this.rootURL = rootURL;
164     }
165 
166     /***
167      * Initialize the context.
168      * This includes adding the context to itself under the name <code>context</code> and
169      * making the System Properties available as <code>systemScope</code>
170      */
171     private void init() {
172         variables.put("context",this);
173         try {
174             variables.put("systemScope", System.getProperties() );
175         } catch (SecurityException e) {
176             log.debug("security exception accessing system properties", e);
177         }
178     }
179 
180     /***
181      * @return the parent context for this context
182      */
183     public JellyContext getParent() {
184         return parent;
185     }
186 
187     /***
188      * @return the scope of the given name, such as the 'parent' scope.
189      * If Jelly is used in a Servlet situation then 'request', 'session' and 'application' are other names
190      * for scopes
191      */
192     public JellyContext getScope(String name) {
193         if ( "parent".equals( name ) ) {
194             return getParent();
195         }
196         return null;
197     }
198 
199     /***
200      * Finds the variable value of the given name in this context or in any other parent context.
201      * If this context does not contain the variable, then its parent is used and then its parent
202      * and so forth until the context with no parent is found.
203      *
204      * @return the value of the variable in this or one of its descendant contexts or null
205      *  if the variable could not be found.
206      */
207     public Object findVariable(String name) {
208         Object answer = variables.get(name);
209         boolean definedHere = answer != null || variables.containsKey(name);
210 
211         if (definedHere) return answer;
212 
213         if ( answer == null && parent != null ) {
214             answer = parent.findVariable(name);
215         }
216         // ### this is a hack - remove this when we have support for pluggable Scopes
217         if ( answer == null ) {
218             answer = getSystemProperty(name);
219         }
220 
221         if (log.isDebugEnabled()) {
222             log.debug("findVariable: " + name + " value: " + answer );
223         }
224         return answer;
225     }
226 
227 
228     /*** @return the value of the given variable name */
229     public Object getVariable(String name) {
230         Object value = variables.get(name);
231         boolean definedHere = value != null || variables.containsKey(name);
232 
233         if (definedHere) return value;
234 
235         if ( value == null && isInherit() ) {
236             JellyContext parentContext = getParent();
237             if (parentContext != null) {
238                 value = parentContext.getVariable( name );
239             }
240         }
241 
242         // ### this is a hack - remove this when we have support for pluggable Scopes
243         if ( value == null ) {
244             value = getSystemProperty(name);
245         }
246 
247         return value;
248     }
249 
250     /***
251      * Get a system property and handle security exceptions
252      * @param name the name of the property to retrieve
253      * @return the value of the property, or null if a SecurityException occurs
254      */
255     private Object getSystemProperty(String name) {
256         try {
257             return System.getProperty(name);
258         }
259         catch (SecurityException e) {
260             log.debug("security exception accessing system properties", e);
261         }
262         return null;
263     }
264 
265     /***
266      * @return the value of the given variable name in the given variable scope
267      * @param name is the name of the variable
268      * @param scopeName is the optional scope name such as 'parent'. For servlet environments
269      * this could be 'application', 'session' or 'request'.
270      */
271     public Object getVariable(String name, String scopeName) {
272         JellyContext scope = getScope(scopeName);
273         if ( scope != null ) {
274             return scope.getVariable(name);
275         }
276         return null;
277     }
278 
279 
280 
281     /*** Sets the value of the named variable */
282     public void setVariable(String name, Object value) {
283         if ( isExport() ) {
284             getParent().setVariable( name, value );
285             return;
286         }
287         if (value == null) {
288             variables.remove(name);
289         }
290         else {
291             variables.put(name, value);
292         }
293     }
294 
295     /***
296      * Sets the value of the given variable name in the given variable scope
297      * @param name is the name of the variable
298      * @param scopeName is the optional scope name such as 'parent'. For servlet environments
299      *  this could be 'application', 'session' or 'request'.
300      * @param value is the value of the attribute
301      */
302     public void setVariable(String name, String scopeName, Object value) {
303         JellyContext scope = getScope(scopeName);
304         if ( scope != null ) {
305             scope.setVariable(name, value);
306         }
307     }
308 
309     /*** Removes the given variable */
310     public void removeVariable(String name) {
311         variables.remove(name);
312     }
313 
314     /***
315      * Removes the given variable in the specified scope.
316      *
317      * @param name is the name of the variable
318      * @param scopeName is the optional scope name such as 'parent'. For servlet environments
319      *  this could be 'application', 'session' or 'request'.
320      */
321     public void removeVariable(String name, String scopeName) {
322         JellyContext scope = getScope(scopeName);
323         if ( scope != null ) {
324             scope.removeVariable(name);
325         }
326     }
327 
328     /***
329      * @return an Iterator over the current variable names in this
330      * context
331      */
332     public Iterator getVariableNames() {
333         return variables.keySet().iterator();
334     }
335 
336     /***
337      * @return the Map of variables in this scope
338      */
339     public Map getVariables() {
340         return variables;
341     }
342 
343     /***
344      * Sets the Map of variables to use
345      */
346     public void setVariables(Map variables) {
347         // I have seen this fail when the passed Map contains a key, value
348         // pair where the value is null
349         for (Iterator iter = variables.entrySet().iterator(); iter.hasNext();) {
350             Map.Entry element = (Map.Entry) iter.next();
351             if (element.getValue() != null) {
352                 this.variables.put(element.getKey(), element.getValue());
353             }
354         }
355         //this.variables.putAll( variables );
356     }
357 
358     /***
359      * A factory method to create a new child context of the
360      * current context.
361      */
362     public JellyContext newJellyContext(Map newVariables) {
363         // XXXX: should allow this new context to
364         // XXXX: inherit parent contexts?
365         // XXXX: Or at least publish the parent scope
366         // XXXX: as a Map in this new variable scope?
367         newVariables.put("parentScope", variables);
368         JellyContext answer = createChildContext();
369         answer.setVariables(newVariables);
370         return answer;
371     }
372 
373     /***
374      * A factory method to create a new child context of the
375      * current context.
376      */
377     public JellyContext newJellyContext() {
378         return createChildContext();
379     }
380 
381     /*** Registers the given tag library against the given namespace URI.
382      * This should be called before the parser is used.
383      */
384     public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
385         if (log.isDebugEnabled()) {
386             log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
387         }
388         taglibs.put(namespaceURI, taglib);
389 
390         if (isExportLibraries() && parent != null) {
391             parent.registerTagLibrary( namespaceURI, taglib );
392         }
393     }
394 
395     /*** Registers the given tag library class name against the given namespace URI.
396      * The class will be loaded via the given ClassLoader
397      * This should be called before the parser is used.
398      */
399     public void registerTagLibrary(
400         String namespaceURI,
401         String className) {
402 
403         if (log.isDebugEnabled()) {
404             log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
405         }
406         taglibs.put(namespaceURI, className);
407 
408         if (isExportLibraries() && parent != null) {
409             parent.registerTagLibrary( namespaceURI, className );
410         }
411     }
412 
413     public boolean isTagLibraryRegistered(String namespaceURI) {
414         boolean answer = taglibs.containsKey( namespaceURI );
415         if (answer) {
416             return true;
417         }
418         else if ( parent != null ) {
419             return parent.isTagLibraryRegistered(namespaceURI);
420         }
421         else {
422             return false;
423         }
424     }
425 
426     /***
427      * @return the TagLibrary for the given namespace URI or null if one could not be found
428      */
429     public TagLibrary getTagLibrary(String namespaceURI) {
430 
431         // use my own mapping first, so that namespaceURIs can
432         // be redefined inside child contexts...
433 
434         Object answer = taglibs.get(namespaceURI);
435 
436         if ( answer == null && parent != null ) {
437             answer = parent.getTagLibrary( namespaceURI );
438         }
439 
440         if ( answer instanceof TagLibrary ) {
441             return (TagLibrary) answer;
442         }
443         else if ( answer instanceof String ) {
444             String className = (String) answer;
445             Class theClass = null;
446             try {
447                 theClass = getClassLoader().loadClass(className);
448             }
449             catch (ClassNotFoundException e) {
450                 log.error("Could not find the class: " + className, e);
451             }
452             if ( theClass != null ) {
453                 try {
454                     Object object = theClass.newInstance();
455                     if (object instanceof TagLibrary) {
456                         taglibs.put(namespaceURI, object);
457                         return (TagLibrary) object;
458                     }
459                     else {
460                         log.error(
461                             "The tag library object mapped to: "
462                                 + namespaceURI
463                                 + " is not a TagLibrary. Object = "
464                                 + object);
465                     }
466                 }
467                 catch (Exception e) {
468                     log.error(
469                         "Could not instantiate instance of class: " + className + ". Reason: " + e,
470                         e);
471                 }
472             }
473         }
474 
475         return null;
476     }
477 
478     /***
479      * Attempts to parse the script from the given uri using the
480      * {@link #getResource} method then returns the compiled script.
481      */
482     public Script compileScript(String uri) throws JellyException {
483         XMLParser parser = getXMLParser();
484         parser.setContext(this);
485         InputStream in = getResourceAsStream(uri);
486         if (in == null) {
487             throw new JellyException("Could not find Jelly script: " + uri);
488         }
489         Script script = null;
490         try {
491             script = parser.parse(in);
492         } catch (IOException e) {
493             throw new JellyException(JellyContext.BAD_PARSE, e);
494         } catch (SAXException e) {
495             throw new JellyException(JellyContext.BAD_PARSE, e);
496         }
497 
498         return script.compile();
499     }
500 
501     /***
502      * Attempts to parse the script from the given URL using the
503      * {@link #getResource} method then returns the compiled script.
504      */
505     public Script compileScript(URL url) throws JellyException {
506         XMLParser parser = getXMLParser();
507         parser.setContext(this);
508 
509         Script script = null;
510         try {
511             script = parser.parse(url.toString());
512         } catch (IOException e) {
513             throw new JellyException(JellyContext.BAD_PARSE, e);
514         } catch (SAXException e) {
515             throw new JellyException(JellyContext.BAD_PARSE, e);
516         }
517 
518         return script.compile();
519     }
520 
521     /***
522      * Attempts to parse the script from the given InputSource using the
523      * {@link #getResource} method then returns the compiled script.
524      */
525     public Script compileScript(InputSource source) throws JellyException {
526         XMLParser parser = getXMLParser();
527         parser.setContext(this);
528 
529         Script script = null;
530         try {
531             script = parser.parse(source);
532         } catch (IOException e) {
533             throw new JellyException(JellyContext.BAD_PARSE, e);
534         } catch (SAXException e) {
535             throw new JellyException(JellyContext.BAD_PARSE, e);
536         }
537 
538         return script.compile();
539     }
540 
541     /***
542      * @return a thread pooled XMLParser to avoid the startup overhead
543      * of the XMLParser
544      */
545     protected XMLParser getXMLParser() {
546         XMLParser parser = createXMLParser();
547         return parser;
548     }
549 
550     /***
551      * Factory method to allow JellyContext implementations to overload how an XMLParser
552      * is created - such as to overload what the default ExpressionFactory should be.
553      */
554     protected XMLParser createXMLParser() {
555         return new XMLParser();
556     }
557 
558     /***
559      * Parses the script from the given File then compiles it and runs it.
560      *
561      * @return the new child context that was used to run the script
562      */
563     public JellyContext runScript(File file, XMLOutput output) throws JellyException {
564         try {
565             return runScript(file.toURL(), output, JellyContext.DEFAULT_EXPORT,
566                 JellyContext.DEFAULT_INHERIT);
567         } catch (MalformedURLException e) {
568             throw new JellyException(e.toString());
569         }
570     }
571 
572     /***
573      * Parses the script from the given URL then compiles it and runs it.
574      *
575      * @return the new child context that was used to run the script
576      */
577     public JellyContext runScript(URL url, XMLOutput output) throws JellyException {
578         return runScript(url, output, JellyContext.DEFAULT_EXPORT,
579             JellyContext.DEFAULT_INHERIT);
580     }
581 
582     /***
583      * Parses the script from the given InputSource then compiles it and runs it.
584      *
585      * @return the new child context that was used to run the script
586      */
587     public JellyContext runScript(InputSource source, XMLOutput output) throws JellyException {
588         return runScript(source, output, JellyContext.DEFAULT_EXPORT,
589             JellyContext.DEFAULT_INHERIT);
590     }
591 
592     /***
593      * Parses the script from the given uri using the
594      * JellyContext.getResource() API then compiles it and runs it.
595      *
596      * @return the new child context that was used to run the script
597      */
598     public JellyContext runScript(String uri, XMLOutput output) throws JellyException {
599         URL url = null;
600         try {
601             url = getResource(uri);
602         } catch (MalformedURLException e) {
603             throw new JellyException(e.toString());
604         }
605 
606         if (url == null) {
607             throw new JellyException("Could not find Jelly script: " + url);
608         }
609         return runScript(url, output, JellyContext.DEFAULT_EXPORT,
610             JellyContext.DEFAULT_INHERIT);
611     }
612 
613     /***
614      * Parses the script from the given uri using the
615      * JellyContext.getResource() API then compiles it and runs it.
616      *
617      * @return the new child context that was used to run the script
618      */
619     public JellyContext runScript(String uri, XMLOutput output,
620                           boolean export, boolean inherit) throws JellyException {
621         URL url = null;
622         try {
623             url = getResource(uri);
624         } catch (MalformedURLException e) {
625             throw new JellyException(e.toString());
626         }
627 
628         if (url == null) {
629             throw new JellyException("Could not find Jelly script: " + url);
630         }
631 
632         return runScript(url, output, export, inherit);
633     }
634 
635     /***
636      * Parses the script from the given file then compiles it and runs it.
637      *
638      * @return the new child context that was used to run the script
639      */
640     public JellyContext runScript(File file, XMLOutput output,
641                           boolean export, boolean inherit) throws JellyException {
642         try {
643             return runScript(file.toURL(), output, export, inherit);
644         } catch (MalformedURLException e) {
645             throw new JellyException(e.toString());
646         }
647     }
648 
649     /***
650      * Parses the script from the given URL then compiles it and runs it.
651      *
652      * @return the new child context that was used to run the script
653      */
654     public JellyContext runScript(URL url, XMLOutput output,
655                           boolean export, boolean inherit) throws JellyException {
656         return runScript(new InputSource(url.toString()), output, export, inherit);
657     }
658 
659     /***
660      * Parses the script from the given InputSource then compiles it and runs it.
661      *
662      * @return the new child context that was used to run the script
663      */
664     public JellyContext runScript(InputSource source, XMLOutput output,
665                           boolean export, boolean inherit) throws JellyException {
666         Script script = compileScript(source);
667 
668         URL newJellyContextURL = null;
669         try {
670             newJellyContextURL = getJellyContextURL(source);
671         } catch (MalformedURLException e) {
672             throw new JellyException(e.toString());
673         }
674 
675         JellyContext newJellyContext = newJellyContext();
676         newJellyContext.setRootURL( newJellyContextURL );
677         newJellyContext.setCurrentURL( newJellyContextURL );
678         newJellyContext.setExport( export );
679         newJellyContext.setInherit( inherit );
680 
681         if ( inherit ) {
682             // use the same variable scopes
683             newJellyContext.variables = this.variables;
684         }
685 
686         if (log.isDebugEnabled() ) {
687             log.debug( "About to run script: " + source.getSystemId() );
688             log.debug( "root context URL: " + newJellyContext.rootURL );
689             log.debug( "current context URL: " + newJellyContext.currentURL );
690         }
691 
692         script.run(newJellyContext, output);
693 
694         return newJellyContext;
695     }
696 
697     /***
698      * Returns a URL for the given resource from the specified path.
699      * If the uri starts with "/" then the path is taken as relative to
700      * the current context root.
701      * If the uri is a well formed URL then it is used.
702      * If the uri is a file that exists and can be read then it is used.
703      * Otherwise the uri is interpreted as relative to the current context (the
704      * location of the current script).
705      */
706     public URL getResource(String uri) throws MalformedURLException {
707         if (uri.startsWith("/")) {
708             // append this uri to the context root
709             return createRelativeURL(rootURL, uri.substring(1));
710         }
711         else {
712             try {
713                 return new URL(uri);
714             }
715             catch (MalformedURLException e) {
716                 // lets try find a relative resource
717                 try {
718                     return createRelativeURL(currentURL, uri);
719                 } catch (MalformedURLException e2) {
720                     throw e;
721                 }
722             }
723         }
724     }
725 
726     /***
727      * Attempts to open an InputStream to the given resource at the specified path.
728      * If the uri starts with "/" then the path is taken as relative to
729      * the current context root. If the uri is a well formed URL then it
730      * is used. Otherwise the uri is interpreted as relative to the current
731      * context (the location of the current script).
732      *
733      * @return null if this resource could not be loaded, otherwise the resources
734      *  input stream is returned.
735      */
736     public InputStream getResourceAsStream(String uri) {
737         try {
738             URL url = getResource(uri);
739             return url.openStream();
740         }
741         catch (Exception e) {
742             if (log.isTraceEnabled()) {
743                 log.trace(
744                     "Caught exception attempting to open: " + uri + ". Exception: " + e,
745                     e);
746             }
747             return null;
748         }
749     }
750 
751 
752     // Properties
753     //-------------------------------------------------------------------------
754 
755     /***
756      * @return the current root context URL from which all absolute resource URIs
757      *  will be relative to. For example in a web application the root URL will
758      *  map to the web directory which contains the WEB-INF directory.
759      */
760     public URL getRootURL() {
761         return rootURL;
762     }
763 
764     /***
765      * Sets the current root context URL from which all absolute resource URIs
766      *  will be relative to. For example in a web application the root URL will
767      *  map to the web directory which contains the WEB-INF directory.
768      */
769     public void setRootURL(URL rootURL) {
770         this.rootURL = rootURL;
771     }
772 
773 
774     /***
775      * @return the current URL context of the current script that is executing.
776      *  This URL context is used to deduce relative scripts when relative URIs are
777      *  used in calls to {@link #getResource} to process relative scripts.
778      */
779     public URL getCurrentURL() {
780         return currentURL;
781     }
782 
783     /***
784      * Sets the current URL context of the current script that is executing.
785      *  This URL context is used to deduce relative scripts when relative URIs are
786      *  used in calls to {@link #getResource} to process relative scripts.
787      */
788     public void setCurrentURL(URL currentURL) {
789         this.currentURL = currentURL;
790     }
791 
792     /***
793      * Returns whether caching of Tag instances, per thread, is enabled.
794      * Caching Tags can boost performance, on some JVMs, by reducing the cost of
795      * object construction when running Jelly inside a multi-threaded application server
796      * such as a Servlet engine.
797      *
798      * @return whether caching of Tag instances is enabled.
799      */
800     public boolean isCacheTags() {
801         return cacheTags;
802     }
803 
804     /***
805      * Sets whether caching of Tag instances, per thread, is enabled.
806      * Caching Tags can boost performance, on some JVMs, by reducing the cost of
807      * object construction when running Jelly inside a multi-threaded application server
808      * such as a Servlet engine.
809      *
810      * @param cacheTags Whether caching should be enabled or disabled.
811      */
812     public void setCacheTags(boolean cacheTags) {
813         this.cacheTags = cacheTags;
814     }
815 
816     /***
817      * Returns whether we export tag libraries to our parents context
818      * @return boolean
819      */
820     public boolean isExportLibraries() {
821         return exportLibraries;
822     }
823 
824     /***
825      * Sets whether we export tag libraries to our parents context
826      * @param exportLibraries The exportLibraries to set
827      */
828     public void setExportLibraries(boolean exportLibraries) {
829         this.exportLibraries = exportLibraries;
830     }
831 
832 
833     /***
834      * Sets whether we should export variable definitions to our parent context
835      */
836     public void setExport(boolean export) {
837         this.export = export;
838     }
839 
840     /***
841      * @return whether we should export variable definitions to our parent context
842      */
843     public boolean isExport() {
844         return this.export;
845     }
846 
847     /***
848      * Sets whether we should inherit variables from our parent context
849      */
850     public void setInherit(boolean inherit) {
851         this.inherit = inherit;
852     }
853 
854     /***
855      * @return whether we should inherit variables from our parent context
856      */
857     public boolean isInherit() {
858         return this.inherit;
859     }
860 
861 
862     /***
863      * Return the class loader to be used for instantiating application objects
864      * when required.  This is determined based upon the following rules:
865      * <ul>
866      * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
867      * <li>The thread context class loader, if it exists and the
868      *     <code>useContextClassLoader</code> property is set to true</li>
869      * <li>The class loader used to load the XMLParser class itself.
870      * </ul>
871      */
872     public ClassLoader getClassLoader() {
873         return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
874     }
875 
876     /***
877      * Set the class loader to be used for instantiating application objects
878      * when required.
879      *
880      * @param classLoader The new class loader to use, or <code>null</code>
881      *  to revert to the standard rules
882      */
883     public void setClassLoader(ClassLoader classLoader) {
884         this.classLoader = classLoader;
885     }
886 
887     /***
888      * Return the boolean as to whether the context classloader should be used.
889      */
890     public boolean getUseContextClassLoader() {
891         return useContextClassLoader;
892     }
893 
894     /***
895      * Determine whether to use the Context ClassLoader (the one found by
896      * calling <code>Thread.currentThread().getContextClassLoader()</code>)
897      * to resolve/load classes.  If not
898      * using Context ClassLoader, then the class-loading defaults to
899      * using the calling-class' ClassLoader.
900      *
901      * @param use determines whether to use JellyContext ClassLoader.
902      */
903     public void setUseContextClassLoader(boolean use) {
904         useContextClassLoader = use;
905     }
906 
907 
908     // Implementation methods
909     //-------------------------------------------------------------------------
910     /***
911      * @return a new relative URL from the given root and with the addition of the
912      * extra relative URI
913      *
914      * @param rootURL is the root context from which the relative URI will be applied
915      * @param relativeURI is the relative URI (without a leading "/")
916      * @throws MalformedURLException if the URL is invalid.
917      */
918     protected URL createRelativeURL(URL rootURL, String relativeURI)
919         throws MalformedURLException {
920         URL url = rootURL;
921         if (url == null) {
922             File file = new File(System.getProperty("user.dir"));
923             url = file.toURL();
924         }
925         String urlText = url.toString() + relativeURI;
926         if ( log.isDebugEnabled() ) {
927             log.debug("Attempting to open url: " + urlText);
928         }
929         return new URL(urlText);
930     }
931 
932     /***
933      * Strips off the name of a script to create a new context URL
934      */
935     protected URL getJellyContextURL(URL url) throws MalformedURLException {
936         String text = url.toString();
937         int idx = text.lastIndexOf('/');
938         text = text.substring(0, idx + 1);
939         return new URL(text);
940     }
941 
942     /***
943      * Strips off the name of a script to create a new context URL
944      */
945     protected URL getJellyContextURL(InputSource source) throws MalformedURLException {
946         String text = source.getSystemId();
947         int idx = text.lastIndexOf('/');
948         text = text.substring(0, idx + 1);
949         return new URL(text);
950     }
951 
952     /***
953      * Factory method to create a new child of this context
954      */
955     protected JellyContext createChildContext() {
956         return new JellyContext(this);
957     }
958 
959     /***
960      * Change the parent context to the one provided
961      * @param context the new parent context
962      */
963     protected void setParent(JellyContext context)
964     {
965         parent = context;
966         this.variables.put("parentScope", parent.variables);
967         // need to re-export tag libraries to the new parent
968         if (isExportLibraries() && parent != null) {
969             for (Iterator keys = taglibs.keySet().iterator(); keys.hasNext();)
970             {
971                 String namespaceURI = (String) keys.next();
972                 Object tagLibOrClassName = taglibs.get(namespaceURI);
973                 if (tagLibOrClassName instanceof TagLibrary)
974                 {
975                     parent.registerTagLibrary( namespaceURI, (TagLibrary) tagLibOrClassName );
976                 }
977                 else
978                 {
979                     parent.registerTagLibrary( namespaceURI, (String) tagLibOrClassName );
980                 }
981             }
982         }
983 
984     }
985 
986 }