/** * Intialize the Runtime macro. At the init time no implementation so we just save the values to * use at the render time. * * @param rs runtime services * @param context InternalContextAdapter * @param node node containing the macro call */ public void init(RuntimeServices rs, InternalContextAdapter context, Node node) { super.init(rs, context, node); rsvc = rs; this.node = node; /** * Apply strictRef setting only if this really looks like a macro, so strict mode doesn't balk * at things like #E0E0E0 in a template. compare with ")" is a simple #foo() style macro, * comparing to "#end" is a block style macro. We use starts with because the token may end with * '\n' */ Token t = node.getLastToken(); if (t.image.startsWith(")") || t.image.startsWith("#end")) { strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); } // Validate that none of the arguments are plain words, (VELOCITY-614) // they should be string literals, references, inline maps, or inline lists for (int n = 0; n < node.jjtGetNumChildren(); n++) { Node child = node.jjtGetChild(n); if (child.getType() == ParserTreeConstants.JJTWORD) { badArgsErrorMsg = "Invalid arg '" + child.getFirstToken().image + "' in macro #" + macroName + " at " + Log.formatFileString(child); if (strictRef) // If strict, throw now { /* indicate col/line assuming it starts at 0 * this will be corrected one call up */ throw new TemplateInitException(badArgsErrorMsg, context.getCurrentTemplateName(), 0, 0); } } } }
/** * This method is used with BlockMacro when we want to render a macro with a body AST. * * @param context * @param writer * @param node * @param body AST block that was enclosed in the macro body. * @return true if the rendering is successful * @throws IOException * @throws ResourceNotFoundException * @throws ParseErrorException * @throws MethodInvocationException */ public boolean render(InternalContextAdapter context, Writer writer, Node node, Renderable body) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { VelocimacroProxy vmProxy = null; String renderingTemplate = context.getCurrentTemplateName(); /** first look in the source template */ Object o = rsvc.getVelocimacro(macroName, getTemplateName(), renderingTemplate); if (o != null) { // getVelocimacro can only return a VelocimacroProxy so we don't need the // costly instanceof check vmProxy = (VelocimacroProxy) o; } /** if not found, look in the macro libraries. */ if (vmProxy == null) { List macroLibraries = context.getMacroLibraries(); if (macroLibraries != null) { for (int i = macroLibraries.size() - 1; i >= 0; i--) { o = rsvc.getVelocimacro(macroName, (String) macroLibraries.get(i), renderingTemplate); // get the first matching macro if (o != null) { vmProxy = (VelocimacroProxy) o; break; } } } } if (vmProxy != null) { try { // mainly check the number of arguments vmProxy.checkArgs(context, node, body != null); } catch (TemplateInitException die) { throw new ParseErrorException( die.getMessage() + " at " + Log.formatFileString(node), new Info(node)); } if (badArgsErrorMsg != null) { throw new TemplateInitException( badArgsErrorMsg, context.getCurrentTemplateName(), node.getColumn(), node.getLine()); } try { preRender(context); return vmProxy.render(context, writer, node, body); } catch (StopCommand stop) { if (!stop.isFor(this)) { throw stop; } return true; } catch (RuntimeException e) { /** * We catch, the exception here so that we can record in the logs the template and line * number of the macro call which generate the exception. This information is especially * important for multiple macro call levels. this is also true for the following catch * blocks. */ rsvc.getLog() .error("Exception in macro #" + macroName + " called at " + Log.formatFileString(node)); throw e; } catch (IOException e) { rsvc.getLog() .error("Exception in macro #" + macroName + " called at " + Log.formatFileString(node)); throw e; } finally { postRender(context); } } else if (strictRef) { throw new VelocityException( "Macro '#" + macroName + "' is not defined at " + Log.formatFileString(node)); } /** If we cannot find an implementation write the literal text */ writer.write(getLiteral()); return true; }