req.action = action.substring(0, action.length() - 7);
req.setAction(action.substring(0, action.length() - 7));
    public void run() {        // first, set a local variable to the current transactor thread so we know        // when it's time to quit because another thread took over.        Transactor localrtx = (Transactor) Thread.currentThread();        try {            // while this thread is serving requests            while (localrtx == rtx) {                // root object reference                Object root;                // object reference to ressolve request path                Object currentElement;                // request path object                requestPath = new RequestPath(app);                int tries = 0;                boolean done = false;                String error = null;                while (!done && localrtx == rtx) {                    currentElement = null;                    try {                        // initialize scripting engine                        initScriptingEngine();                        // update scripting prototypes                        scriptingEngine.updatePrototypes();                        // Transaction name is used for logging etc.                        StringBuffer txname = new StringBuffer(app.getName());                        txname.append(":").append(req.getMethod().toLowerCase()).append(":");                        txname.append((error == null) ? req.path : "error");                        // begin transaction                        localrtx.begin(txname.toString());                        String action = null;                        root = app.getDataRoot();                        req.startTime = System.currentTimeMillis();                        initGlobals(root);                        if (error != null) {                            res.error = error;                        }                        switch (reqtype) {                            case HTTP:                                if (session.message != null) {                                    // bring over the message from a redirect                                    res.message = session.message;                                    session.message = null;                                }                                try {                                    if (error != null) {                                        // there was an error in the previous loop, call error handler                                        currentElement = root;                                        // do not reset the requestPath so error handler can use the original one                                        // get error handler action                                        String errorAction = app.props.getProperty("error",                                                "error");                                        action = getAction(currentElement, errorAction, null);                                        if (action == null) {                                            throw new RuntimeException(error);                                        }                                    } else if ((req.path == null) ||                                            "".equals(req.path.trim())) {                                        currentElement = root;                                        requestPath.add(null, currentElement);                                        action = getAction(currentElement, null, req.getMethod());                                        if (action == null) {                                            throw new FrameworkException("Action not found");                                        }                                    } else {                                        // march down request path...                                        StringTokenizer st = new StringTokenizer(req.path,                                                "/");                                        int ntokens = st.countTokens();                                        // limit path to < 50 tokens                                        if (ntokens > 50) {                                            throw new RuntimeException("Path too long");                                        }                                        String[] pathItems = new String[ntokens];                                        for (int i = 0; i < ntokens; i++)                                            pathItems[i] = st.nextToken();                                        currentElement = root;                                        requestPath.add(null, currentElement);                                        for (int i = 0; i < ntokens; i++) {                                            if (currentElement == null) {                                                throw new FrameworkException("Object not found.");                                            }                                            if (pathItems[i].length() == 0) {                                                continue;                                            }                                            // if we're at the last element of the path,                                            // try to interpret it as action name.                                            if (i == (ntokens - 1)) {                                                action = getAction(currentElement,                                                        pathItems[i], req.getMethod());                                            }                                            if (action == null) {                                                currentElement = getChildElement(currentElement,                                                        pathItems[i]);                                                // add object to request path if suitable                                                if (currentElement != null) {                                                    // add to requestPath array                                                    requestPath.add(pathItems[i], currentElement);                                                }                                            }                                        }                                        if (currentElement == null) {                                            throw new FrameworkException("Object not found.");                                        }                                        if (action == null) {                                            action = getAction(currentElement, null, req.getMethod());                                        }                                        if (action == null) {                                            throw new FrameworkException("Action not found");                                        }                                    }                                } catch (FrameworkException notfound) {                                    if (error != null) {                                        // we already have an error and the error template wasn't found,                                        // display it instead of notfound message                                        throw new RuntimeException();                                    }                                    // The path could not be resolved. Check if there is a "not found" action                                    // specified in the property file.                                    res.status = 404;                                    String notFoundAction = app.props.getProperty("notfound",                                            "notfound");                                    currentElement = root;                                    action = getAction(currentElement, notFoundAction, null);                                    if (action == null) {                                        throw new FrameworkException(notfound.getMessage());                                    }                                }                                // register path objects with their prototype names in                                // res.handlers                                Map macroHandlers = res.getMacroHandlers();                                int l = requestPath.size();                                Prototype[] protos = new Prototype[l];                                for (int i = 0; i < l; i++) {                                    Object obj = requestPath.get(i);                                    protos[i] = app.getPrototype(obj);                                    // immediately register objects with their direct prototype name                                    if (protos[i] != null) {                                        macroHandlers.put(protos[i].getName(), obj);                                        macroHandlers.put(protos[i].getLowerCaseName(), obj);                                    }                                }                                // in a second pass, we register path objects with their indirect                                // (i.e. parent prototype) names, starting at the end and only                                // if the name isn't occupied yet.                                for (int i = l - 1; i >= 0; i--) {                                    if (protos[i] != null) {                                        protos[i].registerParents(macroHandlers, requestPath.get(i));                                    }                                }                                /////////////////////////////////////////////////////////////////////////////                                // end of path resolution section                                /////////////////////////////////////////////////////////////////////////////                                // beginning of execution section                                try {                                    // set the req.action property, cutting off the _action suffix                                    req.action = action.substring(0, action.length() - 7);                                    // reset skin recursion detection counter                                    skinDepth = 0;                                    // try calling onRequest() function on object before                                    // calling the actual action                                    try {                                        if (scriptingEngine.hasFunction(currentElement,                                                "onRequest")) {                                            scriptingEngine.invoke(currentElement,                                                    "onRequest",                                                    new Object[0],                                                    ScriptingEngine.ARGS_WRAP_DEFAULT);                                        }                                    } catch (RedirectException redir) {                                        throw redir;                                    }                                    // reset skin recursion detection counter                                    skinDepth = 0;                                    // do the actual action invocation                                    scriptingEngine.invoke(currentElement, action,                                            new Object[0],                                            ScriptingEngine.ARGS_WRAP_DEFAULT);                                } catch (RedirectException redirect) {                                    // if there is a message set, save it on the user object for the next request                                    if (res.message != null) {                                        session.message = res.message;                                    }                                }                                // check if we're still the one and only or if the waiting thread has given up on us already                                commitTransaction();                                done = true;                                break;                            case XMLRPC:                            case EXTERNAL:                                try {                                    currentElement = root;                                    if (functionName.indexOf('.') > -1) {                                        StringTokenizer st = new StringTokenizer(functionName, ".");                                        int cnt = st.countTokens();                                        for (int i = 1; i < cnt; i++) {                                            String next = st.nextToken();                                            currentElement = getChildElement(currentElement,                                                    next);                                        }                                        if (currentElement == null) {                                            throw new FrameworkException("Method name \"" +                                                    functionName +                                                    "\" could not be resolved.");                                        }                                        functionName = st.nextToken();                                    }                                    if (reqtype == XMLRPC) {                                        // check XML-RPC access permissions                                        String proto = app.getPrototypeName(currentElement);                                        app.checkXmlRpcAccess(proto, functionName);                                    }                                    // reset skin recursion detection counter                                    skinDepth = 0;                                    result = scriptingEngine.invoke(currentElement, functionName, args,                                            ScriptingEngine.ARGS_WRAP_XMLRPC);                                    commitTransaction();                                } catch (Exception x) {                                    abortTransaction();                                    app.logEvent("Exception in " + Thread.currentThread() + ": " +                                            x);                                    // If the transactor thread has been killed by the invoker thread we don't have to                                    // bother for the error message, just quit.                                    if (localrtx != rtx) {                                        return;                                    }                                    this.exception = x;                                }                                done = true;                                break;                            case INTERNAL:                                // if thisObject is an instance of NodeHandle, get the node object itself.                                if ((thisObject != null) && thisObject instanceof NodeHandle) {                                    thisObject = ((NodeHandle) thisObject).getNode(app.nmgr.safe);                                    // see if a valid node was returned                                    if (thisObject == null) {                                        reqtype = NONE;                                        abortTransaction();                                        done = true;                                        break;                                    }                                }                                // avoid going into transaction if called function doesn't exist.                                boolean functionexists = true;                                // this only works for the (common) case that method is a plain                                // method name, not an obj.method path                                if (functionName.indexOf('.') < 0) {                                    functionexists = scriptingEngine.hasFunction(thisObject, functionName);                                }                                if (!functionexists) {                                    // function doesn't exist, nothing to do here.                                    reqtype = NONE;                                    abortTransaction();                                } else {                                    try {                                        // reset skin recursion detection counter                                        skinDepth = 0;                                        result = scriptingEngine.invoke(thisObject, functionName, args,                                                ScriptingEngine.ARGS_WRAP_DEFAULT);                                        commitTransaction();                                    } catch (Exception x) {                                        abortTransaction();                                        app.logEvent("Exception in " + Thread.currentThread() +                                                ": " + x);                                        // If the transactor thread has been killed by the invoker thread we don't have to                                        // bother for the error message, just quit.                                        if (localrtx != rtx) {                                            return;                                        }                                        this.exception = x;                                    }                                }                                done = true;                                break;                        } // switch (reqtype)                    } catch (AbortException x) {                        // res.abort() just aborts the transaction and                        // leaves the respons untouched                        abortTransaction();                        done = true;                    } catch (ConcurrencyException x) {                        res.reset();                        if (++tries < 8) {                            // try again after waiting some period                            abortTransaction();                            try {                                // wait a bit longer with each try                                int base = 800 * tries;                                Thread.sleep((long) (base + (Math.random() * base * 2)));                            } catch (Exception ignore) {                            }                            continue;                        } else {                            abortTransaction();                            if (error == null)                                error = "Application too busy, please try again later";                            // error in error action. use traditional minimal error message                            res.writeErrorReport(app.getName(), error);                            done = true;                        }                    } catch (Throwable x) {                        abortTransaction();                        // If the transactor thread has been killed by the invoker thread we don't have to                        // bother for the error message, just quit.                        if (localrtx != rtx) {                            return;                        }                        res.reset();                        // check if we tried to process the error already                        if (error == null) {                            app.errorCount += 1;                            app.logError("Error in " + Thread.currentThread(), x);                            // set done to false so that the error will be processed                            done = false;                            error = x.getMessage();                            if ((error == null) || (error.length() == 0)) {                                error = x.toString();                            }                            if (error == null) {                                error = "Unspecified error";                            }                        } else {                            // error in error action. use traditional minimal error message                            res.writeErrorReport(app.getName(), error);                            done = true;                        }                    }                }                // exit execution context                scriptingEngine.exitContext();                notifyAndWait();            }        } finally {            localrtx.closeConnections();        }    }