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