Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ClayTemplateParser |
|
| 3.7142857142857144;3.714 |
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: ClayTemplateParser.java 467434 2006-10-24 18:48:48Z gvanmatre $ | |
20 | */ | |
21 | package org.apache.shale.clay.config; | |
22 | ||
23 | import java.io.BufferedReader; | |
24 | import java.io.IOException; | |
25 | import java.io.InputStreamReader; | |
26 | import java.io.StringWriter; | |
27 | import java.net.URL; | |
28 | import java.nio.charset.Charset; | |
29 | import java.util.Iterator; | |
30 | import java.util.List; | |
31 | import java.util.Map; | |
32 | ||
33 | import org.apache.commons.logging.Log; | |
34 | import org.apache.commons.logging.LogFactory; | |
35 | import org.apache.shale.clay.config.beans.ComponentBean; | |
36 | import org.apache.shale.clay.config.beans.ComponentConfigBean; | |
37 | import org.apache.shale.clay.config.beans.ConfigBean; | |
38 | import org.apache.shale.clay.config.beans.ElementBean; | |
39 | import org.apache.shale.clay.config.beans.TemplateConfigBean; | |
40 | import org.apache.shale.clay.parser.AttributeTokenizer; | |
41 | import org.apache.shale.clay.parser.Node; | |
42 | import org.apache.shale.clay.parser.Parser; | |
43 | import org.apache.shale.clay.parser.Token; | |
44 | import org.apache.shale.clay.parser.builder.Builder; | |
45 | import org.apache.shale.clay.parser.builder.BuilderFactory; | |
46 | import org.apache.shale.util.Messages; | |
47 | import org.xml.sax.SAXException; | |
48 | ||
49 | /** | |
50 | * <p> | |
51 | * This class is responsible for loading an HTML template into a graph of | |
52 | * {@link ComponentBean}'s that represents a JSF component tree. It is used by | |
53 | * {@link org.apache.shale.clay.config.beans.TemplateConfigBean}, a subclass of | |
54 | * {@link org.apache.shale.clay.config.beans.ComponentConfigBean}. | |
55 | * </p> | |
56 | */ | |
57 | 41 | public class ClayTemplateParser implements ClayConfigParser { |
58 | ||
59 | /** | |
60 | * <p> | |
61 | * Commons logging utility object static instance. | |
62 | * </p> | |
63 | */ | |
64 | private static Log log; | |
65 | static { | |
66 | 1 | log = LogFactory |
67 | 2 | .getLog(org.apache.shale.clay.config.ClayXmlParser.class); |
68 | } | |
69 | ||
70 | /** | |
71 | * <p> | |
72 | * Message resources for this class. | |
73 | * </p> | |
74 | */ | |
75 | 1 | private static Messages messages = new Messages( |
76 | "org.apache.shale.clay.Bundle", ClayConfigureListener.class | |
77 | .getClassLoader()); | |
78 | ||
79 | /** | |
80 | * <p> | |
81 | * Object pool for HTML template configuration files. | |
82 | * </p> | |
83 | */ | |
84 | 41 | private ConfigBean config = null; |
85 | ||
86 | /** | |
87 | * <p> | |
88 | * Sets an object pool for HTML template configuration files. | |
89 | * </p> | |
90 | * | |
91 | * @param config | |
92 | * handler | |
93 | */ | |
94 | public void setConfig(ConfigBean config) { | |
95 | 81 | this.config = config; |
96 | 81 | } |
97 | ||
98 | /** | |
99 | * <p> | |
100 | * Returns an object pool for HTML template configuration files. | |
101 | * </p> | |
102 | * | |
103 | * @return config handler | |
104 | */ | |
105 | public ConfigBean getConfig() { | |
106 | 18 | return config; |
107 | } | |
108 | ||
109 | /** | |
110 | * <p> | |
111 | * Loads the <code>templateURL</code> identified by the | |
112 | * <code>templateName</code> into a graph of {@link ComponentBean}'s. | |
113 | * </p> | |
114 | * | |
115 | * @param templateURL | |
116 | * template file | |
117 | * @param templateName | |
118 | * jsfid | |
119 | * @exception SAXException | |
120 | * XML parse error | |
121 | * @exception IOException | |
122 | * XML parse error | |
123 | */ | |
124 | public void loadConfigFile(URL templateURL, String templateName) | |
125 | throws IOException, SAXException { | |
126 | ||
127 | 32 | ((ComponentConfigBean) config).addChild(generateElement(templateURL, |
128 | templateName)); | |
129 | 31 | } |
130 | ||
131 | /** | |
132 | * <p> | |
133 | * Loads the template file and parses it into a composition of metadata | |
134 | * that's used by the {@link org.apache.shale.clay.component.Clay} | |
135 | * component. This metadata is used to construct a JSF subtree within target | |
136 | * view. | |
137 | * </p> | |
138 | * | |
139 | * @param templateURL | |
140 | * template file | |
141 | * @param templateName | |
142 | * jsfid | |
143 | * @return config bean graph holding the template file | |
144 | * @exception IOException | |
145 | * loading template file | |
146 | */ | |
147 | protected ComponentBean generateElement(URL templateURL, String templateName) | |
148 | throws IOException { | |
149 | ||
150 | 32 | if (log.isInfoEnabled()) { |
151 | 32 | log.info(messages.getMessage("loading.template", |
152 | new Object[] { templateName })); | |
153 | } | |
154 | ||
155 | 32 | ComponentBean root = new ComponentBean(); |
156 | 32 | root.setJsfid(templateName); |
157 | 32 | root.setComponentType("javax.faces.HtmlOutputText"); |
158 | ||
159 | // generate the document | |
160 | ||
161 | 32 | StringBuffer buffer = loadTemplate(templateURL); |
162 | ||
163 | 32 | List roots = new Parser().parse(buffer); |
164 | 32 | Iterator ri = roots.iterator(); |
165 | 140 | while (ri.hasNext()) { |
166 | 108 | Node node = (Node) ri.next(); |
167 | 108 | Builder renderer = getBuilder(node); |
168 | 108 | ElementBean child = renderer.createElement(node); |
169 | ||
170 | 108 | root.addChild(child); |
171 | 108 | if (renderer.isChildrenAllowed()) { |
172 | 23 | renderer.encode(node, child, child); |
173 | 23 | } else { |
174 | 85 | renderer.encode(node, child, root); |
175 | } | |
176 | 108 | } |
177 | ||
178 | 32 | roots.clear(); |
179 | 32 | roots = null; |
180 | 32 | buffer.setLength(0); |
181 | 32 | buffer = null; |
182 | 32 | ri = null; |
183 | ||
184 | // verify there is not a duplicate component id within a naming | |
185 | // container. | |
186 | 32 | config.checkTree(root); |
187 | ||
188 | // compress the tree merging adjacent verbatim nodes | |
189 | 31 | if (config instanceof TemplateConfigBean) { |
190 | 31 | ((TemplateConfigBean) config).optimizeTree(root); |
191 | } | |
192 | ||
193 | 31 | return root; |
194 | } | |
195 | ||
196 | /** | |
197 | * <p>Loads the template file respecting the encoding type. | |
198 | * The file encoding type is determined by calling | |
199 | * the <code>getCharacterEncoding()</code> method. | |
200 | * </p> | |
201 | * | |
202 | * @param templateURL target template to load | |
203 | * @return content of the template | |
204 | * @throws IOException error loading the template | |
205 | */ | |
206 | public StringBuffer loadTemplate(URL templateURL) throws IOException { | |
207 | ||
208 | 32 | StringBuffer buff = new StringBuffer(); |
209 | 32 | BufferedReader in = null; |
210 | 32 | String enc = getCharacterEncoding(templateURL); |
211 | ||
212 | 32 | if (log.isDebugEnabled()) { |
213 | log.debug(messages.getMessage("template.encoding", | |
214 | new Object[] { enc, templateURL.getFile() })); | |
215 | } | |
216 | ||
217 | try { | |
218 | ||
219 | 32 | in = new BufferedReader(new InputStreamReader(templateURL.openStream(), enc)); |
220 | 1514 | while (in.ready()) { |
221 | 1482 | buff.append(in.readLine()).append("\n"); |
222 | 1482 | } |
223 | ||
224 | } catch (IOException e) { | |
225 | log.error(messages.getMessage("loading.template.exception", | |
226 | new Object[] { templateURL.getFile() }), e); | |
227 | throw e; | |
228 | } finally { | |
229 | 32 | if (in != null) { |
230 | 32 | in.close(); |
231 | 32 | } |
232 | } | |
233 | ||
234 | 32 | return buff; |
235 | ||
236 | } | |
237 | ||
238 | ||
239 | /** | |
240 | * <p>Returns the encoding type used to open the <code>templateURL</code>. | |
241 | * The template encoding type is resolved using three overrides. The first | |
242 | * step is to look in the target template for a comment token that defines | |
243 | * the charset. The first 512 chars of the <code>templateURL</code> are read | |
244 | * and scanned for a special comment token.<br/></br/> | |
245 | * | |
246 | * For example: <-- ### clay:page charset="UTF-8" /### --><br/><br/> | |
247 | * | |
248 | * If the Clay page directive is not found, the next override is an | |
249 | * initialization parameter in the web.xml. The value of this parameter | |
250 | * is a global override for all templates.<br/><br/> | |
251 | * | |
252 | * For example:<br/> | |
253 | * <context-param><br/> | |
254 | * <param-name>org.apache.shale.clay.HTML_TEMPLATE_CHARSET | |
255 | * </param-name><br/> | |
256 | * <param-value>UTF-8</param-value><br/> | |
257 | * </context-param><br/><br/> | |
258 | * | |
259 | * Otherwise, the defaut is the VM's "<code>file.encoding</code>" | |
260 | * system parameter. | |
261 | * | |
262 | * @param templateURL template URL | |
263 | * @return charset encoding used to read the template | |
264 | * @throws IOException unable to read the template document | |
265 | */ | |
266 | public String getCharacterEncoding(URL templateURL) throws IOException { | |
267 | ||
268 | 36 | InputStreamReader in = null; |
269 | 36 | StringWriter snippet = new StringWriter(); |
270 | 36 | char[] chars = new char[512]; |
271 | 36 | String enc = null; |
272 | ||
273 | ||
274 | try { | |
275 | // read in the top of the template | |
276 | 36 | in = new InputStreamReader(templateURL.openStream()); |
277 | 36 | int n = in.read(chars); |
278 | 36 | snippet.write(chars, 0, n); |
279 | ||
280 | // look for the comment page directive containing the charset | |
281 | 36 | int s = snippet.getBuffer().indexOf(Parser.START_CHARSET_TOKEN); |
282 | 36 | if (s > -1) { |
283 | 18 | int e = snippet.getBuffer().indexOf(Parser.END_CHARSET_TOKEN, s); |
284 | 18 | AttributeTokenizer tokenizer = new AttributeTokenizer(snippet.getBuffer(), s, e, 1, 0); |
285 | 18 | Iterator ti = tokenizer.iterator(); |
286 | 90 | while (ti.hasNext()) { |
287 | 72 | Map.Entry attribute = (Map.Entry) ti.next(); |
288 | 72 | Token key = (Token) attribute.getKey(); |
289 | //check the attribute name, we are only interested | |
290 | //in "charset" | |
291 | 72 | if (key != null && key.getRawText() != null |
292 | && key.getRawText().equalsIgnoreCase("charset")) { | |
293 | 18 | Token value = (Token) attribute.getValue(); |
294 | ||
295 | //look for the value of the charset attribute | |
296 | 18 | if (value != null && value.getRawText() != null) { |
297 | // if it is supported, use the value for the encoding | |
298 | 18 | if (Charset.isSupported(value.getRawText())) { |
299 | 18 | enc = value.getRawText(); |
300 | 18 | } else { |
301 | log.error(messages.getMessage("template.encoding.notsupported", | |
302 | new Object[] { value.getRawText() })); | |
303 | } | |
304 | } | |
305 | } | |
306 | 72 | } |
307 | } | |
308 | ||
309 | ||
310 | } finally { | |
311 | 36 | if (in != null) { |
312 | 36 | in.close(); |
313 | } | |
314 | 36 | if (snippet != null) { |
315 | 36 | snippet.close(); |
316 | 36 | } |
317 | } | |
318 | ||
319 | 36 | if (enc == null) { |
320 | 18 | enc = getConfig().getServletContext().getInitParameter(Globals.CLAY_HTML_CHARSET); |
321 | 18 | if (enc != null) { |
322 | 2 | if (!Charset.isSupported(enc)) { |
323 | 1 | log.error(messages.getMessage("template.encoding.notsupported", |
324 | new Object[] { enc })); | |
325 | 1 | enc = System.getProperty("file.encoding"); |
326 | 1 | } |
327 | } else { | |
328 | 16 | enc = System.getProperty("file.encoding"); |
329 | } | |
330 | } | |
331 | ||
332 | 36 | return enc; |
333 | } | |
334 | ||
335 | /** | |
336 | * <p>Returns the {@link org.apache.shale.clay.parser.builder.Builder} that | |
337 | * is assigned the task of converting the html node to a corresponding component | |
338 | * metadata used to construct a JSF resource.</p> | |
339 | * | |
340 | * @param node markup node | |
341 | * @return builder that maps markup to config beans | |
342 | */ | |
343 | public Builder getBuilder(Node node) { | |
344 | 108 | return BuilderFactory.getRenderer(node); |
345 | } | |
346 | ||
347 | } |