* Remove synchronization problems in request timeout handling

* Implement skin parameter handling via res.handlers.param
* Implement onResponse() callback
* Slim down renderSkin method implementation
* Implement res.buffer to access current page output
* Implement res.stop() to stop request execution
* Do not reuse per thread scope, create a new one for each request
This commit is contained in:
hns 2007-01-29 16:00:44 +00:00
parent e6c287b35f
commit 21838fe606
10 changed files with 236 additions and 290 deletions

View file

@ -29,7 +29,7 @@ public class RedirectException extends Error {
/** /**
* Creates a new RedirectException object. * Creates a new RedirectException object.
* *
* @param url ... * @param url the URL
*/ */
public RedirectException(String url) { public RedirectException(String url) {
super("Redirection Request to " + url); super("Redirection Request to " + url);
@ -37,29 +37,11 @@ public class RedirectException extends Error {
} }
/** /**
* * Return the URL to redirect to.
* * @return the URL
* @return ...
*/ */
public String getMessage() { public String getUrl() {
return url; return url;
} }
/**
*
*
* @param s ...
*/
public void printStackTrace(java.io.PrintStream s) {
// do nothing
}
/**
*
*
* @param w ...
*/
public void printStackTrace(java.io.PrintWriter w) {
// do nothing
}
} }

View file

