package org.apache.cocoon.components.flow;
import java.lang.System;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
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.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.CategoryNode;
import org.apache.cocoon.components.treeprocessor.InvokeContext;
import org.apache.cocoon.components.treeprocessor.MapStackResolver;
import org.apache.cocoon.components.treeprocessor.ProcessingNode;
import org.apache.cocoon.components.treeprocessor.sitemap.PipelinesNode;
import org.apache.cocoon.environment.Context;
import org.apache.cocoon.environment.Environment;
/**
* 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:ovidiu@cup.hp.com">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;
protected CategoryNode resources;
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;
}
public void setResources(CategoryNode resources)
{
this.resources = resources;
}
/**
* 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)
throws Exception
{
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 processPipeline(String name, Map pipelineArgs, Object bizData,
WebContinuation continuation,
Environment environment, InvokeContext ctx)
throws Exception
{
if (ctx == null) {
String msg = "Cannot invoke pipeline with a null InvokeContext! Make sure"
+ " you're calling the pipeline during the execution of a request.";
throw new RuntimeException(msg);
}
environment.setAttribute("bean-dict", bizData);
environment.setAttribute("kont", continuation);
ProcessingNode pipeline
= resources.getNodeByName(MapStackResolver.unescape(name));
if (pipelineArgs != null)
ctx.pushMap(pipelineArgs);
try {
pipeline.invoke(environment, ctx);
}
finally {
environment.removeAttribute("bean-dict");
environment.removeAttribute("kont");
if (pipelineArgs != null)
ctx.popMap();
}
}
public void forwardTo(String uri, Object bizData,
WebContinuation continuation,
Environment environment, InvokeContext ctx)
throws Exception
{
if (ctx == null) {
String msg = "Cannot invoke pipeline with a null InvokeContext! Make sure"
+ " you're calling the pipeline during the execution of a request.";
throw new RuntimeException(msg);
}
environment.setAttribute("bean-dict", bizData);
environment.setAttribute("kont", continuation);
try {
PipelinesNode.getRedirector(environment).redirect(false, uri);
}
finally {
environment.removeAttribute("bean-dict");
environment.removeAttribute("kont");
}
}
}