• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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