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 java.io.Writer;
23 import java.io.IOException;
24 import java.io.StringWriter;
25
26 import org.apache.commons.lang.text.StrBuilder;
27 import org.apache.velocity.context.InternalContextAdapter;
28 import org.apache.velocity.exception.TemplateInitException;
29 import org.apache.velocity.exception.VelocityException;
30 import org.apache.velocity.runtime.Renderable;
31 import org.apache.velocity.runtime.RuntimeConstants;
32 import org.apache.velocity.runtime.RuntimeServices;
33 import org.apache.velocity.runtime.log.Log;
34 import org.apache.velocity.runtime.parser.node.Node;
35
36 /**
37 * Directive that puts an unrendered AST block in the context
38 * under the specified key, postponing rendering until the
39 * reference is used and rendered.
40 *
41 * @author Andrew Tetlaw
42 * @author Nathan Bubna
43 * @version $Id: Define.java 686842 2008-08-18 18:29:31Z nbubna $
44 */
45 public class Define extends Directive
46 {
47 private String key;
48 private Node block;
49 private Log log;
50 private int maxDepth;
51 private String definingTemplate;
52
53 /**
54 * Return name of this directive.
55 */
56 public String getName()
57 {
58 return "define";
59 }
60
61 /**
62 * Return type of this directive.
63 */
64 public int getType()
65 {
66 return BLOCK;
67 }
68
69 /**
70 * simple init - get the key
71 */
72 public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
73 throws TemplateInitException
74 {
75 super.init(rs, context, node);
76
77 log = rs.getLog();
78
79 /*
80 * default max depth of two is used because intentional recursion is
81 * unlikely and discouraged, so make unintentional ones end fast
82 */
83 maxDepth = rs.getInt(RuntimeConstants.DEFINE_DIRECTIVE_MAXDEPTH, 2);
84
85 /*
86 * first token is the name of the block. We don't even check the format,
87 * just assume it looks like this: $block_name. Should we check if it has
88 * a '$' or not?
89 */
90 key = node.jjtGetChild(0).getFirstToken().image.substring(1);
91
92 /**
93 * No checking is done. We just grab the second child node and assume
94 * that it's the block!
95 */
96 block = node.jjtGetChild(1);
97
98 /**
99 * keep tabs on the template this came from
100 */
101 definingTemplate = context.getCurrentTemplateName();
102 }
103
104 /**
105 * directive.render() simply makes an instance of the Block inner class
106 * and places it into the context as indicated.
107 */
108 public boolean render(InternalContextAdapter context, Writer writer, Node node)
109 {
110 /* put a Block instance into the context,
111 * using the user-defined key, for later inline rendering.
112 */
113 context.put(key, new Block(context, this));
114 return true;
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 */
122 protected String id(InternalContextAdapter context)
123 {
124 StrBuilder str = new StrBuilder(100)
125 .append("block $").append(key)
126 .append(" (defined in ").append(definingTemplate)
127 .append(" [line ").append(getLine())
128 .append(", column ").append(getColumn()).append("])");
129
130 if (!context.getCurrentTemplateName().equals(definingTemplate))
131 {
132 str.append(" used in ").append(context.getCurrentTemplateName());
133 }
134
135 return str.toString();
136 }
137
138 /**
139 * actual class placed in the context, holds the context and writer
140 * being used for the render, as well as the parent (which already holds
141 * everything else we need).
142 */
143 public static class Block implements Renderable
144 {
145 private InternalContextAdapter context;
146 private Define parent;
147 private int depth;
148
149 public Block(InternalContextAdapter context, Define parent)
150 {
151 this.context = context;
152 this.parent = parent;
153 }
154
155 /**
156 *
157 */
158 public boolean render(InternalContextAdapter context, Writer writer)
159 {
160 try
161 {
162 depth++;
163 if (depth > parent.maxDepth)
164 {
165 /* this is only a debug message, as recursion can
166 * happen in quasi-innocent situations and is relatively
167 * harmless due to how we handle it here.
168 * this is more to help anyone nuts enough to intentionally
169 * use recursive block definitions and having problems
170 * pulling it off properly.
171 */
172 parent.log.debug("Max recursion depth reached for "+parent.id(context));
173 depth--;
174 return false;
175 }
176 else
177 {
178 parent.block.render(context, writer);
179 depth--;
180 return true;
181 }
182 }
183 catch (IOException e)
184 {
185 String msg = "Failed to render "+parent.id(context)+" to writer";
186 parent.log.error(msg, e);
187 throw new RuntimeException(msg, e);
188 }
189 catch (VelocityException ve)
190 {
191 String msg = "Failed to render "+parent.id(context)+" due to "+ve;
192 parent.log.error(msg, ve);
193 throw ve;
194 }
195 }
196
197 public String toString()
198 {
199 Writer stringwriter = new StringWriter();
200 if(render(context,stringwriter))
201 {
202 return stringwriter.toString();
203 }
204 else
205 {
206 return null;
207 }
208 }
209 }
210 }