@ -58,6 +58,16 @@ public class ResponseBean implements Serializable {
res.encodeXml(obj); res.encodeXml(obj);
} }
/**
* Write an object to the response buffer by converting it to a string
* and then encoding it for form/text area content use.
*
* @param obj the object to write to the response buffer
*/
public void encodeForm(Object obj) {
res.encodeForm(obj);
}
/** /**
* Write an object to the response buffer by converting it to a string * Write an object to the response buffer by converting it to a string
* and then HTML-formatting it. * and then HTML-formatting it.
@ -88,6 +98,15 @@ public class ResponseBean implements Serializable {
res.forward(url); res.forward(url);
} }
/**
* Immediately stop processing the current request
*
* @throws RedirectException to immediately terminate the request
*/
public void stop() throws RedirectException {
res.redirect(null);
}
/** /**
* Reset the response object, clearing all content previously written to it * Reset the response object, clearing all content previously written to it
*/ */
@ -481,6 +500,15 @@ public class ResponseBean implements Serializable {
return res.popStringBuffer(); return res.popStringBuffer();
} }
/**
* Returns the current response buffer as string.
*
* @return the response buffer as string
*/
public String getBuffer() {
return res.getBuffer().toString();
}
/** /**
* Commit changes made during the course of the current transaction * Commit changes made during the course of the current transaction
* and start a new one * and start a new one

View file

@ -387,12 +387,24 @@ public final class RequestEvaluator implements Runnable {
ScriptingEngine.ARGS_WRAP_DEFAULT, ScriptingEngine.ARGS_WRAP_DEFAULT,
false); false);
} }
// try calling onResponse() function on object before
// calling the actual action
scriptingEngine.invoke(currentElement,
"onResponse",
EMPTY_ARGS,
ScriptingEngine.ARGS_WRAP_DEFAULT,
false);
} catch (RedirectException redirect) { } catch (RedirectException redirect) {
// if there is a message set, save it on the user object for the next request // if there is a message set, save it on the user object for the next request
session.storeResponseMessages(res); if (res.getRedirect() != null)
session.storeResponseMessages(res);
} }
// check if we're still the one and only or if the waiting thread has given up on us already // check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
commitTransaction(); commitTransaction();
done = true; done = true;
@ -438,8 +450,14 @@ public final class RequestEvaluator implements Runnable {
functionName, args, functionName, args,
ScriptingEngine.ARGS_WRAP_XMLRPC, ScriptingEngine.ARGS_WRAP_XMLRPC,
false); false);
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
commitTransaction(); commitTransaction();
} catch (Exception x) { } catch (Exception x) {
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
app.logError(txname + ": " + error, x); app.logError(txname + ": " + error, x);
@ -466,8 +484,14 @@ public final class RequestEvaluator implements Runnable {
args, args,
ScriptingEngine.ARGS_WRAP_DEFAULT, ScriptingEngine.ARGS_WRAP_DEFAULT,
true); true);
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
commitTransaction(); commitTransaction();
} catch (Exception x) { } catch (Exception x) {
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
app.logError(txname + ": " + error, x); app.logError(txname + ": " + error, x);
@ -487,6 +511,9 @@ public final class RequestEvaluator implements Runnable {
} catch (AbortException x) { } catch (AbortException x) {
// res.abort() just aborts the transaction and // res.abort() just aborts the transaction and
// leaves the response untouched // leaves the response untouched
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
done = true; done = true;
} catch (ConcurrencyException x) { } catch (ConcurrencyException x) {
@ -494,6 +521,9 @@ public final class RequestEvaluator implements Runnable {
if (++tries < 8) { if (++tries < 8) {
// try again after waiting some period // try again after waiting some period
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
try { try {
@ -508,6 +538,9 @@ public final class RequestEvaluator implements Runnable {
rtx = null; rtx = null;
} }
} else { } else {
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
if (error == null) if (error == null)
@ -519,6 +552,9 @@ public final class RequestEvaluator implements Runnable {
} }
} catch (Throwable x) { } catch (Throwable x) {
String txname = localrtx.getTransactionName(); String txname = localrtx.getTransactionName();
// check if request is still valid, or if the requesting thread has stopped waiting already
if (localrtx != rtx)
return;
abortTransaction(); abortTransaction();
// If the transactor thread has been killed by the invoker thread we don't have to // If the transactor thread has been killed by the invoker thread we don't have to
@ -581,6 +617,7 @@ public final class RequestEvaluator implements Runnable {
/** /**
* Called by the transactor thread when it has successfully fulfilled a request. * Called by the transactor thread when it has successfully fulfilled a request.
* @throws Exception transaction couldn't be committed
*/ */
synchronized void commitTransaction() throws Exception { synchronized void commitTransaction() throws Exception {
Transactor localrtx = (Transactor) Thread.currentThread(); Transactor localrtx = (Transactor) Thread.currentThread();
@ -592,11 +629,17 @@ public final class RequestEvaluator implements Runnable {
} }
} }
/**
* Called by the transactor thread when the request didn't terminate successfully.
*/
synchronized void abortTransaction() { synchronized void abortTransaction() {
Transactor localrtx = (Transactor) Thread.currentThread(); Transactor localrtx = (Transactor) Thread.currentThread();
localrtx.abort(); localrtx.abort();
} }
/**
* Initialize and start the transactor thread.
*/
private synchronized void startTransactor() { private synchronized void startTransactor() {
if (!app.isRunning()) { if (!app.isRunning()) {
throw new ApplicationStoppedException(); throw new ApplicationStoppedException();
@ -653,11 +696,10 @@ public final class RequestEvaluator implements Runnable {
* waiting thread when it times out and stops waiting, or from an outside * waiting thread when it times out and stops waiting, or from an outside
* thread. If currently active kill the request, otherwise just notify. * thread. If currently active kill the request, otherwise just notify.
*/ */
public synchronized void stopTransactor() { synchronized boolean stopTransactor() {
Transactor t = rtx; Transactor t = rtx;
rtx = null; rtx = null;
boolean stopped = false;
if (t != null && t.isActive()) { if (t != null && t.isActive()) {
// let the scripting engine know that the // let the scripting engine know that the
// current transaction is being aborted. // current transaction is being aborted.
@ -665,15 +707,17 @@ public final class RequestEvaluator implements Runnable {
scriptingEngine.abort(); scriptingEngine.abort();
} }
app.logEvent("Killing Thread " + t); app.logEvent("Request timeout for thread " + t);
reqtype = NONE; reqtype = NONE;
t.kill(); t.kill();
t.abort(); t.abort();
t.closeConnections(); t.closeConnections();
stopped = true;
} }
notifyAll(); notifyAll();
return stopped;
} }
/** /**
@ -694,9 +738,7 @@ public final class RequestEvaluator implements Runnable {
startTransactor(); startTransactor();
wait(app.requestTimeout); wait(app.requestTimeout);
if (reqtype != NONE) { if (reqtype != NONE && stopTransactor()) {
app.logEvent("Stopping Thread for Request " + app.getName() + "/" + req.getPath());
stopTransactor();
res.reset(); res.reset();
res.reportError(app.getName(), "Request timed out"); res.reportError(app.getName(), "Request timed out");
} }
@ -749,8 +791,7 @@ public final class RequestEvaluator implements Runnable {
startTransactor(); startTransactor();
wait(app.requestTimeout); wait(app.requestTimeout);
if (reqtype != NONE) { if (reqtype != NONE && stopTransactor()) {
stopTransactor();
exception = new RuntimeException("Request timed out"); exception = new RuntimeException("Request timed out");
} }
@ -784,8 +825,7 @@ public final class RequestEvaluator implements Runnable {
startTransactor(); startTransactor();
wait(); wait();
if (reqtype != NONE) { if (reqtype != NONE && stopTransactor()) {
stopTransactor();
exception = new RuntimeException("Request timed out"); exception = new RuntimeException("Request timed out");
} }
@ -853,8 +893,7 @@ public final class RequestEvaluator implements Runnable {
startTransactor(); startTransactor();
wait(timeout); wait(timeout);
if (reqtype != NONE) { if (reqtype != NONE && stopTransactor()) {
stopTransactor();
exception = new RuntimeException("Request timed out"); exception = new RuntimeException("Request timed out");
} }

View file

@ -144,23 +144,46 @@ public final class Skin {
return new String(source, 0, sourceLength); return new String(source, 0, sourceLength);
} }
/**
* Render this skin and return it as string
*/
public String renderAsString(RequestEvaluator reval, Object thisObject, Object paramObject)
throws RedirectException, UnsupportedEncodingException {
String result = "";
ResponseTrans res = reval.getResponse();
res.pushStringBuffer();
try {
render(reval, thisObject, paramObject);
} finally {
result = res.popStringBuffer();
}
return result;
}
/** /**
* Render this skin * Render this skin
*/ */
public void render(RequestEvaluator reval, Object thisObject, Map paramObject) public void render(RequestEvaluator reval, Object thisObject, Object paramObject)
throws RedirectException, UnsupportedEncodingException { throws RedirectException, UnsupportedEncodingException {
// check for endless skin recursion // check for endless skin recursion
if (++reval.skinDepth > 50) { if (++reval.skinDepth > 50) {
throw new RuntimeException("Recursive skin invocation suspected"); throw new RuntimeException("Recursive skin invocation suspected");
} }
ResponseTrans res = reval.getResponse();
if (macros == null) { if (macros == null) {
reval.getResponse().write(source, 0, sourceLength); res.write(source, 0, sourceLength);
reval.skinDepth--; reval.skinDepth--;
return; return;
} }
// register param object, remember previous one to reset afterwards
Map handlers = res.getMacroHandlers();
Object previousParam = handlers.put("param", paramObject);
try { try {
int written = 0; int written = 0;
Map handlerCache = null; Map handlerCache = null;
@ -171,18 +194,23 @@ public final class Skin {
for (int i = 0; i < macros.length; i++) { for (int i = 0; i < macros.length; i++) {
if (macros[i].start > written) { if (macros[i].start > written) {
reval.getResponse().write(source, written, macros[i].start - written); res.write(source, written, macros[i].start - written);
} }
macros[i].render(reval, thisObject, paramObject, handlerCache); macros[i].render(reval, thisObject, handlerCache);
written = macros[i].end; written = macros[i].end;
} }
if (written < sourceLength) { if (written < sourceLength) {
reval.getResponse().write(source, written, sourceLength - written); res.write(source, written, sourceLength - written);
} }
} finally { } finally {
reval.skinDepth--; reval.skinDepth--;
if (previousParam == null) {
handlers.remove("param");
} else {
handlers.put("param", previousParam);
}
} }
} }
@ -395,8 +423,7 @@ public final class Skin {
/** /**
* Render the macro given a handler object * Render the macro given a handler object
*/ */
public void render(RequestEvaluator reval, Object thisObject, Map paramObject, public void render(RequestEvaluator reval, Object thisObject, Map handlerCache)
Map handlerCache)
throws RedirectException, UnsupportedEncodingException { throws RedirectException, UnsupportedEncodingException {
// immediately return for comment macros // immediately return for comment macros
if (isCommentMacro) { if (isCommentMacro) {
@ -414,10 +441,6 @@ public final class Skin {
} else if ("request".equalsIgnoreCase(handler)) { } else if ("request".equalsIgnoreCase(handler)) {
renderFromRequest(reval); renderFromRequest(reval);
return;
} else if ("param".equalsIgnoreCase(handler)) {
renderFromParam(reval, paramObject);
return; return;
} else if ("session".equalsIgnoreCase(handler)) { } else if ("session".equalsIgnoreCase(handler)) {
renderFromSession(reval); renderFromSession(reval);

View file

@ -161,19 +161,17 @@ public class Transactor extends Thread {
/** /**
* * Returns true if a transaction is currently active.
* * @return true if currently a transaction is active
* @return ...
*/ */
public boolean isActive() { public boolean isActive() {
return active; return active;
} }
/** /**
* * Register a db connection with this transactor thread.
* * @param src the db source
* @param src ... * @param con the connection
* @param con ...
*/ */
public void registerConnection(DbSource src, Connection con) { public void registerConnection(DbSource src, Connection con) {
sqlConnections.put(src, con); sqlConnections.put(src, con);
@ -182,11 +180,9 @@ public class Transactor extends Thread {
} }
/** /**
* * Get a db connection that was previously registered with this transactor thread.
* * @param src the db source
* @param src ... * @return the connection
*
* @return ...
*/ */
public Connection getConnection(DbSource src) { public Connection getConnection(DbSource src) {
Connection con = (Connection) sqlConnections.get(src); Connection con = (Connection) sqlConnections.get(src);
@ -218,9 +214,7 @@ public class Transactor extends Thread {
public synchronized void begin(String name) throws Exception { public synchronized void begin(String name) throws Exception {
if (killed) { if (killed) {
throw new DatabaseException("Transaction started on killed thread"); throw new DatabaseException("Transaction started on killed thread");
} } else if (active) {
if (active) {
abort(); abort();
} }
@ -241,10 +235,10 @@ public class Transactor extends Thread {
*/ */
public synchronized void commit() throws Exception { public synchronized void commit() throws Exception {
if (killed) { if (killed) {
abort(); throw new DatabaseException("commit() called on killed transactor thread");
} else if (!active) {
return; return;
} }
int inserted = 0; int inserted = 0;
int updated = 0; int updated = 0;
int deleted = 0; int deleted = 0;
@ -396,7 +390,6 @@ public class Transactor extends Thread {
// clear the node collections // clear the node collections
recycle(); recycle();
// close any JDBC connections associated with this transactor thread // close any JDBC connections associated with this transactor thread
closeConnections(); closeConnections();
@ -420,34 +413,39 @@ public class Transactor extends Thread {
* Kill this transaction thread. Used as last measure only. * Kill this transaction thread. Used as last measure only.
*/ */
public synchronized void kill() { public synchronized void kill() {
killed = true; killed = true;
interrupt();
// The thread is told to stop by setting the thread flag in the EcmaScript
// evaluator, so we can hope that it stops without doing anything else.
try {
join(500);
} catch (InterruptedException ir) {
// interrupted by other thread
}
// Interrupt the thread if it has not noticed the flag (e.g. because it is busy // Interrupt the thread if it has not noticed the flag (e.g. because it is busy
// reading from a network socket). // reading from a network socket).
if (isAlive()) { if (isAlive()) {
interrupt(); interrupt();
try {
join(1000);
} catch (InterruptedException ir) {
// interrupted by other thread
}
}
try { if (isAlive() && "true".equals(nmgr.app.getProperty("requestTimeoutStop", "true"))) {
join(1000); // still running - check if we ought to stop() it
} catch (InterruptedException ir) { try {
// interrupted by other thread Thread.sleep(2000);
} if (isAlive()) {
// thread is still running, pull emergency break
nmgr.app.logEvent("Stopping Thread for Transactor " + this);
stop();
}
} catch (InterruptedException ir) {
// interrupted by other thread
}
} }
} }
/** /**
* * Closes all open JDBC connections
*/ */
public void closeConnections() { public void closeConnections() {
// nmgr.app.logEvent("Cleaning up Transactor thread");
if (sqlConnections != null) { if (sqlConnections != null) {
for (Iterator i = sqlConnections.values().iterator(); i.hasNext();) { for (Iterator i = sqlConnections.values().iterator(); i.hasNext();) {
try { try {

View file

@ -39,7 +39,7 @@ import java.io.*;
public class GlobalObject extends ImporterTopLevel implements PropertyRecorder { public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
Application app; Application app;
RhinoCore core; RhinoCore core;
GlobalObject sharedGlobal = null; boolean isThreadScope = false;
// fields to implement PropertyRecorder // fields to implement PropertyRecorder
private volatile boolean isRecording = false; private volatile boolean isRecording = false;
@ -51,12 +51,12 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
* @param core ... * @param core ...
* @param app ... * @param app ...
*/ */
public GlobalObject(RhinoCore core, Application app, boolean perThread) { public GlobalObject(RhinoCore core, Application app, boolean isThreadScope) {
this.core = core; this.core = core;
this.app = app; this.app = app;
if (perThread) { this.isThreadScope = isThreadScope;
sharedGlobal = core.global; if (isThreadScope) {
setPrototype(sharedGlobal); setPrototype(core.global);
setParentScope(null); setParentScope(null);
} }
} }
@ -64,10 +64,8 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
/** /**
* Initializes the global object. This is only done for the shared * Initializes the global object. This is only done for the shared
* global objects not the per-thread ones. * global objects not the per-thread ones.
*
* @throws PropertyException ...
*/ */
public void init() throws PropertyException { public void init() {
String[] globalFuncs = { String[] globalFuncs = {
"renderSkin", "renderSkinAsString", "getProperty", "renderSkin", "renderSkinAsString", "getProperty",
"authenticate", "createSkin", "format", "encode", "authenticate", "createSkin", "format", "encode",
@ -127,18 +125,10 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
if (isRecording) { if (isRecording) {
changedProperties.add(name); changedProperties.add(name);
} }
// check if this is a per-thread scope instance // expose thread scope as global variable "global"
if (sharedGlobal != null) { if (isThreadScope && "global".equals(name)) {
// make thread scope accessible as "global" return this;
if ("global".equals(name)) {
return this;
}
// use synchronized get on fast changing per-thread scopes just to be sure
synchronized(this) {
return super.get(name, start);
}
} }
// we're the shared scope
return super.get(name, start); return super.get(name, start);
} }
@ -153,22 +143,11 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
public boolean renderSkin(Object skinobj, Object paramobj) public boolean renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, "global");
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin("global", skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
if (skin != null) { if (skin != null) {
skin.render(engine.reval, null, param); skin.render(engine.reval, null,
(paramobj == Undefined.instance) ? null : paramobj);
} }
return true; return true;
@ -185,25 +164,11 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
public String renderSkinAsString(Object skinobj, Object paramobj) public String renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, "global");
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin("global", skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
if (skin != null) { if (skin != null) {
ResponseTrans res = engine.getResponse(); return skin.renderAsString(engine.reval, null,
res.pushStringBuffer(); (paramobj == Undefined.instance) ? null : paramobj);
skin.render(engine.reval, null, param);
return res.popStringBuffer();
} }
return ""; return "";
@ -671,7 +636,9 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
} }
String str = (String) args[i]; String str = (String) args[i];
if (obj.has(str, obj)) { if (obj.has(str, obj)) {
obj.setAttributes(str, obj.getAttributes(str) | DONTENUM); int attr = obj.getAttributes(str);
if ((attr & PERMANENT) == 0)
obj.setAttributes(str, attr | DONTENUM);
} }
} }
return null; return null;
@ -719,6 +686,6 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder {
} }
public String toString() { public String toString() {
return sharedGlobal == null ? "[Shared Scope]" : "[Thread Scope]"; return isThreadScope ? "[Thread Scope]" : "[Shared Scope]";
} }
} }

View file

@ -197,24 +197,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
public boolean jsFunction_renderSkin(Object skinobj, Object paramobj) public boolean jsFunction_renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, className);
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin(className, skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
checkNode(); checkNode();
if (skin != null) { if (skin != null) {
skin.render(engine.reval, node, param); skin.render(engine.reval, node,
(paramobj == Undefined.instance) ? null : paramobj);
} }
return true; return true;
@ -278,27 +267,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj) public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, className);
Object param = paramobj == Undefined.instance ? null : paramobj;
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin(className, skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
checkNode(); checkNode();
if (skin != null) { if (skin != null) {
ResponseTrans res = engine.getResponse(); return skin.renderAsString(engine.reval, node,
res.pushStringBuffer(); (paramobj == Undefined.instance) ? null : paramobj);
skin.render(engine.reval, node, param);
return res.popStringBuffer();
} }
return ""; return "";

View file

@ -78,22 +78,11 @@ public class JavaObject extends NativeJavaObject {
public boolean renderSkin(Object skinobj, Object paramobj) public boolean renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, protoName);
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin(protoName, skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
if (skin != null) { if (skin != null) {
skin.render(engine.reval, javaObject, param); skin.render(engine.reval, javaObject,
(paramobj == Undefined.instance) ? null : paramobj);
} }
return true; return true;
@ -110,25 +99,11 @@ public class JavaObject extends NativeJavaObject {
public String renderSkinAsString(Object skinobj, Object paramobj) public String renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
RhinoEngine engine = RhinoEngine.getRhinoEngine(); RhinoEngine engine = RhinoEngine.getRhinoEngine();
Skin skin; Skin skin = engine.toSkin(skinobj, protoName);
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
skin = (Skin) skinobj;
} else {
skin = engine.getSkin(protoName, skinobj.toString());
}
Map param = RhinoCore.getSkinParam(paramobj);
if (skin != null) { if (skin != null) {
ResponseTrans res = engine.getResponse(); return skin.renderAsString(engine.reval, javaObject,
res.pushStringBuffer(); (paramobj == Undefined.instance) ? null : paramobj);
skin.render(engine.reval, javaObject, param);
return res.popStringBuffer();
} }
return ""; return "";

View file

@ -635,7 +635,7 @@ public final class RhinoCore implements ScopeProvider {
return hobj; return hobj;
} }
protected String postProcessHref(Object obj, String protoName, String basicHref) protected String postProcessHref(Object obj, String protoName, String href)
throws UnsupportedEncodingException, IOException { throws UnsupportedEncodingException, IOException {
// check if the app.properties specify a href-function to post-process the // check if the app.properties specify a href-function to post-process the
// basic href. // basic href.
@ -655,7 +655,7 @@ public final class RhinoCore implements ScopeProvider {
try { try {
result = eng.invoke(handler, hrefFunction, result = eng.invoke(handler, hrefFunction,
new Object[] { basicHref }, new Object[] {href},
ScriptingEngine.ARGS_WRAP_DEFAULT, ScriptingEngine.ARGS_WRAP_DEFAULT,
false); false);
} catch (ScriptingException x) { } catch (ScriptingException x) {
@ -667,7 +667,7 @@ public final class RhinoCore implements ScopeProvider {
" returned null"); " returned null");
} }
basicHref = result.toString(); href = result.toString();
break; break;
} }
handler = app.getParentElement(handler); handler = app.getParentElement(handler);
@ -696,23 +696,17 @@ public final class RhinoCore implements ScopeProvider {
} }
if (skin != null) { if (skin != null) {
Scriptable param = Context.getCurrentContext().newObject(global);
param.put("path", param, href);
href = skin.renderAsString(eng.getRequestEvaluator(), handler, param);
break; break;
} }
handler = app.getParentElement(handler); handler = app.getParentElement(handler);
} }
if (skin != null) {
eng.getResponse().pushStringBuffer();
HashMap param = new HashMap();
param.put("path", basicHref);
skin.render(eng.getRequestEvaluator(), handler, param);
basicHref = eng.getResponse().popStringBuffer().trim();
}
} }
return basicHref; return href;
} }
/** /**
@ -741,30 +735,6 @@ public final class RhinoCore implements ScopeProvider {
return skinpath; return skinpath;
} }
protected static Map getSkinParam(Object paramobj) {
Map param = null;
if ((paramobj != null) && (paramobj != Undefined.instance)) {
param = new HashMap();
if (paramobj instanceof Scriptable) {
Scriptable sp = (Scriptable) paramobj;
Object[] ids = sp.getIds();
for (int i = 0; i < ids.length; i++) {
Object obj = sp.get(ids[i].toString(), sp);
if (obj instanceof Wrapper) {
param.put(ids[i], ((Wrapper) obj).unwrap());
} else if (obj != Undefined.instance) {
param.put(ids[i], obj);
}
}
}
}
return param;
}
/** /**
* Add a code resource to a given prototype by immediately compiling and evaluating it. * Add a code resource to a given prototype by immediately compiling and evaluating it.
* *

View file

@ -58,15 +58,11 @@ public class RhinoEngine implements ScriptingEngine {
// the rhino core // the rhino core
RhinoCore core; RhinoCore core;
// remember global variables from last invokation to be able to
// do lazy cleanup
Map lastGlobals = null;
// the global vars set by extensions // the global vars set by extensions
HashMap extensionGlobals; HashMap extensionGlobals;
// the thread currently running this engine // the thread currently running this engine
Thread thread; volatile Thread thread;
// thread local engine registry // thread local engine registry
static ThreadLocal engines = new ThreadLocal(); static ThreadLocal engines = new ThreadLocal();
@ -92,8 +88,6 @@ public class RhinoEngine implements ScriptingEngine {
context = core.contextFactory.enter(); context = core.contextFactory.enter();
try { try {
global = new GlobalObject(core, app, true);
extensionGlobals = new HashMap(); extensionGlobals = new HashMap();
if (Server.getServer() != null) { if (Server.getServer() != null) {
@ -153,13 +147,13 @@ public class RhinoEngine implements ScriptingEngine {
* This method is called before an execution context is entered to let the * This method is called before an execution context is entered to let the
* engine know it should update its prototype information. * engine know it should update its prototype information.
*/ */
public void updatePrototypes() throws IOException { public synchronized void updatePrototypes() throws IOException {
// remember the current thread as our thread - we do this here so // remember the current thread as our thread - we do this here so
// the thread is already set when the RequestEvaluator calls // the thread is already set when the RequestEvaluator calls
// Application.getDataRoot(), which may result in a function invocation // Application.getDataRoot(), which may result in a function invocation
// (chicken and egg problem, kind of) // (chicken and egg problem, kind of)
thread = Thread.currentThread(); thread = Thread.currentThread();
global = new GlobalObject(core, app, true);
context = core.contextFactory.enter(); context = core.contextFactory.enter();
if (core.hasTracer) { if (core.hasTracer) {
@ -182,30 +176,26 @@ public class RhinoEngine implements ScriptingEngine {
thread = Thread.currentThread(); thread = Thread.currentThread();
// set globals on the global object // set globals on the global object
if (globals != lastGlobals) { // add globals from extensions
// add globals from extensions globals.putAll(extensionGlobals);
globals.putAll(extensionGlobals); // loop through global vars and set them
// loop through global vars and set them for (Iterator i = globals.keySet().iterator(); i.hasNext();) {
for (Iterator i = globals.keySet().iterator(); i.hasNext();) { String k = (String) i.next();
String k = (String) i.next(); Object v = globals.get(k);
Object v = globals.get(k); Scriptable scriptable;
Scriptable scriptable;
// create a special wrapper for the path object. // create a special wrapper for the path object.
// other objects are wrapped in the default way. // other objects are wrapped in the default way.
if (v == null) { if (v == null) {
continue; continue;
} else if (v instanceof RequestPath) { } else if (v instanceof RequestPath) {
scriptable = new PathWrapper((RequestPath) v, core); scriptable = new PathWrapper((RequestPath) v, core);
scriptable.setPrototype(core.pathProto); scriptable.setPrototype(core.pathProto);
} else { } else {
scriptable = Context.toObject(v, global); scriptable = Context.toObject(v, global);
}
global.put(k, global, scriptable);
} }
// remember the globals set on this evaluator
lastGlobals = globals; global.put(k, global, scriptable);
} }
} }
@ -218,19 +208,7 @@ public class RhinoEngine implements ScriptingEngine {
engines.set(null); engines.set(null);
core.contextFactory.exit(); core.contextFactory.exit();
thread = null; thread = null;
global = null;
// loop through previous globals and unset them, if necessary.
if (lastGlobals != null) {
for (Iterator i = lastGlobals.keySet().iterator(); i.hasNext();) {
String g = (String) i.next();
try {
global.delete(g);
} catch (Exception x) {
app.logEvent("Error unsetting global property: " + g);
}
}
lastGlobals = null;
}
} }
/** /**
@ -342,21 +320,17 @@ public class RhinoEngine implements ScriptingEngine {
* Let the evaluator know that the current evaluation has been * Let the evaluator know that the current evaluation has been
* aborted. * aborted.
*/ */
public synchronized void abort() { public void abort() {
// current request has been aborted. // current request has been aborted.
Thread t = thread; Thread t = thread;
// set thread to null
thread = null;
if (t != null && t.isAlive()) { if (t != null && t.isAlive()) {
t.interrupt(); t.interrupt();
if ("true".equals(app.getProperty("requestTimeoutStop", "true"))) { try {
try { t.join(1000);
Thread.sleep(5000); } catch (InterruptedException ir) {
if (t.isAlive()) { // interrupted by other thread
// thread is still running, gotta stop it.
t.stop();
}
} catch (InterruptedException i) {
// interrupted, ignore
}
} }
} }
} }
@ -552,6 +526,21 @@ public class RhinoEngine implements ScriptingEngine {
return core; return core;
} }
/**
* Try to get a skin from the parameter object.
*/
public Skin toSkin(Object skinobj, String protoName) throws IOException {
if (skinobj instanceof Wrapper) {
skinobj = ((Wrapper) skinobj).unwrap();
}
if (skinobj instanceof Skin) {
return (Skin) skinobj;
} else {
return getSkin(protoName, skinobj.toString());
}
}
/** /**
* Get a skin for the given prototype and skin name. This method considers the * Get a skin for the given prototype and skin name. This method considers the
* skinpath set in the current response object and does per-response skin * skinpath set in the current response object and does per-response skin