2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * $Id: Builder.java 467434 2006-10-24 18:48:48Z gvanmatre $
20   */
21  package org.apache.shale.clay.parser.builder;
22  
23  import java.util.Iterator;
24  import java.util.Map;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.shale.clay.config.beans.AttributeBean;
29  import org.apache.shale.clay.config.beans.ComponentBean;
30  import org.apache.shale.clay.config.beans.ConfigBean;
31  import org.apache.shale.clay.config.beans.ConfigBeanFactory;
32  import org.apache.shale.clay.config.beans.ElementBean;
33  import org.apache.shale.clay.config.beans.SymbolBean;
34  import org.apache.shale.clay.parser.Node;
35  import org.apache.shale.clay.parser.Token;
36  import org.apache.shale.util.Messages;
37  
38  /***
39   * <p>
40   * The abstract document node converter handles building meta components,
41   * {@link org.apache.shale.clay.config.beans.ComponentBean}, for a parsed html
42   * document fragment. The {@link org.apache.shale.clay.parser.Parser} loads the
43   * HTML document into a tree structure of
44   * {@link org.apache.shale.clay.parser.Node}. Each node in the parsed document
45   * tree is mapped to a <code>Builder</code> by a a subclass of
46   * {@link org.apache.shale.clay.parser.builder.chain.BuilderRuleContext}.
47   * </p>
48   */
49  public abstract class Builder {
50  
51      /***
52       * <p>
53       * Message resources for this class.
54       * </p>
55       */
56      protected static Messages messages = new Messages(
57              "org.apache.shale.clay.Bundle", Builder.class
58              .getClassLoader());
59  
60      /***
61       * <p>
62       * Common Logger utility class.
63       * </p>
64       */
65      private static Log log;
66      static {
67          log = LogFactory.getLog(Builder.class);
68      }
69  
70      /***
71       * <p>
72       * This method returns <code>true</code> if the faces component that it
73       * builds allows children but the default is <code>false</code>.
74       * </p>
75       *
76       * @return <code>false</code>
77       */
78      public boolean isChildrenAllowed() {
79          return false;
80      }
81  
82      /***
83       * <p>
84       * Returns a <code>jsfid</code> for the component.
85       * </p>
86       *
87       * @param node markup
88       * @return jsfid
89       */
90      protected abstract String getJsfid(Node node);
91  
92      /***
93       * <p>
94       * Returns the faces component type registered in the faces configuration or the
95       * fully qualified class name for <code>ActionListeners</code> and
96       * <code>ValueChangeListeners</code>.
97       * </p>
98       *
99       * @param node markup
100      * @return component type
101      */
102     protected abstract String getComponentType(Node node);
103 
104     /***
105      * <p>
106      * An integer value used to order {@link ElementBean}'s in the
107      * <code>children</code> collection of a {@link ComponentBean}. When read
108      * for the configuration files by the
109      * {@link org.apache.shale.clay.config.ClayConfigureListener}, the
110      * <code>renderId</code> is the <strong>"signature"</strong> when
111      * resolving inheritance and overrides. Used here, the component structure
112      * must take on the form of the HTML document.
113      * </p>
114      */
115     private static int renderId = 0;
116 
117     /***
118      * <p>
119      * Returns the next generated <code>renderId</code>.
120      * <p>
121      *
122      * @return unique id
123      */
124     protected synchronized int getRenderId() {
125         return renderId++;
126     }
127 
128     /***
129      * <p>
130      * Returns <code>true</code> if the builder handles converting the node's
131      * children or <code>false</code> if it's handled by the parent. The
132      * <code>isBodyAllowed</code> attribute can also
133      * be used to override the default option. This flag allows the HTML to be
134      * mocked up but the body ignored when converted into a
135      * {@link ComponentBean} graph.
136      * <p>
137      *
138      * @param node markup
139      * @param target child config bean
140      * @return <code>false</code> if the node's children should be ignored
141      */
142     protected boolean getBuildNodeBody(Node node, ElementBean target) {
143         return (target != null && !target.getIsBodyAllowed());
144     }
145 
146     /***
147      * <p>
148      * Called to being building a {@link ElementBean} from a {@link Node}. This
149      * follows the JSF pattern used by the Components and Renders.
150      * </p>
151      *
152      * @param node markup
153      * @param target child config bean
154      * @param root parent config bean
155      */
156     protected void encodeBegin(Node node, ElementBean target,
157             ComponentBean root) {
158 
159         assignNode(node, target);
160     }
161 
162     /***
163      * <p>
164      * Factory method that creates a {@link AttributeBean} from the <code>original</code>
165      * replacing the <code>value</code>.
166      * </p>
167      *
168      * @param original attribute being cloned
169      * @param value attribute property value override
170      * @param target owner of the attribute
171      * @return cloned original with the value overridden
172      */
173     protected AttributeBean createAttribute(AttributeBean original, String value, ComponentBean target) {
174         AttributeBean attr = new AttributeBean();
175         attr.setName(original.getName());
176         attr.setValue(value);
177         attr.setBindingType(original.getBindingType());
178         target.addAttribute(attr);
179         return attr;
180     }
181 
182 
183 
184 
185     /***
186      * <p>
187      * Recursively builds the clay meta component data from the parse
188      * document of {@link Node}'s. A similar design pattern found in JSF.
189      * </p>
190      *
191      * @param node markup
192      * @param target child config bean
193      * @param root parent config bean
194      */
195     protected void encodeChildren(Node node, ElementBean target,
196             ComponentBean root) {
197 
198         if (!getBuildNodeBody(node, target)) {
199             Iterator ci = node.getChildren().iterator();
200             while (ci.hasNext()) {
201                 Node child = (Node) ci.next();
202                 Builder childRenderer = getBuilder(child);
203 
204                 ElementBean targetChild = childRenderer.createElement(child);
205                 root.addChild(targetChild);
206                 //if the child component allows children, pass it to the render as
207                 // the root, otherwise, add the child to the current root
208                 if (childRenderer.isChildrenAllowed()) {
209                     childRenderer.encode(child, targetChild, targetChild);
210                 } else {
211                     childRenderer.encode(child, targetChild, root);
212                 }
213             }
214         }
215 
216     }
217 
218     /***
219      * <p>
220      * This call is invoked for any final processing.
221      * </p>
222      *
223      * @param node markup
224      * @param target child config bean
225      * @param root parent config bean
226      */
227     protected void encodeEnd(Node node, ElementBean target,
228             ComponentBean root) {
229 
230     }
231 
232     /***
233      * <p>
234      * Factory method that creates a {@link ElementBean} from a {@link Node}.
235      * </p>
236      *
237      * @param node markup
238      * @return target config bean from the markup node
239      */
240     public ElementBean createElement(Node node) {
241 
242         ElementBean target = new ElementBean();
243         target.setJsfid(getJsfid(node));
244         if (!node.isComment() && node.isStart()) {
245             String jsfid = (String) node.getAttributes().get("jsfid");
246             if (jsfid != null) {
247                target.setJsfid(jsfid);
248             }
249         }
250         target.setComponentType(getComponentType(node));
251         target.setRenderId(getRenderId());
252 
253         return target;
254     }
255 
256     /***
257      * <p>
258      * The call that begins the conversion of a {@link Node} to a
259      * {@link ComponentBean}. Each element in the html document will be
260      * converted into a Clay meta component.
261      * </p>
262      *
263      * @param node markup
264      * @param target child config bean
265      * @param root parent config bean
266      */
267     public void encode(Node node, ElementBean target, ComponentBean root) {
268 
269         if (log.isDebugEnabled()) {
270             log.debug(messages.getMessage("encode.begin", new Object[] {node}));
271         }
272 
273         encodeBegin(node, target, root);
274         encodeChildren(node, target, root);
275         encodeEnd(node, target, root);
276 
277         if (log.isDebugEnabled()) {
278             log.debug(messages.getMessage("encode.end", new Object[] {node}));
279         }
280 
281     }
282 
283 
284     /***
285      * <p>
286      * This method resolves the <code>jsfid</code> attribute for an HTML
287      * element to a component definition in the XML configuration files.
288      * </p>
289      *
290      * @param node markup
291      * @param target child config bean
292      */
293     protected void assignNode(Node node, ElementBean target) {
294         String id = (String) node.getAttributes().get("id");
295         if (id == null) {
296             id = (String) node.getAttributes().get("name");
297         }
298 
299         target.setId(id);
300 
301         // look to see if this node should be bound to a component
302         if (target.getJsfid() != null) {
303             // lookup the ConfigBean that handles the id
304             ConfigBean config = ConfigBeanFactory.findConfig(target.getJsfid());
305             // disconnect component type
306             target.setComponentType(null);
307 
308 
309             try {
310                //assign the parent
311                config.assignParent(target);
312                // resolve inheritance
313                config.realizingInheritance(target);
314             } catch (RuntimeException e) {
315                 log.error(e);
316                 throw new RuntimeException(
317                         messages.getMessage("parser.unresolved",
318                         new Object[] {node.getToken(), node.getToken().getRawText()}));
319             }
320 
321             // if the inheritance is broken, toggle back to the default
322             if (target.getComponentType() == null) {
323                 target.setComponentType(this.getComponentType(node));
324             }
325 
326         }
327 
328         // HTML attributes will override the declarative component
329         assignAttributes(node, target);
330 
331     }
332 
333     /***
334      * <p>
335      * This method applies the HTML attribute overrides to the declarative
336      * component, an object representation of the XML configuration.
337      * </p>
338      *
339      * @param node markup
340      * @param target child config bean
341      */
342     protected void assignAttributes(Node node, ComponentBean target) {
343         // override with html attributes
344 
345         Iterator ai = node.getAttributes().entrySet().iterator();
346         next: while (ai.hasNext()) {
347             Map.Entry e = (Map.Entry) ai.next();
348 
349             if (e.getKey().equals("id")
350                 || e.getKey().equals("jsfid")
351                 || e.getKey().equals("allowbody")
352                 || e.getKey().equals("facetname")) {
353 
354                 continue next;
355             }
356 
357             AttributeBean original = null;
358             Token valueToken = (Token) e.getValue();
359             if (valueToken != null) {
360                 original = target.getAttribute((String) e.getKey());
361                 if (original != null) {
362                     createAttribute(original, valueToken.getRawText(), target);
363                 } else  {
364                     //any token that is not an attribute in the target becomes a symbol
365                     StringBuffer identifier = new StringBuffer((String) e.getKey());
366                     identifier.insert(0, '@');
367                     SymbolBean symbol = new SymbolBean();
368                     symbol.setName(identifier.toString());
369                     symbol.setValue(valueToken.getRawText());
370                     target.addSymbol(symbol);
371                 }
372             }
373         }
374 
375         if (node.getAttributes().containsKey("allowbody")) {
376            target.setAllowBody((String) node.getAttributes().get("allowbody"));
377         }
378 
379         if (node.getAttributes().containsKey("facetname")) {
380             target.setFacetName((String) node.getAttributes().get("facetname"));
381         }
382 
383 
384     }
385 
386     /***
387      * <p>Returns the {@link org.apache.shale.clay.parser.builder.Builder} that
388      * is assigned the task of converting the html node to a corresponding component
389      * metadata used to construct a JSF resource.</p>
390      *
391      * @param node markup node
392      * @return builder that maps markup to config beans
393      */
394     public Builder getBuilder(Node node) {
395         return BuilderFactory.getRenderer(node);
396     }
397 
398 }