0001: /*
0002: * Helma License Notice
0003: *
0004: * The contents of this file are subject to the Helma License
0005: * Version 2.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://adele.helma.org/download/helma/license.txt
0008: *
0009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
0010: *
0011: * $RCSfile$
0012: * $Author: hannes $
0013: * $Revision: 8638 $
0014: * $Date: 2007-11-12 15:43:48 +0100 (Mon, 12 Nov 2007) $
0015: */
0016:
0017: package helma.framework.core;
0018:
0019: import helma.framework.*;
0020: import helma.framework.repository.Resource;
0021: import helma.objectmodel.ConcurrencyException;
0022: import helma.util.*;
0023: import helma.scripting.ScriptingEngine;
0024:
0025: import java.util.*;
0026: import java.io.UnsupportedEncodingException;
0027: import java.io.Reader;
0028: import java.io.InputStreamReader;
0029: import java.io.IOException;
0030:
0031: /**
0032: * This represents a Helma skin, i.e. a template created from containing Macro tags
0033: * that will be dynamically evaluated.. It uses the request path array
0034: * from the RequestEvaluator object to resolve Macro handlers by type name.
0035: */
0036: public final class Skin {
0037:
0038: private Macro[] macros;
0039: private Application app;
0040: private char[] source;
0041: private int offset, length; // start and end index of skin content
0042: private HashSet sandbox;
0043: private HashMap subskins;
0044: private Skin parentSkin = this ;
0045:
0046: static private final int PARSE_MACRONAME = 0;
0047: static private final int PARSE_PARAM = 1;
0048: static private final int PARSE_DONE = 2;
0049:
0050: static private final int ENCODE_NONE = 0;
0051: static private final int ENCODE_HTML = 1;
0052: static private final int ENCODE_XML = 2;
0053: static private final int ENCODE_FORM = 3;
0054: static private final int ENCODE_URL = 4;
0055: static private final int ENCODE_ALL = 5;
0056:
0057: static private final int HANDLER_RESPONSE = 0;
0058: static private final int HANDLER_REQUEST = 1;
0059: static private final int HANDLER_SESSION = 2;
0060: static private final int HANDLER_PARAM = 3;
0061: static private final int HANDLER_GLOBAL = 4;
0062: static private final int HANDLER_THIS = 5;
0063: static private final int HANDLER_OTHER = 6;
0064:
0065: static private final int FAIL_DEFAULT = 0;
0066: static private final int FAIL_SILENT = 1;
0067: static private final int FAIL_VERBOSE = 2;
0068:
0069: /**
0070: * Create a skin without any restrictions on which macros are allowed to be called from it
0071: */
0072: public Skin(String content, Application app) {
0073: this .app = app;
0074: this .sandbox = null;
0075: this .source = content.toCharArray();
0076: this .offset = 0;
0077: this .length = source.length;
0078: parse();
0079: }
0080:
0081: /**
0082: * Create a skin with a sandbox which contains the names of macros allowed to be called
0083: */
0084: public Skin(String content, Application app, HashSet sandbox) {
0085: this .app = app;
0086: this .sandbox = sandbox;
0087: this .source = content.toCharArray();
0088: this .offset = 0;
0089: length = source.length;
0090: parse();
0091: }
0092:
0093: /**
0094: * Create a skin without any restrictions on the macros from a char array.
0095: */
0096: public Skin(char[] content, int length, Application app) {
0097: this .app = app;
0098: this .sandbox = null;
0099: this .source = content;
0100: this .offset = 0;
0101: this .length = length;
0102: parse();
0103: }
0104:
0105: /**
0106: * Subskin constructor.
0107: */
0108: private Skin(Skin parentSkin, Macro anchorMacro) {
0109: this .parentSkin = parentSkin;
0110: this .app = parentSkin.app;
0111: this .sandbox = parentSkin.sandbox;
0112: this .source = parentSkin.source;
0113: this .offset = anchorMacro.end;
0114: this .length = parentSkin.length;
0115: parentSkin.addSubskin(anchorMacro.name, this );
0116: parse();
0117: }
0118:
0119: public static Skin getSkin(Resource res, Application app)
0120: throws IOException {
0121: String encoding = app.getProperty("skinCharset");
0122: Reader reader;
0123: if (encoding == null) {
0124: reader = new InputStreamReader(res.getInputStream());
0125: } else {
0126: reader = new InputStreamReader(res.getInputStream(),
0127: encoding);
0128: }
0129:
0130: int length = (int) res.getLength();
0131: char[] characterBuffer = new char[length];
0132: int read = 0;
0133: try {
0134: while (read < length) {
0135: int r = reader.read(characterBuffer, read, length
0136: - read);
0137: if (r == -1)
0138: break;
0139: read += r;
0140: }
0141: } finally {
0142: reader.close();
0143: }
0144: return new Skin(characterBuffer, read, app);
0145: }
0146:
0147: /**
0148: * Parse a skin object from source text
0149: */
0150: private void parse() {
0151: ArrayList partBuffer = new ArrayList();
0152:
0153: boolean escape = false;
0154: for (int i = offset; i < (length - 1); i++) {
0155: if (source[i] == '<' && source[i + 1] == '%' && !escape) {
0156: // found macro start tag
0157: Macro macro = new Macro(i, 2);
0158: if (macro.isSubskinMacro) {
0159: new Skin(parentSkin, macro);
0160: length = i;
0161: break;
0162: } else {
0163: partBuffer.add(macro);
0164: }
0165: i = macro.end - 1;
0166: } else {
0167: escape = source[i] == '\\' && !escape;
0168: }
0169: }
0170:
0171: macros = new Macro[partBuffer.size()];
0172: partBuffer.toArray(macros);
0173: }
0174:
0175: private void addSubskin(String name, Skin subskin) {
0176: if (subskins == null) {
0177: subskins = new HashMap();
0178: }
0179: subskins.put(name, subskin);
0180: }
0181:
0182: /**
0183: * Check if this skin has a main skin, as opposed to consisting just of subskins
0184: * @return true if this skin contains a main skin
0185: */
0186: public boolean hasMainskin() {
0187: return length - offset > 0 || subskins == null;
0188: }
0189:
0190: /**
0191: * Check if this skin contains a subskin with the given name
0192: * @param name a subskin name
0193: * @return true if the given subskin exists
0194: */
0195: public boolean hasSubskin(String name) {
0196: return subskins != null && subskins.containsKey(name);
0197: }
0198:
0199: /**
0200: * Get a subskin by name
0201: * @param name the subskin name
0202: * @return the subskin
0203: */
0204: public Skin getSubskin(String name) {
0205: return subskins == null ? null : (Skin) subskins.get(name);
0206: }
0207:
0208: /**
0209: * Return an array of subskin names defined in this skin
0210: * @return a string array containing this skin's substrings
0211: */
0212: public String[] getSubskinNames() {
0213: return subskins == null ? new String[0] : (String[]) subskins
0214: .keySet().toArray(new String[0]);
0215: }
0216:
0217: /**
0218: * Get the raw source text this skin was parsed from
0219: */
0220: public String getSource() {
0221: return new String(source, offset, length - offset);
0222: }
0223:
0224: /**
0225: * Render this skin and return it as string
0226: */
0227: public String renderAsString(RequestEvaluator reval,
0228: Object this Object, Object paramObject)
0229: throws RedirectException, UnsupportedEncodingException {
0230: String result = "";
0231: ResponseTrans res = reval.getResponse();
0232: res.pushBuffer(null);
0233: try {
0234: render(reval, this Object, paramObject);
0235: } finally {
0236: result = res.popString();
0237: }
0238: return result;
0239: }
0240:
0241: /**
0242: * Render this skin
0243: */
0244: public void render(RequestEvaluator reval, Object this Object,
0245: Object paramObject) throws RedirectException,
0246: UnsupportedEncodingException {
0247: // check for endless skin recursion
0248: if (++reval.skinDepth > 50) {
0249: throw new RuntimeException(
0250: "Recursive skin invocation suspected");
0251: }
0252:
0253: ResponseTrans res = reval.getResponse();
0254:
0255: if (macros == null) {
0256: res.write(source, offset, length - offset);
0257: reval.skinDepth--;
0258: return;
0259: }
0260:
0261: // register param object, remember previous one to reset afterwards
0262: Map handlers = res.getMacroHandlers();
0263: Object previousParam = handlers.put("param", paramObject);
0264: Skin previousSkin = res.switchActiveSkin(parentSkin);
0265:
0266: try {
0267: int written = offset;
0268: Map handlerCache = null;
0269:
0270: if (macros.length > 3) {
0271: handlerCache = new HashMap();
0272: }
0273: RenderContext cx = new RenderContext(reval, this Object,
0274: handlerCache);
0275:
0276: for (int i = 0; i < macros.length; i++) {
0277: if (macros[i].start > written) {
0278: res.write(source, written, macros[i].start
0279: - written);
0280: }
0281:
0282: macros[i].render(cx);
0283: written = macros[i].end;
0284: }
0285:
0286: if (written < length) {
0287: res.write(source, written, length - written);
0288: }
0289: } finally {
0290: reval.skinDepth--;
0291: res.switchActiveSkin(previousSkin);
0292: if (previousParam == null) {
0293: handlers.remove("param");
0294: } else {
0295: handlers.put("param", previousParam);
0296: }
0297: }
0298: }
0299:
0300: /**
0301: * Check if a certain macro is present in this skin. The macro name is in handler.name notation
0302: */
0303: public boolean containsMacro(String macroname) {
0304: for (int i = 0; i < macros.length; i++) {
0305: if (macros[i] instanceof Macro) {
0306: Macro m = macros[i];
0307:
0308: if (macroname.equals(m.name)) {
0309: return true;
0310: }
0311: }
0312: }
0313:
0314: return false;
0315: }
0316:
0317: /**
0318: * Adds a macro to the list of allowed macros. The macro is in handler.name notation.
0319: */
0320: public void allowMacro(String macroname) {
0321: if (sandbox == null) {
0322: sandbox = new HashSet();
0323: }
0324:
0325: sandbox.add(macroname);
0326: }
0327:
0328: private Object processParameter(Object value, RenderContext cx)
0329: throws Exception {
0330: if (value instanceof Macro) {
0331: return ((Macro) value).invokeAsParameter(cx);
0332: } else {
0333: return value;
0334: }
0335: }
0336:
0337: class Macro {
0338: final int start, end;
0339: String name;
0340: String[] path;
0341: int handlerType = HANDLER_OTHER;
0342: int encoding = ENCODE_NONE;
0343: boolean hasNestedMacros = false;
0344:
0345: // default render parameters - may be overridden if macro changes
0346: // param.prefix/suffix/default
0347: StandardParams standardParams = new StandardParams();
0348: Map namedParams = null;
0349: List positionalParams = null;
0350: // filters defined via <% foo | bar %>
0351: Macro filterChain;
0352:
0353: // comment macros are silently dropped during rendering
0354: boolean isCommentMacro = false;
0355: // subskin macros delimits the beginning of a new subskin
0356: boolean isSubskinMacro = false;
0357:
0358: /**
0359: * Create and parse a new macro.
0360: * @param start the start of the macro within the skin source
0361: * @param macroOffset offset of the macro content from the start index
0362: */
0363: Macro(int start, int macroOffset) {
0364: this .start = start;
0365:
0366: int i = parse(macroOffset, false);
0367:
0368: if (isSubskinMacro) {
0369: if (i + 1 < length && source[i] == '\r'
0370: && source[i + 1] == '\n')
0371: end = Math.min(length, i + 2);
0372: else if (i < length
0373: && (source[i] == '\r' || source[i] == '\n'))
0374: end = Math.min(length, i + 1);
0375: else
0376: end = Math.min(length, i);
0377: } else {
0378: end = Math.min(length, i);
0379: }
0380:
0381: path = StringUtils.split(name, ".");
0382: if (path.length <= 1) {
0383: handlerType = HANDLER_GLOBAL;
0384: } else {
0385: String handlerName = path[0];
0386: if ("this".equalsIgnoreCase(handlerName)) {
0387: handlerType = HANDLER_THIS;
0388: } else if ("response".equalsIgnoreCase(handlerName)) {
0389: handlerType = HANDLER_RESPONSE;
0390: } else if ("request".equalsIgnoreCase(handlerName)) {
0391: handlerType = HANDLER_REQUEST;
0392: } else if ("session".equalsIgnoreCase(handlerName)) {
0393: handlerType = HANDLER_SESSION;
0394: } else if ("param".equalsIgnoreCase(handlerName)) {
0395: handlerType = HANDLER_PARAM;
0396: }
0397: }
0398: }
0399:
0400: private int parse(int macroOffset, boolean lenient) {
0401: int state = PARSE_MACRONAME;
0402: boolean escape = false;
0403: char quotechar = '\u0000';
0404: String lastParamName = null;
0405: StringBuffer b = new StringBuffer();
0406: int i;
0407:
0408: loop: for (i = start + macroOffset; i < length - 1; i++) {
0409:
0410: switch (source[i]) {
0411:
0412: case '<':
0413:
0414: if (state == PARSE_PARAM && quotechar == '\u0000'
0415: && b.length() == 0 && source[i + 1] == '%') {
0416: Macro macro = new Macro(i, 2);
0417: addParameter(lastParamName, macro);
0418: lastParamName = null;
0419: b.setLength(0);
0420: i = macro.end - 1;
0421: } else {
0422: b.append(source[i]);
0423: escape = false;
0424: }
0425: break;
0426:
0427: case '%':
0428:
0429: if ((state != PARSE_PARAM || quotechar == '\u0000' || lenient)
0430: && source[i + 1] == '>') {
0431: state = PARSE_DONE;
0432: break loop;
0433: }
0434: b.append(source[i]);
0435: escape = false;
0436: break;
0437:
0438: case '/':
0439:
0440: b.append(source[i]);
0441: escape = false;
0442:
0443: if (state == PARSE_MACRONAME
0444: && "//".equals(b.toString())) {
0445: isCommentMacro = true;
0446: // search macro end tag
0447: while (i < length - 1
0448: && (source[i] != '%' || source[i + 1] != '>')) {
0449: i++;
0450: }
0451: state = PARSE_DONE;
0452: break loop;
0453: }
0454: break;
0455:
0456: case '#':
0457:
0458: if (state == PARSE_MACRONAME && b.length() == 0) {
0459: // this is a subskin/skinlet
0460: isSubskinMacro = true;
0461: break;
0462: }
0463: b.append(source[i]);
0464: escape = false;
0465: break;
0466:
0467: case '|':
0468:
0469: if (!escape && quotechar == '\u0000') {
0470: filterChain = new Macro(i, 1);
0471: i = filterChain.end - 2;
0472: lastParamName = null;
0473: b.setLength(0);
0474: state = PARSE_DONE;
0475: break loop;
0476: }
0477: b.append(source[i]);
0478: escape = false;
0479: break;
0480:
0481: case '\\':
0482:
0483: if (escape) {
0484: b.append(source[i]);
0485: }
0486:
0487: escape = !escape;
0488:
0489: break;
0490:
0491: case '"':
0492: case '\'':
0493:
0494: if (!escape && state == PARSE_PARAM) {
0495: if (quotechar == source[i]) {
0496: if (source[i + 1] != '%'
0497: && !Character
0498: .isWhitespace(source[i + 1])
0499: && !lenient) {
0500: // closing quotes and next character is not space or end tag -
0501: // switch to lenient mode
0502: reset();
0503: return parse(macroOffset, true);
0504: }
0505: // add parameter
0506: addParameter(lastParamName, b.toString());
0507: lastParamName = null;
0508: b.setLength(0);
0509: quotechar = '\u0000';
0510: } else if (quotechar == '\u0000') {
0511: quotechar = source[i];
0512: b.setLength(0);
0513: } else {
0514: b.append(source[i]);
0515: }
0516: } else {
0517: b.append(source[i]);
0518: }
0519:
0520: escape = false;
0521:
0522: break;
0523:
0524: case ' ':
0525: case '\t':
0526: case '\n':
0527: case '\r':
0528: case '\f':
0529:
0530: if (state == PARSE_MACRONAME && b.length() > 0) {
0531: name = b.toString().trim();
0532: b.setLength(0);
0533: state = PARSE_PARAM;
0534: } else if (state == PARSE_PARAM) {
0535: if (quotechar == '\u0000') {
0536: if (b.length() > 0) {
0537: // add parameter
0538: addParameter(lastParamName, b
0539: .toString());
0540: lastParamName = null;
0541: b.setLength(0);
0542: }
0543: } else {
0544: b.append(source[i]);
0545: escape = false;
0546: }
0547: }
0548:
0549: break;
0550:
0551: case '=':
0552:
0553: if (!escape && quotechar == '\u0000'
0554: && state == PARSE_PARAM
0555: && lastParamName == null) {
0556: lastParamName = b.toString().trim();
0557: b.setLength(0);
0558: } else {
0559: b.append(source[i]);
0560: escape = false;
0561: }
0562:
0563: break;
0564:
0565: default:
0566: b.append(source[i]);
0567: escape = false;
0568: }
0569:
0570: if (i == length - 2
0571: && !lenient
0572: && (state != PARSE_DONE || quotechar != '\u0000')) {
0573: // macro tag is not properly terminated, switch to lenient mode
0574: reset();
0575: return parse(macroOffset, true);
0576: }
0577: }
0578:
0579: if (b.length() > 0) {
0580: if (name == null) {
0581: name = b.toString().trim();
0582: } else {
0583: addParameter(lastParamName, b.toString());
0584: }
0585: }
0586:
0587: if (state != PARSE_DONE) {
0588: app.logError("Unterminated Macro Tag: " + this );
0589: }
0590:
0591: return i + 2;
0592: }
0593:
0594: private void reset() {
0595: filterChain = null;
0596: name = null;
0597: standardParams = new StandardParams();
0598: namedParams = null;
0599: positionalParams = null;
0600: }
0601:
0602: private void addParameter(String name, Object value) {
0603: if (!(value instanceof String)) {
0604: hasNestedMacros = true;
0605: }
0606: if (name == null) {
0607: // take shortcut for positional parameters
0608: if (positionalParams == null) {
0609: positionalParams = new ArrayList();
0610: }
0611: positionalParams.add(value);
0612: return;
0613: }
0614: // check if this is parameter is relevant to us
0615: if ("prefix".equals(name)) {
0616: standardParams.prefix = value;
0617: } else if ("suffix".equals(name)) {
0618: standardParams.suffix = value;
0619: } else if ("encoding".equals(name)) {
0620: if ("html".equals(value)) {
0621: encoding = ENCODE_HTML;
0622: } else if ("xml".equals(value)) {
0623: encoding = ENCODE_XML;
0624: } else if ("form".equals(value)) {
0625: encoding = ENCODE_FORM;
0626: } else if ("url".equals(value)) {
0627: encoding = ENCODE_URL;
0628: } else if ("all".equals(value)) {
0629: encoding = ENCODE_ALL;
0630: } else {
0631: app
0632: .logEvent("Unrecognized encoding in skin macro: "
0633: + value);
0634: }
0635: } else if ("default".equals(name)) {
0636: standardParams.defaultValue = value;
0637: } else if ("failmode".equals(name)) {
0638: standardParams.setFailMode(value);
0639: }
0640:
0641: // Add parameter to parameter map
0642: if (namedParams == null) {
0643: namedParams = new HashMap();
0644: }
0645: namedParams.put(name, value);
0646: }
0647:
0648: private Object invokeAsMacro(RenderContext cx,
0649: StandardParams stdParams, boolean asObject)
0650: throws Exception {
0651:
0652: // immediately return for comment macros
0653: if (isCommentMacro || name == null) {
0654: return null;
0655: }
0656:
0657: if ((sandbox != null) && !sandbox.contains(name)) {
0658: throw new RuntimeException("Macro " + name
0659: + " not allowed in sandbox");
0660: }
0661:
0662: Object handler = null;
0663: Object value = null;
0664: ScriptingEngine engine = cx.reval.scriptingEngine;
0665:
0666: if (handlerType != HANDLER_GLOBAL) {
0667: handler = cx.resolveHandler(path[0], handlerType);
0668: handler = resolvePath(handler, cx.reval);
0669: }
0670:
0671: if (handlerType == HANDLER_GLOBAL || handler != null) {
0672: // check if a function called name_macro is defined.
0673: // if so, the macro evaluates to the function. Otherwise,
0674: // a property/field with the name is used, if defined.
0675: String propName = path[path.length - 1];
0676: String funcName = resolveFunctionName(handler, propName
0677: + "_macro", engine);
0678:
0679: // remember length of response buffer before calling macro
0680: StringBuffer buffer = cx.reval.getResponse()
0681: .getBuffer();
0682: int bufLength = buffer.length();
0683:
0684: if (funcName != null) {
0685:
0686: Object[] arguments = prepareArguments(0, cx);
0687: // get reference to rendered named params for after invocation
0688: Map params = (Map) arguments[0];
0689: value = cx.reval.invokeDirectFunction(handler,
0690: funcName, arguments);
0691:
0692: // update StandardParams to override defaults in case the macro changed anything
0693: if (stdParams != null)
0694: stdParams.readFrom(params);
0695:
0696: // if macro has a filter chain and didn't return anything, use output
0697: // as filter argument.
0698: if (asObject && value == null
0699: && buffer.length() > bufLength) {
0700: value = buffer.substring(bufLength);
0701: buffer.setLength(bufLength);
0702: }
0703:
0704: return filter(value, cx);
0705: } else {
0706: if (handlerType == HANDLER_RESPONSE) {
0707: // some special handling for response handler
0708: if ("message".equals(propName))
0709: value = cx.reval.getResponse().getMessage();
0710: else if ("error".equals(propName))
0711: value = cx.reval.getResponse().getError();
0712: if (value != null)
0713: return filter(value, cx);
0714: }
0715: // display error message unless onUnhandledMacro is defined or silent failmode is on
0716: if (!engine.hasProperty(handler, propName)) {
0717: if (engine.hasFunction(handler,
0718: "onUnhandledMacro", false)) {
0719: Object[] arguments = prepareArguments(1, cx);
0720: arguments[0] = propName;
0721: value = cx.reval.invokeDirectFunction(
0722: handler, "onUnhandledMacro",
0723: arguments);
0724: // if macro has a filter chain and didn't return anything, use output
0725: // as filter argument.
0726: if (asObject && value == null
0727: && buffer.length() > bufLength) {
0728: value = buffer.substring(bufLength);
0729: buffer.setLength(bufLength);
0730: }
0731: } else if (standardParams.verboseFailmode(
0732: handler, engine)) {
0733: throw new UnhandledMacroException(name);
0734: }
0735: } else {
0736: value = engine.getProperty(handler, propName);
0737: }
0738: return filter(value, cx);
0739: }
0740: } else if (standardParams.verboseFailmode(handler, engine)) {
0741: throw new UnhandledMacroException(name);
0742: }
0743: return filter(null, cx);
0744: }
0745:
0746: /**
0747: * Render this macro as nested macro, only converting to string
0748: * if necessary.
0749: */
0750: Object invokeAsParameter(RenderContext cx) throws Exception {
0751: StandardParams stdParams = standardParams.render(cx);
0752: Object value = invokeAsMacro(cx, stdParams, true);
0753: if (stdParams.prefix != null || stdParams.suffix != null) {
0754: ResponseTrans res = cx.reval.getResponse();
0755: res.pushBuffer(null);
0756: writeResponse(value, cx.reval, stdParams, true);
0757: return res.popString();
0758: } else if (stdParams.defaultValue != null
0759: && (value == null || "".equals(value))) {
0760: return stdParams.defaultValue;
0761: } else {
0762: return value;
0763: }
0764: }
0765:
0766: /**
0767: * Render the macro given a handler object.
0768: */
0769: void render(RenderContext cx) throws RedirectException,
0770: UnsupportedEncodingException {
0771: StringBuffer buffer = cx.reval.getResponse().getBuffer();
0772: // remember length of response buffer before calling macro
0773: int bufLength = buffer.length();
0774: try {
0775: StandardParams stdParams = standardParams.render(cx);
0776: boolean asObject = filterChain != null;
0777: Object value = invokeAsMacro(cx, stdParams, asObject);
0778:
0779: // check if macro wrote out to response buffer
0780: if (buffer.length() == bufLength) {
0781: // If the macro function didn't write anything to the response itself,
0782: // we interpret its return value as macro output.
0783: writeResponse(value, cx.reval, stdParams, true);
0784: } else {
0785: // if an encoding is specified, re-encode the macro's output
0786: if (encoding != ENCODE_NONE) {
0787: String output = buffer.substring(bufLength);
0788:
0789: buffer.setLength(bufLength);
0790: writeResponse(output, cx.reval, stdParams,
0791: false);
0792: } else {
0793: // insert prefix,
0794: if (stdParams.prefix != null) {
0795: buffer.insert(bufLength, stdParams.prefix);
0796: }
0797: // append suffix
0798: if (stdParams.suffix != null) {
0799: buffer.append(stdParams.suffix);
0800: }
0801: }
0802:
0803: // Append macro return value even if it wrote something to the response,
0804: // but don't render default value in case it returned nothing.
0805: // We do this for the sake of consistency.
0806: writeResponse(value, cx.reval, stdParams, false);
0807: }
0808:
0809: } catch (RedirectException redir) {
0810: throw redir;
0811: } catch (ConcurrencyException concur) {
0812: throw concur;
0813: } catch (TimeoutException timeout) {
0814: throw timeout;
0815: } catch (UnhandledMacroException unhandled) {
0816: String msg = "Unhandled Macro: "
0817: + unhandled.getMessage();
0818: cx.reval.getResponse().write(" [" + msg + "] ");
0819: app.logError(msg);
0820: } catch (Exception x) {
0821: String msg = x.getMessage();
0822: if ((msg == null) || (msg.length() < 10)) {
0823: msg = x.toString();
0824: }
0825: msg = new StringBuffer("Macro error in ").append(name)
0826: .append(": ").append(msg).toString();
0827: cx.reval.getResponse().write(" [" + msg + "] ");
0828: app.logError(msg, x);
0829: }
0830: }
0831:
0832: private Object filter(Object returnValue, RenderContext cx)
0833: throws Exception {
0834: // invoke filter chain if defined
0835: if (filterChain != null) {
0836: return filterChain.invokeAsFilter(returnValue, cx);
0837: } else {
0838: return returnValue;
0839: }
0840: }
0841:
0842: private Object invokeAsFilter(Object returnValue,
0843: RenderContext cx) throws Exception {
0844:
0845: if (name == null) {
0846: throw new RuntimeException("Empty macro filter");
0847: } else if (sandbox != null && !sandbox.contains(name)) {
0848: throw new RuntimeException("Macro " + name
0849: + " not allowed in sandbox");
0850: }
0851: Object handlerObject = null;
0852:
0853: if (handlerType != HANDLER_GLOBAL) {
0854: handlerObject = cx.resolveHandler(path[0], handlerType);
0855: handlerObject = resolvePath(handlerObject, cx.reval);
0856: }
0857:
0858: String propName = path[path.length - 1] + "_filter";
0859: String funcName = resolveFunctionName(handlerObject,
0860: propName, cx.reval.scriptingEngine);
0861:
0862: if (funcName != null) {
0863: Object[] arguments = prepareArguments(1, cx);
0864: arguments[0] = returnValue;
0865: Object retval = cx.reval.invokeDirectFunction(
0866: handlerObject, funcName, arguments);
0867:
0868: return filter(retval, cx);
0869: } else {
0870: throw new RuntimeException("Undefined Filter " + name);
0871: }
0872: }
0873:
0874: private Object[] prepareArguments(int offset, RenderContext cx)
0875: throws Exception {
0876: int nPosArgs = (positionalParams == null) ? 0
0877: : positionalParams.size();
0878: Object[] arguments = new Object[offset + 1 + nPosArgs];
0879:
0880: if (namedParams == null) {
0881: arguments[offset] = new SystemMap(4);
0882: } else if (hasNestedMacros) {
0883: SystemMap map = new SystemMap(
0884: (int) (namedParams.size() * 1.5));
0885: for (Iterator it = namedParams.entrySet().iterator(); it
0886: .hasNext();) {
0887: Map.Entry entry = (Map.Entry) it.next();
0888: Object value = entry.getValue();
0889: if (!(value instanceof String))
0890: value = processParameter(value, cx);
0891: map.put(entry.getKey(), value);
0892: }
0893: arguments[offset] = map;
0894: } else {
0895: // pass a clone/copy of the parameter map so if the script changes it,
0896: arguments[offset] = new CopyOnWriteMap(namedParams);
0897: }
0898: if (positionalParams != null) {
0899: for (int i = 0; i < nPosArgs; i++) {
0900: Object value = positionalParams.get(i);
0901: if (!(value instanceof String))
0902: value = processParameter(value, cx);
0903: arguments[offset + 1 + i] = value;
0904: }
0905: }
0906: return arguments;
0907: }
0908:
0909: private Object resolvePath(Object handler,
0910: RequestEvaluator reval) throws Exception {
0911: for (int i = 1; i < path.length - 1; i++) {
0912: Object[] arguments = { path[i] };
0913: Object next = reval.invokeDirectFunction(handler,
0914: "getMacroHandler", arguments);
0915: if (next != null) {
0916: handler = next;
0917: } else if (!reval.scriptingEngine
0918: .isTypedObject(handler)) {
0919: handler = reval.scriptingEngine.getProperty(
0920: handler, path[i]);
0921: if (handler == null) {
0922: return null;
0923: }
0924: } else {
0925: return null;
0926: }
0927: }
0928: return handler;
0929: }
0930:
0931: private String resolveFunctionName(Object handler,
0932: String functionName, ScriptingEngine engine) {
0933: if (handlerType == HANDLER_GLOBAL) {
0934: String[] macroPath = app.globalMacroPath;
0935: if (macroPath == null || macroPath.length == 0) {
0936: if (engine.hasFunction(null, functionName, false))
0937: return functionName;
0938: } else {
0939: for (int i = 0; i < macroPath.length; i++) {
0940: String path = macroPath[i];
0941: String funcName = path == null
0942: || path.length() == 0 ? functionName
0943: : path + "." + functionName;
0944: if (engine.hasFunction(null, funcName, true))
0945: return funcName;
0946: }
0947: }
0948: } else {
0949: if (engine.hasFunction(handler, functionName, false))
0950: return functionName;
0951: }
0952: return null;
0953: }
0954:
0955: /**
0956: * Utility method for writing text out to the response object.
0957: */
0958: void writeResponse(Object value, RequestEvaluator reval,
0959: StandardParams stdParams, boolean useDefault)
0960: throws Exception {
0961: String text;
0962: StringBuffer buffer = reval.getResponse().getBuffer();
0963:
0964: if (value == null || "".equals(value)) {
0965: if (useDefault) {
0966: text = (String) stdParams.defaultValue;
0967: } else {
0968: return;
0969: }
0970: } else {
0971: text = reval.scriptingEngine.toString(value);
0972: }
0973:
0974: if ((text != null) && (text.length() > 0)) {
0975: // only write prefix/suffix if value is not null, if we write the default
0976: // value provided by the macro tag, we assume it's already complete
0977: if (stdParams.prefix != null && value != null) {
0978: buffer.append(stdParams.prefix);
0979: }
0980:
0981: switch (encoding) {
0982: case ENCODE_NONE:
0983: buffer.append(text);
0984:
0985: break;
0986:
0987: case ENCODE_HTML:
0988: HtmlEncoder.encode(text, buffer);
0989:
0990: break;
0991:
0992: case ENCODE_XML:
0993: HtmlEncoder.encodeXml(text, buffer);
0994:
0995: break;
0996:
0997: case ENCODE_FORM:
0998: HtmlEncoder.encodeFormValue(text, buffer);
0999:
1000: break;
1001:
1002: case ENCODE_URL:
1003: buffer.append(UrlEncoded.encode(text, app.charset));
1004:
1005: break;
1006:
1007: case ENCODE_ALL:
1008: HtmlEncoder.encodeAll(text, buffer);
1009:
1010: break;
1011: }
1012:
1013: if (stdParams.suffix != null && value != null) {
1014: buffer.append(stdParams.suffix);
1015: }
1016: }
1017: }
1018:
1019: public String toString() {
1020: return "[Macro: " + name + "]";
1021: }
1022:
1023: /**
1024: * Return the full name of the macro in handler.name notation
1025: * @return the macro name
1026: */
1027: String getName() {
1028: return name;
1029: }
1030: }
1031:
1032: class StandardParams {
1033: Object prefix = null;
1034: Object suffix = null;
1035: Object defaultValue = null;
1036: int failmode = FAIL_DEFAULT;
1037:
1038: StandardParams() {
1039: }
1040:
1041: StandardParams(Map map) {
1042: readFrom(map);
1043: }
1044:
1045: void readFrom(Map map) {
1046: prefix = map.get("prefix");
1047: suffix = map.get("suffix");
1048: defaultValue = map.get("default");
1049: }
1050:
1051: boolean containsMacros() {
1052: return !(prefix instanceof String)
1053: || !(suffix instanceof String)
1054: || !(defaultValue instanceof String);
1055: }
1056:
1057: void setFailMode(Object value) {
1058: if ("silent".equals(value))
1059: failmode = FAIL_SILENT;
1060: else if ("verbose".equals(value))
1061: failmode = FAIL_VERBOSE;
1062: else if (value != null)
1063: app.logEvent("unrecognized failmode value: " + value);
1064: }
1065:
1066: boolean verboseFailmode(Object handler, ScriptingEngine engine) {
1067: return (failmode == FAIL_VERBOSE)
1068: || (failmode == FAIL_DEFAULT && (handler == null || engine
1069: .isTypedObject(handler)));
1070: }
1071:
1072: StandardParams render(RenderContext cx) throws Exception {
1073: if (!containsMacros())
1074: return this ;
1075: StandardParams stdParams = new StandardParams();
1076: stdParams.prefix = renderToString(prefix, cx);
1077: stdParams.suffix = renderToString(suffix, cx);
1078: stdParams.defaultValue = renderToString(defaultValue, cx);
1079: return stdParams;
1080: }
1081:
1082: String renderToString(Object obj, RenderContext cx)
1083: throws Exception {
1084: Object value = processParameter(obj, cx);
1085: if (value == null)
1086: return null;
1087: else if (value instanceof String)
1088: return (String) value;
1089: else
1090: return cx.reval.scriptingEngine.toString(value);
1091: }
1092:
1093: }
1094:
1095: class RenderContext {
1096: final RequestEvaluator reval;
1097: final Object this Object;
1098: final Map handlerCache;
1099:
1100: RenderContext(RequestEvaluator reval, Object this Object,
1101: Map handlerCache) {
1102: this .reval = reval;
1103: this .this Object = this Object;
1104: this .handlerCache = handlerCache;
1105: }
1106:
1107: private Object resolveHandler(String handlerName,
1108: int handlerType) {
1109: switch (handlerType) {
1110: case HANDLER_THIS:
1111: return this Object;
1112: case HANDLER_RESPONSE:
1113: return reval.getResponse().getResponseData();
1114: case HANDLER_REQUEST:
1115: return reval.getRequest().getRequestData();
1116: case HANDLER_SESSION:
1117: return reval.getSession().getCacheNode();
1118: }
1119:
1120: // try to get handler from handlerCache first
1121: if (handlerCache != null
1122: && handlerCache.containsKey(handlerName)) {
1123: return handlerCache.get(handlerName);
1124: }
1125:
1126: // if handler object wasn't found in cache retrieve it
1127: if (this Object != null) {
1128: // not a global macro - need to find handler object
1129: // was called with this object - check it or its parents for matching prototype
1130: if (handlerName.equalsIgnoreCase(app
1131: .getPrototypeName(this Object))) {
1132: // we already have the right handler object
1133: // put the found handler object into the cache so we don't have to look again
1134: if (handlerCache != null)
1135: handlerCache.put(handlerName, this Object);
1136: return this Object;
1137: } else {
1138: // the handler object is not what we want
1139: Object obj = this Object;
1140:
1141: // walk down parent chain to find handler object,
1142: // limiting to 50 passes to avoid infinite loops
1143: int maxloop = 50;
1144: while (obj != null && maxloop-- > 0) {
1145: Prototype proto = app.getPrototype(obj);
1146:
1147: if ((proto != null)
1148: && proto.isInstanceOf(handlerName)) {
1149: if (handlerCache != null)
1150: handlerCache.put(handlerName, obj);
1151: return obj;
1152: }
1153:
1154: obj = app.getParentElement(obj);
1155: }
1156: }
1157: }
1158:
1159: Map macroHandlers = reval.getResponse().getMacroHandlers();
1160: Object obj = macroHandlers.get(handlerName);
1161: if (handlerCache != null && obj != null) {
1162: handlerCache.put(handlerName, obj);
1163: }
1164: return obj;
1165: }
1166: }
1167:
1168: /**
1169: * Exception type for unhandled macros
1170: */
1171: class UnhandledMacroException extends Exception {
1172: UnhandledMacroException(String name) {
1173: super(name);
1174: }
1175: }
1176: }
|