blob: 371fe6cc83dbfdba02aae325bf6171f3013e690b [file] [log] [blame]
/*
* Copyright 2002,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jelly;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.jelly.parser.XMLParser;
import org.apache.commons.jelly.util.ClassLoaderUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* <p><code>JellyContext</code> represents the Jelly context.</p>
*
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @version $Revision: 1.10 $
*/
public class JellyContext {
/** The Log to which logging calls will be made. */
private static final Log log = LogFactory.getLog(JellyContext.class);
/** Default for inheritance of variables **/
private static final boolean DEFAULT_INHERIT = true;
/** Default for export of variables **/
private static final boolean DEFAULT_EXPORT = false;
/** String used to denote a script can't be parsed */
private static final String BAD_PARSE = "Could not parse Jelly script";
/**
* The class loader to use for instantiating application objects.
* If not specified, the context class loader, or the class loader
* used to load this class itself, is used, based on the value of the
* <code>useContextClassLoader</code> variable.
*/
protected ClassLoader classLoader;
/**
* Do we want to use the Context ClassLoader when loading classes
* for instantiating new objects? Default is <code>false</code>.
*/
protected boolean useContextClassLoader = false;
/** The root URL context (where scripts are located from) */
private URL rootURL;
/** The current URL context (where relative scripts are located from) */
private URL currentURL;
/** Tag libraries found so far */
private Map taglibs = new Hashtable();
/** synchronized access to the variables in scope */
private Map variables = new Hashtable();
/** The parent context */
private JellyContext parent;
/** Do we inherit variables from parent context? */
private boolean inherit = JellyContext.DEFAULT_INHERIT;
/** Do we export our variables to parent context? */
private boolean export = JellyContext.DEFAULT_EXPORT;
/** Should we export tag libraries to our parents context */
private boolean exportLibraries = true;
/** Holds a Map containing thread-specific data for Scripts.
* Scripts may hold a single object in this map using the
* {@link #setThreadLocalScriptData(Script,Object)}
*/
private ThreadLocal threadLocalScriptData = new ThreadLocal();
// THINKME: Script objects are like Object (for equals and hashCode) I think.
// It should be asy to optimize hash-map distribution, e.g. by
// shifting the hashcode return value (presuming Object.hashcode()
// is something like an address)
/**
* Create a new context with the currentURL set to the rootURL
*/
public JellyContext() {
this.currentURL = rootURL;
init();
}
/**
* Create a new context with the given rootURL
* @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
*/
public JellyContext(URL rootURL) {
this( rootURL, rootURL );
}
/**
* Create a new context with the given rootURL and currentURL
* @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
* @param currentURL the root URL used in resolving relative resources
*/
public JellyContext(URL rootURL, URL currentURL) {
this.rootURL = rootURL;
this.currentURL = currentURL;
init();
}
/**
* Create a new context with the given parent context.
* The parent's rootURL and currentURL are set on the child, and the parent's variables are
* available in the child context under the name <code>parentScope</code>.
*
* @param parent the parent context for the newly created context.
*/
public JellyContext(JellyContext parent) {
this.parent = parent;
this.rootURL = parent.rootURL;
this.currentURL = parent.currentURL;
this.variables.put("parentScope", parent.variables);
init();
}
/**
* Create a new context with the given parent context.
* The parent's rootURL are set on the child, and the parent's variables are
* available in the child context under the name <code>parentScope</code>.
*
* @param parentJellyContext the parent context for the newly created context.
* @param currentURL the root URL used in resolving relative resources
*/
public JellyContext(JellyContext parentJellyContext, URL currentURL) {
this(parentJellyContext);
this.currentURL = currentURL;
}
/**
* Create a new context with the given parent context.
* The parent's variables are available in the child context under the name <code>parentScope</code>.
*
* @param parentJellyContext the parent context for the newly created context.
* @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
* @param currentURL the root URL used in resolving relative resources
*/
public JellyContext(JellyContext parentJellyContext, URL rootURL, URL currentURL) {
this(parentJellyContext, currentURL);
this.rootURL = rootURL;
}
/**
* Initialize the context.
* This includes adding the context to itself under the name <code>context</code> and
* making the System Properties available as <code>systemScope</code>
*/
private void init() {
variables.put("context",this);
try {
variables.put("systemScope", System.getProperties() );
} catch (SecurityException e) {
log.debug("security exception accessing system properties", e);
}
}
/**
* @return the parent context for this context
*/
public JellyContext getParent() {
return parent;
}
/**
* @return the scope of the given name, such as the 'parent' scope.
* If Jelly is used in a Servlet situation then 'request', 'session' and 'application' are other names
* for scopes
*/
public JellyContext getScope(String name) {
if ( "parent".equals( name ) ) {
return getParent();
}
return null;
}
/**
* Finds the variable value of the given name in this context or in any other parent context.
* If this context does not contain the variable, then its parent is used and then its parent
* and so forth until the context with no parent is found.
*
* @return the value of the variable in this or one of its descendant contexts or null
* if the variable could not be found.
*/
public Object findVariable(String name) {
Object answer = variables.get(name);
boolean definedHere = answer != null || variables.containsKey(name);
if (definedHere) return answer;
if ( answer == null && parent != null ) {
answer = parent.findVariable(name);
}
// ### this is a hack - remove this when we have support for pluggable Scopes
if ( answer == null ) {
answer = getSystemProperty(name);
}
if (log.isDebugEnabled()) {
log.debug("findVariable: " + name + " value: " + answer );
}
return answer;
}
/** @return the value of the given variable name */
public Object getVariable(String name) {
Object value = variables.get(name);
boolean definedHere = value != null || variables.containsKey(name);
if (definedHere) return value;
if ( value == null && isInherit() ) {
JellyContext parentContext = getParent();
if (parentContext != null) {
value = parentContext.getVariable( name );
}
}
// ### this is a hack - remove this when we have support for pluggable Scopes
if ( value == null ) {
value = getSystemProperty(name);
}
return value;
}
/**
* Get a system property and handle security exceptions
* @param name the name of the property to retrieve
* @return the value of the property, or null if a SecurityException occurs
*/
private Object getSystemProperty(String name) {
try {
return System.getProperty(name);
}
catch (SecurityException e) {
log.debug("security exception accessing system properties", e);
}
return null;
}
/**
* @return the value of the given variable name in the given variable scope
* @param name is the name of the variable
* @param scopeName is the optional scope name such as 'parent'. For servlet environments
* this could be 'application', 'session' or 'request'.
*/
public Object getVariable(String name, String scopeName) {
JellyContext scope = getScope(scopeName);
if ( scope != null ) {
return scope.getVariable(name);
}
return null;
}
/** Sets the value of the named variable */
public void setVariable(String name, Object value) {
if ( isExport() ) {
getParent().setVariable( name, value );
return;
}
if (value == null) {
variables.remove(name);
}
else {
variables.put(name, value);
}
}
/**
* Sets the value of the given variable name in the given variable scope
* @param name is the name of the variable
* @param scopeName is the optional scope name such as 'parent'. For servlet environments
* this could be 'application', 'session' or 'request'.
* @param value is the value of the attribute
*/
public void setVariable(String name, String scopeName, Object value) {
JellyContext scope = getScope(scopeName);
if ( scope != null ) {
scope.setVariable(name, value);
}
}
/** Removes the given variable */
public void removeVariable(String name) {
variables.remove(name);
}
/**
* Removes the given variable in the specified scope.
*
* @param name is the name of the variable
* @param scopeName is the optional scope name such as 'parent'. For servlet environments
* this could be 'application', 'session' or 'request'.
*/
public void removeVariable(String name, String scopeName) {
JellyContext scope = getScope(scopeName);
if ( scope != null ) {
scope.removeVariable(name);
}
}
/**
* @return an Iterator over the current variable names in this
* context
*/
public Iterator getVariableNames() {
return variables.keySet().iterator();
}
/**
* @return the Map of variables in this scope
*/
public Map getVariables() {
return variables;
}
/**
* Sets the Map of variables to use
*/
public void setVariables(Map variables) {
// I have seen this fail when the passed Map contains a key, value
// pair where the value is null
for (Iterator iter = variables.entrySet().iterator(); iter.hasNext();) {
Map.Entry element = (Map.Entry) iter.next();
if (element.getValue() != null) {
this.variables.put(element.getKey(), element.getValue());
}
}
//this.variables.putAll( variables );
}
/**
* A factory method to create a new child context of the
* current context.
*/
public JellyContext newJellyContext(Map newVariables) {
// XXXX: should allow this new context to
// XXXX: inherit parent contexts?
// XXXX: Or at least publish the parent scope
// XXXX: as a Map in this new variable scope?
newVariables.put("parentScope", variables);
JellyContext answer = createChildContext();
answer.setVariables(newVariables);
return answer;
}
/**
* A factory method to create a new child context of the
* current context.
*/
public JellyContext newJellyContext() {
return createChildContext();
}
/** Gets the Script data item that may have previously been stored
* by the script, in this context, for the current thread.
*
* @return the tag associated with the current context and thread
*/
public Object getThreadScriptData(Script script) {
if( script == null )
return null;
Tag tag = (Tag) getThreadScriptDataMap().get(script);
if( tag == null && getParent() != null) {
return getParent().getThreadScriptData(script);
} else {
return tag;
}
}
/** Gets a per-thread (thread local) Map of data for use by
* Scripts.
* @return the thread local Map of Script data */
public Map getThreadScriptDataMap() {
Map rv;
Map data = (Map) threadLocalScriptData.get();
if (data == null) {
rv = new HashMap();
threadLocalScriptData.set(rv);
} else {
rv = data;
}
return rv;
}
/** Stores an object that lasts for the life of this context
* and is local to the current thread. This method is
* mainly intended to store Tag instances. However, any
* Script that wants to cache data can use this
* method.
*/
public void setThreadScriptData(Script script, Object data) {
getThreadScriptDataMap().put(script,data);
}
/** Clears variables set by Tags (basically, variables set in a Jelly script)
* and data stored by {@link Script} instances.
* @see #clearVariables()
* @see #clearThreadScriptData()
* @see #clearScriptData()
*/
public void clear() {
getThreadScriptDataMap().clear();
variables.clear();
}
/** Clears variables set by Tags (variables set while running a Jelly script)
* @see #clear()
* @see #clearThreadScriptData()
* @see #clearScriptData()
*/
public void clearVariables() {
variables.clear();
}
/** Clears data cached by {@link Script} instances,
* for this context, <strong>for the current thread</strong>.
* The data cleared could be cached Tag instances or other data
* saved by Script classes.
* @see #clear()
* @see #clearVariables()
* @see #clearScriptData()
*/
public void clearThreadScriptData() {
getThreadScriptDataMap().clear();
}
/** Clears data cached by {@link Script} instances,
* for this context, <strong>for all threads</strong>.
* The data cleared could be cached Tag instances or other data
* saved by Script classes.
* @see #clear()
* @see #clearThreadScriptData()
* @see #clearVariables()
*/
public void clearScriptData() {
threadLocalScriptData = new ThreadLocal();
}
/** Registers the given tag library against the given namespace URI.
* This should be called before the parser is used.
*/
public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
if (log.isDebugEnabled()) {
log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
}
taglibs.put(namespaceURI, taglib);
if (isExportLibraries() && parent != null) {
parent.registerTagLibrary( namespaceURI, taglib );
}
}
/** Registers the given tag library class name against the given namespace URI.
* The class will be loaded via the given ClassLoader
* This should be called before the parser is used.
*/
public void registerTagLibrary(
String namespaceURI,
String className) {
if (log.isDebugEnabled()) {
log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
}
taglibs.put(namespaceURI, className);
if (isExportLibraries() && parent != null) {
parent.registerTagLibrary( namespaceURI, className );
}
}
public boolean isTagLibraryRegistered(String namespaceURI) {
boolean answer = taglibs.containsKey( namespaceURI );
if (answer) {
return true;
}
else if ( parent != null ) {
return parent.isTagLibraryRegistered(namespaceURI);
}
else {
return false;
}
}
/**
* @return the TagLibrary for the given namespace URI or null if one could not be found
*/
public TagLibrary getTagLibrary(String namespaceURI) {
// use my own mapping first, so that namespaceURIs can
// be redefined inside child contexts...
Object answer = taglibs.get(namespaceURI);
if ( answer == null && parent != null ) {
answer = parent.getTagLibrary( namespaceURI );
}
if ( answer instanceof TagLibrary ) {
return (TagLibrary) answer;
}
else if ( answer instanceof String ) {
String className = (String) answer;
Class theClass = null;
try {
theClass = getClassLoader().loadClass(className);
}
catch (ClassNotFoundException e) {
log.error("Could not find the class: " + className, e);
}
if ( theClass != null ) {
try {
Object object = theClass.newInstance();
if (object instanceof TagLibrary) {
taglibs.put(namespaceURI, object);
return (TagLibrary) object;
}
else {
log.error(
"The tag library object mapped to: "
+ namespaceURI
+ " is not a TagLibrary. Object = "
+ object);
}
}
catch (Exception e) {
log.error(
"Could not instantiate instance of class: " + className + ". Reason: " + e,
e);
}
}
}
return null;
}
/**
* Attempts to parse the script from the given uri using the
* {@link #getResource} method then returns the compiled script.
*/
public Script compileScript(String uri) throws JellyException {
XMLParser parser = getXMLParser();
parser.setContext(this);
InputStream in = getResourceAsStream(uri);
if (in == null) {
throw new JellyException("Could not find Jelly script: " + uri);
}
Script script = null;
try {
script = parser.parse(in);
} catch (IOException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
} catch (SAXException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
}
return script.compile();
}
/**
* Attempts to parse the script from the given URL using the
* {@link #getResource} method then returns the compiled script.
*/
public Script compileScript(URL url) throws JellyException {
XMLParser parser = getXMLParser();
parser.setContext(this);
Script script = null;
try {
script = parser.parse(url.toString());
} catch (IOException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
} catch (SAXException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
}
return script.compile();
}
/**
* Attempts to parse the script from the given InputSource using the
* {@link #getResource} method then returns the compiled script.
*/
public Script compileScript(InputSource source) throws JellyException {
XMLParser parser = getXMLParser();
parser.setContext(this);
Script script = null;
try {
script = parser.parse(source);
} catch (IOException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
} catch (SAXException e) {
throw new JellyException(JellyContext.BAD_PARSE, e);
}
return script.compile();
}
/**
* @return a thread pooled XMLParser to avoid the startup overhead
* of the XMLParser
*/
protected XMLParser getXMLParser() {
XMLParser parser = createXMLParser();
return parser;
}
/**
* Factory method to allow JellyContext implementations to overload how an XMLParser
* is created - such as to overload what the default ExpressionFactory should be.
*/
protected XMLParser createXMLParser() {
return new XMLParser();
}
/**
* Parses the script from the given File then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(File file, XMLOutput output) throws JellyException {
try {
return runScript(file.toURL(), output, JellyContext.DEFAULT_EXPORT,
JellyContext.DEFAULT_INHERIT);
} catch (MalformedURLException e) {
throw new JellyException(e.toString());
}
}
/**
* Parses the script from the given URL then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(URL url, XMLOutput output) throws JellyException {
return runScript(url, output, JellyContext.DEFAULT_EXPORT,
JellyContext.DEFAULT_INHERIT);
}
/**
* Parses the script from the given InputSource then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(InputSource source, XMLOutput output) throws JellyException {
return runScript(source, output, JellyContext.DEFAULT_EXPORT,
JellyContext.DEFAULT_INHERIT);
}
/**
* Parses the script from the given uri using the
* JellyContext.getResource() API then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(String uri, XMLOutput output) throws JellyException {
URL url = null;
try {
url = getResource(uri);
} catch (MalformedURLException e) {
throw new JellyException(e.toString());
}
if (url == null) {
throw new JellyException("Could not find Jelly script: " + url);
}
return runScript(url, output, JellyContext.DEFAULT_EXPORT,
JellyContext.DEFAULT_INHERIT);
}
/**
* Parses the script from the given uri using the
* JellyContext.getResource() API then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(String uri, XMLOutput output,
boolean export, boolean inherit) throws JellyException {
URL url = null;
try {
url = getResource(uri);
} catch (MalformedURLException e) {
throw new JellyException(e.toString());
}
if (url == null) {
throw new JellyException("Could not find Jelly script: " + url);
}
return runScript(url, output, export, inherit);
}
/**
* Parses the script from the given file then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(File file, XMLOutput output,
boolean export, boolean inherit) throws JellyException {
try {
return runScript(file.toURL(), output, export, inherit);
} catch (MalformedURLException e) {
throw new JellyException(e.toString());
}
}
/**
* Parses the script from the given URL then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(URL url, XMLOutput output,
boolean export, boolean inherit) throws JellyException {
return runScript(new InputSource(url.toString()), output, export, inherit);
}
/**
* Parses the script from the given InputSource then compiles it and runs it.
*
* @return the new child context that was used to run the script
*/
public JellyContext runScript(InputSource source, XMLOutput output,
boolean export, boolean inherit) throws JellyException {
Script script = compileScript(source);
URL newJellyContextURL = null;
try {
newJellyContextURL = getJellyContextURL(source);
} catch (MalformedURLException e) {
throw new JellyException(e.toString());
}
JellyContext newJellyContext = newJellyContext();
newJellyContext.setRootURL( newJellyContextURL );
newJellyContext.setCurrentURL( newJellyContextURL );
newJellyContext.setExport( export );
newJellyContext.setInherit( inherit );
if ( inherit ) {
// use the same variable scopes
newJellyContext.variables = this.variables;
}
if (log.isDebugEnabled() ) {
log.debug( "About to run script: " + source.getSystemId() );
log.debug( "root context URL: " + newJellyContext.rootURL );
log.debug( "current context URL: " + newJellyContext.currentURL );
}
script.run(newJellyContext, output);
return newJellyContext;
}
/**
* Returns a URL for the given resource from the specified path.
* If the uri starts with "/" then the path is taken as relative to
* the current context root.
* If the uri is a well formed URL then it is used.
* If the uri is a file that exists and can be read then it is used.
* Otherwise the uri is interpreted as relative to the current context (the
* location of the current script).
*/
public URL getResource(String uri) throws MalformedURLException {
if (uri.startsWith("/")) {
// append this uri to the context root
return createRelativeURL(rootURL, uri.substring(1));
}
else {
try {
return new URL(uri);
}
catch (MalformedURLException e) {
// lets try find a relative resource
try {
return createRelativeURL(currentURL, uri);
} catch (MalformedURLException e2) {
throw e;
}
}
}
}
/**
* Attempts to open an InputStream to the given resource at the specified path.
* If the uri starts with "/" then the path is taken as relative to
* the current context root. If the uri is a well formed URL then it
* is used. Otherwise the uri is interpreted as relative to the current
* context (the location of the current script).
*
* @return null if this resource could not be loaded, otherwise the resources
* input stream is returned.
*/
public InputStream getResourceAsStream(String uri) {
try {
URL url = getResource(uri);
return url.openStream();
}
catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace(
"Caught exception attempting to open: " + uri + ". Exception: " + e,
e);
}
return null;
}
}
// Properties
//-------------------------------------------------------------------------
/**
* @return the current root context URL from which all absolute resource URIs
* will be relative to. For example in a web application the root URL will
* map to the web directory which contains the WEB-INF directory.
*/
public URL getRootURL() {
return rootURL;
}
/**
* Sets the current root context URL from which all absolute resource URIs
* will be relative to. For example in a web application the root URL will
* map to the web directory which contains the WEB-INF directory.
*/
public void setRootURL(URL rootURL) {
this.rootURL = rootURL;
}
/**
* @return the current URL context of the current script that is executing.
* This URL context is used to deduce relative scripts when relative URIs are
* used in calls to {@link #getResource} to process relative scripts.
*/
public URL getCurrentURL() {
return currentURL;
}
/**
* Sets the current URL context of the current script that is executing.
* This URL context is used to deduce relative scripts when relative URIs are
* used in calls to {@link #getResource} to process relative scripts.
*/
public void setCurrentURL(URL currentURL) {
this.currentURL = currentURL;
}
/**
* Returns whether we export tag libraries to our parents context
* @return boolean
*/
public boolean isExportLibraries() {
return exportLibraries;
}
/**
* Sets whether we export tag libraries to our parents context
* @param exportLibraries The exportLibraries to set
*/
public void setExportLibraries(boolean exportLibraries) {
this.exportLibraries = exportLibraries;
}
/**
* Sets whether we should export variable definitions to our parent context
*/
public void setExport(boolean export) {
this.export = export;
}
/**
* @return whether we should export variable definitions to our parent context
*/
public boolean isExport() {
return this.export;
}
/**
* Sets whether we should inherit variables from our parent context
*/
public void setInherit(boolean inherit) {
this.inherit = inherit;
}
/**
* @return whether we should inherit variables from our parent context
*/
public boolean isInherit() {
return this.inherit;
}
/**
* Return the class loader to be used for instantiating application objects
* when required. This is determined based upon the following rules:
* <ul>
* <li>The class loader set by <code>setClassLoader()</code>, if any</li>
* <li>The thread context class loader, if it exists and the
* <code>useContextClassLoader</code> property is set to true</li>
* <li>The class loader used to load the XMLParser class itself.
* </ul>
*/
public ClassLoader getClassLoader() {
return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
}
/**
* Set the class loader to be used for instantiating application objects
* when required.
*
* @param classLoader The new class loader to use, or <code>null</code>
* to revert to the standard rules
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Return the boolean as to whether the context classloader should be used.
*/
public boolean getUseContextClassLoader() {
return useContextClassLoader;
}
/**
* Determine whether to use the Context ClassLoader (the one found by
* calling <code>Thread.currentThread().getContextClassLoader()</code>)
* to resolve/load classes. If not
* using Context ClassLoader, then the class-loading defaults to
* using the calling-class' ClassLoader.
*
* @param use determines whether to use JellyContext ClassLoader.
*/
public void setUseContextClassLoader(boolean use) {
useContextClassLoader = use;
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* @return a new relative URL from the given root and with the addition of the
* extra relative URI
*
* @param rootURL is the root context from which the relative URI will be applied
* @param relativeURI is the relative URI (without a leading "/")
* @throws MalformedURLException if the URL is invalid.
*/
protected URL createRelativeURL(URL rootURL, String relativeURI)
throws MalformedURLException {
URL url = rootURL;
if (url == null) {
File file = new File(System.getProperty("user.dir"));
url = file.toURL();
}
String urlText = url.toString() + relativeURI;
if ( log.isDebugEnabled() ) {
log.debug("Attempting to open url: " + urlText);
}
return new URL(urlText);
}
/**
* Strips off the name of a script to create a new context URL
*/
protected URL getJellyContextURL(URL url) throws MalformedURLException {
String text = url.toString();
int idx = text.lastIndexOf('/');
text = text.substring(0, idx + 1);
return new URL(text);
}
/**
* Strips off the name of a script to create a new context URL
*/
protected URL getJellyContextURL(InputSource source) throws MalformedURLException {
String text = source.getSystemId();
if (text != null) {
int idx = text.lastIndexOf('/');
text = text.substring(0, idx + 1);
return new URL(text);
} else {
return null;
}
}
/**
* Factory method to create a new child of this context
*/
protected JellyContext createChildContext() {
return new JellyContext(this);
}
/**
* Change the parent context to the one provided
* @param context the new parent context
*/
protected void setParent(JellyContext context)
{
parent = context;
this.variables.put("parentScope", parent.variables);
// need to re-export tag libraries to the new parent
if (isExportLibraries() && parent != null) {
for (Iterator keys = taglibs.keySet().iterator(); keys.hasNext();)
{
String namespaceURI = (String) keys.next();
Object tagLibOrClassName = taglibs.get(namespaceURI);
if (tagLibOrClassName instanceof TagLibrary)
{
parent.registerTagLibrary( namespaceURI, (TagLibrary) tagLibOrClassName );
}
else
{
parent.registerTagLibrary( namespaceURI, (String) tagLibOrClassName );
}
}
}
}
}