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: TemplateConfigBean.java 516834 2007-03-11 01:33:36Z gvanmatre $
20   */
21  package org.apache.shale.clay.config.beans;
22  
23  import java.util.BitSet;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.TreeMap;
28  
29  import javax.servlet.ServletContext;
30  
31  import org.apache.shale.clay.config.ClayTemplateParser;
32  import org.apache.shale.clay.config.Globals;
33  
34  /***
35   * <p>The second type of top-level object pool.  This implementation
36   * is designed to provide Tapestry like template composition.  The
37   * top-level {@link ComponentBean} is materialized from a HTML fragment
38   * where HTML elements are bound to meta components defined in the
39   * XML configuration files and cached by an instance of {@link ComponentConfigBean}
40   * </p>
41   */
42  public class TemplateConfigBean extends ComponentConfigBean {
43  
44      /***
45       * <p>Returns a {@link ComponentBean} that is materialized
46       * using a HTML template fragment.  The <code>templateName</code>
47       * is the name of the file relative to the context root of the
48       * web application</p>
49       * @param templateName name of the markup template
50       * @return root component bean for the <code>templateName</code>
51       */
52      public ComponentBean getElement(String templateName) {
53  
54          StringBuffer jsfid = new StringBuffer(templateName);
55          if (jsfid.length() > 0 &&  jsfid.charAt(0) != '/') {
56            jsfid.insert(0, '/');
57          }
58  
59          // look for a watcher identified by the template name
60          WatchDog watchDog = (WatchDog) watchDogs.get(jsfid.toString());
61  
62          //if a watcher doesn't exist, check for a common
63          if (watchDog == null) {
64              watchDog = (WatchDog) watchDogs.get(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG);
65          }
66  
67          if (watchDog == null || super.getElement(jsfid.toString()) == null) {
68              //The first time the page is created, create a watcher
69  
70              watchDog = new WatchDog(getConfigDefinitions(jsfid.toString()), jsfid.toString());
71  
72              // register by name
73              watchDogs.put(watchDog.getName(), watchDog);
74  
75              //loads the HTML template the first time and when file
76              //has been modified
77              watchDog.refresh(false);
78          }
79  
80          // returns the cached element
81          return super.getElement(jsfid.toString());
82      }
83  
84  
85      /***
86       * <p>Returns an integer value use to order the registered {@link ConfigBean} instances
87       * with the {@link ConfigBeanFactory}.
88       * </p>
89       *
90       * @return weight value of <code>1</code>
91       */
92      public int getWeight() {
93          return 1;
94      }
95  
96      /***
97       * <p>Overrides the super call to change the condition of the filter.  This
98       * {@link ConfigBean} can create components where the id end in the suffix
99       * defined in the web deployment descriptor as a initialization parameter with
100      * the name defined by <code>Globals.CLAY_HTML_TEMPLATE_SUFFIX</code>  Or, using
101      * the default defined by <code>Globals.CLAY_DEFAULT_HTML_TEMPLATE_SUFFIX</code>
102      *
103      * @param id jsfid
104      * @return <code>true</code> if the <code>jsfid</code> can be handled here
105      */
106     public boolean validMoniker(String id) {
107         return id.endsWith(suffixes[0]);
108     }
109 
110     /***
111      * <p>This is an overridden method called from the init method.
112      * It loads an instance of the {@link ClayTemplateParser} and
113      * establishes a Map collection to hold the resource
114      * {@link org.apache.shale.clay.config.beans.ComponentConfigBean$WatchDog}'s.</p>
115      */
116     protected void loadConfigFiles() {
117         parser = new ClayTemplateParser();
118         parser.setConfig(this);
119 
120         watchDogs = Collections.synchronizedMap(new TreeMap());
121     }
122 
123 
124     /***
125      * <p>The HTML templates are loaded on-demand.  Override this method forcing the auto
126      * load option on.  The XML configuration files are only effected by the
127      * <code>auto-reload-clay-files</code> initialization parameter.</p>
128      *
129      * @param context web container servlet context
130      */
131     public void init(ServletContext context) {
132         super.init(context);
133 
134         isWatchDogOn = true;
135     }
136 
137 
138     /***
139      * <p>If the <code>watchDogName</code> equals
140      * the {@link ComponentBean} that defines the selected
141      * template, remove it.</p>
142      *
143      * @param watchDogName grouping of template files
144      */
145     protected void clear(String watchDogName) {
146         if (!watchDogName.equals(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG)) {
147             //unassign a single template component
148             ComponentBean b = (ComponentBean) displayElements.get(watchDogName);
149             displayElements.remove(watchDogName);
150             if (b != null) {
151                 try {
152                     unassignParent(b);
153                 } catch (RuntimeException e1) {
154                     // log.error(e1);
155                 }
156             }
157             b = null;
158         } else {
159            super.clear(watchDogName);
160         }
161     }
162 
163 
164     /***
165      * <p>If the <code>forceReload</code> is <code>true</code>,
166      * the <code>displayElements</code> cache is invalidated.
167      * A <code>true</code> value is returned if cache has
168      * been cleared.</p>
169      *
170      * @param forceReload invalidate the cache flag
171      * @return <code>true</code> if the templates were reloaded
172      */
173     public boolean refresh(boolean forceReload) {
174         if (forceReload) {
175             //remove all old templates
176             Iterator wi = watchDogs.entrySet().iterator();
177             while (wi.hasNext()) {
178                 Map.Entry e = (Map.Entry) wi.next();
179                 WatchDog watchDog = (WatchDog) e.getValue();
180                 clear(watchDog.getName());
181                 if (watchDog != null) {
182                     watchDog.destroy();
183                 }
184 
185             }
186             watchDogs.clear();
187         }
188 
189         return forceReload;
190     }
191 
192 
193     /***
194      * <p>
195      * Returns the root metadata component that is used to add to the component
196      * tree. It locates the {@link ComponentBean} using the <code>jsfid</code>
197      * attribute as the key. A call to the {@link ConfigBeanFactory} locates the
198      * correct {@link ConfigBean} used to find the {@link ComponentBean}. </p>
199      *
200      * @param jsfid parent id of a config bean
201      * @return parent config bean
202      */
203     protected ComponentBean getTopLevelElement(String jsfid) {
204 
205         if (validMoniker(jsfid)) {
206             return getElement(jsfid);
207         }
208 
209         //broaden the search to the other ConfigBean's
210         ConfigBean config = ConfigBeanFactory.findConfig(jsfid);
211 
212         if (config == null) {
213             throw new NullPointerException(messages
214                     .getMessage("config.notloaded", new Object[] { jsfid }));
215         }
216 
217         if (config == this) {
218             throw new NullPointerException(messages.getMessage(
219                     "jsfid.notfound", new Object[] { jsfid }));
220         }
221 
222         // find the top-level display element associated with the subtree
223         ComponentBean b = config.getElement(jsfid);
224         if (b == null) {
225             throw new NullPointerException(messages.getMessage(
226                     "jsfid.notfound", new Object[] { jsfid }));
227         }
228 
229         return b;
230     }
231 
232 
233     /***
234      * <p>Determines if the <code>node</code> is a transient
235      * <code>outputText</code> (<strong>verbatim</strong>) component.</p>
236      *
237      * @param node a config bean that represents a template token
238      * @return <code>true</code> if the node is a verbatim node
239      */
240     private boolean isVerbatim(ComponentBean node) {
241 
242         AttributeBean attr = null;
243         if ((node.getJsfid().equals("verbatim") || node.getJsfid().equals("f:verbatim"))
244              && node.getComponentType().equals("javax.faces.HtmlOutputText")) {
245             attr = node.getAttribute("isTransient");
246             if (attr != null) {
247                 if (attr.getValue() != null && attr.getValue().length() > 0) {
248                     return (Character.toLowerCase(attr.getValue().charAt(0)) == 't');
249                 }
250             }
251         }
252 
253         return false;
254     }
255 
256 
257     /***
258      * <p>Recursively walks down the graph of meta-data {@link ComponentBean}'s
259      * looking at the children of the <code>root</code>.  Adjacent
260      * children that are both <code>verbatim</code> component
261      * definitions are merged.  If there is only one child and
262      * the child and root nodes are both <code>verbatim</code>
263      * definitions, the child is merged up to the root.</p>
264      *
265      * @param root top config bean that represents a markup template
266      */
267     public void optimizeTree(ComponentBean root) {
268 
269         // children is a TreeSet that is returned as a Collection.
270         int size = root.getChildren().size();
271         ComponentBean[] children = new ComponentBean[size];
272         BitSet verbatimSet = new BitSet(size);
273         verbatimSet.clear(0, size);
274 
275         StringBuffer buff = new StringBuffer();
276 
277         int i = 0;
278         Iterator ci = root.getChildrenIterator();
279         while (ci.hasNext()) {
280             children[i] = (ComponentBean) ci.next();
281             if (isVerbatim(children[i])) {
282                 verbatimSet.set(i);
283             }
284 
285             if (children[i].getChildren().size() > 0) {
286                 optimizeTree(children[i]);    // merge children for the top down
287                 // starting a the botton of the tree.
288             }
289 
290             i++;
291         }
292 
293         int s = -1;
294         while ((s = verbatimSet.nextSetBit(++s)) > -1) {
295 
296             merge: for (int j = s + 1; j < children.length; j++) {
297                 if (verbatimSet.get(j)) {
298                     buff.setLength(0);
299 
300                     // grap the value attribute of the first one in the stack
301                     // and concat to a buffer
302                     AttributeBean attrTop = children[s].getAttribute("value");
303                     if (attrTop != null) {
304                         if (attrTop.getValue() != null) {
305                             buff.append(attrTop.getValue());
306                         }
307                     } else {
308                         break merge;   // a verbatim without a value should never happen
309                     }
310 
311                     AttributeBean attrNext = children[j].getAttribute("value");  // the next in sequence to be merged
312                     if (attrNext != null) {
313                         if (attrNext.getValue() != null) {
314                             buff.append(attrNext.getValue());
315                         }
316                     } else {
317                         continue merge;   // a verbatim without a value should never happen
318                     }
319                     // merge node values
320                     attrTop.setValue(buff.toString());
321                     root.getChildren().remove(children[j]); // delete the node after merge from the parent
322 
323                 } else {
324                     // the verbatims are not in sequence (true, false, true)
325                     s = j;
326                     break merge;
327                 }
328             }
329 
330 
331         }
332 
333         // if the root is a verbatim and the only child is a verbatim
334         // merge up to the root
335         if (isVerbatim(root) && root.getChildren().size() == 1
336                 && isVerbatim(children[0])) {
337 
338             buff.setLength(0);
339 
340             // grap the value attribute of the first one in the stack
341             // and concat to a buffer
342             AttributeBean attrTop = root.getAttribute("value");
343             if (attrTop != null) {
344                 if (attrTop.getValue() != null) {
345                     buff.append(attrTop.getValue());
346                 }
347 
348                 AttributeBean attrNext = children[0].getAttribute("value");  // the next in sequence to be merged
349                 if (attrNext != null) {
350                     if (attrNext.getValue() != null) {
351                         buff.append(attrNext.getValue());
352                     }
353                 }
354                 // merge node values
355                 attrTop.setValue(buff.toString());
356                 root.getChildren().clear(); // delete the node after merge from the parent
357             }
358         }
359 
360 
361     }
362 
363 }