ovidiu 02/05/19 12:19:39 Modified: src/java/org/apache/cocoon cocoon.roles src/java/org/apache/cocoon/components/treeprocessor treeprocessor-builtins.xml src/java/org/apache/cocoon/components/treeprocessor/sitemap CallNodeBuilder.java Added: src/java/org/apache/cocoon/components/flow AbstractInterpreter.java ContinuationsManager.java ContinuationsManagerImpl.java Interpreter.java InterpreterSelector.java ScriptSource.java WebContinuation.java flow.xconf src/java/org/apache/cocoon/components/flow/javascript JSCocoon.java JSGlobal.java JSLog.java JSWebContinuation.java JavaScriptInterpreter.java system.js src/java/org/apache/cocoon/components/language/markup/xsp/java jpath.xsl src/java/org/apache/cocoon/components/treeprocessor/sitemap CallFunctionNode.java ContinueNode.java ContinueNodeBuilder.java ScriptNode.java ScriptNodeBuilder.java src/java/org/apache/cocoon/transformation AugmentTransformer.java Log: Added the continuations based control flow layer from scratchpad/schecoon/. Revision Changes Path 1.28 +13 -0 xml-cocoon2/src/java/org/apache/cocoon/cocoon.roles Index: cocoon.roles =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/cocoon.roles,v retrieving revision 1.27 retrieving revision 1.28 diff -u -r1.27 -r1.28 --- cocoon.roles 6 May 2002 13:22:30 -0000 1.27 +++ cocoon.roles 19 May 2002 19:19:38 -0000 1.28 @@ -210,6 +210,19 @@ shorthand="autoincrement-modules" default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"/> + + <!-- Control flow layer: the interpreters selector and continuations + manager + --> + + <role name="org.apache.cocoon.components.flow.Interpreter" + default-class="org.apache.cocoon.components.flow.InterpreterSelector" + shorthand="flow-interpreters"/> + + <role name="org.apache.cocoon.components.flow.ContinuationsManager" + default-class="org.apache.cocoon.components.flow.ContinuationsManagerImpl" + shorthand="continuations"/> + <!-- DEPRECATED, use the xml-parser instead ! --> <role name="org.apache.cocoon.components.resolver.Resolver" shorthand="resolver" 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/AbstractInterpreter.java Index: AbstractInterpreter.java =================================================================== package org.apache.cocoon.components.flow; import java.lang.System; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.logger.AbstractLoggable; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.cocoon.Constants; import org.apache.cocoon.components.source.SourceFactory; import org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode; import org.apache.cocoon.environment.Context; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ForwardRedirector; /** * Abstract superclass for various scripting languages used by Cocoon * for flow control. Defines some useful behavior like the ability to * reload script files if they get modified (useful when doing * development), and passing the control to Cocoon's sitemap for * result page generation. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 15, 2002 */ public abstract class AbstractInterpreter extends AbstractLoggable implements Component, Composable, Contextualizable, Interpreter, ThreadSafe { /** * Hash table of source locations -> ScriptSource instances */ protected HashMap scripts = new HashMap(); /** * List of source locations that need to be resolved. */ protected ArrayList needResolve = new ArrayList(); /** * When was the last time we checked for script modifications. Used * only if {@link reloadScripts} is true. */ protected long lastTimeCheck = 0; protected org.apache.cocoon.environment.Context context; protected ComponentManager manager; protected ContinuationsManager continuationsMgr; /** * Whether reloading of scripts should be done. Specified through * the "reload-scripts" attribute in <code>flow.xmap</code>. */ protected boolean reloadScripts; /** * Interval between two checks for modified script files. Specified * through the "check-time" XML attribute in <code>flow.xmap</code>. */ protected long checkTime; public void compose(ComponentManager manager) throws ComponentException { this.manager = manager; // System.out.println("AbstractInterpreter: ComponentManager = " + manager); // FIXME: Why is the manager null here? if (manager != null) continuationsMgr = (ContinuationsManager)manager.lookup(ContinuationsManager.ROLE); } public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException { this.context = (Context)context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); } public void setReloadScripts(boolean yn) { reloadScripts = yn; } public void setCheckTime(long time) { checkTime = time; } /** * Registers a source file with the interpreter. Using this method * an implementation keeps track of all the script files which are * compiled. This allows them to reload the script files which get * modified on the file system. * * <p>The parsing/compilation of a script file by an interpreter * happens in two phases. In the first phase the file's location is * registered in the <code>needResolve</code> array. * * <p>The second is possible only when a Cocoon * <code>Environment</code> is passed to the Interpreter. This * allows the file location to be resolved using Cocoon's * <code>SourceFactory</code> class. * * <p>Once a file's location can be resolved, it is removed from the * <code>needResolve</code> array and placed in the * <code>scripts</code> hash table. The key in this hash table is * the file location string, and the value is a * DelayedRefreshSourceWrapper instance which keeps track of when * the file needs to re-read. * * @param source the location of the script * * @see org.apache.cocoon.components.source.SourceFactory * @see org.apache.cocoon.environment.Environment * @see org.apache.cocoon.components.source.DelayedRefreshSourceWrapper */ public void register(String source) { synchronized(this) { needResolve.add(source); } } /** * Unregister the source file. Called from <code>ScriptNode</code> * when a tree corresponding to a sitemap is decommissioned. * * @param source a <code>String</code> value */ public void unregister(String source) { synchronized(this) { scripts.remove(source); int index = needResolve.indexOf(source); if (index != -1) needResolve.remove(index); } } /** * Reloads any modified script files. * * <p>It checks to see if any of the files already read in (those * present in the <code>scripts</code> hash map) have been * modified. * * <p>It also checks to see if any script files have been registered * with the interpreter since the last call to * <code>checkForModifiedScripts</code>. These files are stored in * the temporary array <code>needResolve</code>. If any such files * are found, they are read in. * * @param environment an <code>Environment</code> value */ public void checkForModifiedScripts(Environment environment) throws Exception { if (reloadScripts && System.currentTimeMillis() >= lastTimeCheck + checkTime) { // FIXME: should we worry about synchronization? Iterator iter = scripts.values().iterator(); while (iter.hasNext()) { ScriptSource src = (ScriptSource)iter.next(); if (src.getLastModified() > lastTimeCheck) { try { src.refresh(environment); } catch (Exception ex) { System.out.println("Error reading script " + src.getSourceName() + ", ignoring!"); } } } } // FIXME: remove the need for synchronization synchronized (this) { int size = needResolve.size(); for (int i = 0; i < size; i++) { String source = (String)needResolve.get(0); ScriptSource src = new ScriptSource(this, source); scripts.put(source, src); needResolve.remove(0); try { src.refresh(environment); } catch (Exception ex) { System.out.println("Error reading script " + source + ", ignoring!"); } } } // Update the time of the last check. If an exception occurs, this // is not executed, so the next request will force a reparse of // the script files because of an old time stamp. lastTimeCheck = System.currentTimeMillis(); } public void forwardTo(String uri, Object bizData, WebContinuation continuation, Environment environment) throws Exception { environment.setAttribute("bean-dict", bizData); if (continuation != null) environment.setAttribute("kont", continuation); try { PipelinesNode.getRedirector(environment) .redirect(false, "cocoon:/" + uri); } finally { environment.removeAttribute("bean-dict"); if (continuation != null) environment.removeAttribute("kont"); } } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/ContinuationsManager.java Index: ContinuationsManager.java =================================================================== package org.apache.cocoon.components.flow; /** * The interface of the Continuations manager. * * The continuation manager maintains a forrest of {@link * WebContinuation} trees. Each tree defines the flow of control for a * user within the application. * * A <code>WebContinuation</code> is created for a continuation object * from the scripting language used. A continuation object in the * implementation of the scripting language is an opaque object * here. It is only stored inside the <code>WebContinuation</code>, * without being interpreted in any way. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 19, 2002 * @see WebContinuation */ public interface ContinuationsManager { public final String ROLE = "org.apache.cocoon.components.flow.ContinuationsManager"; /** * Create a <code>WebContinuation</code> object given a native * continuation object and its parent. If the parent continuation is * null, the <code>WebContinuation</code> returned becomes the root * of a tree in the forrest. * * @param kont an <code>Object</code> value * @param parentKont a <code>WebContinuation</code> value * @param timeToLive an <code>int</code> value indicating how long * in seconds this continuation will live in the server if not * accessed * @return a <code>WebContinuation</code> value * @see WebContinuation */ public WebContinuation createWebContinuation(Object kont, WebContinuation parentKont, int timeToLive); /** * Invalidates a <code>WebContinuation</code>. This effectively * means that the continuation object associated with it will no * longer be accessible from Web pages. Invalidating a * <code>WebContinuation</code> invalidates all the * <code>WebContinuation</code>s which are children of it. * * @param k a <code>WebContinuation</code> value */ public void invalidateWebContinuation(WebContinuation k); /** * Given a <code>WebContinuation</code> id, retrieve the associated * <code>WebContinuation</code> object. * * @param id a <code>String</code> value * @return a <code>WebContinuation</code> object, or null if no such * <code>WebContinuation</code> could be found. */ public WebContinuation lookupWebContinuation(String id); } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Index: ContinuationsManagerImpl.java =================================================================== package org.apache.cocoon.components.flow; import java.security.SecureRandom; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.logger.AbstractLoggable; import org.apache.avalon.framework.thread.ThreadSafe; /** * The default implementation of {@link ContinuationsManager}. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 19, 2002 * @see ContinuationsManager */ public class ContinuationsManagerImpl extends AbstractLoggable implements ContinuationsManager, Component, Configurable, ThreadSafe { static final int CONTINUATION_ID_LENGTH = 20; protected SecureRandom random = null; protected byte[] bytes; /** * How long does a continuation exist in memory since the last * access? The time is in seconds, and the default is 3600 (1 hour). */ protected int defaultTimeToLive; /** * Maintains the forrest of <code>WebContinuation</code> trees. */ protected Set forrest = Collections.synchronizedSet(new HashSet()); /** * Association between <code>WebContinuation</code> ids and the * corresponding <code>WebContinuation</code> object. */ protected Map idToWebCont = Collections.synchronizedMap(new HashMap()); /** * Sorted set of <code>WebContinuation</code> instances, based on * their expiration time. This is used by the background thread to * invalidate continuations. */ protected SortedSet expirations = new TreeSet(); public ContinuationsManagerImpl() throws Exception { random = SecureRandom.getInstance("SHA1PRNG"); random.setSeed(System.currentTimeMillis()); bytes = new byte[CONTINUATION_ID_LENGTH]; } public void configure(Configuration config) { defaultTimeToLive = config.getAttributeAsInteger("time-to-live", 3600); } public WebContinuation createWebContinuation(Object kont, WebContinuation parentKont, int timeToLive) { int ttl = (timeToLive == 0 ? defaultTimeToLive : timeToLive); WebContinuation wk = new WebContinuation(kont, parentKont, this, ttl); if (parentKont == null) forrest.add(wk); expirations.add(wk); // No need to add the WebContinuation in idToWebCont as it was // already done during its construction. return wk; } public void invalidateWebContinuation(WebContinuation wk) { WebContinuation parent = wk.getParentContinuation(); if (parent == null) forrest.remove(wk); else { List parentKids = parent.getChildren(); parentKids.remove(wk); } _invalidate(wk); } protected void _invalidate(WebContinuation wk) { idToWebCont.remove(wk.getId()); expirations.remove(wk); // Invalidate all the children continuations as well List children = wk.getChildren(); int size = children.size(); for (int i = 0; i < size; i++) _invalidate((WebContinuation)children.get(i)); } public WebContinuation lookupWebContinuation(String id) { return (WebContinuation)idToWebCont.get(id); } /** * Generate a unique identifier for a * <code>WebContinuation</code>. The identifier is generated using a * cryptographically strong algorithm to prevent people to generate * their own identifiers. * * <p>It has the side effect of interning the continuation object in * the <code>idToWebCont</code> hash table. * * @param wk a <code>WebContinuation</code> object for which the * identifier should be generated. * @return the <code>String</code> identifier of the * <code>WebContinuation</code> */ public String generateContinuationId(WebContinuation wk) { char[] result = new char[bytes.length * 2]; String continuationId = null; while (true) { random.nextBytes(bytes); for (int i = 0; i < CONTINUATION_ID_LENGTH; i++) { byte ch = bytes[i]; result[2 * i] = Character.forDigit(Math.abs(ch >> 4), 16); result[2 * i + 1] = Character.forDigit(Math.abs(ch & 0x0f), 16); } continuationId = new String(result); synchronized(idToWebCont) { if (!idToWebCont.containsKey(continuationId)) { idToWebCont.put(continuationId, wk); break; } } } return continuationId; } public void displayAllContinuations() { Iterator iter = forrest.iterator(); while (iter.hasNext()) ((WebContinuation)iter.next()).display(); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/Interpreter.java Index: Interpreter.java =================================================================== package org.apache.cocoon.components.flow; import java.util.List; import java.util.Map; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.Source; /** * The interface to the flow scripting languages. This interface is * for a component, which implements the appropriate language to be * used for describing the flow. A system could have multiple * components that implement this interface, each of them for a * different scripting language. * * <p>A flow script defines what is the page flow in an interactive * Web application. Usually the flow is defined in a high level * programming language which provides the notion of continuations, * which allows for the flow of the application to be described as a * simple procedural program, without having to think about the * application as a finite state machine which changes its internal * state on each HTTP request from the client browser. * * <p>However an implementation may choose to use its own * representation of an application, which may include XML * representations of finite state machines. Note: this API has no * provision for such implementations. * * <p>The component represented by this interface is called in three * situations: * * <ul> * * <li> * * <p>From the sitemap, to invoke a top level function defined in a * * given implementation language of the flow. This is done from * the * sitemap using the construction: * * <pre> * <map:call function="..." language="..."/> * </pre> * * <p>The <code>language</code> attribute can be ignored if the * * default language is used. * * <li> * * <p>From the sitemap, to continue a previously started * computation. A previously started computation is saved in the * form of a continuation inside the flow implementation language. * * <p>This case is similar with the above one, but the function * invoked has a special name, specific to each language * implementation. See the language implementation for more * information on the function name and the arguments it receives. * * <li> * * <p>From a program in the flow layer. This is done to invoke a * pipeline defined in the sitemap, to generate the response of the * request. * * </ul> * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 11, 2002 * @see InterpreterSelector */ public interface Interpreter { public static class Argument { public String name; public String value; public Argument(String name, String value) { this.name = name; this.value = value; } public String toString() { return name + ": " + value; } } public static String ROLE = "org.apache.cocoon.components.flow.Interpreter"; /** * Load a script given its location. * * @param environment an <code>Environment</code> value * @param source a <code>String</code> value * @return a <code>Source</code> value * @exception Exception if an error occurs */ Source readScript(Environment environment, String source) throws Exception; /** * This method is called from the sitemap, using the syntax * * <pre> * <map:call function="..."/> * </pre> * * The method will execute the named function, which must be defined * in the given language. There is no assumption made on how various * arguments are passed to the function. * * <p>The <code>params</code> argument is a <code>List</code> object * that contains <code>Interpreter.Argument</code> instances, * representing the parameters to be passed to the called * function. An <code>Argument</code> instance is a key-value pair, * where the key is the name of the parameter, and the value is its * desired value. Most languages will ignore the name value and * simply pass to the function, in a positional order, the values of * the argument. Some languages however can pass the arguments in a * different order than the original prototype of the function. For * these languages the ability to associate the actual argument with * a formal parameter using its name is essential. * * <p>A particular language implementation may decide to put the * environment, request, response etc. objects in the dynamic scope * available to the function at the time of the call. Other * implementations may decide to pass these as arguments to the * called function. * * <p>The current implementation assumes the sitemap implementation * is TreeProcessor. * * @param funName a <code>String</code> value, the name of the * function to call * @param params a <code>List</code> object whose components are * CallFunctionNode.Argument instances. The interpretation of the * parameters is left to the actual implementation of the * interpreter. * @param env an <code>Environment</code> value */ void callFunction(String funName, List params, Environment env) throws Exception; /** * Forward the request to a Cocoon pipeline. * * @param URI a <code>String</code>, the URI of the forwarded request * @param bizData an <code>Object</code>, the business data object * to be made available to the forwarded pipeline * @param continuation a <code>WebContinuation</code>, the * continuation to be called to resume the processing * @param environment an <code>Environment</code>, the Cocoon environment * invocation context. * @exception Exception if an error occurs */ void forwardTo(String uri, Object bizData, WebContinuation continuation, Environment environment) throws Exception; /** * Continues a previously started processing. The continuation * object where the processing should start from is indicated by the * <code>continuationId</code> string. * * @param continuationId a <code>String</code> value * * @param params a <code>List</code> value, containing the * parameters to be passed when invoking the continuation. As * opposed to the parameters passed by <code>callFunction</code>, * these parameters will only become available in the language's * environment, if at all. * * @param environment an <code>Environment</code> value * @exception Exception if an error occurs */ void handleContinuation(String continuationId, List params, Environment environment) throws Exception; } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/InterpreterSelector.java Index: InterpreterSelector.java =================================================================== package org.apache.cocoon.components.flow; import org.apache.avalon.excalibur.component.ExcaliburComponentSelector; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.thread.ThreadSafe; public class InterpreterSelector extends ExcaliburComponentSelector implements Configurable, ThreadSafe { String defaultLanguage; public void configure(Configuration config) throws ConfigurationException { super.configure(config); defaultLanguage = config.getAttribute("default", null); boolean reloadScripts = config.getAttributeAsBoolean("reload-scripts", false); long checkTime = config.getAttributeAsLong("check-time", 1000L); // Finish the initialization of the already created components Configuration[] configurations = config.getChildren("component-instance"); if (configurations.length == 0) throw new ConfigurationException("No languages defined!"); for (int i = 0; i < configurations.length; i++) { Configuration conf = configurations[i]; String hint = conf.getAttribute("name").trim(); Interpreter interp; try { interp = (Interpreter)this.select(hint); } catch (ComponentException ex) { throw new ConfigurationException("Could not find component for hint " + hint + ": " + ex.toString()); } if (interp instanceof AbstractInterpreter) { ((AbstractInterpreter)interp).setReloadScripts(reloadScripts); ((AbstractInterpreter)interp).setCheckTime(checkTime); } if (i == 0 && defaultLanguage == null) defaultLanguage = hint; } } public String getDefaultLanguage() { return defaultLanguage; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/ScriptSource.java Index: ScriptSource.java =================================================================== package org.apache.cocoon.components.flow; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ModifiableSource; import org.apache.cocoon.environment.Source; /** * Representation of a source in a scripting language. Loosely modeled * after Source and ModifiableSource, but doesn't carry all the XML * baggage which is not needed here. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 16, 2002 */ public class ScriptSource { Interpreter interp; String sourceName; Source source; boolean isModifiable; public ScriptSource(Interpreter interp, String sourceName) { this.interp = interp; this.sourceName = sourceName; } public String getSourceName() { return sourceName; } public long getLastModified() { if (isModifiable) ((ModifiableSource)source).refresh(); return source == null ? 0 : source.getLastModified(); } public void refresh(Environment environment) throws Exception { source = interp.readScript(environment, sourceName); isModifiable = source instanceof ModifiableSource; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/WebContinuation.java Index: WebContinuation.java =================================================================== package org.apache.cocoon.components.flow; import java.lang.Comparable; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Representation of continuations in a Web environment. * * <p>Because a user may click on the back button of the browser and * restart a saved computation in a continuation, each * <code>WebContinuation</code> becomes the parent of a subtree of * continuations. * * <p>If there is no parent <code>WebContinuation</code>, the created * continuation becomes the root of a tree of * <code>WebContinuation</code>s. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 19, 2002 */ public class WebContinuation implements Comparable { /** * The continuation this object represents. */ protected Object continuation; /** * The parent <code>WebContinuation</code> from which processing * last started. If null, there is no parent continuation * associated, and this is the first one to be created in a * processing. In this case this <code>WebContinuation</code> * instance becomes the root of the tree maintained by the * <code>ContinuationsManager</code>. * * @see ContinuationsManager */ protected WebContinuation parentContinuation; /** * The children continuations. These are continuations created by * resuming the processing from the point stored by * <code>continuation</code>. */ protected List children = new ArrayList(); /** * The continuation id used to represent this instance in Web pages. */ protected String id; /** * A user definable object. This is present for convenience, to * store any information associated with this * <code>WebContinuation</code> a particular implementation might * need. */ protected Object userObject; /** * When was this continuation accessed last time. Each time the * continuation is accessed, this time is set to the time of the * access. */ protected long lastAccessTime; /** * Indicates how long does this continuation will live (in * seconds). The continuation will be removed once the current time * is bigger than <code>lastAccessTime + timeToLive</code>. */ protected int timeToLive; /** * Create a <code>WebContinuation</code> object. Saves the object in * the hash table of continuations maintained by * <code>manager</code> (this is done as a side effect of obtaining * and identifier from it). * * @param continuation an <code>Object</code> value * @param parentContinuation a <code>WebContinuation</code> value * @param manager a <code>ContinuationsManagerImpl</code> value */ public WebContinuation(Object continuation, WebContinuation parentContinuation, ContinuationsManagerImpl manager, int timeToLive) { this.continuation = continuation; this.parentContinuation = parentContinuation; id = manager.generateContinuationId(this); this.timeToLive = timeToLive; if (parentContinuation != null) this.parentContinuation.children.add(this); } /** * Return the continuation object. * * @return an <code>Object</code> value */ public Object getContinuation() { updateLastAccessTime(); return continuation; } /** * Return the ancestor continuation situated <code>level</code>s * above the current continuation. The current instance is * considered to be at level 0. The parent continuation of the * receiving instance at level 1, its parent is at level 2 relative * to the receiving instance. If <code>level</code> is bigger than * the depth of the tree, the root of the tree is returned. * * @param level an <code>int</code> value * @return a <code>WebContinuation</code> value */ public WebContinuation getContinuation(int level) { if (level <= 0) { updateLastAccessTime(); return this; } else if (parentContinuation == null) return this; else return parentContinuation.getContinuation(level - 1); } /** * Return the parent <code>WebContinuation</code>. Equivalent with * <code>getContinuation(1)</code>. * * @return a <code>WebContinuation</code> value */ public WebContinuation getParentContinuation() { return parentContinuation; } /** * Return the children <code>WebContinuation</code> which were * created as a result of resuming the processing from the current * <code>continuation</code>. * * @return a <code>List</code> value */ public List getChildren() { return children; } /** * Returns the string identifier of this * <code>WebContinuation</code>. * * @return a <code>String</code> value */ public String getId() { return id; } /** * Sets the user object associated with this instance. * * @param obj an <code>Object</code> value */ public void setUserObject(Object obj) { this.userObject = obj; } /** * Obtains the user object associated with this instance. * * @return an <code>Object</code> value */ public Object getUserObject() { return userObject; } /** * Returns the hash code of the associated identifier. * * @return an <code>int</code> value */ public int hashCode() { return id.hashCode(); } /** * True if the identifiers are the same, false otherwise. * * @param another an <code>Object</code> value * @return a <code>boolean</code> value */ public boolean equals(Object another) { if (another instanceof WebContinuation) return id.equals(((WebContinuation)another).id); return false; } /** * Compares the expiration time of this instance with that of the * WebContinuation passed as argument. * * <p><b>Note:</b> this class has a natural ordering that is * inconsistent with <code>equals</code>.</p>. * * @param other an <code>Object</code> value, which should be a * <code>WebContinuation</code> instance * @return an <code>int</code> value */ public int compareTo(Object other) { WebContinuation wk = (WebContinuation)other; return (int)((lastAccessTime + timeToLive) - (wk.lastAccessTime + wk.timeToLive)); } /** * Debugging method. * * <p>Assumes the receiving instance as the root of a tree and * displays the tree of continuations. */ public void display() { display(0); } /** * Debugging method. * * <p>Displays the receiving instance as if it is at the * <code>indent</code> depth in the tree of continuations. Each * level is indented 2 spaces. * * @param depth an <code>int</code> value */ protected void display(int depth) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < depth; i++) buf.append(" "); String spaces = buf.toString(); System.out.print(spaces); System.out.println("WebContinuation " + id); int size = children.size(); depth++; for (int i = 0; i < size; i++) ((WebContinuation)children.get(i)).display(depth); } /** * Update the continuation in the */ protected void updateLastAccessTime() { lastAccessTime = (new Date()).getTime(); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/flow.xconf Index: flow.xconf =================================================================== <?xml version="1.0"?> <xconf xpath="/cocoon" unless="comment()[contains(., 'Flow interpreter')]"> <!-- Flow interpreter support. The attributes recognized by the <flow-interpreters> element are: default (string value): the default interpreted language assumed for <map:script> elements which do not specify a "language" attribute. If not present, the first language that's described within the <flow-interpreters> element is assumed to be the default language. reload-scripts (boolean value, default false): whether to check if the scripts source files are modified. Checking for modification is an expensive operation, so leave it disabled in a production environment. If not present it is assumed to be "false". When "true" *all* script files are checked for modification on each function invocation done using <map:call function="...">, but not more frequent than the value of "check-time" (see below). check-time (long value, default 1000): time in miliseconds between the checks for the last modification date of script files. Within <flow-interpreters> only <component-instance> elements are recognized. The attributes recognized by this element are "name" and "class". "name" specifies the name of a scripting language, and "class" defines the Java class that implements it. See org.apache.cocoon.components.flow.Interpreter for the Cocoon interface with an scripting language interpreter. --> <flow-interpreters default="JavaScript" reload-scripts="true" check-time="2000" logger="flow"> <component-instance name="JavaScript" class="org.apache.cocoon.components.flow.javascript.JavaScriptInterpreter"> <load-on-startup>resource://org/apache/cocoon/components/flow/javascript/system.js</load-on-startup> </component-instance> <!-- Temporarily disable Scheme, until full support is completed --> <!-- <component-instance name="Scheme" class="org.apache.cocoon.components.flow.scheme.SchemeInterpreter"> <load-on-startup>resource://org/apache/cocoon/components/flow/scheme/system.scm</load-on-startup> <heap>/WEB-INF/sisc.heap</heap> </component-instance> --> </flow-interpreters> <continuations time-to-live="3600"/> </xconf> 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/JSCocoon.java Index: JSCocoon.java =================================================================== package org.apache.cocoon.components.flow.javascript; import java.util.HashMap; import java.util.Map; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; import org.apache.cocoon.components.flow.AbstractInterpreter; import org.apache.cocoon.components.flow.ContinuationsManager; import org.apache.cocoon.components.flow.ContinuationsManagerImpl; import org.apache.cocoon.environment.Context; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.Response; import org.apache.cocoon.environment.Session; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.Wrapper; /** * JavaScript interface to various Cocoon abstractions. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 16, 2002 */ public class JSCocoon extends ScriptableObject { protected AbstractInterpreter interp; protected Scriptable scope; protected NativeArray parameters; protected Environment environment; protected ComponentManager manager; public JSCocoon() {} public String getClassName() { return "Cocoon"; } public void setScope(Scriptable scope) { this.scope = scope; } public Scriptable getScope() { return scope; } public void setParameters(NativeArray parameters) { this.parameters = parameters; } public void setInterpreter(AbstractInterpreter interp) { this.interp = interp; } public void setContext(ComponentManager manager, Environment environment) { this.manager = manager; this.environment = environment; } public void invalidateContext() { manager = null; environment = null; } public NativeArray jsGet_parameters() { return parameters; } public AbstractInterpreter jsGet_interpreter() { return interp; } public Environment jsGet_environment() { return environment; } public Request jsGet_request() { Map objectModel = environment.getObjectModel(); return ObjectModelHelper.getRequest(objectModel); } public Response jsGet_response() { Map objectModel = environment.getObjectModel(); return ObjectModelHelper.getResponse(objectModel); } public Session jsGet_session() { return jsGet_request().getSession(); } public Context jsGet_context() { Map objectModel = environment.getObjectModel(); return ObjectModelHelper.getContext(objectModel); } /** * Load the file specified as argument. Registers the file with the * interpreter and then forces its loading by calling {@link * AbstractInterpreter#checkForModifiedScripts}. * * @param filename a <code>String</code> value * @return an <code>Object</code> value * @exception Exception if an error occurs */ public Object jsFunction_load(String filename) throws Exception { try { interp.register(filename); interp.checkForModifiedScripts(environment); } catch (Exception ex) { ex.printStackTrace(); throw ex; } return null; } public String jsFunction_toString() { return "[object " + toString() + "]"; } public void jsFunction_forwardTo(String uri, Object bizData, Object cont) throws Exception { if (bizData instanceof Wrapper) bizData = ((Wrapper)bizData).unwrap(); else if (bizData instanceof Scriptable) bizData = jsobjectToMap((Scriptable)bizData); JSWebContinuation kont = (JSWebContinuation)cont; interp.forwardTo(uri, bizData, kont.getWebContinuation(), environment); } public void jsFunction_diplayAllContinuations() throws ComponentException { ContinuationsManager continuationsMgr = (ContinuationsManager)manager.lookup(ContinuationsManager.ROLE); try { if (continuationsMgr instanceof ContinuationsManagerImpl) ((ContinuationsManagerImpl)continuationsMgr).displayAllContinuations(); } finally { manager.release((Component)continuationsMgr); } } // All right, this breaks the encapsulation, but I couldn't find any // better way to obtain the ComponentManager for a // JSWebContinuation. ComponentManager getComponentManager() { return manager; } public static Map jsobjectToMap(Scriptable jsobject) { HashMap hash = new HashMap(); Object[] ids = jsobject.getIds(); for (int i = 0; i < ids.length; i++) { String key = ScriptRuntime.toString(ids[i]); Object value = jsobject.get(key, jsobject); if (value == Undefined.instance) value = null; else value = ScriptRuntime.toPrimitive(value); hash.put(key, value); } return hash; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/JSGlobal.java Index: JSGlobal.java =================================================================== package org.apache.cocoon.components.flow.javascript; import org.mozilla.javascript.Context; import org.mozilla.javascript.tools.shell.Global; /** * Functions and variables in the global JavaScript scope. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 17, 2002 */ public class JSGlobal extends Global { public JSGlobal(Context cx) { super(cx); } public String getClassName() { return "Global"; } public static void print(String message) { System.out.println(message); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/JSLog.java Index: JSLog.java =================================================================== package org.apache.cocoon.components.flow.javascript; import org.apache.log.Logger; import org.mozilla.javascript.ScriptableObject; /** * JavaScript interface to the Cocoon log facility. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 16, 2002 */ public class JSLog extends ScriptableObject { Logger logger; public JSLog() {} public String getClassName() { return "Log"; } public void setLogger(Logger logger) { this.logger = logger; } public void jsFunction_debug(String message) { logger.debug(message); } public void jsFunction_info(String message) { logger.info(message); } public void jsFunction_warn(String message) { logger.warn(message); } public void jsFunction_error(String message) { logger.error(message); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/JSWebContinuation.java Index: JSWebContinuation.java =================================================================== package org.apache.cocoon.components.flow.javascript; import org.apache.avalon.framework.component.ComponentManager; import org.apache.cocoon.components.flow.ContinuationsManager; import org.apache.cocoon.components.flow.WebContinuation; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; public class JSWebContinuation extends ScriptableObject { protected JSCocoon cocoon; protected WebContinuation wk; protected ContinuationsManager continuationsMgr; public JSWebContinuation() {} public String getClassName() { return "WebContinuation"; } public JSCocoon getJSCocoon() { return cocoon; } public WebContinuation getWebContinuation() { return wk; } public static Scriptable jsConstructor(Context cx, Object[] args, Function ctorObj, boolean inNewExpr) throws Exception { JSCocoon cocoon = (JSCocoon)args[0]; ComponentManager manager = cocoon.getComponentManager(); ContinuationsManager contMgr = (ContinuationsManager)manager.lookup(ContinuationsManager.ROLE); Object kont = args[1]; JSWebContinuation pjswk = (JSWebContinuation)args[2]; WebContinuation pwk = (pjswk == null ? null : pjswk.wk); int ttl; if (args[3] == Undefined.instance) ttl = 0; else { Number timeToLive = (Number)args[3]; ttl = (timeToLive == null ? 0 : timeToLive.intValue()); } JSWebContinuation jswk = new JSWebContinuation(); WebContinuation wk = contMgr.createWebContinuation(kont, pwk, ttl); wk.setUserObject(jswk); jswk.cocoon = cocoon; jswk.wk = wk; jswk.continuationsMgr = contMgr; return jswk; } public String jsGet_id() { return wk.getId(); } public Object jsGet_continuation() { return wk.getContinuation(); } public void jsFunction_invalidate() { continuationsMgr.invalidateWebContinuation(wk); } public void jsFunction_display() { wk.display(); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/JavaScriptInterpreter.java Index: JavaScriptInterpreter.java =================================================================== package org.apache.cocoon.components.flow.javascript; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.List; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.flow.AbstractInterpreter; import org.apache.cocoon.components.flow.WebContinuation; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.Source; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.PropertyException; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; /** * Interface with the JavaScript interpreter. * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since March 25, 2002 */ public class JavaScriptInterpreter extends AbstractInterpreter implements Configurable, Initializable { // This is the only optimization level that supports continuations // in the Christoper Oliver's Rhino JavaScript implementation static int OPTIMIZATION_LEVEL = -2; JSGlobal scope; public void configure(Configuration config) { String loadOnStartup = config.getChild("load-on-startup", true).getValue(null); if (loadOnStartup != null) register(loadOnStartup); } public void initialize() throws Exception { Context context = Context.enter(); context.setOptimizationLevel(OPTIMIZATION_LEVEL); try { scope = new JSGlobal(context); // Register some handy classes with JavaScript, so we can make // use of them from the flow layer. // Access to the Cocoon log ScriptableObject.defineClass(scope, JSLog.class); // Access to Cocoon internal objects ScriptableObject.defineClass(scope, JSCocoon.class); // Wrapper for WebContinuation ScriptableObject.defineClass(scope, JSWebContinuation.class); // Define some functions on the top level scope String[] names = { "print" }; try { ((ScriptableObject)scope) .defineFunctionProperties(names, JSGlobal.class, ScriptableObject.DONTENUM); } catch (PropertyException e) { throw new Error(e.getMessage()); } // Define some global variables in JavaScript Object args[] = {}; Scriptable log = context.newObject(scope, "Log", args); ((JSLog)log).setLogger(getLogger()); scope.put("log", scope, log); } catch (Exception e) { context.exit(); System.out.println("problem initializing JavaScriptInterpreter: "); e.printStackTrace(); throw e; } } protected Scriptable enterContext(Environment environment) throws Exception { Context context = Context.enter(); context.setOptimizationLevel(OPTIMIZATION_LEVEL); context.setCompileFunctionsWithDynamicScope(true); Scriptable thrScope = context.newObject(scope); thrScope.setPrototype(scope); // We want 'thrScope' to be a new top-level scope, so set its // parent scope to null. This means that any variables created // by assignments will be properties of "thrScope". thrScope.setParentScope(null); // Put in the thread scope the Cocoon object, which gives access // to the interpreter object, and some Cocoon objects. See // JSCocoon for more details. Object args[] = {}; Scriptable cocoon = context.newObject(scope, "Cocoon", args); ((JSCocoon)cocoon).setInterpreter(this); ((JSCocoon)cocoon).setContext(manager, environment); ((JSCocoon)cocoon).setScope(thrScope); thrScope.put("cocoon", thrScope, cocoon); return thrScope; } /** * Remove the Cocoon object from the JavaScript thread scope so it * can be garbage collected, together with all the objects it * contains. */ protected void exitContext(Scriptable thrScope) { JSCocoon cocoon = (JSCocoon)thrScope.get("cocoon", thrScope); cocoon.invalidateContext(); Context.getCurrentContext().exit(); } public Source readScript(Environment environment, String sourceName) throws Exception { Scriptable thrScope = null; Source source = null; System.out.println("Reading file " + sourceName); try { thrScope = enterContext(environment); source = environment.resolve(sourceName); InputStream inputStream = source.getInputStream(); Reader reader = new BufferedReader(new InputStreamReader(inputStream)); Context ctx = Context.getCurrentContext(); ctx.evaluateReader(scope, reader, sourceName, 1, null); } catch (Exception ex) { ex.printStackTrace(); throw ex; } finally { exitContext(thrScope); } return source; } /** * Calls a JavaScript function, passing <code>params</code> as its * arguments. In addition to this, it makes available the parameters * through the <code>cocoon.parameters</code> JavaScript array * (indexed by the parameter names). * * @param funName a <code>String</code> value * @param params a <code>List</code> value * @param environment an <code>Environment</code> value * @exception Exception if an error occurs */ public void callFunction(String funName, List params, Environment environment) throws Exception { Scriptable thrScope = null; checkForModifiedScripts(environment); try { thrScope = enterContext(environment); JSCocoon cocoon = (JSCocoon)thrScope.get("cocoon", thrScope); Object callFunction = scope.get("callFunction", thrScope); if (callFunction == Scriptable.NOT_FOUND) throw new RuntimeException("Cannot find 'callFunction' " + "(system.js not loaded?)"); Object fun = scope.get(funName, thrScope); if (fun == Scriptable.NOT_FOUND) throw new RuntimeException("'" + funName + "' is undefined!"); if (!(fun instanceof Function)) throw new RuntimeException("'" + funName + "' is not a function!"); Context cx = Context.getCurrentContext(); int size = (params != null ? params.size() : 0); Object[] funArgs = new Object[size]; NativeArray funArgsArray = new NativeArray(funArgs); NativeArray parameters = new NativeArray(size); if (size != 0) { for (int i = 0; i < size; i++) { Interpreter.Argument arg = (Interpreter.Argument)params.get(i); funArgs[i] = ScriptRuntime.toObject(cx, thrScope, arg.value); parameters.put(arg.name, parameters, arg.value); } } cocoon.setParameters(parameters); Object callFunArgs[] = { fun, funArgsArray }; ((Function) callFunction).call(cx, thrScope, thrScope, callFunArgs); } finally { exitContext(thrScope); } } public void handleContinuation(String id, List params, Environment environment) throws Exception { WebContinuation wk = continuationsMgr.lookupWebContinuation(id); if (wk == null) throw new RuntimeException("No continuation with id " + id); Context context = Context.enter(); context.setOptimizationLevel(OPTIMIZATION_LEVEL); context.setCompileFunctionsWithDynamicScope(true); // Obtain the JS continuation object from it, and setup the // JSCocoon object associated in the dynamic scope of the saved // continuation with the environment and context objects. JSWebContinuation jswk = (JSWebContinuation)wk.getUserObject(); JSCocoon cocoon = jswk.getJSCocoon(); cocoon.setContext(manager, environment); Scriptable kScope = cocoon.getScope(); // We can now resume the processing from the state saved by the // continuation object. Setup the JavaScript Context object. Object handleContFunction = scope.get("handleContinuation", kScope); if (handleContFunction == Scriptable.NOT_FOUND) throw new RuntimeException("Cannot find 'handleContinuation' " + "(system.js not loaded?)"); Object args[] = { jswk }; int size = (params != null ? params.size() : 0); NativeArray parameters = new NativeArray(size); if (size != 0) { for (int i = 0; i < size; i++) { Interpreter.Argument arg = (Interpreter.Argument)params.get(i); parameters.put(arg.name, parameters, arg.value); } } cocoon.setParameters(parameters); try { ((Function)handleContFunction).call(context, kScope, kScope, args); } finally { context.exit(); } } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/system.js Index: system.js =================================================================== // system.js // // JavaScript definitions // // Author: Ovidiu Predescu <[EMAIL PROTECTED]> // Date: March 19, 2002 // var suicide; var lastContinuation = null; function callFunction(func, args) { suicide = new Continuation(); return func.apply(this, args); } function sendPage(uri, bizData, timeToLive) { var kont = _sendPage(uri, bizData, timeToLive); lastContinuation = kont; return kont; } function _sendPage(uri, bizData, timeToLive) { var k = new Continuation(); var kont = new WebContinuation(cocoon, k, lastContinuation, timeToLive); cocoon.forwardTo(uri, bizData, kont); suicide(); } function sendPageAndContinue(uri, bizData) { cocoon.forwardTo(uri, bizData, null); } function handleContinuation(kont) { kont.continuation(kont); } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/language/markup/xsp/java/jpath.xsl Index: jpath.xsl =================================================================== <?xml version="1.0" encoding="utf-8"?> <!-- Author: Ovidiu Predescu "[EMAIL PROTECTED]" Date: February 15, 2002 --> <xsl:stylesheet version="1.0" xmlns:xsp="http://apache.org/xsp" xmlns:jpath="http://apache.org/xsp/jpath/1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="xsp:page"> <xsp:page> <xsl:apply-templates select="@*"/> <xsp:structure> <xsp:include>java.util.List</xsp:include> <xsp:include>java.util.Iterator</xsp:include> <xsp:include>org.apache.cocoon.environment.Environment</xsp:include> <xsp:include>org.apache.cocoon.components.flow.WebContinuation</xsp:include> <xsp:include>org.apache.commons.jxpath.JXPath</xsp:include> <xsp:include>org.apache.commons.jxpath.JXPathContext</xsp:include> <xsp:include>org.apache.commons.jxpath.CompiledExpression</xsp:include> </xsp:structure> <xsp:logic> </xsp:logic> <xsp:init-page> Object bean = ((Environment)resolver).getAttribute("bean-dict"); WebContinuation kont = (WebContinuation)((Environment)resolver).getAttribute("kont"); JXPathContext jxpathContext = JXPathContext.newContext(bean); // Generate the compiled representation of the JXPath // expressions used by this page. <xsl:apply-templates mode="compile"/> </xsp:init-page> <xsl:apply-templates/> </xsp:page> </xsl:template> <xsl:template match="jpath:if | jpath:when" mode="compile"> <xsl:variable name="var-name"> <xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@test"/> </xsl:call-template> </xsl:variable> CompiledExpression <xsl:value-of select="$var-name"/> = jxpathContext.compile("<xsl:value-of select="@test"/>"); <xsl:apply-templates select="jpath:*" mode="compile"/> </xsl:template> <xsl:template match="jpath:for-each | jpath:value-of" mode="compile"> <xsl:variable name="var-name"> <xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@select"/> </xsl:call-template> </xsl:variable> CompiledExpression <xsl:value-of select="$var-name"/> = jxpathContext.compile("<xsl:value-of select="@select"/>"); <xsl:apply-templates select="jpath:*" mode="compile"/> </xsl:template> <xsl:template match="*|@*|text()|processing-instruction()" mode="compile"> <xsl:apply-templates mode="compile"/> </xsl:template> <xsl:template name="get-var-name"> <xsl:param name="expr"/> jxpath_<xsl:value-of select="translate($expr, ' 	

