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 }