* 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.framework.repository.Resource;
import helma.objectmodel.ConcurrencyException; import helma.objectmodel.ConcurrencyException;
import helma.util.*; import helma.util.*;
import helma.scripting.ScriptingException;
import java.util.*; import java.util.*;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -34,16 +33,25 @@ import java.io.IOException;
* from the RequestEvaluator object to resolve Macro handlers by type name. * from the RequestEvaluator object to resolve Macro handlers by type name.
*/ */
public final class Skin { public final class Skin {
static final int HANDLER = 0; static private final int PARSE_MACRONAME = 0;
static final int MACRO = 1; static private final int PARSE_PARAMNAME = 1;
static final int PARAMNAME = 2; static private final int PARSE_PARAMVALUE = 2;
static final int PARAMVALUE = 3;
static final int ENCODE_NONE = 0; static private final int ENCODE_NONE = 0;
static final int ENCODE_HTML = 1; static private final int ENCODE_HTML = 1;
static final int ENCODE_XML = 2; static private final int ENCODE_XML = 2;
static final int ENCODE_FORM = 3; static private final int ENCODE_FORM = 3;
static final int ENCODE_URL = 4; static private final int ENCODE_URL = 4;
static final int ENCODE_ALL = 5; 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 Macro[] macros;
private Application app; private Application app;
private char[] source; private char[] source;
@ -114,22 +122,15 @@ public final class Skin {
private void parse() { private void parse() {
ArrayList partBuffer = new ArrayList(); ArrayList partBuffer = new ArrayList();
boolean escape = false;
for (int i = 0; i < (sourceLength - 1); i++) { 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 // found macro start tag
int j = i + 2; Macro macro = new Macro(i, 2);
partBuffer.add(macro);
// search macr end tag i = macro.end - 1;
while ((j < (sourceLength - 1)) && } else {
((source[j] != '%') || (source[j + 1] != '>'))) { escape = source[i] == '\\' && !escape;
j++;
}
if (j > (i + 2)) {
partBuffer.add(new Macro(i, j + 2));
}
i = j + 1;
} }
} }
@ -222,7 +223,7 @@ public final class Skin {
if (macros[i] instanceof Macro) { if (macros[i] instanceof Macro) {
Macro m = macros[i]; Macro m = macros[i];
if (macroname.equals(m.fullName)) { if (macroname.equals(m.name)) {
return true; return true;
} }
} }
@ -242,58 +243,113 @@ public final class Skin {
sandbox.add(macroname); 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 { class Macro {
final int start; final int start, end;
final int end;
String handler;
String name; String name;
String fullName; String[] path;
int handler = HANDLER_OTHER;
int encoding = ENCODE_NONE; int encoding = ENCODE_NONE;
boolean hasNestedMacros = false;
// default render parameters - may be overridden if macro changes // default render parameters - may be overridden if macro changes
// param.prefix/suffix/default // param.prefix/suffix/default
RenderParameters defaultRenderParams = new RenderParameters(); StandardParams standardParams = new StandardParams();
Map params = null; Map params = null;
// filters defined via <% foo | bar %>
Macro filterChain;
// comment macros are silently dropped during rendering // comment macros are silently dropped during rendering
boolean isCommentMacro = false; 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.start = start;
this.end = end;
int state = HANDLER; int state = PARSE_MACRONAME;
boolean escape = false; boolean escape = false;
char quotechar = '\u0000'; char quotechar = '\u0000';
String lastParamName = null; String lastParamName = null;
StringBuffer b = new StringBuffer(); 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]) { 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 '/': case '/':
b.append(source[i]); b.append(source[i]);
escape = false; escape = false;
if (state == HANDLER && "//".equals(b.toString())) { if (state == PARSE_MACRONAME && "//".equals(b.toString())) {
isCommentMacro = true; isCommentMacro = true;
return; // search macro end tag
while (i < sourceLength - 1 &&
(source[i] != '%' || source[i + 1] != '>')) {
i++;
}
break loop;
} }
break; break;
case '.': case '|':
if (state == HANDLER) { if (state == PARSE_PARAMNAME) {
handler = b.toString().trim(); filterChain = new Macro(i, 1);
i = filterChain.end - 2;
lastParamName = null;
b.setLength(0); b.setLength(0);
state = MACRO; break loop;
} else { }
b.append(source[i]); b.append(source[i]);
escape = false; escape = false;
}
break; break;
case '\\': case '\\':
@ -309,13 +365,13 @@ public final class Skin {
case '"': case '"':
case '\'': case '\'':
if (!escape && (state == PARAMVALUE)) { if (!escape && (state == PARSE_PARAMVALUE)) {
if (quotechar == source[i]) { if (quotechar == source[i]) {
// add parameter // add parameter
addParameter(lastParamName, b.toString()); addParameter(lastParamName, b.toString());
lastParamName = null; lastParamName = null;
b.setLength(0); b.setLength(0);
state = PARAMNAME; state = PARSE_PARAMNAME;
quotechar = '\u0000'; quotechar = '\u0000';
} else if (quotechar == '\u0000') { } else if (quotechar == '\u0000') {
quotechar = source[i]; quotechar = source[i];
@ -337,17 +393,17 @@ public final class Skin {
case '\r': case '\r':
case '\f': case '\f':
if ((state == MACRO) || ((state == HANDLER) && (b.length() > 0))) { if (state == PARSE_MACRONAME && b.length() > 0) {
name = b.toString().trim(); name = b.toString().trim();
b.setLength(0); b.setLength(0);
state = PARAMNAME; state = PARSE_PARAMNAME;
} else if ((state == PARAMVALUE) && (quotechar == '\u0000')) { } else if ((state == PARSE_PARAMVALUE) && (quotechar == '\u0000')) {
// add parameter // add parameter
addParameter(lastParamName, b.toString()); addParameter(lastParamName, b.toString());
lastParamName = null; lastParamName = null;
b.setLength(0); b.setLength(0);
state = PARAMNAME; state = PARSE_PARAMNAME;
} else if (state == PARAMVALUE) { } else if (state == PARSE_PARAMVALUE) {
b.append(source[i]); b.append(source[i]);
escape = false; escape = false;
} else { } else {
@ -358,10 +414,10 @@ public final class Skin {
case '=': case '=':
if (state == PARAMNAME) { if (state == PARSE_PARAMNAME) {
lastParamName = b.toString().trim(); lastParamName = b.toString().trim();
b.setLength(0); b.setLength(0);
state = PARAMVALUE; state = PARSE_PARAMVALUE;
} else { } else {
b.append(source[i]); b.append(source[i]);
escape = false; escape = false;
@ -375,42 +431,65 @@ public final class Skin {
} }
} }
this.end = Math.min(sourceLength, i + 2);
if (b.length() > 0) { if (b.length() > 0) {
if (lastParamName != null) { if (lastParamName != null) {
// add parameter // add parameter
addParameter(lastParamName, b.toString()); addParameter(lastParamName, b.toString());
} else if (state <= MACRO) { } else if (state == PARSE_MACRONAME) {
name = b.toString().trim(); name = b.toString().trim();
} }
} }
if (handler == null) { path = StringUtils.split(name, ".");
fullName = name; if (path.length <= 1) {
handler = HANDLER_GLOBAL;
} else { } 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 // check if this is parameter is relevant to us
if ("prefix".equals(name)) { if ("prefix".equals(name)) {
defaultRenderParams.prefix = value; standardParams.prefix = value;
} else if ("suffix".equals(name)) { } else if ("suffix".equals(name)) {
defaultRenderParams.suffix = value; standardParams.suffix = value;
} else if ("encoding".equals(name)) { } else if ("encoding".equals(name)) {
if ("html".equalsIgnoreCase(value)) { if ("html".equals(value)) {
encoding = ENCODE_HTML; encoding = ENCODE_HTML;
} else if ("xml".equalsIgnoreCase(value)) { } else if ("xml".equals(value)) {
encoding = ENCODE_XML; encoding = ENCODE_XML;
} else if ("form".equalsIgnoreCase(value)) { } else if ("form".equals(value)) {
encoding = ENCODE_FORM; encoding = ENCODE_FORM;
} else if ("url".equalsIgnoreCase(value)) { } else if ("url".equals(value)) {
encoding = ENCODE_URL; encoding = ENCODE_URL;
} else if ("all".equalsIgnoreCase(value)) { } else if ("all".equals(value)) {
encoding = ENCODE_ALL; encoding = ENCODE_ALL;
} else {
app.logEvent("Unrecognized encoding in skin macro: " + value);
} }
} else if ("default".equals(name)) { } else if ("default".equals(name)) {
defaultRenderParams.defaultValue = value; standardParams.defaultValue = value;
} else if ("failmode".equals(name)) {
standardParams.setFailMode(value);
} }
// Add parameter to parameter map // Add parameter to parameter map
@ -430,94 +509,29 @@ public final class Skin {
return; return;
} }
if ((sandbox != null) && !sandbox.contains(fullName)) { if ((sandbox != null) && !sandbox.contains(name)) {
reval.getResponse().write("[Macro " + fullName + " not allowed in sandbox]"); throw new RuntimeException("Macro " + name + " 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;
} }
try {
Object handlerObject = null; Object handlerObject = null;
// flag to tell whether we found our invocation target object try {
boolean objectFound = true;
if (handler != null) { if (handler != HANDLER_GLOBAL) {
// try to get handler from handlerCache first handlerObject = resolveHandler(thisObject, reval, handlerCache);
if (handlerCache != null) { handlerObject = resolvePath(handlerObject, reval);
handlerObject = handlerCache.get(handler);
} }
if (handlerObject == null) { if (handler == HANDLER_GLOBAL || 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 (objectFound) {
// 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 funcName = name + "_macro"; String propName = path[path.length - 1];
String funcName = propName + "_macro";
if (reval.scriptingEngine.hasFunction(handlerObject, funcName)) { if (reval.scriptingEngine.hasFunction(handlerObject, funcName)) {
StringBuffer buffer = reval.getResponse().getBuffer(); StringBuffer buffer = reval.getResponse().getBuffer();
RenderParameters renderParams = defaultRenderParams; StandardParams stdParams = standardParams;
// remember length of response buffer before calling macro // remember length of response buffer before calling macro
int bufLength = buffer.length(); int bufLength = buffer.length();
@ -528,67 +542,100 @@ public final class Skin {
if (params == null) { if (params == null) {
arguments = new Object[] { new SystemMap(4) }; 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 { } else {
wrappedParams = new CopyOnWriteMap(params); wrappedParams = new CopyOnWriteMap(params);
arguments = new Object[] { wrappedParams }; arguments = new Object[] { wrappedParams };
} }
Object value = reval.invokeDirectFunction(handlerObject, Object value = reval.invokeDirectFunction(handlerObject,
funcName, funcName,
arguments); arguments);
// if parameter map was modified create new renderParams to override defaults // if parameter map was modified create new renderParams to override defaults
if (wrappedParams != null && wrappedParams.wasModified()) { 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 // check if macro wrote out to response buffer
if (buffer.length() == bufLength) { if (buffer.length() == bufLength) {
// If the macro function didn't write anything to the response itself, // If the macro function didn't write anything to the response itself,
// we interpret its return value as macro output. // we interpret its return value as macro output.
writeResponse(value, buffer, renderParams, true); writeResponse(value, reval, thisObject, handlerCache, stdParams, true);
} else { } else {
// if an encoding is specified, re-encode the macro's output // if an encoding is specified, re-encode the macro's output
if (encoding != ENCODE_NONE) { if (encoding != ENCODE_NONE) {
String output = buffer.substring(bufLength); String output = buffer.substring(bufLength);
buffer.setLength(bufLength); buffer.setLength(bufLength);
writeResponse(output, buffer, renderParams, false); writeResponse(output, reval, thisObject, handlerCache, stdParams, false);
} else { } else {
// insert prefix, // insert prefix,
if (renderParams.prefix != null) { if (stdParams.prefix != null) {
buffer.insert(bufLength, renderParams.prefix); buffer.insert(bufLength, stdParams.prefix);
} }
// append suffix // append suffix
if (renderParams.suffix != null) { if (stdParams.suffix != null) {
buffer.append(renderParams.suffix); buffer.append(stdParams.suffix);
} }
} }
// Append macro return value even if it wrote something to the response, // Append macro return value even if it wrote something to the response,
// but don't render default value in case it returned nothing. // but don't render default value in case it returned nothing.
// We do this for the sake of consistency. // We do this for the sake of consistency.
writeResponse(value, buffer, renderParams, false); writeResponse(value, reval, thisObject, handlerCache, stdParams, false);
} }
} else { } else {
// for unhandled global macros display error message, if (handler == HANDLER_RESPONSE) {
// otherwise try property lookup // some special handling for response handler
if (handlerObject == null) { Object value = null;
String msg = "[Macro unhandled: " + fullName + "]"; 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 + " "); reval.getResponse().write(" " + msg + " ");
app.logEvent(msg); app.logEvent(msg);
}
} else { } else {
Object value = reval.scriptingEngine.get(handlerObject, name); Object value = getProperty(handlerObject, propName, reval);
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true); writeResponse(value, reval, thisObject, handlerCache, standardParams, true);
} }
} }
} else { } else {
String msg = "[Macro unhandled: " + fullName + "]"; if (!standardParams.silentFailMode) {
String msg = "[Macro unhandled: " + name + "]";
reval.getResponse().write(" " + msg + " "); reval.getResponse().write(" " + msg + " ");
app.logEvent(msg); app.logEvent(msg);
}
} }
} catch (RedirectException redir) { } catch (RedirectException redir) {
throw redir; throw redir;
@ -601,98 +648,176 @@ public final class Skin {
if ((msg == null) || (msg.length() < 10)) { if ((msg == null) || (msg.length() < 10)) {
msg = x.toString(); msg = x.toString();
} }
msg = new StringBuffer("Macro error in ").append(fullName) msg = new StringBuffer("Macro error in ").append(name)
.append(": ").append(msg).toString(); .append(": ").append(msg).toString();
reval.getResponse().write(" [" + msg + "] "); reval.getResponse().write(" [" + msg + "] ");
app.logError(msg, x); app.logError(msg, x);
} }
} }
private void renderFromResponse(RequestEvaluator reval) private Object invokeFilter(RequestEvaluator reval, Object returnValue,
throws UnsupportedEncodingException { Object thisObject, Map handlerCache)
Object value = null; throws Exception {
if ("message".equals(name)) { if ((sandbox != null) && !sandbox.contains(name)) {
value = reval.getResponse().getMessage(); throw new RuntimeException("Macro " + name + " not allowed in sandbox");
} else if ("error".equals(name)) { }
value = reval.getResponse().getError(); Object handlerObject = null;
if (handler != HANDLER_GLOBAL) {
handlerObject = resolveHandler(thisObject, reval, handlerCache);
handlerObject = resolvePath(handlerObject, reval);
} }
if (value == null) { String funcName = name + "_filter";
value = reval.getResponse().get(name);
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;
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 };
writeResponse(value, reval.getResponse().getBuffer(), defaultRenderParams, true);
}
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]");
} else { } else {
Object value = paramObject.get(name); 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 {
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. * Utility method for writing text out to the response object.
*/ */
void writeResponse(Object value, StringBuffer buffer, void writeResponse(Object value, RequestEvaluator reval,
RenderParameters renderParams, boolean useDefault) Object thisObject, Map handlerCache,
throws UnsupportedEncodingException { StandardParams stdParams, boolean useDefault)
throws Exception {
String text; 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 (value == null) {
if (useDefault) { if (useDefault) {
text = renderParams.defaultValue; text = (String) stdParams.defaultValue;
} else { } else {
return; return;
} }
} else { } else {
// do not render doubles as doubles unless text = reval.getScriptingEngine().toString(value);
// 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();
}
} }
if ((text != null) && (text.length() > 0)) { if ((text != null) && (text.length() > 0)) {
// only write prefix/suffix if value is not null, if we write the default // 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 // value provided by the macro tag, we assume it's already complete
if (renderParams.prefix != null && value != null) { if (stdParams.prefix != null && value != null) {
buffer.append(renderParams.prefix); buffer.append(stdParams.prefix);
} }
switch (encoding) { switch (encoding) {
@ -727,36 +852,65 @@ public final class Skin {
break; break;
} }
if (renderParams.suffix != null && value != null) { if (stdParams.suffix != null && value != null) {
buffer.append(renderParams.suffix); buffer.append(stdParams.suffix);
} }
} }
} }
public String toString() { public String toString() {
return "[Macro: " + fullName + "]"; return "[Macro: " + name + "]";
} }
/** /**
* Return the full name of the macro in handler.name notation * Return the full name of the macro in handler.name notation
* @return the macro name
*/ */
public String getFullName() { public String getName() {
return fullName; return name;
} }
} }
class RenderParameters { class StandardParams {
String prefix = null; Object prefix = null;
String suffix = null; Object suffix = null;
String defaultValue = 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"));
}
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;
} }
RenderParameters(Map map) {
prefix = (String) map.get("prefix");
suffix = (String) map.get("suffix");
defaultValue = (String) map.get("default");
}
} }
} }

View file

@ -109,14 +109,35 @@ public interface ScriptingEngine {
/** /**
* 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); 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. * Get an IPathElement that offers introspection services into the application.
* If this method returns null, no introspection is available for this kind of engine. * 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); Object f = ScriptableObject.getProperty(obj, functionName);
if ((f == ScriptableObject.NOT_FOUND) || !(f instanceof Function)) { if (!(f instanceof Function)) {
return null; return null;
} }
@ -353,51 +353,69 @@ public class RhinoEngine implements ScriptingEngine {
Object func = ScriptableObject.getProperty(op, fname); 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 * Check if an object has a defined property (public field if it
* is a java object) with that name. * is a java object) with that name.
*/ */
public Object get(Object obj, String propname) { public Object getProperty(Object obj, String propname) {
if ((obj == null) || (propname == null)) { if ((obj == null) || (propname == null))
return 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); Scriptable so = Context.toObject(obj, global);
try { try {
Object prop = so.get(propname, so); Object prop = so.get(propname, so);
if ((prop == null) || (prop == Undefined.instance) if ((prop == null)
|| (prop == Undefined.instance)
|| (prop == ScriptableObject.NOT_FOUND)) { || (prop == ScriptableObject.NOT_FOUND)) {
return null; return null;
} else if (prop instanceof Wrapper) { } else if (prop instanceof Wrapper) {
return ((Wrapper) prop).unwrap(); return ((Wrapper) prop).unwrap();
} else { } else {
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 // not all Rhino types convert to a string as expected
// when calling toString() - try to do better by using // when calling toString() - try to do better by using
// Rhino's ScriptRuntime.toString(). Note that this // Rhino's ScriptRuntime.toString(). Note that this
@ -405,15 +423,11 @@ public class RhinoEngine implements ScriptingEngine {
// a string representation of the object - which is // a string representation of the object - which is
// currently the case since it's only used in Skin rendering. // currently the case since it's only used in Skin rendering.
try { try {
return ScriptRuntime.toString(prop); return ScriptRuntime.toString(obj);
} catch (Exception x) { } catch (Exception x) {
// just return original property object // just return original property object
} }
return prop; return obj.toString();
}
} catch (Exception esx) {
return null;
}
} }
/** /**