0001: /*
0002: * Helma License Notice
0003: *
0004: * The contents of this file are subject to the Helma License
0005: * Version 2.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://adele.helma.org/download/helma/license.txt
0008: *
0009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
0010: *
0011: * $RCSfile$
0012: * $Author: hannes $
0013: * $Revision: 8700 $
0014: * $Date: 2007-12-11 11:03:48 +0100 (Die, 11 Dez 2007) $
0015: */
0016:
0017: package helma.framework.core;
0018:
0019: import helma.framework.*;
0020: import helma.objectmodel.*;
0021: import helma.objectmodel.db.*;
0022: import helma.scripting.*;
0023: import java.lang.reflect.*;
0024: import java.util.*;
0025:
0026: import org.apache.xmlrpc.XmlRpcRequestProcessor;
0027: import org.apache.xmlrpc.XmlRpcServerRequest;
0028:
0029: /**
0030: * This class does the work for incoming requests. It holds a transactor thread
0031: * and an EcmaScript evaluator to get the work done. Incoming threads are
0032: * blocked until the request has been serviced by the evaluator, or the timeout
0033: * specified by the application has passed. In the latter case, the evaluator thread
0034: * is killed and an error message is returned.
0035: */
0036: public final class RequestEvaluator implements Runnable {
0037: static final int NONE = 0; // no request
0038: static final int HTTP = 1; // via HTTP gateway
0039: static final int XMLRPC = 2; // via XML-RPC
0040: static final int INTERNAL = 3; // generic function call, e.g. by scheduler
0041: static final int EXTERNAL = 4; // function from script etc
0042:
0043: public static final Object[] EMPTY_ARGS = new Object[0];
0044:
0045: public final Application app;
0046:
0047: protected ScriptingEngine scriptingEngine;
0048:
0049: // skin depth counter, used to avoid recursive skin rendering
0050: protected int skinDepth;
0051:
0052: private volatile RequestTrans req;
0053: private volatile ResponseTrans res;
0054:
0055: // the one and only transactor thread
0056: private volatile Transactor rtx;
0057:
0058: // the type of request to be serviced,
0059: // used to coordinate worker and waiter threads
0060: private volatile int reqtype;
0061:
0062: // the object on which to invoke a function, if specified
0063: private volatile Object this Object;
0064:
0065: // the method to be executed
0066: private volatile Object function;
0067:
0068: // the session object associated with the current request
0069: private volatile Session session;
0070:
0071: // arguments passed to the function
0072: private volatile Object[] args;
0073:
0074: // the result of the operation
0075: private volatile Object result;
0076:
0077: // the exception thrown by the evaluator, if any.
0078: private volatile Exception exception;
0079:
0080: /**
0081: * Create a new RequestEvaluator for this application.
0082: * @param app the application
0083: */
0084: public RequestEvaluator(Application app) {
0085: this .app = app;
0086: }
0087:
0088: protected synchronized void initScriptingEngine() {
0089: if (scriptingEngine == null) {
0090: String engineClassName = app.getProperty("scriptingEngine",
0091: "helma.scripting.rhino.RhinoEngine");
0092: try {
0093: app.setCurrentRequestEvaluator(this );
0094: Class clazz = app.getClassLoader().loadClass(
0095: engineClassName);
0096:
0097: scriptingEngine = (ScriptingEngine) clazz.newInstance();
0098: scriptingEngine.init(app, this );
0099: } catch (Exception x) {
0100: Throwable t = x;
0101:
0102: if (x instanceof InvocationTargetException) {
0103: t = ((InvocationTargetException) x)
0104: .getTargetException();
0105: }
0106:
0107: app
0108: .logEvent("******************************************");
0109: app.logEvent("*** Error creating scripting engine: ");
0110: app.logEvent("*** " + t.toString());
0111: app
0112: .logEvent("******************************************");
0113: app.logError("Error creating scripting engine", t);
0114:
0115: // null out invalid scriptingEngine
0116: scriptingEngine = null;
0117: // rethrow exception
0118: if (t instanceof RuntimeException) {
0119: throw ((RuntimeException) t);
0120: } else {
0121: throw new RuntimeException(t.toString(), t);
0122: }
0123: } finally {
0124: app.setCurrentRequestEvaluator(null);
0125: }
0126: }
0127: }
0128:
0129: /**
0130: *
0131: */
0132: public void run() {
0133: // first, set a local variable to the current transactor thread so we know
0134: // when it's time to quit because another thread took over.
0135: Transactor localrtx = (Transactor) Thread.currentThread();
0136:
0137: // spans whole execution loop - close connections in finally clause
0138: try {
0139:
0140: // while this thread is serving requests
0141: while (localrtx == rtx) {
0142:
0143: // object reference to ressolve request path
0144: Object currentElement;
0145:
0146: // Get req and res into local variables to avoid memory caching problems
0147: // in unsynchronized method.
0148: RequestTrans req = getRequest();
0149: ResponseTrans res = getResponse();
0150:
0151: // request path object
0152: RequestPath requestPath = new RequestPath(app);
0153:
0154: int tries = 0;
0155: boolean done = false;
0156: String error = null;
0157: String functionName = function instanceof String ? (String) function
0158: : null;
0159:
0160: while (!done && localrtx == rtx) {
0161: // catch errors in path resolution and script execution
0162: try {
0163:
0164: // initialize scripting engine
0165: initScriptingEngine();
0166: app.setCurrentRequestEvaluator(this );
0167: // update scripting prototypes
0168: scriptingEngine.updatePrototypes();
0169:
0170: // avoid going into transaction if called function doesn't exist.
0171: // this only works for the (common) case that method is a plain
0172: // method name, not an obj.method path
0173: if (reqtype == INTERNAL) {
0174: // if object is an instance of NodeHandle, get the node object itself.
0175: if (this Object instanceof NodeHandle) {
0176: this Object = ((NodeHandle) this Object)
0177: .getNode(app.nmgr.safe);
0178: // If no valid node object return immediately
0179: if (this Object == null) {
0180: done = true;
0181: reqtype = NONE;
0182: break;
0183: }
0184: }
0185: // If function doesn't exist, return immediately
0186: if (functionName != null
0187: && !scriptingEngine.hasFunction(
0188: this Object, functionName,
0189: true)) {
0190: app.logEvent(missingFunctionMessage(
0191: this Object, functionName));
0192: done = true;
0193: reqtype = NONE;
0194: break;
0195: }
0196: } else if (function != null
0197: && functionName == null) {
0198: // only internal requests may pass a function instead of a function name
0199: throw new IllegalStateException(
0200: "No function name in non-internal request ");
0201: }
0202:
0203: // Transaction name is used for logging etc.
0204: StringBuffer txname = new StringBuffer(app
0205: .getName());
0206: txname.append(":").append(
0207: req.getMethod().toLowerCase()).append(
0208: ":");
0209: txname.append((error == null) ? req.getPath()
0210: : "error");
0211:
0212: // begin transaction
0213: localrtx.begin(txname.toString());
0214:
0215: Object root = app.getDataRoot();
0216: initGlobals(root, requestPath);
0217:
0218: String action = null;
0219:
0220: if (error != null) {
0221: res.setError(error);
0222: }
0223:
0224: switch (reqtype) {
0225: case HTTP:
0226:
0227: // bring over the message from a redirect
0228: session.recoverResponseMessages(res);
0229:
0230: // catch redirect in path resolution or script execution
0231: try {
0232: // catch object not found in path resolution
0233: try {
0234: if (error != null) {
0235: // there was an error in the previous loop, call error handler
0236: currentElement = root;
0237: res.setStatus(500);
0238:
0239: // do not reset the requestPath so error handler can use the original one
0240: // get error handler action
0241: String errorAction = app.props
0242: .getProperty("error",
0243: "error");
0244:
0245: action = getAction(
0246: currentElement,
0247: errorAction, req);
0248:
0249: if (action == null) {
0250: throw new RuntimeException(
0251: error);
0252: }
0253: } else if ((req.getPath() == null)
0254: || "".equals(req.getPath()
0255: .trim())) {
0256: currentElement = root;
0257: requestPath.add(null,
0258: currentElement);
0259:
0260: action = getAction(
0261: currentElement, null,
0262: req);
0263:
0264: if (action == null) {
0265: throw new NotFoundException(
0266: "Action not found");
0267: }
0268: } else {
0269: // march down request path...
0270: StringTokenizer st = new StringTokenizer(
0271: req.getPath(), "/");
0272: int ntokens = st.countTokens();
0273:
0274: // limit path to < 50 tokens
0275: if (ntokens > 50) {
0276: throw new RuntimeException(
0277: "Path too long");
0278: }
0279:
0280: String[] pathItems = new String[ntokens];
0281:
0282: for (int i = 0; i < ntokens; i++)
0283: pathItems[i] = st
0284: .nextToken();
0285:
0286: currentElement = root;
0287: requestPath.add(null,
0288: currentElement);
0289:
0290: for (int i = 0; i < ntokens; i++) {
0291: if (currentElement == null) {
0292: throw new NotFoundException(
0293: "Object not found.");
0294: }
0295:
0296: if (pathItems[i].length() == 0) {
0297: continue;
0298: }
0299:
0300: // if we're at the last element of the path,
0301: // try to interpret it as action name.
0302: if (i == (ntokens - 1)
0303: && !req
0304: .getPath()
0305: .endsWith(
0306: "/")) {
0307: action = getAction(
0308: currentElement,
0309: pathItems[i],
0310: req);
0311: }
0312:
0313: if (action == null) {
0314: currentElement = getChildElement(
0315: currentElement,
0316: pathItems[i]);
0317:
0318: // add object to request path if suitable
0319: if (currentElement != null) {
0320: // add to requestPath array
0321: requestPath
0322: .add(
0323: pathItems[i],
0324: currentElement);
0325: }
0326: }
0327: }
0328:
0329: if (currentElement == null) {
0330: throw new NotFoundException(
0331: "Object not found.");
0332: }
0333:
0334: if (action == null) {
0335: action = getAction(
0336: currentElement,
0337: null, req);
0338: }
0339:
0340: if (action == null) {
0341: throw new NotFoundException(
0342: "Action not found");
0343: }
0344: }
0345: } catch (NotFoundException notfound) {
0346: if (error != null) {
0347:
0348: // we already have an error and the error template wasn't found,
0349: // display it instead of notfound message
0350: throw new RuntimeException();
0351: }
0352:
0353: // The path could not be resolved. Check if there is a "not found" action
0354: // specified in the property file.
0355: res.setStatus(404);
0356:
0357: String notFoundAction = app.props
0358: .getProperty("notfound",
0359: "notfound");
0360:
0361: currentElement = root;
0362: action = getAction(currentElement,
0363: notFoundAction, req);
0364:
0365: if (action == null) {
0366: throw new NotFoundException(
0367: notfound.getMessage());
0368: }
0369: }
0370:
0371: // register path objects with their prototype names in
0372: // res.handlers
0373: Map macroHandlers = res
0374: .getMacroHandlers();
0375: int l = requestPath.size();
0376: Prototype[] protos = new Prototype[l];
0377:
0378: for (int i = 0; i < l; i++) {
0379:
0380: Object obj = requestPath.get(i);
0381:
0382: protos[i] = app.getPrototype(obj);
0383:
0384: // immediately register objects with their direct prototype name
0385: if (protos[i] != null) {
0386: macroHandlers.put(protos[i]
0387: .getName(), obj);
0388: macroHandlers.put(protos[i]
0389: .getLowerCaseName(),
0390: obj);
0391: }
0392: }
0393:
0394: // in a second pass, we register path objects with their indirect
0395: // (i.e. parent prototype) names, starting at the end and only
0396: // if the name isn't occupied yet.
0397: for (int i = l - 1; i >= 0; i--) {
0398: if (protos[i] != null) {
0399: protos[i].registerParents(
0400: macroHandlers,
0401: requestPath.get(i));
0402: }
0403: }
0404:
0405: /////////////////////////////////////////////////////////////////////////////
0406: // end of path resolution section
0407: /////////////////////////////////////////////////////////////////////////////
0408: // beginning of execution section
0409:
0410: // set the req.action property, cutting off the _action suffix
0411: req.setAction(action);
0412:
0413: // reset skin recursion detection counter
0414: skinDepth = 0;
0415:
0416: // try calling onRequest() function on object before
0417: // calling the actual action
0418: scriptingEngine
0419: .invoke(
0420: currentElement,
0421: "onRequest",
0422: EMPTY_ARGS,
0423: ScriptingEngine.ARGS_WRAP_DEFAULT,
0424: false);
0425:
0426: // reset skin recursion detection counter
0427: skinDepth = 0;
0428:
0429: // do the actual action invocation
0430: if (req.isXmlRpc()) {
0431: XmlRpcRequestProcessor xreqproc = new XmlRpcRequestProcessor();
0432: XmlRpcServerRequest xreq = xreqproc
0433: .decodeRequest(req
0434: .getServletRequest()
0435: .getInputStream());
0436: Vector args = xreq.getParameters();
0437: args.add(0, xreq.getMethodName());
0438: result = scriptingEngine
0439: .invoke(
0440: currentElement,
0441: action,
0442: args.toArray(),
0443: ScriptingEngine.ARGS_WRAP_XMLRPC,
0444: false);
0445: res.writeXmlRpcResponse(result);
0446: } else {
0447: scriptingEngine
0448: .invoke(
0449: currentElement,
0450: action,
0451: EMPTY_ARGS,
0452: ScriptingEngine.ARGS_WRAP_DEFAULT,
0453: false);
0454: }
0455:
0456: // try calling onResponse() function on object before
0457: // calling the actual action
0458: scriptingEngine
0459: .invoke(
0460: currentElement,
0461: "onResponse",
0462: EMPTY_ARGS,
0463: ScriptingEngine.ARGS_WRAP_DEFAULT,
0464: false);
0465:
0466: } catch (RedirectException redirect) {
0467: // if there is a message set, save it on the user object for the next request
0468: if (res.getRedirect() != null)
0469: session.storeResponseMessages(res);
0470: }
0471:
0472: // check if request is still valid, or if the requesting thread has stopped waiting already
0473: if (localrtx != rtx) {
0474: return;
0475: }
0476: commitTransaction();
0477: done = true;
0478:
0479: break;
0480:
0481: case XMLRPC:
0482: case EXTERNAL:
0483:
0484: try {
0485: currentElement = root;
0486:
0487: if (functionName.indexOf('.') > -1) {
0488: StringTokenizer st = new StringTokenizer(
0489: functionName, ".");
0490: int cnt = st.countTokens();
0491:
0492: for (int i = 1; i < cnt; i++) {
0493: String next = st.nextToken();
0494: currentElement = getChildElement(
0495: currentElement, next);
0496: }
0497:
0498: if (currentElement == null) {
0499: throw new NotFoundException(
0500: "Method name \""
0501: + function
0502: + "\" could not be resolved.");
0503: }
0504:
0505: functionName = st.nextToken();
0506: }
0507:
0508: if (reqtype == XMLRPC) {
0509: // check XML-RPC access permissions
0510: String proto = app
0511: .getPrototypeName(currentElement);
0512: app.checkXmlRpcAccess(proto,
0513: functionName);
0514: }
0515:
0516: // reset skin recursion detection counter
0517: skinDepth = 0;
0518: if (!scriptingEngine.hasFunction(
0519: currentElement, functionName,
0520: false)) {
0521: throw new NotFoundException(
0522: missingFunctionMessage(
0523: currentElement,
0524: functionName));
0525: }
0526: result = scriptingEngine
0527: .invoke(
0528: currentElement,
0529: functionName,
0530: args,
0531: ScriptingEngine.ARGS_WRAP_XMLRPC,
0532: false);
0533: // check if request is still valid, or if the requesting thread has stopped waiting already
0534: if (localrtx != rtx) {
0535: return;
0536: }
0537: commitTransaction();
0538: } catch (Exception x) {
0539: // check if request is still valid, or if the requesting thread has stopped waiting already
0540: if (localrtx != rtx) {
0541: return;
0542: }
0543: abortTransaction();
0544: app.logError(txname + ": " + error, x);
0545:
0546: // If the transactor thread has been killed by the invoker thread we don't have to
0547: // bother for the error message, just quit.
0548: if (localrtx != rtx) {
0549: return;
0550: }
0551:
0552: this .exception = x;
0553: }
0554:
0555: done = true;
0556: break;
0557:
0558: case INTERNAL:
0559:
0560: try {
0561: // reset skin recursion detection counter
0562: skinDepth = 0;
0563:
0564: result = scriptingEngine
0565: .invoke(
0566: this Object,
0567: function,
0568: args,
0569: ScriptingEngine.ARGS_WRAP_DEFAULT,
0570: true);
0571: // check if request is still valid, or if the requesting thread has stopped waiting already
0572: if (localrtx != rtx) {
0573: return;
0574: }
0575: commitTransaction();
0576: } catch (Exception x) {
0577: // check if request is still valid, or if the requesting thread has stopped waiting already
0578: if (localrtx != rtx) {
0579: return;
0580: }
0581: abortTransaction();
0582: app.logError(txname + ": " + error, x);
0583:
0584: // If the transactor thread has been killed by the invoker thread we don't have to
0585: // bother for the error message, just quit.
0586: if (localrtx != rtx) {
0587: return;
0588: }
0589:
0590: this .exception = x;
0591: }
0592:
0593: done = true;
0594: break;
0595:
0596: } // switch (reqtype)
0597: } catch (AbortException x) {
0598: // res.abort() just aborts the transaction and
0599: // leaves the response untouched
0600: // check if request is still valid, or if the requesting thread has stopped waiting already
0601: if (localrtx != rtx) {
0602: return;
0603: }
0604: abortTransaction();
0605: done = true;
0606: } catch (ConcurrencyException x) {
0607: res.reset();
0608:
0609: if (++tries < 8) {
0610: // try again after waiting some period
0611: // check if request is still valid, or if the requesting thread has stopped waiting already
0612: if (localrtx != rtx) {
0613: return;
0614: }
0615: abortTransaction();
0616:
0617: try {
0618: // wait a bit longer with each try
0619: int base = 800 * tries;
0620: Thread.sleep((long) (base + (Math
0621: .random()
0622: * base * 2)));
0623: } catch (InterruptedException interrupt) {
0624: // we got interrrupted, create minimal error message
0625: res.reportError(app.getName(), error);
0626: done = true;
0627: // and release resources and thread
0628: rtx = null;
0629: }
0630: } else {
0631: // check if request is still valid, or if the requesting thread has stopped waiting already
0632: if (localrtx != rtx) {
0633: return;
0634: }
0635: abortTransaction();
0636:
0637: if (error == null)
0638: error = "Application too busy, please try again later";
0639:
0640: // error in error action. use traditional minimal error message
0641: res.reportError(app.getName(), error);
0642: done = true;
0643: }
0644: } catch (Throwable x) {
0645: String txname = localrtx.getTransactionName();
0646: // check if request is still valid, or if the requesting thread has stopped waiting already
0647: if (localrtx != rtx) {
0648: return;
0649: }
0650: abortTransaction();
0651:
0652: // If the transactor thread has been killed by the invoker thread we don't have to
0653: // bother for the error message, just quit.
0654: if (localrtx != rtx) {
0655: return;
0656: }
0657:
0658: res.reset();
0659:
0660: // check if we tried to process the error already,
0661: // or if this is an XML-RPC request
0662: if (error == null) {
0663: if (!(x instanceof NotFoundException)) {
0664: app.errorCount += 1;
0665: }
0666:
0667: // set done to false so that the error will be processed
0668: done = false;
0669: error = x.getMessage();
0670:
0671: if ((error == null)
0672: || (error.length() == 0)) {
0673: error = x.toString();
0674: }
0675:
0676: if (error == null) {
0677: error = "Unspecified error";
0678: }
0679:
0680: app.logError(txname + ": " + error, x);
0681:
0682: if (req.isXmlRpc()) {
0683: // if it's an XML-RPC exception immediately generate error response
0684: if (!(x instanceof Exception)) {
0685: // we need an exception to pass to XML-RPC responder
0686: x = new Exception(x.toString(), x);
0687: }
0688: res.writeXmlRpcError((Exception) x);
0689: done = true;
0690: }
0691: } else {
0692: // error in error action. use traditional minimal error message
0693: res.reportError(app.getName(), error);
0694: done = true;
0695: }
0696: } finally {
0697: app.setCurrentRequestEvaluator(null);
0698: }
0699: }
0700:
0701: // exit execution context
0702: if (scriptingEngine != null)
0703: scriptingEngine.exitContext();
0704:
0705: notifyAndWait();
0706:
0707: }
0708: } finally {
0709: localrtx.closeConnections();
0710: }
0711: }
0712:
0713: /**
0714: * Called by the transactor thread when it has successfully fulfilled a request.
0715: * @throws Exception transaction couldn't be committed
0716: */
0717: synchronized void commitTransaction() throws Exception {
0718: Transactor localrtx = (Transactor) Thread.currentThread();
0719:
0720: if (localrtx == rtx) {
0721: localrtx.commit();
0722: } else {
0723: throw new TimeoutException();
0724: }
0725: }
0726:
0727: /**
0728: * Called by the transactor thread when the request didn't terminate successfully.
0729: */
0730: synchronized void abortTransaction() {
0731: Transactor localrtx = (Transactor) Thread.currentThread();
0732: localrtx.abort();
0733: }
0734:
0735: /**
0736: * Initialize and start the transactor thread.
0737: */
0738: private synchronized void startTransactor() {
0739: if (!app.isRunning()) {
0740: throw new ApplicationStoppedException();
0741: }
0742:
0743: if ((rtx == null) || !rtx.isAlive()) {
0744: // app.logEvent ("Starting Thread");
0745: rtx = new Transactor(this , app.threadgroup, app.nmgr);
0746: rtx.setContextClassLoader(app.getClassLoader());
0747: rtx.start();
0748: } else {
0749: notifyAll();
0750: }
0751: }
0752:
0753: /**
0754: * Tell waiting thread that we're done, then wait for next request
0755: */
0756: synchronized void notifyAndWait() {
0757: Transactor localrtx = (Transactor) Thread.currentThread();
0758:
0759: // make sure there is only one thread running per instance of this class
0760: // if localrtx != rtx, the current thread has been aborted and there's no need to notify
0761: if (localrtx != rtx) {
0762: // A new request came in while we were finishing the last one.
0763: // Return to run() to get the work done.
0764: localrtx.closeConnections();
0765: return;
0766: }
0767:
0768: reqtype = NONE;
0769: notifyAll();
0770:
0771: try {
0772: // wait for request, max 10 min
0773: wait(1000 * 60 * 10);
0774: } catch (InterruptedException ix) {
0775: // we got interrrupted, releases resources and thread
0776: rtx = null;
0777: }
0778:
0779: // if no request arrived, release ressources and thread
0780: if ((reqtype == NONE) && (rtx == localrtx)) {
0781: // comment this in to release not just the thread, but also the scripting engine.
0782: // currently we don't do this because of the risk of memory leaks (objects from
0783: // framework referencing into the scripting engine)
0784: // scriptingEngine = null;
0785: rtx = null;
0786: }
0787: }
0788:
0789: /**
0790: * Stop this request evaluator's current thread. This is called by the
0791: * waiting thread when it times out and stops waiting, or from an outside
0792: * thread. If currently active kill the request, otherwise just notify.
0793: */
0794: synchronized boolean stopTransactor() {
0795: Transactor t = rtx;
0796: rtx = null;
0797: boolean stopped = false;
0798: if (t != null && t.isActive()) {
0799: // let the scripting engine know that the
0800: // current transaction is being aborted.
0801: if (scriptingEngine != null) {
0802: scriptingEngine.abort();
0803: }
0804:
0805: app.logEvent("Request timeout for thread " + t);
0806:
0807: reqtype = NONE;
0808:
0809: t.kill();
0810: t.abort();
0811: t.closeConnections();
0812: stopped = true;
0813: }
0814: notifyAll();
0815: return stopped;
0816: }
0817:
0818: /**
0819: * Invoke an action function for a HTTP request. The function is dispatched
0820: * in a new thread and waits for it to finish.
0821: *
0822: * @param req the incoming HTTP request
0823: * @param session the client's session
0824: * @return the result returned by the invocation
0825: * @throws Exception any exception thrown by the invocation
0826: */
0827: public synchronized ResponseTrans invokeHttp(RequestTrans req,
0828: Session session) throws Exception {
0829: initObjects(req, session);
0830:
0831: app.activeRequests.put(req, this );
0832:
0833: startTransactor();
0834: wait(app.requestTimeout);
0835:
0836: if (reqtype != NONE && stopTransactor()) {
0837: res.reset();
0838: res.reportError(app.getName(), "Request timed out");
0839: }
0840:
0841: session.commit(this );
0842: return res;
0843: }
0844:
0845: /**
0846: * This checks if the Evaluator is already executing an equal request.
0847: * If so, attach to it and wait for it to complete. Otherwise return null,
0848: * so the application knows it has to run the request.
0849: */
0850: public synchronized ResponseTrans attachHttpRequest(RequestTrans req)
0851: throws Exception {
0852: // Get a reference to the res object at the time we enter
0853: ResponseTrans localRes = res;
0854:
0855: if ((localRes == null) || !req.equals(this .req)) {
0856: return null;
0857: }
0858:
0859: if (reqtype != NONE) {
0860: wait(app.requestTimeout);
0861: }
0862:
0863: return localRes;
0864: }
0865:
0866: /*
0867: * TODO invokeXmlRpc(), invokeExternal() and invokeInternal() are basically the same
0868: * and should be unified
0869: */
0870:
0871: /**
0872: * Invoke a function for an XML-RPC request. The function is dispatched in a new thread
0873: * and waits for it to finish.
0874: *
0875: * @param functionName the name of the function to invoke
0876: * @param args the arguments
0877: * @return the result returned by the invocation
0878: * @throws Exception any exception thrown by the invocation
0879: */
0880: public synchronized Object invokeXmlRpc(String functionName,
0881: Object[] args) throws Exception {
0882: initObjects(functionName, XMLRPC, RequestTrans.XMLRPC);
0883: this .function = functionName;
0884: this .args = args;
0885:
0886: startTransactor();
0887: wait(app.requestTimeout);
0888:
0889: if (reqtype != NONE && stopTransactor()) {
0890: exception = new RuntimeException("Request timed out");
0891: }
0892:
0893: // reset res for garbage collection (res.data may hold reference to evaluator)
0894: res = null;
0895:
0896: if (exception != null) {
0897: throw (exception);
0898: }
0899:
0900: return result;
0901: }
0902:
0903: /**
0904: * Invoke a function for an external request. The function is dispatched
0905: * in a new thread and waits for it to finish.
0906: *
0907: * @param functionName the name of the function to invoke
0908: * @param args the arguments
0909: * @return the result returned by the invocation
0910: * @throws Exception any exception thrown by the invocation
0911: */
0912: public synchronized Object invokeExternal(String functionName,
0913: Object[] args) throws Exception {
0914: initObjects(functionName, EXTERNAL, RequestTrans.EXTERNAL);
0915: this .function = functionName;
0916: this .args = args;
0917:
0918: startTransactor();
0919: wait();
0920:
0921: if (reqtype != NONE && stopTransactor()) {
0922: exception = new RuntimeException("Request timed out");
0923: }
0924:
0925: // reset res for garbage collection (res.data may hold reference to evaluator)
0926: res = null;
0927:
0928: if (exception != null) {
0929: throw (exception);
0930: }
0931:
0932: return result;
0933: }
0934:
0935: /**
0936: * Invoke a function internally and directly, using the thread we're running on.
0937: *
0938: * @param obj the object to invoke the function on
0939: * @param function the function or name of the function to invoke
0940: * @param args the arguments
0941: * @return the result returned by the invocation
0942: * @throws Exception any exception thrown by the invocation
0943: */
0944: public Object invokeDirectFunction(Object obj, Object function,
0945: Object[] args) throws Exception {
0946: return scriptingEngine.invoke(obj, function, args,
0947: ScriptingEngine.ARGS_WRAP_DEFAULT, true);
0948: }
0949:
0950: /**
0951: * Invoke a function internally. The function is dispatched in a new thread
0952: * and waits for it to finish.
0953: *
0954: * @param object the object to invoke the function on
0955: * @param function the function or name of the function to invoke
0956: * @param args the arguments
0957: * @return the result returned by the invocation
0958: * @throws Exception any exception thrown by the invocation
0959: */
0960: public synchronized Object invokeInternal(Object object,
0961: Object function, Object[] args) throws Exception {
0962: // give internal call more time (15 minutes) to complete
0963: return invokeInternal(object, function, args, 60000L * 15);
0964: }
0965:
0966: /**
0967: * Invoke a function internally. The function is dispatched in a new thread
0968: * and waits for it to finish.
0969: *
0970: * @param object the object to invoke the function on
0971: * @param function the function or name of the function to invoke
0972: * @param args the arguments
0973: * @param timeout the time in milliseconds to wait for the function to return, or
0974: * -1 to wait indefinitely
0975: * @return the result returned by the invocation
0976: * @throws Exception any exception thrown by the invocation
0977: */
0978: public synchronized Object invokeInternal(Object object,
0979: Object function, Object[] args, long timeout)
0980: throws Exception {
0981: initObjects(function, INTERNAL, RequestTrans.INTERNAL);
0982: this Object = object;
0983: this .function = function;
0984: this .args = args;
0985:
0986: startTransactor();
0987: if (timeout < 0)
0988: wait();
0989: else
0990: wait(timeout);
0991:
0992: if (reqtype != NONE && stopTransactor()) {
0993: exception = new RuntimeException("Request timed out");
0994: }
0995:
0996: // reset res for garbage collection (res.data may hold reference to evaluator)
0997: res = null;
0998:
0999: if (exception != null) {
1000: throw (exception);
1001: }
1002:
1003: return result;
1004: }
1005:
1006: /**
1007: * Init this evaluator's objects from a RequestTrans for a HTTP request
1008: *
1009: * @param req
1010: * @param session
1011: */
1012: private synchronized void initObjects(RequestTrans req,
1013: Session session) {
1014: this .req = req;
1015: this .reqtype = HTTP;
1016: this .session = session;
1017: res = new ResponseTrans(app, req);
1018: result = null;
1019: exception = null;
1020: }
1021:
1022: /**
1023: * Init this evaluator's objects for an internal, external or XML-RPC type
1024: * request.
1025: *
1026: * @param function the function name or object
1027: * @param reqtype the request type
1028: * @param reqtypeName the request type name
1029: */
1030: private synchronized void initObjects(Object function, int reqtype,
1031: String reqtypeName) {
1032: this .reqtype = reqtype;
1033: String functionName = function instanceof String ? (String) function
1034: : "<function>";
1035: req = new RequestTrans(reqtypeName, functionName);
1036: session = new Session(functionName, app);
1037: res = new ResponseTrans(app, req);
1038: result = null;
1039: exception = null;
1040: }
1041:
1042: /**
1043: * Initialize the globals in the scripting engine for the current request.
1044: *
1045: * @param root
1046: * @throws ScriptingException
1047: */
1048: private synchronized void initGlobals(Object root,
1049: Object requestPath) throws ScriptingException {
1050: HashMap globals = new HashMap();
1051:
1052: globals.put("root", root);
1053: globals.put("session", new SessionBean(session));
1054: globals.put("req", new RequestBean(req));
1055: globals.put("res", new ResponseBean(res));
1056: globals.put("app", new ApplicationBean(app));
1057: globals.put("path", requestPath);
1058:
1059: // enter execution context
1060: scriptingEngine.enterContext(globals);
1061: }
1062:
1063: /**
1064: * Get the child element with the given name from the given object.
1065: *
1066: * @param obj
1067: * @param name
1068: * @return
1069: * @throws ScriptingException
1070: */
1071: private Object getChildElement(Object obj, String name)
1072: throws ScriptingException {
1073: if (scriptingEngine.hasFunction(obj, "getChildElement", false)) {
1074: return scriptingEngine.invoke(obj, "getChildElement",
1075: new Object[] { name },
1076: ScriptingEngine.ARGS_WRAP_DEFAULT, false);
1077: }
1078:
1079: if (obj instanceof IPathElement) {
1080: return ((IPathElement) obj).getChildElement(name);
1081: }
1082:
1083: return null;
1084: }
1085:
1086: /**
1087: * Null out some fields, mostly for the sake of garbage collection.
1088: */
1089: synchronized void recycle() {
1090: res = null;
1091: req = null;
1092: session = null;
1093: function = null;
1094: args = null;
1095: result = null;
1096: exception = null;
1097: }
1098:
1099: /**
1100: * Check if an action with a given name is defined for a scripted object. If it is,
1101: * return the action's function name. Otherwise, return null.
1102: */
1103: public String getAction(Object obj, String action, RequestTrans req) {
1104: if (obj == null)
1105: return null;
1106:
1107: if (action == null)
1108: action = "main";
1109:
1110: StringBuffer buffer = new StringBuffer(action)
1111: .append("_action");
1112: // record length so we can check without method
1113: // afterwards for GET, POST, HEAD requests
1114: int length = buffer.length();
1115:
1116: if (req.checkXmlRpc()) {
1117: // append _methodname
1118: buffer.append("_xmlrpc");
1119: if (scriptingEngine.hasFunction(obj, buffer.toString(),
1120: false)) {
1121: // handle as XML-RPC request
1122: req.setMethod(RequestTrans.XMLRPC);
1123: return buffer.toString();
1124: }
1125: // cut off method in case it has been appended
1126: buffer.setLength(length);
1127: }
1128:
1129: String method = req.getMethod();
1130: // append HTTP method to action name
1131: if (method != null) {
1132: // append _methodname
1133: buffer.append('_').append(method.toLowerCase());
1134: if (scriptingEngine.hasFunction(obj, buffer.toString(),
1135: false))
1136: return buffer.toString();
1137:
1138: // cut off method in case it has been appended
1139: buffer.setLength(length);
1140: }
1141:
1142: // if no method specified or "ordinary" request try action without method
1143: if (method == null || "GET".equalsIgnoreCase(method)
1144: || "POST".equalsIgnoreCase(method)
1145: || "HEAD".equalsIgnoreCase(method)) {
1146: if (scriptingEngine.hasFunction(obj, buffer.toString(),
1147: false))
1148: return buffer.toString();
1149: }
1150:
1151: return null;
1152: }
1153:
1154: /**
1155: * Returns this evaluator's scripting engine
1156: */
1157: public ScriptingEngine getScriptingEngine() {
1158: if (scriptingEngine == null) {
1159: initScriptingEngine();
1160: }
1161: return scriptingEngine;
1162: }
1163:
1164: /**
1165: * Get the request object for the current request.
1166: *
1167: * @return the request object
1168: */
1169: public synchronized RequestTrans getRequest() {
1170: return req;
1171: }
1172:
1173: /**
1174: * Get the response object for the current request.
1175: *
1176: * @return the response object
1177: */
1178: public synchronized ResponseTrans getResponse() {
1179: return res;
1180: }
1181:
1182: /**
1183: * Get the current transactor thread
1184: *
1185: * @return the current transactor thread
1186: */
1187: public synchronized Transactor getThread() {
1188: return rtx;
1189: }
1190:
1191: /**
1192: * Return the current session
1193: *
1194: * @return the session for the current request
1195: */
1196: public synchronized Session getSession() {
1197: return session;
1198: }
1199:
1200: private String missingFunctionMessage(Object obj, String funcName) {
1201: if (obj == null)
1202: return "Function " + funcName
1203: + " not defined in global scope";
1204: else
1205: return "Function " + funcName + " not defined for " + obj;
1206: }
1207: }
|