~`!@%^*()-+=[]{}\|,./?><', '')"/> </xsl:template> <xsl:template match="jpath:if"> <xsl:choose> <xsl:when test="@test"> <xsp:logic> if (<xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@test"/> </xsl:call-template> .getValue(jxpathContext) != null) { <xsl:apply-templates/> } </xsp:logic> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Required 'test' attribute in <jpath:if> is missing!</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="jpath:choose"> <xsp:logic> if (0) { } <xsl:apply-templates select="jpath:when|jpath:otherwise"/> </xsp:logic> </xsl:template> <xsl:template match="jpath:when"> <xsp:logic> else if (<xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@test"/> </xsl:call-template> .getValue(jxpathContext) != null) { <xsl:apply-templates/> } </xsp:logic> </xsl:template> <xsl:template match="jpath:otherwise"> <xsp:logic> else { <xsl:apply-templates/> } </xsp:logic> </xsl:template> <xsl:template match="jpath:for-each"> <xsl:variable name="old-context"> oldJPathContext<xsl:value-of select="count(ancestor-or-self::*)"/> </xsl:variable> <xsl:choose> <xsl:when test="@select"> <xsp:logic> { List selection = <xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@test"/> </xsl:call-template> .eval(jxpathContext); if (selection != null) { Iterator iter = selection.iterator(); JPathContext <xsl:value-of select="$old-context"/> = jxpathContext; while (iter.hasNext()) { Object current = iter.next(); jxpathContext = JPathContext.newContext(current); </xsp:logic> <xsl:apply-templates/> <xsp:logic> } jxpathContext = <xsl:value-of select="$old-context"/>; } } </xsp:logic> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Required 'select' attribute in <jpath:for-each> is missing!</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="jpath:value-of"> <xsp:expr> <xsl:choose> <xsl:when test="@select"> <xsl:call-template name="get-var-name"> <xsl:with-param name="expr" select="@select"/> </xsl:call-template> .getValue(jxpathContext) </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Required 'select' attribute in <jpath:value-of> is missing!</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsp:expr> </xsl:template> <xsl:template match="jpath:continuation"> <xsl:variable name="level"> <xsl:choose> <xsl:when test="@select"> <xsl:value-of select="@select"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsp:expr> kont.getContinuation(<xsl:value-of select="$level"/>).getId() </xsp:expr> </xsl:template> <xsl:template match="@*|*|text()|processing-instruction()"> <!-- Catch all template. Just pass along unmodified everything we don't handle. --> <xsl:copy> <xsl:apply-templates select="@*|*|text()|processing-instruction()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> 1.3 +4 -0 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/treeprocessor-builtins.xml Index: treeprocessor-builtins.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/treeprocessor-builtins.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- treeprocessor-builtins.xml 17 Mar 2002 21:55:22 -0000 1.2 +++ treeprocessor-builtins.xml 19 May 2002 19:19:39 -0000 1.3 @@ -149,6 +149,10 @@ <node name="serialize" builder="org.apache.cocoon.components.treeprocessor.sitemap.SerializeNodeBuilder"/> + <node name="script" builder="org.apache.cocoon.components.treeprocessor.sitemap.ScriptNodeBuilder"/> + + <node name="continue" builder="org.apache.cocoon.components.treeprocessor.sitemap.ContinueNodeBuilder"/> + <node name="handle-errors" builder="org.apache.cocoon.components.treeprocessor.sitemap.HandleErrorsNodeBuilder"/> </nodes> 1.3 +35 -12 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/CallNodeBuilder.java Index: CallNodeBuilder.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/CallNodeBuilder.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- CallNodeBuilder.java 13 Mar 2002 17:42:22 -0000 1.2 +++ CallNodeBuilder.java 19 May 2002 19:19:39 -0000 1.3 @@ -50,22 +50,25 @@ */ package org.apache.cocoon.components.treeprocessor.sitemap; + + +import java.util.*; +import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; - import org.apache.cocoon.components.treeprocessor.AbstractProcessingNodeBuilder; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.components.treeprocessor.CategoryNodeBuilder; -import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.LinkedProcessingNodeBuilder; +import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.ProcessingNode; - -import java.util.*; +import org.apache.cocoon.components.treeprocessor.sitemap.CallFunctionNode; +import org.apache.cocoon.components.treeprocessor.sitemap.CallNode; /** * * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> - * @version CVS $Id: CallNodeBuilder.java,v 1.2 2002/03/13 17:42:22 ovidiu Exp $ + * @version CVS $Id: CallNodeBuilder.java,v 1.3 2002/05/19 19:19:39 ovidiu Exp $ */ public class CallNodeBuilder extends AbstractProcessingNodeBuilder @@ -73,25 +76,45 @@ protected ProcessingNode node; protected String resourceName; + protected String functionName; - public ProcessingNode buildNode(Configuration config) throws Exception { + public ProcessingNode buildNode(Configuration config) + throws Exception + { + this.resourceName = config.getAttribute("resource", null); + this.functionName = config.getAttribute("function", null); + + if (resourceName == null && functionName == null) + throw new ConfigurationException("<map:call> must have either a 'resource' or a 'function' attribute!"); + + if (resourceName != null) + this.node = new CallNode(); + else + this.node = new CallFunctionNode(functionName); - this.resourceName = config.getAttribute("resource"); - this.node = new CallNode(); this.treeBuilder.setupNode(this.node, config); + if (node instanceof Configurable) + ((Configurable)this.node).configure(config); return this.node; } - public void linkNode() throws Exception { - CategoryNode resources = CategoryNodeBuilder.getCategoryNode(this.treeBuilder, "resources"); + public void linkNode() + throws Exception + { + CategoryNode resources + = CategoryNodeBuilder.getCategoryNode(this.treeBuilder, "resources"); if (resources == null) { - String msg = "This sitemap contains no resources. Cannot call at " + this.node.getLocation(); + String msg = "This sitemap contains no resources. Cannot call at " + + this.node.getLocation(); getLogger().error(msg); throw new ConfigurationException(msg); } - ((CallNode)this.node).setResource(resources, this.resourceName); + if (resourceName != null) + ((CallNode)this.node).setResource(resources, this.resourceName); + else + ((CallFunctionNode)this.node).setResources(resources); } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/CallFunctionNode.java Index: CallFunctionNode.java =================================================================== package org.apache.cocoon.components.treeprocessor.sitemap; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.flow.InterpreterSelector; import org.apache.cocoon.components.treeprocessor.AbstractProcessingNode; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.components.treeprocessor.InvokeContext; import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.ParameterizableProcessingNode; import org.apache.cocoon.components.treeprocessor.ProcessingNode; import org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.Redirector; import org.apache.cocoon.sitemap.PatternException; public class CallFunctionNode extends AbstractProcessingNode implements Configurable, Composable, Initializable { protected String functionName; protected List parameters; protected MapStackResolver resourceResolver; protected ComponentManager manager; protected CategoryNode resources; protected String language; public static List resolveList(List expressions, List mapStack) throws PatternException { int size; if (expressions == null || (size = expressions.size()) == 0) return Collections.EMPTY_LIST; List result = new ArrayList(size); for (int i = 0; i < size; i++) { Interpreter.Argument arg = (Interpreter.Argument)expressions.get(i); String value = MapStackResolver.getResolver(arg.value).resolve(mapStack); result.add (new Interpreter.Argument(arg.name, value)); } return result; } public CallFunctionNode(String funName) { functionName = funName; } public void setResources(CategoryNode resources) throws Exception { this.resources = resources; } /** * Obtain the configuration specific to this node type and update * the internal state. * * @param config a <code>Configuration</code> value * @exception ConfigurationException if an error occurs */ public void configure(Configuration config) throws ConfigurationException { language = config.getAttribute("language", null); parameters = new ArrayList(); Configuration[] params = config.getChildren("parameter"); for (int i = 0; i < params.length; i++) { Configuration param = params[i]; String name = param.getAttribute("name", null); String value = param.getAttribute("value", null); parameters.add(new Interpreter.Argument(name, value)); } try { if (MapStackResolver.needsResolve(functionName)) { // Will always be resolved at invoke time this.resourceResolver = MapStackResolver.getResolver(functionName); } } catch (PatternException ex) { throw new ConfigurationException(ex.toString()); } } public void compose(ComponentManager manager) { this.manager = manager; } public void initialize() throws Exception { InterpreterSelector selector = (InterpreterSelector)manager.lookup(Interpreter.ROLE); if (language == null) language = selector.getDefaultLanguage(); } public boolean invoke(Environment env, InvokeContext context) throws Exception { List params = null; // Resolve parameters if (this.parameters != null) params = resolveList(this.parameters, context.getMapStack()); String name = functionName; if (resourceResolver != null) { // Need to resolve the function name at runtime name = resourceResolver.resolve(context.getMapStack()); } InterpreterSelector selector = (InterpreterSelector)manager.lookup(Interpreter.ROLE); if (language == null) language = selector.getDefaultLanguage(); // Obtain the Interpreter instance for this language Interpreter interpreter = (Interpreter)selector.select(language); // Obtain the redirector Redirector redirector = PipelinesNode.getRedirector(env); try { interpreter.callFunction(name, params, env /*, redirector*/); } finally { selector.release((Component)interpreter); } return true; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/ContinueNode.java Index: ContinueNode.java =================================================================== package org.apache.cocoon.components.treeprocessor.sitemap; import java.util.ArrayList; import java.util.List; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.components.flow.ContinuationsManager; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.flow.InterpreterSelector; import org.apache.cocoon.components.treeprocessor.AbstractProcessingNode; import org.apache.cocoon.components.treeprocessor.InvokeContext; import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.Redirector; import org.apache.cocoon.sitemap.PatternException; public class ContinueNode extends AbstractProcessingNode implements Configurable, Composable { protected String continuationId; protected List parameters; protected MapStackResolver continuationIdResolver; protected ComponentManager manager; public ContinueNode(String contId) { this.continuationId = contId; } public void configure(Configuration config) throws ConfigurationException { parameters = new ArrayList(); Configuration[] params = config.getChildren("parameter"); for (int i = 0; i < params.length; i++) { Configuration param = params[i]; String name = param.getAttribute("name", null); String value = param.getAttribute("value", null); parameters.add(new Interpreter.Argument(name, value)); } try { // The continuation id should would need to be resolved at all // times, but who knows... if (MapStackResolver.needsResolve(continuationId)) { this.continuationIdResolver = MapStackResolver.getResolver(continuationId); } } catch (PatternException ex) { throw new ConfigurationException(ex.toString()); } } public void compose(ComponentManager manager) { this.manager = manager; } public boolean invoke(Environment env, InvokeContext context) throws Exception { List params = null; // Resolve parameters if (this.parameters != null) params = CallFunctionNode.resolveList(this.parameters, context.getMapStack()); String contId = continuationId; if (continuationIdResolver != null) { contId = continuationIdResolver.resolve(context.getMapStack()); } InterpreterSelector selector = (InterpreterSelector)manager.lookup(Interpreter.ROLE); // FIXME: How to detect the language associated with the // continuation object? Use the default language for now, but it // should be fixed ASAP. String language = selector.getDefaultLanguage(); // Obtain the Interpreter instance for this language Interpreter interpreter = (Interpreter)selector.select(language); // Obtain the redirector Redirector redirector = PipelinesNode.getRedirector(env); try { interpreter.handleContinuation(contId, params, env /*, redirector*/); } finally { selector.release((Component)interpreter); } return true; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/ContinueNodeBuilder.java Index: ContinueNodeBuilder.java =================================================================== package org.apache.cocoon.components.treeprocessor.sitemap; import org.apache.avalon.excalibur.component.ExcaliburComponentSelector; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.ComponentSelector; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.language.generator.GeneratorSelector; import org.apache.cocoon.components.treeprocessor.AbstractProcessingNodeBuilder; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.components.treeprocessor.CategoryNodeBuilder; import org.apache.cocoon.components.treeprocessor.LinkedProcessingNodeBuilder; import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.ProcessingNode; public class ContinueNodeBuilder extends AbstractProcessingNodeBuilder implements Composable { protected ContinueNode node; protected ComponentManager manager; public void compose(ComponentManager manager) { this.manager = manager; } public ProcessingNode buildNode(Configuration config) throws Exception { String contId = config.getAttribute("with"); this.node = new ContinueNode(contId); this.treeBuilder.setupNode(this.node, config); if (node instanceof Configurable) ((Configurable)this.node).configure(config); return this.node; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/ScriptNode.java Index: ScriptNode.java =================================================================== package org.apache.cocoon.components.treeprocessor.sitemap; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.ComponentSelector; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.cocoon.Constants; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.flow.AbstractInterpreter; import org.apache.cocoon.components.treeprocessor.AbstractProcessingNode; import org.apache.cocoon.components.treeprocessor.InvokeContext; import org.apache.cocoon.environment.Context; import org.apache.cocoon.environment.Environment; public class ScriptNode extends AbstractProcessingNode implements Composable, Contextualizable { ComponentManager manager; String source; String language; Context context; public ScriptNode(String source, String language) { this.source = source; this.language = language; } /** * This method should never be called by the TreeProcessor, since a * <map:script> element should not be in an "executable" sitemap * node. * * @param env an <code>Environment</code> value * @param context an <code>InvokeContext</code> value * @return a <code>boolean</code> value * @exception Exception if an error occurs */ public boolean invoke(Environment env, InvokeContext context) throws Exception { return true; } public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException { this.context = (Context)context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); } /** * * Load the script specified by this node. * * @param manager a <code>ComponentManager</code> value */ public void compose(ComponentManager manager) throws ComponentException { this.manager = manager; try { ComponentSelector selector = (ComponentSelector)manager.lookup(Interpreter.ROLE); // Obtain the Interpreter instance for this language Interpreter interpreter = (Interpreter)selector.select(language); if (interpreter instanceof AbstractInterpreter) ((AbstractInterpreter)interpreter).register(source); // else FIXME: what to do if interpreter doesn't inherit from // AbstractInterpreter } catch (Exception ex) { throw new ComponentException("ScriptNode: Couldn't read the source file " + source + " in language " + language + ":" + ex); } } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/components/treeprocessor/sitemap/ScriptNodeBuilder.java Index: ScriptNodeBuilder.java =================================================================== package org.apache.cocoon.components.treeprocessor.sitemap; import org.apache.avalon.excalibur.component.ExcaliburComponentSelector; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.component.ComponentSelector; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.components.flow.Interpreter; import org.apache.cocoon.components.language.generator.GeneratorSelector; import org.apache.cocoon.components.treeprocessor.AbstractProcessingNodeBuilder; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.components.treeprocessor.CategoryNodeBuilder; import org.apache.cocoon.components.treeprocessor.LinkedProcessingNodeBuilder; import org.apache.cocoon.components.treeprocessor.MapStackResolver; import org.apache.cocoon.components.treeprocessor.ProcessingNode; public class ScriptNodeBuilder extends AbstractProcessingNodeBuilder implements Composable { protected ScriptNode node; protected ComponentManager manager; public void compose(ComponentManager manager) { this.manager = manager; } public ProcessingNode buildNode(Configuration config) throws Exception { String source = config.getAttribute("src"); String language = config.getAttribute("language", "JavaScript"); this.node = new ScriptNode(source, language); this.treeBuilder.setupNode(this.node, config); return this.node; } } 1.1 xml-cocoon2/src/java/org/apache/cocoon/transformation/AugmentTransformer.java Index: AugmentTransformer.java =================================================================== package org.apache.cocoon.transformation; import org.apache.avalon.excalibur.pool.Poolable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.environment.Request; import org.apache.cocoon.transformation.AbstractTransformer; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.components.language.markup.xsp.XSPRequestHelper; import org.apache.cocoon.environment.Request; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import java.io.IOException; import java.util.Map; /** * <p>Augments all <code>href</code> attributes with the full path to * the request.</p> * * <p>Usage in sitemap:</p> * * <pre> * <map:transform type="augment"> * <map:parameter name="mount" value="directory/to/be/appended"/> * </map:transform> * </pre> * * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> * @since October 10, 2001 */ public class AugmentTransformer extends AbstractTransformer implements Poolable { Map objectModel; Request request; String baseURI; public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws ProcessingException, SAXException, IOException { this.objectModel = objectModel; request = (Request)objectModel.get("request"); String mountPoint = (String)parameters.getParameter("mount", null); StringBuffer uribuf = new StringBuffer(); boolean isSecure = request.isSecure(); int port = request.getServerPort(); if (isSecure) uribuf.append("https://"); else uribuf.append("http://"); uribuf.append(request.getServerName()); if (isSecure) { if (port != 443) { uribuf.append(":").append(port); } } else { if (port != 80) { uribuf.append(":").append(port); } } if (mountPoint == null) { String requestedURI = request.getRequestURI(); requestedURI = requestedURI.substring(0, requestedURI.lastIndexOf("/")); uribuf.append(requestedURI); uribuf.append("/"); } else { uribuf.append(request.getContextPath()); uribuf.append("/"); uribuf.append(mountPoint); } baseURI = uribuf.toString(); } public void startElement(String uri, String name, String qname, Attributes attrs) throws SAXException { AttributesImpl newAttrs = null; for (int i = 0, size = attrs.getLength(); i < size; i++) { String attrName = attrs.getLocalName(i); if (attrName.equals("href")) { String value = attrs.getValue(i); // Don't touch the attribute if it's an absolute URL if (value.startsWith("http:") || value.startsWith("https:")) continue; if (newAttrs == null) newAttrs = new AttributesImpl(attrs); String newValue = baseURI + value; newAttrs.setValue(i, newValue); } } if (newAttrs == null) super.startElement(uri, name, qname, attrs); else super.startElement(uri, name, qname, newAttrs); } public void recycle() { this.objectModel = null; } }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]