* Refactor and enhance Skin class:

- implement macro/filter pipes <% foo | bar %>
  - implement deep macros <% foo.bar.foo %>
  - implement nested macros <% foo x=<% bar %> %>
  - implement failmode=silent|verbose attribute
* Refactor ScriptingEngine interface and implementation to support
  new skinning features.
This commit is contained in:
hns 2007-03-15 17:15:25 +00:00
parent 1ef63471aa
commit e4784f870d
3 changed files with 471 additions and 282 deletions

View file

@ -20,7 +20,6 @@ import helma.framework.*;
import helma.framework.repository.Resource;
import helma.objectmodel.ConcurrencyException;
import helma.util.*;
import helma.scripting.ScriptingException;
import java.util.*;
import java.io.UnsupportedEncodingException;
@ -34,16 +33,25 @@ import java.io.IOException;
* from the RequestEvaluator object to resolve Macro handlers by type name.
*/
public final class Skin {
static final int HANDLER = 0;
static final int MACRO = 1;
static final int PARAMNAME = 2;
static final int PARAMVALUE = 3;
static final int ENCODE_NONE = 0;
static final int ENCODE_HTML = 1;
static final int ENCODE_XML = 2;
static final int ENCODE_FORM = 3;
static final int ENCODE_URL = 4;
static final int ENCODE_ALL = 5;
static private final int PARSE_MACRONAME = 0;
static private final int PARSE_PARAMNAME = 1;
static private final int PARSE_PARAMVALUE = 2;
static private final int ENCODE_NONE = 0;
static private final int ENCODE_HTML = 1;
static private final int ENCODE_XML = 2;
static private final int ENCODE_FORM = 3;
static private final int ENCODE_URL = 4;
static private final int ENCODE_ALL = 5;
static private final int HANDLER_RESPONSE = 0;
static private final int HANDLER_REQUEST = 1;
static private final int HANDLER_SESSION = 2;
static private final int HANDLER_PARAM = 3;
static private final int HANDLER_GLOBAL = 4;
static private final int HANDLER_THIS = 5;
static private final int HANDLER_OTHER = 6;
private Macro[] macros;
private Application app;
private char[] source;
@ -114,22 +122,15 @@ public final class Skin {
private void parse() {
ArrayList partBuffer = new ArrayList();
boolean escape = false;
for (int i = 0; i < (sourceLength - 1); i++) {
if ((source[i] == '<') && (source[i + 1] == '%')) {
if (source[i] == '<' && source[i + 1] == '%' && !escape) {
// found macro start tag
int j = i + 2;
// search macr end tag
while ((j < (sourceLength - 1)) &&
((source[j] != '%') || (source[j + 1] != '>'))) {
j++;
}
if (j > (i + 2)) {
partBuffer.add(new Macro(i, j + 2));
}
i = j + 1;
Macro macro = new Macro(i, 2);
partBuffer.add(macro);
i = macro.end - 1;
} else {
escape = source[i] == '\\' && !escape;
}
}
@ -222,7 +223,7 @@ public final class Skin {
if (macros[i] instanceof Macro) {
Macro m = macros[i];
if (macroname.equals(m.fullName)) {
if (macroname.equals(m.name)) {
return true;
}
}
@ -242,58 +243,113 @@ public final class Skin {
sandbox.add(macroname);
}
private Object renderMacro(Object value, RequestEvaluator reval,
Object thisObj, Map handlerCache)
throws UnsupportedEncodingException {
if (value instanceof Macro) {
ResponseTrans res = reval.getResponse();
res.pushStringBuffer();
((Macro) value).render(reval, thisObj, handlerCache);
return res.popStringBuffer();
} else {
return value;
}
}
class Macro {
final int start;
final int end;
String handler;
final int start, end;
String name;
String fullName;
String[] path;
int handler = HANDLER_OTHER;
int encoding = ENCODE_NONE;
boolean hasNestedMacros = false;
// default render parameters - may be overridden if macro changes
// param.prefix/suffix/default
RenderParameters defaultRenderParams = new RenderParameters();
StandardParams standardParams = new StandardParams();
Map params = null;
// filters defined via <% foo | bar %>
Macro filterChain;
// comment macros are silently dropped during rendering
boolean isCommentMacro = false;
public Macro(int start, int end) {
/**
* Create and parse a new macro.
* @param start the start of the macro within the skin source
* @param offset offset of the macro content from the start index
*/
public Macro(int start, int offset) {
this.start = start;
this.end = end;
int state = HANDLER;
int state = PARSE_MACRONAME;
boolean escape = false;
char quotechar = '\u0000';
String lastParamName = null;
StringBuffer b = new StringBuffer();
int i;
loop:
for (i = start + offset; i < sourceLength - 1; i++) {
for (int i = start + 2; i < (end - 2); i++) {
switch (source[i]) {
case '<':
if (state == PARSE_PARAMVALUE && quotechar == '\u0000' && source[i + 1] == '%') {
Macro macro = new Macro(i, 2);
hasNestedMacros = true;
addParameter(lastParamName, macro);
lastParamName = null;
b.setLength(0);
state = PARSE_PARAMNAME;
i = macro.end - 1;
} else {
b.append(source[i]);
escape = false;
}
break;
case '%':
if ((state != PARSE_PARAMVALUE || quotechar == '\u0000') && source[i + 1] == '>') {
break loop;
}
b.append(source[i]);
escape = false;
break;
case '/':
b.append(source[i]);
escape = false;
if (state == HANDLER && "//".equals(b.toString())) {
if (state == PARSE_MACRONAME && "//".equals(b.toString())) {
isCommentMacro = true;
return;
// search macro end tag
while (i < sourceLength - 1 &&
(source[i] != '%' || source[i + 1] != '>')) {
i++;
}
break loop;
}
break;
case '.':
case '|':
if (state == HANDLER) {
handler = b.toString().trim();
if (state == PARSE_PARAMNAME) {
filterChain = new Macro(i, 1);
i = filterChain.end - 2;
lastParamName = null;
b.setLength(0);
state = MACRO;
} else {
b.append(source[i]);
escape = false;
break loop;
}
b.append(source[i]);
escape = false;
break;
case '\\':
@ -309,13 +365,13 @@ public final class Skin {
case '"':
case '\'':
if (!escape && (state == PARAMVALUE)) {
if (!escape && (state == PARSE_PARAMVALUE)) {
if (quotechar == source[i]) {
// add parameter
addParameter(lastParamName, b.toString());
lastParamName = null;
b.setLength(0);
state = PARAMNAME;
state = PARSE_PARAMNAME;
quotechar = '\u0000';
} else if (quotechar == '\u0000') {
quotechar = source[i];
@ -337,17 +393,17 @@ public final class Skin {
case '\r':
case '\f':
if ((state == MACRO) || ((state == HANDLER) && (b.length() > 0))) {
if (state == PARSE_MACRONAME && b.length() > 0) {
name = b.toString().trim();
b.setLength(0);
state = PARAMNAME;
} else if ((state == PARAMVALUE) && (quotechar == '\u0000')) {
state = PARSE_PARAMNAME;
} else if ((state == PARSE_PARAMVALUE) && (quotechar == '\u0000')) {
// add parameter
addParameter(lastParamName, b.toString());
lastParamName = null;
b.setLength(0);
state = PARAMNAME;
} else if (state == PARAMVALUE) {
state = PARSE_PARAMNAME;
} else if (state == PARSE_PARAMVALUE) {
b.append(source[i]);
escape = false;
} else {
@ -358,10 +414,10 @@ public final class Skin {
case '=':
if (state == PARAMNAME) {
if (state == PARSE_PARAMNAME) {
lastParamName = b.toString().trim();
b.setLength(0);
state = PARAMVALUE;
state = PARSE_PARAMVALUE;
} else {
b.append(source[i]);
escape = false;
@ -375,42 +431,65 @@ public final class Skin {
}
}
this.end = Math.min(sourceLength, i + 2);
if (b.length() > 0) {
if (lastParamName != null) {
// add parameter
addParameter(lastParamName, b.toString());
} else if (state <= MACRO) {
} else if (state == PARSE_MACRONAME) {
name = b.toString().trim();
}
}
if (handler == null) {
fullName = name;
path = StringUtils.split(name, ".");
if (path.length <= 1) {
handler = HANDLER_GLOBAL;
} else {
fullName = handler + "." + name;
String handlerName = path[0];
if ("this".equalsIgnoreCase(handlerName)) {
handler = HANDLER_THIS;
} else if ("response".equalsIgnoreCase(handlerName)) {
handler = HANDLER_RESPONSE;
} else if ("request".equalsIgnoreCase(handlerName)) {
handler = HANDLER_REQUEST;
} else if ("session".equalsIgnoreCase(handlerName)) {
handler = HANDLER_SESSION;
} else if ("param".equalsIgnoreCase(handlerName)) {
handler = HANDLER_PARAM;
}
}
// Set default failmode unless explicitly set:
// silent for default handlers, verbose
if (params == null || !params.containsKey("failmode")) {
standardParams.silentFailMode = (handler < HANDLER_GLOBAL);
}
}
private void addParameter(String name, String value) {
private void addParameter(String name, Object value) {
// check if this is parameter is relevant to us
if ("prefix".equals(name)) {
defaultRenderParams.prefix = value;
standardParams.prefix = value;
} else if ("suffix".equals(name)) {
defaultRenderParams.suffix = value;
standardParams.suffix = value;
} else if ("encoding".equals(name)) {
if ("html".equalsIgnoreCase(value)) {
if ("html".equals(value)) {
encoding = ENCODE_HTML;
} else if ("xml".equalsIgnoreCase(value)) {
} else if ("xml".equals(value)) {
encoding = ENCODE_XML;
} else if ("form".equalsIgnoreCase(value)) {
} else if ("form".equals(value)) {
encoding = ENCODE_FORM;
} else if ("url".equalsIgnoreCase(value)) {
} else if ("url".equals(value)) {
encoding = ENCODE_URL;
} else if ("all".equalsIgnoreCase(value)) {
} else if ("all".equals(value)) {
encoding = ENCODE_ALL;
} else {
app.logEvent("Unrecognized encoding in skin macro: " + value);
}
} else if ("default".equals(name)) {
defaultRenderParams.defaultValue = value;
standardParams.defaultValue = value;
} else if ("failmode".equals(name)) {
standardParams.setFailMode(value);
}
// Add parameter to parameter map
@ -430,94 +509,29 @@ public final class Skin {
return;
}
if ((sandbox != null) && !sandbox.contains(fullName)) {
reval.getResponse().write("[Macro " + fullName + " not allowed in sandbox]");
return;
} else if ("response".equalsIgnoreCase(handler)) {
renderFromResponse(reval);
return;
} else if ("request".equalsIgnoreCase(handler)) {
renderFromRequest(reval);
return;
} else if ("session".equalsIgnoreCase(handler)) {
renderFromSession(reval);
return;
if ((sandbox != null) && !sandbox.contains(name)) {
throw new RuntimeException("Macro " + name + " not allowed in sandbox");
}
Object handlerObject = null;
try {
Object handlerObject = null;
// flag to tell whether we found our invocation target object
boolean objectFound = true;
if (handler != null) {
// try to get handler from handlerCache first
if (handlerCache != null) {
handlerObject = handlerCache.get(handler);
}
if (handlerObject == null) {
// if handler object wasn't found in cache retrieve it
if ((handlerObject == null) && (thisObject != null)) {
// not a global macro - need to find handler object
// was called with this object - check it or its parents for matching prototype
if (!handler.equals("this") &&
!handler.equalsIgnoreCase(app.getPrototypeName(thisObject))) {
// the handler object is not what we want
Object n = thisObject;
// walk down parent chain to find handler object
while (n != null) {
Prototype proto = app.getPrototype(n);
if ((proto != null) && proto.isInstanceOf(handler)) {
handlerObject = n;
break;
}
n = app.getParentElement(n);
}
} else {
// we already have the right handler object
handlerObject = thisObject;
}
}
if (handlerObject == null) {
// eiter because thisObject == null or the right object wasn't found
// in the object's parent path. Check if a matching macro handler
// is registered with the response object (res.handlers).
handlerObject = reval.getResponse().getMacroHandlers().get(handler);
}
// the macro handler object couldn't be found
if (handlerObject == null) {
objectFound = false;
}
// else put the found handler object into the cache so we don't have to look again
else if (handlerCache != null) {
handlerCache.put(handler, handlerObject);
}
}
} else {
// this is a global macro with no handler specified
handlerObject = null;
if (handler != HANDLER_GLOBAL) {
handlerObject = resolveHandler(thisObject, reval, handlerCache);
handlerObject = resolvePath(handlerObject, reval);
}
if (objectFound) {
if (handler == HANDLER_GLOBAL || handlerObject != null) {
// check if a function called name_macro is defined.
// if so, the macro evaluates to the function. Otherwise,
// a property/field with the name is used, if defined.
String funcName = name + "_macro";
String propName = path[path.length - 1];
String funcName = propName + "_macro";
if (reval.scriptingEngine.hasFunction(handlerObject, funcName)) {
StringBuffer buffer = reval.getResponse().getBuffer();
RenderParameters renderParams = defaultRenderParams;
StandardParams stdParams = standardParams;
// remember length of response buffer before calling macro
int bufLength = buffer.length();
@ -528,67 +542,100 @@ public final class Skin {
if (params == null) {
arguments = new Object[] { new SystemMap(4) };
} else if (hasNestedMacros) {
SystemMap map = new SystemMap((int) (params.size() * 1.5));
ResponseTrans res = reval.getResponse();
for (Iterator it = params.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue();
map.put(entry.getKey(), renderMacro(value, reval, thisObject, handlerCache));
}
if (standardParams.containsMacros())
stdParams = new StandardParams(map);
arguments = new Object[] { map };
} else {
wrappedParams = new CopyOnWriteMap(params);
arguments = new Object[] { wrappedParams };
}
Object value = reval.invokeDirectFunction(handlerObject,
funcName,
arguments);
// if parameter map was modified create new renderParams to override defaults
if (wrappedParams != null && wrappedParams.wasModified()) {
renderParams = new RenderParameters(wrappedParams);
stdParams = new StandardParams(wrappedParams);
}
// check if this macro has a filter chain
if (filterChain != null) {
if (value == null && buffer.length() > bufLength) {
value = buffer.substring(bufLength);
buffer.setLength(bufLength);
}
}
// check if macro wrote out to response buffer
if (buffer.length() == bufLength) {
// If the macro function didn't write anything to the response itself,
// we interpret its return value as macro output.
writeResponse(value, buffer, renderParams, true);
writeResponse(value, reval, thisObject, handlerCache, stdParams, true);
} else {
// if an encoding is specified, re-encode the macro's output
if (encoding != ENCODE_NONE) {
String output = buffer.substring(bufLength);
buffer.setLength(bufLength);
writeResponse(output, buffer, renderParams, false);
writeResponse(output, reval, thisObject, handlerCache, stdParams, false);
} else {
// insert prefix,
if (renderParams.prefix != null) {
buffer.insert(bufLength, renderParams.prefix);
if (stdParams.prefix != null) {
buffer.insert(bufLength, stdParams.prefix);
}
// append suffix
if (renderParams.suffix != null) {
buffer.append(renderParams.suffix);
if (stdParams.suffix != null) {
buffer.append(stdParams.suffix);
}
}
// Append macro return value even if it wrote something to the response,
// but don't render default value in case it returned nothing.
// We do this for the sake of consistency.
writeResponse(value, buffer, renderParams, false);
writeResponse(value, reval, thisObject, handlerCache, stdParams, false);
}
} else {
// for unhandled global macros display error message,
// otherwise try property lookup
if (handlerObject == null) {
String msg = "[Macro unhandled: " + fullName + "]";
reval.getResponse().write(" " + msg + " ");
app.logEvent(msg);
if (handler == HANDLER_RESPONSE) {
// some special handling for response handler
Object value = null;
if ("message".equals(propName)) {
value = reval.getResponse().getMessage();
} else if ("error".equals(propName)) {
value = reval.getResponse().getError();
}
if (value != null) {
writeResponse(value, reval, thisObject, handlerCache, standardParams, true);
return;
}
}
// display error message unless silent failmode is on
if (handlerObject == null || !hasProperty(handlerObject, propName, reval)) {
if (!standardParams.silentFailMode) {
String msg = "[Macro unhandled: " + name + "]";
reval.getResponse().write(" " + msg + " ");
app.logEvent(msg);
}
} else {
Object value = reval.scriptingEngine.get(handlerObject, name);
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
Object value = getProperty(handlerObject, propName, reval);
writeResponse(value, reval, thisObject, handlerCache, standardParams, true);
}
}
} else {
String msg = "[Macro unhandled: " + fullName + "]";
reval.getResponse().write(" " + msg + " ");
app.logEvent(msg);
if (!standardParams.silentFailMode) {
String msg = "[Macro unhandled: " + name + "]";
reval.getResponse().write(" " + msg + " ");
app.logEvent(msg);
}
}
} catch (RedirectException redir) {
throw redir;
@ -601,98 +648,176 @@ public final class Skin {
if ((msg == null) || (msg.length() < 10)) {
msg = x.toString();
}
msg = new StringBuffer("Macro error in ").append(fullName)
msg = new StringBuffer("Macro error in ").append(name)
.append(": ").append(msg).toString();
reval.getResponse().write(" [" + msg + "] ");
app.logError(msg, x);
}
}
private void renderFromResponse(RequestEvaluator reval)
throws UnsupportedEncodingException {
Object value = null;
private Object invokeFilter(RequestEvaluator reval, Object returnValue,
Object thisObject, Map handlerCache)
throws Exception {
if ("message".equals(name)) {
value = reval.getResponse().getMessage();
} else if ("error".equals(name)) {
value = reval.getResponse().getError();
if ((sandbox != null) && !sandbox.contains(name)) {
throw new RuntimeException("Macro " + name + " not allowed in sandbox");
}
Object handlerObject = null;
if (handler != HANDLER_GLOBAL) {
handlerObject = resolveHandler(thisObject, reval, handlerCache);
handlerObject = resolvePath(handlerObject, reval);
}
if (value == null) {
value = reval.getResponse().get(name);
}
String funcName = name + "_filter";
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
}
if (reval.scriptingEngine.hasFunction(handlerObject, funcName)) {
// pass a clone/copy of the parameter map so if the script changes it,
CopyOnWriteMap wrappedParams = null;
Object[] arguments;
private void renderFromRequest(RequestEvaluator reval)
throws UnsupportedEncodingException {
if (reval.getRequest() == null) {
return;
}
Object value = reval.getRequest().get(name);
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
}
private void renderFromSession(RequestEvaluator reval)
throws UnsupportedEncodingException {
if (reval.getSession() == null) {
return;
}
Object value = reval.getSession().getCacheNode().getString(name);
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
}
private void renderFromParam(RequestEvaluator reval, Map paramObject)
throws UnsupportedEncodingException {
if (paramObject == null) {
reval.getResponse().write("[Macro error: Skin requires a parameter object]");
if (params == null) {
arguments = new Object[] { returnValue, new SystemMap(4) };
} else if (hasNestedMacros) {
SystemMap map = new SystemMap((int) (params.size() * 1.5));
ResponseTrans res = reval.getResponse();
for (Iterator it = params.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue();
map.put(entry.getKey(), renderMacro(value, reval, thisObject, handlerCache));
}
arguments = new Object[] { map };
} else {
wrappedParams = new CopyOnWriteMap(params);
arguments = new Object[] { returnValue, wrappedParams };
}
Object retval = reval.invokeDirectFunction(handlerObject,
funcName,
arguments);
if (filterChain == null) {
return retval;
}
return filterChain.invokeFilter(reval, retval, thisObject, handlerCache);
} else {
Object value = paramObject.get(name);
throw new RuntimeException("[Undefined Filter: " + name + "]");
}
}
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
private Object resolveHandler(Object thisObject, RequestEvaluator reval, Map handlerCache) {
if (path.length < 2)
throw new RuntimeException("resolveHandler called on global macro");
switch (handler) {
case HANDLER_THIS:
return thisObject;
case HANDLER_RESPONSE:
return reval.getResponse().getResponseData();
case HANDLER_REQUEST:
return reval.getRequest().getRequestData();
case HANDLER_SESSION:
return reval.getSession().getCacheNode();
}
String handlerName = path[0];
// try to get handler from handlerCache first
if (handlerCache != null && handlerCache.containsKey(handlerName)) {
return handlerCache.get(handlerName);
}
// if handler object wasn't found in cache retrieve it
if (thisObject != null) {
// not a global macro - need to find handler object
// was called with this object - check it or its parents for matching prototype
if (handlerName.equalsIgnoreCase(app.getPrototypeName(thisObject))) {
// we already have the right handler object
// put the found handler object into the cache so we don't have to look again
if (handlerCache != null)
handlerCache.put(handlerName, thisObject);
return thisObject;
} else {
// the handler object is not what we want
Object obj = thisObject;
// walk down parent chain to find handler object
while (obj != null) {
Prototype proto = app.getPrototype(obj);
if ((proto != null) && proto.isInstanceOf(handlerName)) {
if (handlerCache != null)
handlerCache.put(handlerName, obj);
return obj;
}
obj = app.getParentElement(obj);
}
}
}
Map macroHandlers = reval.getResponse().getMacroHandlers();
Object obj = macroHandlers.get(handlerName);
if (handlerCache != null && obj != null) {
handlerCache.put(handlerName, obj);
}
return obj;
}
private Object resolvePath(Object handler, RequestEvaluator reval) {
for (int i = 1; i < path.length - 1; i++) {
handler = getProperty(handler, path[i], reval);
if (handler == null) {
break;
}
}
return handler;
}
private Object getProperty(Object obj, String name,
RequestEvaluator reval) {
if (obj instanceof Map) {
return ((Map) obj).get(name);
} else {
return reval.getScriptingEngine().getProperty(obj, name);
}
}
private boolean hasProperty(Object obj, String name, RequestEvaluator reval) {
if (obj instanceof Map) {
return ((Map) obj).containsKey(name);
} else {
return reval.getScriptingEngine().hasProperty(obj, name);
}
}
/**
* Utility method for writing text out to the response object.
*/
void writeResponse(Object value, StringBuffer buffer,
RenderParameters renderParams, boolean useDefault)
throws UnsupportedEncodingException {
void writeResponse(Object value, RequestEvaluator reval,
Object thisObject, Map handlerCache,
StandardParams stdParams, boolean useDefault)
throws Exception {
String text;
StringBuffer buffer = reval.getResponse().getBuffer();
// invoke filter chain if defined
if (filterChain != null) {
value = filterChain.invokeFilter(reval, value, thisObject, handlerCache);
}
if (value == null) {
if (useDefault) {
text = renderParams.defaultValue;
text = (String) stdParams.defaultValue;
} else {
return;
}
} else {
// do not render doubles as doubles unless
// they actually have a decimal place. This is necessary because
// all numbers are handled as Double in JavaScript.
if (value instanceof Double) {
Double d = (Double) value;
if (d.longValue() == d.doubleValue()) {
text = Long.toString(d.longValue());
} else {
text = d.toString();
}
} else {
text = value.toString();
}
text = reval.getScriptingEngine().toString(value);
}
if ((text != null) && (text.length() > 0)) {
// only write prefix/suffix if value is not null, if we write the default
// value provided by the macro tag, we assume it's already complete
if (renderParams.prefix != null && value != null) {
buffer.append(renderParams.prefix);
if (stdParams.prefix != null && value != null) {
buffer.append(stdParams.prefix);
}
switch (encoding) {
@ -727,36 +852,65 @@ public final class Skin {
break;
}
if (renderParams.suffix != null && value != null) {
buffer.append(renderParams.suffix);
if (stdParams.suffix != null && value != null) {
buffer.append(stdParams.suffix);
}
}
}
public String toString() {
return "[Macro: " + fullName + "]";
return "[Macro: " + name + "]";
}
/**
* Return the full name of the macro in handler.name notation
* @return the macro name
*/
public String getFullName() {
return fullName;
public String getName() {
return name;
}
}
class RenderParameters {
String prefix = null;
String suffix = null;
String defaultValue = null;
class StandardParams {
Object prefix = null;
Object suffix = null;
Object defaultValue = null;
boolean silentFailMode = false;
RenderParameters() {
StandardParams() {}
StandardParams(Map map) {
prefix = map.get("prefix");
suffix = map.get("suffix");
defaultValue = map.get("default");
silentFailMode = "silent".equals(map.get("failmode"));
}
RenderParameters(Map map) {
prefix = (String) map.get("prefix");
suffix = (String) map.get("suffix");
defaultValue = (String) map.get("default");
boolean containsMacros() {
return prefix instanceof Macro
|| suffix instanceof Macro
|| defaultValue instanceof Macro;
}
void setFailMode(Object value) {
if ("silent".equals(value))
silentFailMode = true;
else if ("verbose".equals(value))
silentFailMode = false;
else
app.logEvent("unrecognized failmode value: " + value);
}
StandardParams render(RequestEvaluator reval, Object thisObj, Map handlerCache)
throws UnsupportedEncodingException {
if (!containsMacros())
return this;
StandardParams stdParams = new StandardParams();
stdParams.prefix = renderMacro(prefix, reval, thisObj, handlerCache);
stdParams.suffix = renderMacro(suffix, reval, thisObj, handlerCache);
stdParams.defaultValue = renderMacro(defaultValue, reval, thisObj, handlerCache);
return stdParams;
}
}
}

View file

@ -108,15 +108,36 @@ public interface ScriptingEngine {
public void abort();
/**
* Get a property on an object
* Get a property on an object
* @param thisObject the object
* @param key the property name
* @return true the property value, or null
*/
public Object get(Object thisObject, String key);
public Object getProperty(Object thisObject, String key);
/**
* 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 functionName the function name
* @return true if the function is defined on the object
*/
public boolean hasFunction(Object thisObject, String functionName);
/**
* Return true if a property by that name is defined for that object.
* @param thisObject the object
* @param propertyName the property name
* @return true if the function is defined on the object
*/
public boolean hasProperty(Object thisObject, String propertyName);
/**
* Return a string representation for the given object
* @param obj an object
* @return a string representing the object
*/
public String toString(Object obj);
/**
* Get an IPathElement that offers introspection services into the application.
* If this method returns null, no introspection is available for this kind of engine.

View file

@ -255,7 +255,7 @@ public class RhinoEngine implements ScriptingEngine {
}
Object f = ScriptableObject.getProperty(obj, functionName);
if ((f == ScriptableObject.NOT_FOUND) || !(f instanceof Function)) {
if (!(f instanceof Function)) {
return null;
}
@ -275,7 +275,7 @@ public class RhinoEngine implements ScriptingEngine {
}
// use Context.call() in order to set the context's factory
Object retval = Context.call(core.contextFactory, (Function)f, global, obj, args);
Object retval = Context.call(core.contextFactory, (Function) f, global, obj, args);
if (retval instanceof Wrapper) {
retval = ((Wrapper) retval).unwrap();
@ -353,69 +353,83 @@ public class RhinoEngine implements ScriptingEngine {
Object func = ScriptableObject.getProperty(op, fname);
return func instanceof Function;
return func instanceof Callable;
}
/**
* Check if an object has a value property defined with that name.
*/
public boolean hasProperty(Object obj, String propname) {
if ((obj == null) || (propname == null)) {
return false;
}
String prototypeName = app.getPrototypeName(obj);
if ("user".equalsIgnoreCase(prototypeName)
&& "password".equalsIgnoreCase(propname)) {
return false;
}
// if this is a HopObject, check if the property is defined
// in the type.properties db-mapping.
if (obj instanceof INode && ! "hopobject".equalsIgnoreCase(prototypeName)) {
DbMapping dbm = app.getDbMapping(prototypeName);
if (dbm != null) {
Relation rel = dbm.propertyToRelation(propname);
return rel != null && (rel.isPrimitive() || rel.isCollection());
}
}
Scriptable wrapped = Context.toObject(obj, global);
return wrapped.has(propname, wrapped);
}
/**
* Check if an object has a defined property (public field if it
* is a java object) with that name.
*/
public Object get(Object obj, String propname) {
if ((obj == null) || (propname == null)) {
public Object getProperty(Object obj, String propname) {
if ((obj == null) || (propname == null))
return null;
}
String prototypeName = app.getPrototypeName(obj);
if ("user".equalsIgnoreCase(prototypeName) &&
"password".equalsIgnoreCase(propname)) {
return "[macro access to password property not allowed]";
}
// if this is a HopObject, check if the property is defined
// in the type.properties db-mapping.
if (obj instanceof INode) {
DbMapping dbm = app.getDbMapping(prototypeName);
if (dbm != null) {
Relation rel = dbm.propertyToRelation(propname);
if ((rel == null) || !rel.isPrimitive()) {
return "[property \"" + propname + "\" is not defined for " +
prototypeName + "]";
}
}
}
Scriptable so = Context.toObject(obj, global);
try {
Object prop = so.get(propname, so);
if ((prop == null) || (prop == Undefined.instance)
|| (prop == ScriptableObject.NOT_FOUND)) {
if ((prop == null)
|| (prop == Undefined.instance)
|| (prop == ScriptableObject.NOT_FOUND)) {
return null;
} else if (prop instanceof Wrapper) {
return ((Wrapper) prop).unwrap();
} else {
// not all Rhino types convert to a string as expected
// when calling toString() - try to do better by using
// Rhino's ScriptRuntime.toString(). Note that this
// assumes that people always use this method to get
// a string representation of the object - which is
// currently the case since it's only used in Skin rendering.
try {
return ScriptRuntime.toString(prop);
} catch (Exception x) {
// just return original property object
}
return prop;
}
} catch (Exception esx) {
app.logError("Error getting property " + propname + ": " + esx);
return null;
}
}
/**
* Return a string representation for the given object
* @param obj an object
* @return a string representing the object
*/
public String toString(Object obj) {
// not all Rhino types convert to a string as expected
// when calling toString() - try to do better by using
// Rhino's ScriptRuntime.toString(). Note that this
// assumes that people always use this method to get
// a string representation of the object - which is
// currently the case since it's only used in Skin rendering.
try {
return ScriptRuntime.toString(obj);
} catch (Exception x) {
// just return original property object
}
return obj.toString();
}
/**
* Get an introspector to this engine.
*/