View Javadoc

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 }