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.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: 227285 $
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     /*** Clears variables set by Tags.
382      * @see #clearVariables()
383       */
384     public void clear() {
385         clearVariables();
386     }
387     
388     /*** Clears variables set by Tags (variables set while running a Jelly script)
389      * @see #clear()
390      */
391     protected void clearVariables() {
392         variables.clear();
393     }
394     
395     /*** Registers the given tag library against the given namespace URI.
396      * This should be called before the parser is used.
397      */
398     public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
399         if (log.isDebugEnabled()) {
400             log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
401         }
402         taglibs.put(namespaceURI, taglib);
403 
404         if (isExportLibraries() && parent != null) {
405             parent.registerTagLibrary( namespaceURI, taglib );
406         }
407     }
408 
409     /*** Registers the given tag library class name against the given namespace URI.
410      * The class will be loaded via the given ClassLoader
411      * This should be called before the parser is used.
412      */
413     public void registerTagLibrary(
414         String namespaceURI,
415         String className) {
416 
417         if (log.isDebugEnabled()) {
418             log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
419         }
420         taglibs.put(namespaceURI, className);
421 
422         if (isExportLibraries() && parent != null) {
423             parent.registerTagLibrary( namespaceURI, className );
424         }
425     }
426 
427     public boolean isTagLibraryRegistered(String namespaceURI) {
428         boolean answer = taglibs.containsKey( namespaceURI );
429         if (answer) {
430             return true;
431         }
432         else if ( parent != null ) {
433             return parent.isTagLibraryRegistered(namespaceURI);
434         }
435         else {
436             return false;
437         }
438     }
439 
440     /***
441      * @return the TagLibrary for the given namespace URI or null if one could not be found
442      */
443     public TagLibrary getTagLibrary(String namespaceURI) {
444 
445         // use my own mapping first, so that namespaceURIs can
446         // be redefined inside child contexts...
447 
448         Object answer = taglibs.get(namespaceURI);
449 
450         if ( answer == null && parent != null ) {
451             answer = parent.getTagLibrary( namespaceURI );
452         }
453 
454         if ( answer instanceof TagLibrary ) {
455             return (TagLibrary) answer;
456         }
457         else if ( answer instanceof String ) {
458             String className = (String) answer;
459             Class theClass = null;
460             try {
461                 theClass = getClassLoader().loadClass(className);
462             }
463             catch (ClassNotFoundException e) {
464                 log.error("Could not find the class: " + className, e);
465             }
466             if ( theClass != null ) {
467                 try {
468                     Object object = theClass.newInstance();
469                     if (object instanceof TagLibrary) {
470                         taglibs.put(namespaceURI, object);
471                         return (TagLibrary) object;
472                     }
473                     else {
474                         log.error(
475                             "The tag library object mapped to: "
476                                 + namespaceURI
477                                 + " is not a TagLibrary. Object = "
478                                 + object);
479                     }
480                 }
481                 catch (Exception e) {
482                     log.error(
483                         "Could not instantiate instance of class: " + className + ". Reason: " + e,
484                         e);
485                 }
486             }
487         }
488 
489         return null;
490     }
491 
492     /***
493      * Attempts to parse the script from the given uri using the
494      * {@link #getResource} method then returns the compiled script.
495      */
496     public Script compileScript(String uri) throws JellyException {
497         XMLParser parser = getXMLParser();
498         parser.setContext(this);
499         InputStream in = getResourceAsStream(uri);
500         if (in == null) {
501             throw new JellyException("Could not find Jelly script: " + uri);
502         }
503         Script script = null;
504         try {
505             script = parser.parse(in);
506         } catch (IOException e) {
507             throw new JellyException(JellyContext.BAD_PARSE, e);
508         } catch (SAXException e) {
509             throw new JellyException(JellyContext.BAD_PARSE, e);
510         }
511 
512         return script.compile();
513     }
514 
515     /***
516      * Attempts to parse the script from the given URL using the
517      * {@link #getResource} method then returns the compiled script.
518      */
519     public Script compileScript(URL url) throws JellyException {
520         XMLParser parser = getXMLParser();
521         parser.setContext(this);
522 
523         Script script = null;
524         try {
525             script = parser.parse(url.toString());
526         } catch (IOException e) {
527             throw new JellyException(JellyContext.BAD_PARSE, e);
528         } catch (SAXException e) {
529             throw new JellyException(JellyContext.BAD_PARSE, e);
530         }
531 
532         return script.compile();
533     }
534 
535     /***
536      * Attempts to parse the script from the given InputSource using the
537      * {@link #getResource} method then returns the compiled script.
538      */
539     public Script compileScript(InputSource source) throws JellyException {
540         XMLParser parser = getXMLParser();
541         parser.setContext(this);
542 
543         Script script = null;
544         try {
545             script = parser.parse(source);
546         } catch (IOException e) {
547             throw new JellyException(JellyContext.BAD_PARSE, e);
548         } catch (SAXException e) {
549             throw new JellyException(JellyContext.BAD_PARSE, e);
550         }
551 
552         return script.compile();
553     }
554 
555     /***
556      * @return a thread pooled XMLParser to avoid the startup overhead
557      * of the XMLParser
558      */
559     protected XMLParser getXMLParser() {
560         XMLParser parser = createXMLParser();
561         return parser;
562     }
563 
564     /***
565      * Factory method to allow JellyContext implementations to overload how an XMLParser
566      * is created - such as to overload what the default ExpressionFactory should be.
567      */
568     protected XMLParser createXMLParser() {
569         return new XMLParser();
570     }
571 
572     /***
573      * Parses the script from the given File 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(File file, XMLOutput output) throws JellyException {
578         try {
579             return runScript(file.toURL(), output, JellyContext.DEFAULT_EXPORT,
580                 JellyContext.DEFAULT_INHERIT);
581         } catch (MalformedURLException e) {
582             throw new JellyException(e.toString());
583         }
584     }
585 
586     /***
587      * Parses the script from the given URL then compiles it and runs it.
588      *
589      * @return the new child context that was used to run the script
590      */
591     public JellyContext runScript(URL url, XMLOutput output) throws JellyException {
592         return runScript(url, output, JellyContext.DEFAULT_EXPORT,
593             JellyContext.DEFAULT_INHERIT);
594     }
595 
596     /***
597      * Parses the script from the given InputSource then compiles it and runs it.
598      *
599      * @return the new child context that was used to run the script
600      */
601     public JellyContext runScript(InputSource source, XMLOutput output) throws JellyException {
602         return runScript(source, output, JellyContext.DEFAULT_EXPORT,
603             JellyContext.DEFAULT_INHERIT);
604     }
605 
606     /***
607      * Parses the script from the given uri using the
608      * JellyContext.getResource() API then compiles it and runs it.
609      *
610      * @return the new child context that was used to run the script
611      */
612     public JellyContext runScript(String uri, XMLOutput output) throws JellyException {
613         URL url = null;
614         try {
615             url = getResource(uri);
616         } catch (MalformedURLException e) {
617             throw new JellyException(e.toString());
618         }
619 
620         if (url == null) {
621             throw new JellyException("Could not find Jelly script: " + url);
622         }
623         return runScript(url, output, JellyContext.DEFAULT_EXPORT,
624             JellyContext.DEFAULT_INHERIT);
625     }
626 
627     /***
628      * Parses the script from the given uri using the
629      * JellyContext.getResource() API then compiles it and runs it.
630      *
631      * @return the new child context that was used to run the script
632      */
633     public JellyContext runScript(String uri, XMLOutput output,
634                           boolean export, boolean inherit) throws JellyException {
635         URL url = null;
636         try {
637             url = getResource(uri);
638         } catch (MalformedURLException e) {
639             throw new JellyException(e.toString());
640         }
641 
642         if (url == null) {
643             throw new JellyException("Could not find Jelly script: " + url);
644         }
645 
646         return runScript(url, output, export, inherit);
647     }
648 
649     /***
650      * Parses the script from the given file 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(File file, XMLOutput output,
655                           boolean export, boolean inherit) throws JellyException {
656         try {
657             return runScript(file.toURL(), output, export, inherit);
658         } catch (MalformedURLException e) {
659             throw new JellyException(e.toString());
660         }
661     }
662 
663     /***
664      * Parses the script from the given URL then compiles it and runs it.
665      *
666      * @return the new child context that was used to run the script
667      */
668     public JellyContext runScript(URL url, XMLOutput output,
669                           boolean export, boolean inherit) throws JellyException {
670         return runScript(new InputSource(url.toString()), output, export, inherit);
671     }
672 
673     /***
674      * Parses the script from the given InputSource then compiles it and runs it.
675      *
676      * @return the new child context that was used to run the script
677      */
678     public JellyContext runScript(InputSource source, XMLOutput output,
679                           boolean export, boolean inherit) throws JellyException {
680         Script script = compileScript(source);
681 
682         URL newJellyContextURL = null;
683         try {
684             newJellyContextURL = getJellyContextURL(source);
685         } catch (MalformedURLException e) {
686             throw new JellyException(e.toString());
687         }
688 
689         JellyContext newJellyContext = newJellyContext();
690         newJellyContext.setRootURL( newJellyContextURL );
691         newJellyContext.setCurrentURL( newJellyContextURL );
692         newJellyContext.setExport( export );
693         newJellyContext.setInherit( inherit );
694 
695         if ( inherit ) {
696             // use the same variable scopes
697             newJellyContext.variables = this.variables;
698         }
699 
700         if (log.isDebugEnabled() ) {
701             log.debug( "About to run script: " + source.getSystemId() );
702             log.debug( "root context URL: " + newJellyContext.rootURL );
703             log.debug( "current context URL: " + newJellyContext.currentURL );
704         }
705 
706         script.run(newJellyContext, output);
707 
708         return newJellyContext;
709     }
710 
711     /***
712      * Returns a URL for the given resource from the specified path.
713      * If the uri starts with "/" then the path is taken as relative to
714      * the current context root.
715      * If the uri is a well formed URL then it is used.
716      * If the uri is a file that exists and can be read then it is used.
717      * Otherwise the uri is interpreted as relative to the current context (the
718      * location of the current script).
719      */
720     public URL getResource(String uri) throws MalformedURLException {
721         if (uri.startsWith("/")) {
722             // append this uri to the context root
723             return createRelativeURL(rootURL, uri.substring(1));
724         }
725         else {
726             try {
727                 return new URL(uri);
728             }
729             catch (MalformedURLException e) {
730                 // lets try find a relative resource
731                 try {
732                     return createRelativeURL(currentURL, uri);
733                 } catch (MalformedURLException e2) {
734                     throw e;
735                 }
736             }
737         }
738     }
739 
740     /***
741      * Attempts to open an InputStream to the given resource at the specified path.
742      * If the uri starts with "/" then the path is taken as relative to
743      * the current context root. If the uri is a well formed URL then it
744      * is used. Otherwise the uri is interpreted as relative to the current
745      * context (the location of the current script).
746      *
747      * @return null if this resource could not be loaded, otherwise the resources
748      *  input stream is returned.
749      */
750     public InputStream getResourceAsStream(String uri) {
751         try {
752             URL url = getResource(uri);
753             return url.openStream();
754         }
755         catch (Exception e) {
756             if (log.isTraceEnabled()) {
757                 log.trace(
758                     "Caught exception attempting to open: " + uri + ". Exception: " + e,
759                     e);
760             }
761             return null;
762         }
763     }
764 
765 
766     // Properties
767     //-------------------------------------------------------------------------
768 
769     /***
770      * @return the current root context URL from which all absolute resource URIs
771      *  will be relative to. For example in a web application the root URL will
772      *  map to the web directory which contains the WEB-INF directory.
773      */
774     public URL getRootURL() {
775         return rootURL;
776     }
777 
778     /***
779      * Sets the current root context URL from which all absolute resource URIs
780      *  will be relative to. For example in a web application the root URL will
781      *  map to the web directory which contains the WEB-INF directory.
782      */
783     public void setRootURL(URL rootURL) {
784         this.rootURL = rootURL;
785     }
786 
787 
788     /***
789      * @return the current URL context of the current script that is executing.
790      *  This URL context is used to deduce relative scripts when relative URIs are
791      *  used in calls to {@link #getResource} to process relative scripts.
792      */
793     public URL getCurrentURL() {
794         return currentURL;
795     }
796 
797     /***
798      * Sets the current URL context of the current script that is executing.
799      *  This URL context is used to deduce relative scripts when relative URIs are
800      *  used in calls to {@link #getResource} to process relative scripts.
801      */
802     public void setCurrentURL(URL currentURL) {
803         this.currentURL = currentURL;
804     }
805 
806     /***
807      * Returns whether caching of Tag instances, per thread, is enabled.
808      * Caching Tags can boost performance, on some JVMs, by reducing the cost of
809      * object construction when running Jelly inside a multi-threaded application server
810      * such as a Servlet engine.
811      *
812      * @return whether caching of Tag instances is enabled.
813      */
814     public boolean isCacheTags() {
815         return cacheTags;
816     }
817 
818     /***
819      * Sets whether caching of Tag instances, per thread, is enabled.
820      * Caching Tags can boost performance, on some JVMs, by reducing the cost of
821      * object construction when running Jelly inside a multi-threaded application server
822      * such as a Servlet engine.
823      *
824      * @param cacheTags Whether caching should be enabled or disabled.
825      */
826     public void setCacheTags(boolean cacheTags) {
827         this.cacheTags = cacheTags;
828     }
829 
830     /***
831      * Returns whether we export tag libraries to our parents context
832      * @return boolean
833      */
834     public boolean isExportLibraries() {
835         return exportLibraries;
836     }
837 
838     /***
839      * Sets whether we export tag libraries to our parents context
840      * @param exportLibraries The exportLibraries to set
841      */
842     public void setExportLibraries(boolean exportLibraries) {
843         this.exportLibraries = exportLibraries;
844     }
845 
846 
847     /***
848      * Sets whether we should export variable definitions to our parent context
849      */
850     public void setExport(boolean export) {
851         this.export = export;
852     }
853 
854     /***
855      * @return whether we should export variable definitions to our parent context
856      */
857     public boolean isExport() {
858         return this.export;
859     }
860 
861     /***
862      * Sets whether we should inherit variables from our parent context
863      */
864     public void setInherit(boolean inherit) {
865         this.inherit = inherit;
866     }
867 
868     /***
869      * @return whether we should inherit variables from our parent context
870      */
871     public boolean isInherit() {
872         return this.inherit;
873     }
874 
875 
876     /***
877      * Return the class loader to be used for instantiating application objects
878      * when required.  This is determined based upon the following rules:
879      * <ul>
880      * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
881      * <li>The thread context class loader, if it exists and the
882      *     <code>useContextClassLoader</code> property is set to true</li>
883      * <li>The class loader used to load the XMLParser class itself.
884      * </ul>
885      */
886     public ClassLoader getClassLoader() {
887         return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
888     }
889 
890     /***
891      * Set the class loader to be used for instantiating application objects
892      * when required.
893      *
894      * @param classLoader The new class loader to use, or <code>null</code>
895      *  to revert to the standard rules
896      */
897     public void setClassLoader(ClassLoader classLoader) {
898         this.classLoader = classLoader;
899     }
900 
901     /***
902      * Return the boolean as to whether the context classloader should be used.
903      */
904     public boolean getUseContextClassLoader() {
905         return useContextClassLoader;
906     }
907 
908     /***
909      * Determine whether to use the Context ClassLoader (the one found by
910      * calling <code>Thread.currentThread().getContextClassLoader()</code>)
911      * to resolve/load classes.  If not
912      * using Context ClassLoader, then the class-loading defaults to
913      * using the calling-class' ClassLoader.
914      *
915      * @param use determines whether to use JellyContext ClassLoader.
916      */
917     public void setUseContextClassLoader(boolean use) {
918         useContextClassLoader = use;
919     }
920 
921 
922     // Implementation methods
923     //-------------------------------------------------------------------------
924     /***
925      * @return a new relative URL from the given root and with the addition of the
926      * extra relative URI
927      *
928      * @param rootURL is the root context from which the relative URI will be applied
929      * @param relativeURI is the relative URI (without a leading "/")
930      * @throws MalformedURLException if the URL is invalid.
931      */
932     protected URL createRelativeURL(URL rootURL, String relativeURI)
933         throws MalformedURLException {
934         URL url = rootURL;
935         if (url == null) {
936             File file = new File(System.getProperty("user.dir"));
937             url = file.toURL();
938         }
939         String urlText = url.toString() + relativeURI;
940         if ( log.isDebugEnabled() ) {
941             log.debug("Attempting to open url: " + urlText);
942         }
943         return new URL(urlText);
944     }
945 
946     /***
947      * Strips off the name of a script to create a new context URL
948      */
949     protected URL getJellyContextURL(URL url) throws MalformedURLException {
950         String text = url.toString();
951         int idx = text.lastIndexOf('/');
952         text = text.substring(0, idx + 1);
953         return new URL(text);
954     }
955 
956     /***
957      * Strips off the name of a script to create a new context URL
958      */
959     protected URL getJellyContextURL(InputSource source) throws MalformedURLException {
960         String text = source.getSystemId();
961         if (text != null) {
962             int idx = text.lastIndexOf('/');
963             text = text.substring(0, idx + 1);
964             return new URL(text);
965         } else {
966             return null;
967         }
968         
969     }
970 
971     /***
972      * Factory method to create a new child of this context
973      */
974     protected JellyContext createChildContext() {
975         return new JellyContext(this);
976     }
977 
978     /***
979      * Change the parent context to the one provided
980      * @param context the new parent context
981      */
982     protected void setParent(JellyContext context)
983     {
984         parent = context;
985         this.variables.put("parentScope", parent.variables);
986         // need to re-export tag libraries to the new parent
987         if (isExportLibraries() && parent != null) {
988             for (Iterator keys = taglibs.keySet().iterator(); keys.hasNext();)
989             {
990                 String namespaceURI = (String) keys.next();
991                 Object tagLibOrClassName = taglibs.get(namespaceURI);
992                 if (tagLibOrClassName instanceof TagLibrary)
993                 {
994                     parent.registerTagLibrary( namespaceURI, (TagLibrary) tagLibOrClassName );
995                 }
996                 else
997                 {
998                     parent.registerTagLibrary( namespaceURI, (String) tagLibOrClassName );
999                 }
1000             }
1001         }
1002 
1003     }
1004 
1005 }