1 package org.apache.velocity.runtime.directive; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.context.InternalContextAdapter; 23 import org.apache.velocity.exception.TemplateInitException; 24 import org.apache.velocity.runtime.Renderable; 25 import org.apache.velocity.runtime.RuntimeServices; 26 import org.apache.velocity.runtime.parser.node.Node; 27 import org.apache.velocity.util.StringBuilderWriter; 28 import org.apache.velocity.util.StringUtils; 29 import org.slf4j.Logger; 30 31 import java.io.IOException; 32 import java.io.Writer; 33 34 /** 35 * Directive that puts an unrendered AST block in the context 36 * under the specified key, postponing rendering until the 37 * reference is used and rendered. 38 * 39 * @author Andrew Tetlaw 40 * @author Nathan Bubna 41 * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a> 42 * @since 1.7 43 * @version $Id: Block.java 686842 2008-08-18 18:29:31Z nbubna $ 44 */ 45 public abstract class Block extends Directive 46 { 47 protected Node block; 48 protected Logger log; 49 protected int maxDepth; 50 protected String key; 51 52 /** 53 * Return type of this directive. 54 * @return type, DirectiveConstants.BLOCK or DirectiveConstants.LINE 55 */ 56 @Override getType()57 public int getType() 58 { 59 return BLOCK; 60 } 61 62 /** 63 * simple init - get the key 64 * @param rs 65 * @param context 66 * @param node 67 */ 68 @Override init(RuntimeServices rs, InternalContextAdapter context, Node node)69 public void init(RuntimeServices rs, InternalContextAdapter context, Node node) 70 throws TemplateInitException 71 { 72 super.init(rs, context, node); 73 74 log = rsvc.getLog(); 75 76 /* 77 * No checking is done. We just grab the last child node and assume 78 * that it's the block! 79 */ 80 block = node.jjtGetChild(node.jjtGetNumChildren() - 1); 81 } 82 83 /** 84 * renders block directive 85 * @param context 86 * @param writer 87 * @return success status 88 */ render(InternalContextAdapter context, Writer writer)89 public boolean render(InternalContextAdapter context, Writer writer) 90 { 91 preRender(context); 92 try 93 { 94 return block.render(context, writer); 95 } 96 catch (IOException e) 97 { 98 String msg = "Failed to render " + id(context) + " to writer at " + 99 StringUtils.formatFileString(this); 100 log.error(msg, e); 101 throw new RuntimeException(msg, e); 102 } 103 catch (StopCommand stop) 104 { 105 if (!stop.isFor(this)) 106 { 107 throw stop; 108 } 109 return true; 110 } 111 finally 112 { 113 postRender(context); 114 } 115 } 116 117 /** 118 * Creates a string identifying the source and location of the block 119 * definition, and the current template being rendered if that is 120 * different. 121 * @param context 122 * @return id string 123 */ id(InternalContextAdapter context)124 protected String id(InternalContextAdapter context) 125 { 126 StringBuilder str = new StringBuilder(100) 127 .append("block $").append(key); 128 if (!context.getCurrentTemplateName().equals(getTemplateName())) 129 { 130 str.append(" used in ").append(context.getCurrentTemplateName()); 131 } 132 return str.toString(); 133 } 134 135 /** 136 * actual class placed in the context, holds the context 137 * being used for the render, as well as the parent (which already holds 138 * everything else we need). 139 */ 140 public static class Reference implements Renderable 141 { 142 private InternalContextAdapter context; 143 private Block parent; 144 private int depth; 145 146 /** 147 * @param context 148 * @param parent 149 */ Reference(InternalContextAdapter context, Block parent)150 public Reference(InternalContextAdapter context, Block parent) 151 { 152 this.context = context; 153 this.parent = parent; 154 } 155 156 /** 157 * Render the AST of this block into the writer using the context. 158 * @param context 159 * @param writer 160 * @return success status 161 */ 162 @Override render(InternalContextAdapter context, Writer writer)163 public boolean render(InternalContextAdapter context, Writer writer) 164 { 165 depth++; 166 if (depth > parent.maxDepth) 167 { 168 /* this is only a debug message, as recursion can 169 * happen in quasi-innocent situations and is relatively 170 * harmless due to how we handle it here. 171 * this is more to help anyone nuts enough to intentionally 172 * use recursive block definitions and having problems 173 * pulling it off properly. 174 */ 175 parent.log.debug("Max recursion depth reached for {} at {}", parent.id(context), StringUtils.formatFileString(parent)); 176 depth--; 177 return false; 178 } 179 else 180 { 181 parent.render(context, writer); 182 depth--; 183 return true; 184 } 185 } 186 187 /** 188 * Makes #if( $blockRef ) true without rendering, so long as we aren't beyond max depth. 189 * @return reference value as boolean 190 */ getAsBoolean()191 public boolean getAsBoolean() 192 { 193 return depth <= parent.maxDepth; 194 } 195 196 /** 197 * @return rendered string 198 */ toString()199 public String toString() 200 { 201 Writer writer = new StringBuilderWriter(); 202 if (render(context, writer)) 203 { 204 return writer.toString(); 205 } 206 return null; 207 } 208 } 209 } 210