Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
TemplateConfigBean |
|
| 4.6;4.6 |
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 | 96 | 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 | 35 | StringBuffer jsfid = new StringBuffer(templateName); |
54 | 35 | if (jsfid.length() > 0 && jsfid.charAt(0) != '/') { |
55 | 2 | jsfid.insert(0, '/'); |
56 | } | |
57 | ||
58 | // look for a watcher identified by the template name | |
59 | 35 | WatchDog watchDog = (WatchDog) watchDogs.get(jsfid.toString()); |
60 | ||
61 | //if a watcher doesn't exist, check for a common | |
62 | 35 | if (watchDog == null) { |
63 | 35 | watchDog = (WatchDog) watchDogs.get(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG); |
64 | } | |
65 | ||
66 | 35 | if (watchDog == null || super.getElement(jsfid.toString()) == null) { |
67 | //The first time the page is created, create a watcher | |
68 | ||
69 | 33 | watchDog = new WatchDog(getConfigDefinitions(jsfid.toString()), jsfid.toString()); |
70 | ||
71 | // register by name | |
72 | 33 | watchDogs.put(watchDog.getName(), watchDog); |
73 | ||
74 | //loads the HTML template the first time and when file | |
75 | //has been modified | |
76 | 33 | watchDog.refresh(false); |
77 | 32 | } else { |
78 | //check to see if an existing html template | |
79 | //needs reloaded | |
80 | 2 | watchDog.refresh(false); |
81 | } | |
82 | ||
83 | // returns the cached element | |
84 | 34 | 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 | 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 | 661 | 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 | 40 | parser = new ClayTemplateParser(); |
121 | 40 | parser.setConfig(this); |
122 | ||
123 | 40 | watchDogs = new TreeMap(); |
124 | 40 | } |
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 | 80 | super.init(context); |
136 | ||
137 | 80 | isWatchDogOn = true; |
138 | 80 | } |
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 | 130 | if (!watchDogName.equals(Globals.DEFAULT_COMPONENT_CONFIG_WATCHDOG)) { |
150 | //unassign a single template component | |
151 | 33 | ComponentBean b = (ComponentBean) displayElements.get(watchDogName); |
152 | 33 | displayElements.remove(watchDogName); |
153 | 33 | if (b != null) { |
154 | try { | |
155 | unassignParent(b); | |
156 | } catch (RuntimeException e1) { | |
157 | // log.error(e1); | |
158 | } | |
159 | } | |
160 | 33 | b = null; |
161 | 33 | } else { |
162 | 97 | super.clear(watchDogName); |
163 | } | |
164 | 130 | } |
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 | 643 | if (validMoniker(jsfid)) { |
212 | 1 | return getElement(jsfid); |
213 | } | |
214 | ||
215 | //broaden the search to the other ConfigBean's | |
216 | 642 | ConfigBean config = ConfigBeanFactory.findConfig(jsfid); |
217 | ||
218 | 642 | if (config == null) { |
219 | throw new NullPointerException(messages | |
220 | .getMessage("config.notloaded", new Object[] { jsfid })); | |
221 | } | |
222 | ||
223 | 642 | 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 | 642 | ComponentBean b = config.getElement(jsfid); |
230 | 642 | if (b == null) { |
231 | throw new NullPointerException(messages.getMessage( | |
232 | "jsfid.notfound", new Object[] { jsfid })); | |
233 | } | |
234 | ||
235 | 642 | 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 | 1252 | AttributeBean attr = null; |
249 | 1252 | if ((node.getJsfid().equals("verbatim") || node.getJsfid().equals("f:verbatim")) |
250 | && node.getComponentType().equals("javax.faces.HtmlOutputText")) { | |
251 | 847 | attr = node.getAttribute("isTransient"); |
252 | 847 | if (attr != null) { |
253 | 847 | if (attr.getValue() != null && attr.getValue().length() > 0) { |
254 | 847 | return (Character.toLowerCase(attr.getValue().charAt(0)) == 't'); |
255 | } | |
256 | } | |
257 | } | |
258 | ||
259 | 405 | 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 | 120 | int size = root.getChildren().size(); |
277 | 120 | ComponentBean[] children = new ComponentBean[size]; |
278 | 120 | BitSet verbatimSet = new BitSet(size); |
279 | 120 | verbatimSet.clear(0, size); |
280 | ||
281 | 120 | StringBuffer buff = new StringBuffer(); |
282 | ||
283 | 120 | int i = 0; |
284 | 120 | Iterator ci = root.getChildrenIterator(); |
285 | 1249 | while (ci.hasNext()) { |
286 | 1129 | children[i] = (ComponentBean) ci.next(); |
287 | 1129 | if (isVerbatim(children[i])) { |
288 | 841 | verbatimSet.set(i); |
289 | } | |
290 | ||
291 | 1129 | if (children[i].getChildren().size() > 0) { |
292 | 86 | optimizeTree(children[i]); // merge children for the top down |
293 | // starting a the botton of the tree. | |
294 | } | |
295 | ||
296 | 1129 | i++; |
297 | 1129 | } |
298 | ||
299 | 120 | int s = -1; |
300 | 557 | while ((s = verbatimSet.nextSetBit(++s)) > -1) { |
301 | ||
302 | 1253 | merge: for (int j = s + 1; j < children.length; j++) { |
303 | 1062 | if (verbatimSet.get(j)) { |
304 | 816 | buff.setLength(0); |
305 | ||
306 | // grap the value attribute of the first one in the stack | |
307 | // and concat to a buffer | |
308 | 816 | AttributeBean attrTop = children[s].getAttribute("value"); |
309 | 816 | if (attrTop != null) { |
310 | 816 | if (attrTop.getValue() != null) { |
311 | 816 | buff.append(attrTop.getValue()); |
312 | } | |
313 | } else { | |
314 | break merge; // a verbatim without a value should never happen | |
315 | } | |
316 | ||
317 | 816 | AttributeBean attrNext = children[j].getAttribute("value"); // the next in sequence to be merged |
318 | 816 | if (attrNext != null) { |
319 | 816 | if (attrNext.getValue() != null) { |
320 | 816 | buff.append(attrNext.getValue()); |
321 | } | |
322 | } else { | |
323 | continue merge; // a verbatim without a value should never happen | |
324 | } | |
325 | // merge node values | |
326 | 816 | attrTop.setValue(buff.toString()); |
327 | 816 | root.getChildren().remove(children[j]); // delete the node after merge from the parent |
328 | ||
329 | 816 | } else { |
330 | // the verbatims are not in sequence (true, false, true) | |
331 | 246 | s = j; |
332 | 246 | break merge; |
333 | } | |
334 | } | |
335 | ||
336 | ||
337 | 191 | } |
338 | ||
339 | // if the root is a verbatim and the only child is a verbatim | |
340 | // merge up to the root | |
341 | 120 | if (isVerbatim(root) && root.getChildren().size() == 1 |
342 | && isVerbatim(children[0])) { | |
343 | ||
344 | 3 | buff.setLength(0); |
345 | ||
346 | // grap the value attribute of the first one in the stack | |
347 | // and concat to a buffer | |
348 | 3 | AttributeBean attrTop = root.getAttribute("value"); |
349 | 3 | if (attrTop != null) { |
350 | 3 | if (attrTop.getValue() != null) { |
351 | 3 | buff.append(attrTop.getValue()); |
352 | } | |
353 | ||
354 | 3 | AttributeBean attrNext = children[0].getAttribute("value"); // the next in sequence to be merged |
355 | 3 | if (attrNext != null) { |
356 | 3 | if (attrNext.getValue() != null) { |
357 | 3 | buff.append(attrNext.getValue()); |
358 | } | |
359 | } | |
360 | // merge node values | |
361 | 3 | attrTop.setValue(buff.toString()); |
362 | 3 | root.getChildren().clear(); // delete the node after merge from the parent |
363 | } | |
364 | } | |
365 | ||
366 | ||
367 | 120 | } |
368 | ||
369 | } |