Implement invocation of scripted getChildElement(name) function when resolving

a request path also when objects implement IRequestPath interface.
The biggest change was induced by the need to have an object representing the
request path that is able to grow while the path is being resolved. Previously, this was done
by passing an ArrayList to the scripting engine that was then transformed into a JavaScript
array. This is now done using a proprietary RequestPath object.
This commit is contained in:
hns 2003-08-06 16:36:49 +00:00
parent 8c080a5da3
commit 4b91011578
7 changed files with 341 additions and 72 deletions

View file

@ -115,6 +115,10 @@ public final class Application implements IPathElement, Runnable {
// the URL-prefix to use for links into this application
private String baseURI;
// the name of the root prototype
private String rootPrototype;
// Db mappings for some standard prototypes
private DbMapping rootMapping;
private DbMapping userRootMapping;
private DbMapping userMapping;
@ -1019,13 +1023,9 @@ public final class Application implements IPathElement, Runnable {
* Return a path to be used in a URL pointing to the given element and action
*/
public String getNodeHref(Object elem, String actionName) {
// Object root = getDataRoot ();
// check optional root prototype from app.properties
String rootProto = props.getProperty("rootPrototype");
StringBuffer b = new StringBuffer(baseURI);
composeHref(elem, b, rootProto, 0);
composeHref(elem, b, 0);
if (actionName != null) {
b.append(UrlEncoded.encode(actionName));
@ -1034,13 +1034,12 @@ public final class Application implements IPathElement, Runnable {
return b.toString();
}
private final void composeHref(Object elem, StringBuffer b, String rootProto,
int pathCount) {
private final void composeHref(Object elem, StringBuffer b, int pathCount) {
if ((elem == null) || (pathCount > 20)) {
return;
}
if ((rootProto != null) && rootProto.equals(getPrototypeName(elem))) {
if ((rootPrototype != null) && rootPrototype.equals(getPrototypeName(elem))) {
return;
}
@ -1050,11 +1049,19 @@ public final class Application implements IPathElement, Runnable {
return;
}
composeHref(getParentElement(elem), b, rootProto, pathCount++);
composeHref(getParentElement(elem), b, pathCount++);
b.append(UrlEncoded.encode(getElementName(elem)));
b.append("/");
}
/**
* Returns the baseURI for Hrefs in this application.
*/
public String getBaseURI() {
return baseURI;
}
/**
* This method sets the base URL of this application which will be prepended to
* the actual object path.
@ -1077,6 +1084,13 @@ public final class Application implements IPathElement, Runnable {
return props.containsKey("baseuri");
}
/**
* Returns the prototype name that Hrefs in this application should start with.
*/
public String getRootPrototype() {
return rootPrototype;
}
/**
* Tell other classes whether they should output logging information for this application.
*/
@ -1574,8 +1588,9 @@ public final class Application implements IPathElement, Runnable {
// debug flag
debug = "true".equalsIgnoreCase(props.getProperty("debug"));
String reqTimeout = props.getProperty("requesttimeout", "60");
try {
requestTimeout = Long.parseLong(props.getProperty("requestTimeout", "60")) * 1000L;
requestTimeout = Long.parseLong(reqTimeout) * 1000L;
} catch (Exception ignore) {
// go with default value
requestTimeout = 60000L;
@ -1590,6 +1605,8 @@ public final class Application implements IPathElement, Runnable {
baseURI = "/";
}
rootPrototype = props.getProperty("rootprototype");
// update the XML-RPC access list, containting prototype.method
// entries of functions that may be called via XML-RPC
String xmlrpcAccessProp = props.getProperty("xmlrpcaccess");

View file

@ -186,9 +186,7 @@ public final class Prototype {
Prototype p = parent;
while ((p != null) && !"hopobject".equalsIgnoreCase(p.getName())) {
if (!handlers.containsKey(p.name)) {
handlers.put(p.name, obj);
}
p = p.parent;
}
@ -267,15 +265,6 @@ public final class Prototype {
lastUpdate = System.currentTimeMillis();
}
/**
* Get the time at which this prototype's scripts were checked
* for changes for the last time.
*/
/* public long getLastCheck () {
return lastCheck;
} */
/**
* Signal that the prototype's scripts have been checked for
* changes.

View file

@ -55,7 +55,7 @@ public final class RequestEvaluator implements Runnable {
Object[] args;
// the object path of the request we're evaluating
List requestPath;
RequestPath requestPath;
// the result of the operation
Object result;
@ -122,7 +122,7 @@ public final class RequestEvaluator implements Runnable {
// object refs to ressolve request path
Object currentElement;
requestPath = new ArrayList();
requestPath = new RequestPath(app);
switch (reqtype) {
case HTTP:
@ -158,6 +158,9 @@ public final class RequestEvaluator implements Runnable {
globals.put("path", requestPath);
req.startTime = System.currentTimeMillis();
// enter execution context
scriptingEngine.enterContext(globals);
if (error != null) {
res.error = error;
}
@ -186,7 +189,7 @@ public final class RequestEvaluator implements Runnable {
} else if ((req.path == null) ||
"".equals(req.path.trim())) {
currentElement = root;
requestPath.add(currentElement);
requestPath.add(null, currentElement);
action = getAction(currentElement, null);
@ -210,7 +213,7 @@ public final class RequestEvaluator implements Runnable {
pathItems[i] = st.nextToken();
currentElement = root;
requestPath.add(currentElement);
requestPath.add(null, currentElement);
for (int i = 0; i < ntokens; i++) {
@ -222,9 +225,6 @@ public final class RequestEvaluator implements Runnable {
continue;
}
// we used to do special processing for /user and /users
// here but with the framework cleanup, this stuff has to be
// mounted manually.
// if we're at the last element of the path,
// try to interpret it as action name.
if (i == (ntokens - 1)) {
@ -233,13 +233,13 @@ public final class RequestEvaluator implements Runnable {
}
if (action == null) {
currentElement = app.getChildElement(currentElement,
currentElement = getChildElement(currentElement,
pathItems[i]);
// add object to request path if suitable
if (currentElement != null) {
// add to requestPath array
requestPath.add(currentElement);
requestPath.add(pathItems[i], currentElement);
}
}
}
@ -311,9 +311,6 @@ public final class RequestEvaluator implements Runnable {
/////////////////////////////////////////////////////////////////////////////
// beginning of execution section
try {
// enter execution context
scriptingEngine.enterContext(globals);
// set the req.action property, cutting off the _action suffix
req.action = action.substring(0, action.length() - 7);
@ -459,7 +456,7 @@ public final class RequestEvaluator implements Runnable {
for (int i = 1; i < cnt; i++) {
String next = st.nextToken();
currentElement = app.getChildElement(currentElement,
currentElement = getChildElement(currentElement,
next);
}
@ -841,6 +838,18 @@ public final class RequestEvaluator implements Runnable {
return result;
}
private Object getChildElement(Object obj, String name) throws ScriptingException {
if (scriptingEngine.hasFunction(obj, "getChildElement")) {
return scriptingEngine.invoke(obj, "getChildElement", new Object[] {name}, false);
}
if (obj instanceof IPathElement) {
return ((IPathElement) obj).getChildElement(name);
}
return null;
}
/**
* Stop this request evaluator's current thread. If currently active kill the request, otherwise just
* notify.

View file

@ -0,0 +1,134 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.framework.core;
import java.util.*;
import helma.util.UrlEncoded;
/**
* Represents a URI request path that has been resolved to an object path.
* Offers methods to access objects in the path by index and prototype names,
* and to render the path as URI again.
*/
public class RequestPath {
Application app;
List objects;
List ids;
Map primaryProtos;
Map secondaryProtos;
/**
* Creates a new RequestPath object.
*
* @param app the application we're running in
*/
public RequestPath(Application app) {
this.app = app;
objects = new ArrayList();
ids = new ArrayList();
primaryProtos = new HashMap();
secondaryProtos = new HashMap();
}
/**
* Adds an item to the end of the path.
*
* @param id the item id representing the path in the URL
* @param obj the object to which the id resolves
*/
public void add(String id, Object obj) {
ids.add(id);
objects.add(obj);
Prototype proto = app.getPrototype(obj);
if (proto != null) {
primaryProtos.put(proto.getName(), obj);
proto.registerParents(secondaryProtos, obj);
}
}
/**
* Returns the number of objects in the request path.
*/
public int size() {
return objects.size();
}
/**
* Gets an object in the path by index.
*
* @param idx the index of the object in the request path
*/
public Object get(int idx) {
if (idx < 0 || idx >= objects.size()) {
return null;
}
return objects.get(idx);
}
/**
* Gets an object in the path by prototype name.
*
* @param typeName the prototype name of the object in the request path
*/
public Object getByPrototypeName(String typeName) {
// search primary prototypes first
Object obj = primaryProtos.get(typeName);
if (obj != null) {
return obj;
}
// if that fails, consult secondary prototype map
return secondaryProtos.get(typeName);
}
/**
* Returns the string representation of this path usable for links.
*/
public String href(String action) {
StringBuffer buffer = new StringBuffer(app.getBaseURI());
int start = 1;
String rootPrototype = app.getRootPrototype();
if (rootPrototype != null) {
Object rootObject = getByPrototypeName(rootPrototype);
if (rootObject != null) {
start = objects.indexOf(rootObject) + 1;
}
}
for (int i=start; i<ids.size(); i++) {
buffer.append(UrlEncoded.encode(ids.get(i).toString()));
buffer.append("/");
}
if (action != null) {
buffer.append(UrlEncoded.encode(action));
}
return buffer.toString();
}
}

View file

@ -0,0 +1,134 @@
/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.scripting.rhino;
import helma.framework.core.RequestPath;
import org.mozilla.javascript.*;
import java.lang.reflect.*;
/**
* This class wraps around instances of helma.framework.core.RequestPath and
* exposes them in an array-like fashion to the JavaScript runtime.
*
* @see helma.framework.core.RequestPath
*/
public class PathWrapper extends ScriptableObject {
RequestPath path;
RhinoCore core;
/**
* Zero arg constructor for creating the PathWrapper prototype.
*/
public PathWrapper (RhinoCore core) throws PropertyException, NoSuchMethodException {
this.core = core;
// create a dummy path object
this.path = new RequestPath(core.app);
// initialize properties and functions
int attributes = DONTENUM | READONLY | PERMANENT;
setParentScope(core.global);
setPrototype(null);
defineProperty("length", PathWrapper.class, attributes);
defineFunctionProperties(new String[] {"href"}, PathWrapper.class, attributes);
}
/**
* Creates a new PathWrapper around a RequestPath.
*/
PathWrapper(RequestPath path, RhinoCore core) {
this.path = path;
this.core = core;
}
/**
* Returns a path object in the wrapped path by property name.
*/
public Object get(String name, Scriptable start) {
Object obj = path.getByPrototypeName(name);
if (obj != null) {
return Context.toObject(obj, core.global);
}
return super.get(name, start);
}
/**
* Returns a path object in the wrapped path by property name.
*/
public Object get(int idx, Scriptable start) {
Object obj = path.get(idx);
if (obj != null) {
return Context.toObject(obj, core.global);
}
return null;
}
/**
* Checks if an object with the given name is contained in the path.
*/
public boolean has(String name, Scriptable start) {
return path.getByPrototypeName(name) != null;
}
/**
* Checks if an object with the given index is contained in the path.
*/
public boolean has(int index, Scriptable start) {
return index >= 0 && index < path.size();
}
/**
* Returns a list of array indices 0..length-1.
*/
public Object[] getIds() {
Object[] ids = new Object[path.size()];
for (int i=0; i<ids.length; i++) {
ids[i] = new Integer(i);
}
return ids;
}
/**
* Getter for length property.
*/
public long getLength() {
return path.size();
}
/**
* Returns the wrapped path rendered as URL path.
*/
public String href(Object action) {
if (action != null && action != Undefined.instance) {
return path.href(action.toString());
}
return path.href(null);
}
public String getClassName() {
return "[PathWrapper]";
}
}

View file

@ -55,6 +55,9 @@ public final class RhinoCore {
// the wrap factory
Wrapper wrapper;
// the prototype for path objects
PathWrapper pathProto;
/**
* Create a Rhino evaluator for the given application and request evaluator.
*/
@ -85,12 +88,17 @@ public final class RhinoCore {
g.init();
global = (GlobalObject) context.initStandardObjects(g);
ScriptableObject.defineClass(global, HopObject.class);
ScriptableObject.defineClass(global, FileObject.class);
ScriptableObject.defineClass(global, FtpObject.class);
pathProto = new PathWrapper(this);
ImageObject.init(global);
XmlRpcObject.init(global);
MailObject.init(global, app.getProperties());
putPrototype("hopobject",
(ScriptableObject) ScriptableObject
.getClassPrototype(global, "HopObject"));

View file

@ -187,40 +187,15 @@ public class RhinoEngine implements ScriptingEngine {
Scriptable scriptable = null;
try {
// we do some extra work with the path object: first, we create a native
// JavaScript array, then we register objects by
// their prototype name, which we take from res.handlers.
if ("path".equals(k)) {
Scriptable arr = context.newObject(global, "Array");
List path = (List) v;
int length = path.size();
Scriptable[] wrapped = new Scriptable[length];
// Move through the path list and set the path array.
for (int j = 0; j < length; j++) {
Object pathElem = path.get(j);
Scriptable wrappedElement = Context.toObject(pathElem, global);
arr.put(j, arr, wrappedElement);
wrapped[j] = wrappedElement;
}
// register path elements with their prototypes on the path array
ResponseTrans res = getResponse();
Map handlers = res.getMacroHandlers();
if (handlers != null) {
for (Iterator h = handlers.entrySet().iterator(); h.hasNext(); ) {
Map.Entry entry = (Map.Entry) h.next();
int idx = path.indexOf(entry.getValue());
arr.put(entry.getKey().toString(), arr, wrapped[idx]);
}
}
v = arr;
}
// create a special wrapper for the path object.
// other objects are wrapped in the default way.
if (v instanceof RequestPath) {
scriptable = new PathWrapper((RequestPath) v, core);
scriptable.setPrototype(core.pathProto);
} else {
scriptable = Context.toObject(v, global);
}
global.put(k, global, scriptable);
} catch (Exception x) {
app.logEvent("Error setting global variable " + k + ": " + x);
@ -292,6 +267,8 @@ public class RhinoEngine implements ScriptingEngine {
return null;
} else if (retval instanceof NativeJavaObject) {
return ((NativeJavaObject) retval).unwrap();
} else if (retval instanceof HopObject) {
return ((HopObject) retval).getNode();
} else {
return retval;
}
@ -358,7 +335,7 @@ public class RhinoEngine implements ScriptingEngine {
* is a java object) with that name.
*/
public boolean hasFunction(Object obj, String fname) {
// System.err.println ("HAS_FUNC: "+fname);
// System.err.println ("HAS_FUNC: "+obj+"."+fname);
return core.hasFunction(app.getPrototypeName(obj), fname.replace('.', '_'));
}
@ -367,6 +344,7 @@ public class RhinoEngine implements ScriptingEngine {
* is a java object) with that name.
*/
public Object get(Object obj, String propname) {
// System.err.println ("GET: "+propname);
if ((obj == null) || (propname == null)) {
return null;
}