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