Implement new skin features:

* Set namespace for global macros using app.globalMacroPath
* Implement macro parameter processing using
  app.processMacroParameter() callback and $(...) parameter syntax
* Implement unhandled macro handling using unhandledMacro() callback
* Implement deep macro lookup using getMacroHandler() callback, and
  drop allowDeepMacros app property
* Allow access to HopObject properties that aren't defined in type.properties
This commit is contained in:
hns 2007-04-04 12:46:14 +00:00
parent 7f58c102bf
commit 0559d2d53e
6 changed files with 221 additions and 90 deletions

View file

@ -173,7 +173,16 @@ public final class Application implements Runnable {
// Field to cache unmapped java classes // Field to cache unmapped java classes
private final static String CLASS_NOT_MAPPED = "(unmapped)"; private final static String CLASS_NOT_MAPPED = "(unmapped)";
protected boolean allowDeepMacros = false;
/**
* Function object for macro processing callback
*/
Object processMacroParameter = null;
/**
* Namespace search path for global macros
*/
String[] globalMacroPath = null;
/** /**
* Simple constructor for dead application instances. * Simple constructor for dead application instances.
@ -1641,15 +1650,7 @@ public final class Application implements Runnable {
* for it in the class.properties file. * for it in the class.properties file.
*/ */
public boolean isJavaPrototype(String typename) { public boolean isJavaPrototype(String typename) {
for (Enumeration en = classMapping.elements(); en.hasMoreElements();) { return classMapping.contains(typename);
String value = (String) en.nextElement();
if (typename.equals(value)) {
return true;
}
}
return false;
} }
/** /**
@ -1873,8 +1874,6 @@ public final class Application implements Runnable {
((Logger) eventLog).setLogLevel(debug ? Logger.DEBUG : Logger.INFO); ((Logger) eventLog).setLogLevel(debug ? Logger.DEBUG : Logger.INFO);
} }
allowDeepMacros = "true".equalsIgnoreCase(props.getProperty("allowDeepMacros"));
// set prop read timestamp // set prop read timestamp
lastPropertyRead = props.lastModified(); lastPropertyRead = props.lastModified();
} }

View file

@ -623,6 +623,42 @@ public class ApplicationBean implements Serializable {
return app.getCharset(); return app.getCharset();
} }
/**
* Set the path for global macro resolution
* @param path an array of global namespaces, or null
*/
public void setGlobalMacroPath(String[] path) {
app.globalMacroPath = path;
}
/**
* Get the path for global macro resolution
* @return an array of global namespaces, or null
*/
public String[] getGlobalMacroPath() {
return app.globalMacroPath;
}
/**
* Set a function for processing macro parameters formatted as $(value).
* The function is expected to take the parameter value as string argument,
* and return the processed parameter
* @param obj a callback function
*/
public void setProcessMacroParameter(Object obj) {
app.processMacroParameter = obj;
}
/**
* Get the function for processing macro parameters formatted as $(value).
* The function is expected to take the parameter value as string argument,
* and return the processed parameter
* @return the current macro processor callback function or null
*/
public Object getProcessMacroParameter() {
return app.processMacroParameter;
}
/** /**
* Trigger a synchronous Helma invocation with a default timeout of 30 seconds. * Trigger a synchronous Helma invocation with a default timeout of 30 seconds.
* *

View file

@ -179,8 +179,7 @@ public final class RequestEvaluator implements Runnable {
} }
} }
// If function doesn't exist, return immediately // If function doesn't exist, return immediately
if (functionName != null && functionName.indexOf('.') < 0 && if (functionName != null && !scriptingEngine.hasFunction(thisObject, functionName, true)) {
!scriptingEngine.hasFunction(thisObject, functionName)) {
app.logEvent(missingFunctionMessage(thisObject, functionName)); app.logEvent(missingFunctionMessage(thisObject, functionName));
done = true; done = true;
reqtype = NONE; reqtype = NONE;
@ -448,7 +447,7 @@ public final class RequestEvaluator implements Runnable {
// reset skin recursion detection counter // reset skin recursion detection counter
skinDepth = 0; skinDepth = 0;
if (!scriptingEngine.hasFunction(currentElement, functionName)) { if (!scriptingEngine.hasFunction(currentElement, functionName, false)) {
throw new FrameworkException(missingFunctionMessage(currentElement, functionName)); throw new FrameworkException(missingFunctionMessage(currentElement, functionName));
} }
result = scriptingEngine.invoke(currentElement, result = scriptingEngine.invoke(currentElement,
@ -486,10 +485,6 @@ public final class RequestEvaluator implements Runnable {
// reset skin recursion detection counter // reset skin recursion detection counter
skinDepth = 0; skinDepth = 0;
if (functionName != null && !scriptingEngine.hasFunction(thisObject, functionName)) {
throw new FrameworkException(missingFunctionMessage(thisObject, functionName));
}
result = scriptingEngine.invoke(thisObject, result = scriptingEngine.invoke(thisObject,
function, function,
args, args,
@ -868,7 +863,7 @@ public final class RequestEvaluator implements Runnable {
public Object invokeDirectFunction(Object obj, Object function, Object[] args) public Object invokeDirectFunction(Object obj, Object function, Object[] args)
throws Exception { throws Exception {
return scriptingEngine.invoke(obj, function, args, return scriptingEngine.invoke(obj, function, args,
ScriptingEngine.ARGS_WRAP_DEFAULT, false); ScriptingEngine.ARGS_WRAP_DEFAULT, true);
} }
/** /**
@ -993,7 +988,7 @@ public final class RequestEvaluator implements Runnable {
* @throws ScriptingException * @throws ScriptingException
*/ */
private Object getChildElement(Object obj, String name) throws ScriptingException { private Object getChildElement(Object obj, String name) throws ScriptingException {
if (scriptingEngine.hasFunction(obj, "getChildElement")) { if (scriptingEngine.hasFunction(obj, "getChildElement", false)) {
return scriptingEngine.invoke(obj, "getChildElement", new Object[] {name}, return scriptingEngine.invoke(obj, "getChildElement", new Object[] {name},
ScriptingEngine.ARGS_WRAP_DEFAULT, false); ScriptingEngine.ARGS_WRAP_DEFAULT, false);
} }
@ -1037,7 +1032,7 @@ public final class RequestEvaluator implements Runnable {
if (req.checkXmlRpc()) { if (req.checkXmlRpc()) {
// append _methodname // append _methodname
buffer.append("_xmlrpc"); buffer.append("_xmlrpc");
if (scriptingEngine.hasFunction(obj, buffer.toString())) { if (scriptingEngine.hasFunction(obj, buffer.toString(), false)) {
// handle as XML-RPC request // handle as XML-RPC request
req.setMethod(RequestTrans.XMLRPC); req.setMethod(RequestTrans.XMLRPC);
return buffer.toString(); return buffer.toString();
@ -1051,7 +1046,7 @@ public final class RequestEvaluator implements Runnable {
if (method != null) { if (method != null) {
// append _methodname // append _methodname
buffer.append('_').append(method.toLowerCase()); buffer.append('_').append(method.toLowerCase());
if (scriptingEngine.hasFunction(obj, buffer.toString())) if (scriptingEngine.hasFunction(obj, buffer.toString(), false))
return buffer.toString(); return buffer.toString();
// cut off method in case it has been appended // cut off method in case it has been appended
@ -1062,7 +1057,7 @@ public final class RequestEvaluator implements Runnable {
if (method == null || "GET".equalsIgnoreCase(method) || if (method == null || "GET".equalsIgnoreCase(method) ||
"POST".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method) ||
"HEAD".equalsIgnoreCase(method)) { "HEAD".equalsIgnoreCase(method)) {
if (scriptingEngine.hasFunction(obj, buffer.toString())) if (scriptingEngine.hasFunction(obj, buffer.toString(), false))
return buffer.toString(); return buffer.toString();
} }

View file

@ -319,10 +319,12 @@ public final class Skin {
sandbox.add(macroname); sandbox.add(macroname);
} }
private Object invokeMacro(Object value, RenderContext cx) private Object processParameter(Object value, RenderContext cx)
throws Exception { throws Exception {
if (value instanceof Macro) { if (value instanceof Macro) {
return ((Macro) value).invokeAsMacro(cx, null); return ((Macro) value).invokeAsMacro(cx, null);
} else if (value instanceof ProcessedParameter) {
return ((ProcessedParameter) value).process(cx.reval);
} else { } else {
return value; return value;
} }
@ -376,7 +378,6 @@ public final class Skin {
if (state == PARSE_PARAM && quotechar == '\u0000' if (state == PARSE_PARAM && quotechar == '\u0000'
&& b.length() == 0 && source[i + 1] == '%') { && b.length() == 0 && source[i + 1] == '%') {
Macro macro = new Macro(i, 2); Macro macro = new Macro(i, 2);
hasNestedMacros = true;
addParameter(lastParamName, macro); addParameter(lastParamName, macro);
lastParamName = null; lastParamName = null;
b.setLength(0); b.setLength(0);
@ -455,7 +456,7 @@ public final class Skin {
if (!escape && state == PARSE_PARAM) { if (!escape && state == PARSE_PARAM) {
if (quotechar == source[i]) { if (quotechar == source[i]) {
// add parameter // add parameter
addParameter(lastParamName, b.toString()); addParameter(lastParamName, parseParameter(b.toString()));
lastParamName = null; lastParamName = null;
b.setLength(0); b.setLength(0);
quotechar = '\u0000'; quotechar = '\u0000';
@ -487,7 +488,7 @@ public final class Skin {
if (quotechar == '\u0000') { if (quotechar == '\u0000') {
if (b.length() > 0) { if (b.length() > 0) {
// add parameter // add parameter
addParameter(lastParamName, b.toString()); addParameter(lastParamName, parseParameter(b.toString()));
lastParamName = null; lastParamName = null;
b.setLength(0); b.setLength(0);
} }
@ -533,7 +534,7 @@ public final class Skin {
if (name == null) { if (name == null) {
name = b.toString().trim(); name = b.toString().trim();
} else { } else {
addParameter(lastParamName, b.toString()); addParameter(lastParamName, parseParameter(b.toString()));
} }
} }
@ -560,7 +561,21 @@ public final class Skin {
} }
} }
private Object parseParameter(String str) {
int length = str.length();
if (length > 3 && str.charAt(0) == '$') {
if (str.charAt(1) == '[' && str.charAt(length - 1) == ']')
return new ProcessedParameter(str.substring(2, str.length()-1), 0);
else if (str.charAt(1) == '(' && str.charAt(length - 1) == ')')
return new ProcessedParameter(str.substring(2, str.length()-1), 1);
}
return str;
}
private void addParameter(String name, Object value) { private void addParameter(String name, Object value) {
if (!(value instanceof String)) {
hasNestedMacros = true;
}
if (name == null) { if (name == null) {
// take shortcut for positional parameters // take shortcut for positional parameters
if (positionalParams == null) { if (positionalParams == null) {
@ -613,31 +628,32 @@ public final class Skin {
throw new RuntimeException("Macro " + name + " not allowed in sandbox"); throw new RuntimeException("Macro " + name + " not allowed in sandbox");
} }
Object handlerObject = null; Object handler = null;
Object value = null; Object value = null;
ScriptingEngine engine = cx.reval.scriptingEngine; ScriptingEngine engine = cx.reval.scriptingEngine;
if (handlerType != HANDLER_GLOBAL) { if (handlerType != HANDLER_GLOBAL) {
handlerObject = cx.resolveHandler(path[0], handlerType); handler = cx.resolveHandler(path[0], handlerType);
handlerObject = resolvePath(handlerObject, engine); handler = resolvePath(handler, cx.reval);
} }
if (handlerType == HANDLER_GLOBAL || handlerObject != null) { if (handlerType == HANDLER_GLOBAL || handler != null) {
// check if a function called name_macro is defined. // check if a function called name_macro is defined.
// if so, the macro evaluates to the function. Otherwise, // if so, the macro evaluates to the function. Otherwise,
// a property/field with the name is used, if defined. // a property/field with the name is used, if defined.
String propName = path[path.length - 1]; String propName = path[path.length - 1];
String funcName = propName + "_macro"; String funcName = resolveFunctionName(handler, propName + "_macro", engine);
if (engine.hasFunction(handlerObject, funcName)) {
StringBuffer buffer = cx.reval.getResponse().getBuffer();
// remember length of response buffer before calling macro // remember length of response buffer before calling macro
StringBuffer buffer = cx.reval.getResponse().getBuffer();
int bufLength = buffer.length(); int bufLength = buffer.length();
if (funcName != null) {
Object[] arguments = prepareArguments(0, cx); Object[] arguments = prepareArguments(0, cx);
// get reference to rendered named params for after invocation // get reference to rendered named params for after invocation
Map params = (Map) arguments[0]; Map params = (Map) arguments[0];
value = cx.reval.invokeDirectFunction(handlerObject, value = cx.reval.invokeDirectFunction(handler,
funcName, funcName,
arguments); arguments);
@ -662,16 +678,28 @@ public final class Skin {
if (value != null) if (value != null)
return filter(value, cx); return filter(value, cx);
} }
// display error message unless silent failmode is on // display error message unless unhandledMacro is defined or silent failmode is on
if (!engine.hasProperty(handlerObject, propName) if (!engine.hasProperty(handler, propName)) {
&& standardParams.verboseFailmode(handlerObject, engine)) { if (engine.hasFunction(handler, "unhandledMacro", false)) {
throw new MacroUnhandledException(name); Object[] arguments = prepareArguments(1, cx);
arguments[0] = propName;
value = cx.reval.invokeDirectFunction(handler, "unhandledMacro", arguments);
// if macro has a filter chain and didn't return anything, use output
// as filter argument.
if (filterChain != null && value == null && buffer.length() > bufLength) {
value = buffer.substring(bufLength);
buffer.setLength(bufLength);
}
} else if (standardParams.verboseFailmode(handler, engine)) {
throw new UnhandledMacroException(name);
}
} else {
value = engine.getProperty(handler, propName);
} }
value = engine.getProperty(handlerObject, propName);
return filter(value, cx); return filter(value, cx);
} }
} else if (standardParams.verboseFailmode(handlerObject, engine)) { } else if (standardParams.verboseFailmode(handler, engine)) {
throw new MacroUnhandledException(name); throw new UnhandledMacroException(name);
} }
return filter(null, cx); return filter(null, cx);
} }
@ -723,8 +751,8 @@ public final class Skin {
throw concur; throw concur;
} catch (TimeoutException timeout) { } catch (TimeoutException timeout) {
throw timeout; throw timeout;
} catch (MacroUnhandledException unhandled) { } catch (UnhandledMacroException unhandled) {
String msg = "Macro unhandled: " + unhandled.getMessage(); String msg = "Unhandled Macro: " + unhandled.getMessage();
cx.reval.getResponse().write(" [" + msg + "] "); cx.reval.getResponse().write(" [" + msg + "] ");
app.logError(msg); app.logError(msg);
} catch (Exception x) { } catch (Exception x) {
@ -761,12 +789,14 @@ public final class Skin {
if (handlerType != HANDLER_GLOBAL) { if (handlerType != HANDLER_GLOBAL) {
handlerObject = cx.resolveHandler(path[0], handlerType); handlerObject = cx.resolveHandler(path[0], handlerType);
handlerObject = resolvePath(handlerObject, cx.reval.scriptingEngine); handlerObject = resolvePath(handlerObject, cx.reval);
} }
String funcName = path[path.length - 1] + "_filter"; String propName = path[path.length - 1] + "_filter";
String funcName = resolveFunctionName(handlerObject, propName,
cx.reval.scriptingEngine);
if (cx.reval.scriptingEngine.hasFunction(handlerObject, funcName)) { if (funcName != null) {
Object[] arguments = prepareArguments(1, cx); Object[] arguments = prepareArguments(1, cx);
arguments[0] = returnValue; arguments[0] = returnValue;
Object retval = cx.reval.invokeDirectFunction(handlerObject, Object retval = cx.reval.invokeDirectFunction(handlerObject,
@ -791,7 +821,9 @@ public final class Skin {
for (Iterator it = namedParams.entrySet().iterator(); it.hasNext(); ) { for (Iterator it = namedParams.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next(); Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue(); Object value = entry.getValue();
map.put(entry.getKey(), invokeMacro(value, cx)); if (!(value instanceof String))
value = processParameter(value, cx);
map.put(entry.getKey(), value);
} }
arguments[offset] = map; arguments[offset] = map;
} else { } else {
@ -800,30 +832,54 @@ public final class Skin {
} }
if (positionalParams != null) { if (positionalParams != null) {
for (int i = 0; i < nPosArgs; i++) { for (int i = 0; i < nPosArgs; i++) {
Object param = positionalParams.get(i); Object value = positionalParams.get(i);
if (param instanceof Macro) if (!(value instanceof String))
arguments[offset + 1 + i] = invokeMacro(param, cx); value = processParameter(value, cx);
else arguments[offset + 1 + i] = value;
arguments[offset + 1 + i] = param;
} }
} }
return arguments; return arguments;
} }
private Object resolvePath(Object handler, ScriptingEngine engine) { private Object resolvePath(Object handler, RequestEvaluator reval) throws Exception {
if (path.length > 2 && !app.allowDeepMacros) {
throw new RuntimeException("allowDeepMacros property must be true " +
"in order to enable deep macro paths.");
}
for (int i = 1; i < path.length - 1; i++) { for (int i = 1; i < path.length - 1; i++) {
handler = engine.getProperty(handler, path[i]); Object[] arguments = {path[i]};
Object next = reval.invokeDirectFunction(handler, "getMacroHandler", arguments);
if (next != null) {
handler = next;
} else if (!reval.scriptingEngine.isTypedObject(handler)) {
handler = reval.scriptingEngine.getProperty(handler, path[i]);
if (handler == null) { if (handler == null) {
break; break;
} }
} }
}
return handler; return handler;
} }
private String resolveFunctionName(Object handler, String functionName,
ScriptingEngine engine) {
if (handlerType == HANDLER_GLOBAL) {
String[] macroPath = app.globalMacroPath;
if (macroPath == null || macroPath.length == 0) {
if (engine.hasFunction(null, functionName, false))
return functionName;
} else {
for (int i = 0; i < macroPath.length; i++) {
String path = macroPath[i];
String funcName = path == null || path.length() == 0 ?
functionName : path + "." + functionName;
if (engine.hasFunction(null, funcName, true))
return funcName;
}
}
} else {
if (engine.hasFunction(handler, functionName, false))
return functionName;
}
return null;
}
/** /**
* Utility method for writing text out to the response object. * Utility method for writing text out to the response object.
*/ */
@ -920,9 +976,9 @@ public final class Skin {
} }
boolean containsMacros() { boolean containsMacros() {
return prefix instanceof Macro return !(prefix instanceof String)
|| suffix instanceof Macro || !(suffix instanceof String)
|| defaultValue instanceof Macro; || !(defaultValue instanceof String);
} }
void setFailMode(Object value) { void setFailMode(Object value) {
@ -946,9 +1002,9 @@ public final class Skin {
if (!containsMacros()) if (!containsMacros())
return this; return this;
StandardParams stdParams = new StandardParams(); StandardParams stdParams = new StandardParams();
stdParams.prefix = invokeMacro(prefix, cx); stdParams.prefix = processParameter(prefix, cx);
stdParams.suffix = invokeMacro(suffix, cx); stdParams.suffix = processParameter(suffix, cx);
stdParams.defaultValue = invokeMacro(defaultValue, cx); stdParams.defaultValue = processParameter(defaultValue, cx);
return stdParams; return stdParams;
} }
@ -1019,13 +1075,39 @@ public final class Skin {
return obj; return obj;
} }
} }
}
/** /**
* Processed macro parameter
*/
class ProcessedParameter {
String value;
int type;
ProcessedParameter(String value, int type) {
this.value = value;
this.type = type;
}
Object process(RequestEvaluator reval) throws Exception {
switch (type) {
case 1:
Object function = app.processMacroParameter;
Object[] args = {value};
return reval.invokeDirectFunction(null, function, args);
case 0:
default:
return reval.getResponse().getMacroHandlers().get(value);
}
}
}
/**
* Exception type for unhandled macros * Exception type for unhandled macros
*/ */
class MacroUnhandledException extends Exception { class UnhandledMacroException extends Exception {
MacroUnhandledException(String name) { UnhandledMacroException(String name) {
super(name); super(name);
} }
}
} }

View file

@ -119,9 +119,10 @@ public interface ScriptingEngine {
* Return true if a function by that name is defined for that object. * Return true if a function by that name is defined for that object.
* @param thisObject the object * @param thisObject the object
* @param functionName the function name * @param functionName the function name
* @param resolve if member path in function name should be resolved
* @return true if the function is defined on the object * @return true if the function is defined on the object
*/ */
public boolean hasFunction(Object thisObject, String functionName); public boolean hasFunction(Object thisObject, String functionName, boolean resolve);
/** /**
* Return true if a property by that name is defined for that object. * Return true if a property by that name is defined for that object.

View file

@ -28,6 +28,7 @@ import helma.objectmodel.db.DbMapping;
import helma.objectmodel.db.Relation; import helma.objectmodel.db.Relation;
import helma.scripting.*; import helma.scripting.*;
import helma.scripting.rhino.debug.Tracer; import helma.scripting.rhino.debug.Tracer;
import helma.util.StringUtils;
import org.mozilla.javascript.*; import org.mozilla.javascript.*;
import org.mozilla.javascript.serialize.ScriptableOutputStream; import org.mozilla.javascript.serialize.ScriptableOutputStream;
import org.mozilla.javascript.serialize.ScriptableInputStream; import org.mozilla.javascript.serialize.ScriptableInputStream;
@ -246,10 +247,9 @@ public class RhinoEngine implements ScriptingEngine {
// otherwise replace dots with underscores. // otherwise replace dots with underscores.
if (resolve) { if (resolve) {
if (funcName.indexOf('.') > 0) { if (funcName.indexOf('.') > 0) {
StringTokenizer st = new StringTokenizer(funcName, "."); String[] path = StringUtils.split(funcName, ".");
for (int i=0; i<st.countTokens()-1; i++) { for (int i = 0; i < path.length - 1; i++) {
String propName = st.nextToken(); Object propValue = ScriptableObject.getProperty(obj, path[i]);
Object propValue = ScriptableObject.getProperty(obj, propName);
if (propValue instanceof Scriptable) { if (propValue instanceof Scriptable) {
obj = (Scriptable) propValue; obj = (Scriptable) propValue;
} else { } else {
@ -257,7 +257,7 @@ public class RhinoEngine implements ScriptingEngine {
funcName + " in " + thisObject); funcName + " in " + thisObject);
} }
} }
funcName = st.nextToken(); funcName = path[path.length - 1];
} }
} else { } else {
funcName = funcName.replace('.', '_'); funcName = funcName.replace('.', '_');
@ -357,9 +357,26 @@ public class RhinoEngine implements ScriptingEngine {
* Check if an object has a function property (public method if it * Check if an object has a function property (public method if it
* is a java object) with that name. * is a java object) with that name.
*/ */
public boolean hasFunction(Object obj, String fname) { public boolean hasFunction(Object obj, String fname, boolean resolve) {
if (resolve) {
if (fname.indexOf('.') > 0) {
Scriptable op = obj == null ? global : Context.toObject(obj, global);
String[] path = StringUtils.split(fname, ".");
for (int i = 0; i < path.length; i++) {
Object value = ScriptableObject.getProperty(op, path[i]);
if (value instanceof Scriptable) {
op = (Scriptable) value;
} else {
return false;
}
}
return (op instanceof Function);
}
} else {
// Convert '.' to '_' in function name // Convert '.' to '_' in function name
fname = fname.replace('.', '_'); fname = fname.replace('.', '_');
}
// Treat HopObjects separately - otherwise we risk to fetch database // Treat HopObjects separately - otherwise we risk to fetch database
// references/child objects just to check for function properties. // references/child objects just to check for function properties.
if (obj instanceof INode) { if (obj instanceof INode) {
@ -395,7 +412,8 @@ public class RhinoEngine implements ScriptingEngine {
DbMapping dbm = app.getDbMapping(prototypeName); DbMapping dbm = app.getDbMapping(prototypeName);
if (dbm != null) { if (dbm != null) {
Relation rel = dbm.propertyToRelation(propname); Relation rel = dbm.propertyToRelation(propname);
return rel != null && (rel.isPrimitive() || rel.isCollection()); if (rel != null && (rel.isPrimitive() || rel.isCollection()))
return true;
} }
} }
Scriptable wrapped = Context.toObject(obj, global); Scriptable wrapped = Context.toObject(obj, global);
@ -448,8 +466,8 @@ public class RhinoEngine implements ScriptingEngine {
public boolean isTypedObject(Object obj) { public boolean isTypedObject(Object obj) {
if (obj == null || obj instanceof Map || obj instanceof NativeObject) if (obj == null || obj instanceof Map || obj instanceof NativeObject)
return false; return false;
if (obj instanceof INode) { if (obj instanceof IPathElement) {
String protoName = app.getPrototypeName(obj); String protoName = ((IPathElement) obj).getPrototype();
return protoName != null && !"hopobject".equalsIgnoreCase(protoName); return protoName != null && !"hopobject".equalsIgnoreCase(protoName);
} }
// assume java object is typed // assume java object is typed