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: 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  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          log = LogFactory
67                  .getLog(org.apache.shale.clay.config.ClayXmlParser.class);
68      }
69  
70      /***
71       * <p>
72       * Message resources for this class.
73       * </p>
74       */
75      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      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          this.config = config;
96      }
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         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         ((ComponentConfigBean) config).addChild(generateElement(templateURL,
128                 templateName));
129     }
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         if (log.isInfoEnabled()) {
151             log.info(messages.getMessage("loading.template",
152                     new Object[] { templateName }));
153         }
154 
155         ComponentBean root = new ComponentBean();
156         root.setJsfid(templateName);
157         root.setComponentType("javax.faces.HtmlOutputText");
158 
159         // generate the document
160 
161         StringBuffer buffer = loadTemplate(templateURL);
162 
163         List roots = new Parser().parse(buffer);
164         Iterator ri = roots.iterator();
165         while (ri.hasNext()) {
166             Node node = (Node) ri.next();
167             Builder renderer = getBuilder(node);
168             ElementBean child = renderer.createElement(node);
169 
170             root.addChild(child);
171             if (renderer.isChildrenAllowed()) {
172                 renderer.encode(node, child, child);
173             } else {
174                 renderer.encode(node, child, root);
175             }
176         }
177 
178         roots.clear();
179         roots = null;
180         buffer.setLength(0);
181         buffer = null;
182         ri = null;
183 
184         // verify there is not a duplicate component id within a naming
185         // container.
186         config.checkTree(root);
187 
188         // compress the tree merging adjacent verbatim nodes
189         if (config instanceof TemplateConfigBean) {
190             ((TemplateConfigBean) config).optimizeTree(root);
191         }
192 
193         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         StringBuffer buff = new StringBuffer();
209         BufferedReader in = null;
210         String enc = getCharacterEncoding(templateURL);
211 
212         if (log.isDebugEnabled()) {
213            log.debug(messages.getMessage("template.encoding",
214                new Object[] { enc, templateURL.getFile() }));
215         }
216 
217         try {
218 
219             in = new BufferedReader(new InputStreamReader(templateURL.openStream(), enc));
220             while (in.ready()) {
221                buff.append(in.readLine()).append("\n");
222             }
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            if (in != null) {
230               in.close();
231            }
232         }
233 
234         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: &lt;-- ### 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      * &lt;context-param&gt;<br/>
254      * &nbsp;&nbsp;&lt;param-name&gt;org.apache.shale.clay.HTML_TEMPLATE_CHARSET
255      * &lt;/param-name&gt;<br/>
256      * &nbsp;&nbsp;&lt;param-value&gt;UTF-8&lt;/param-value&gt;<br/>
257      * &lt;/context-param&gt;<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         InputStreamReader in = null;
269         StringWriter snippet = new StringWriter();
270         char[] chars = new char[512];
271         String enc = null;
272 
273 
274         try {
275             // read in the top of the template
276             in = new InputStreamReader(templateURL.openStream());
277             int n = in.read(chars);
278             snippet.write(chars, 0, n);
279 
280             // look for the comment page directive containing the charset
281             int s = snippet.getBuffer().indexOf(Parser.START_CHARSET_TOKEN);
282             if (s > -1) {
283                int e = snippet.getBuffer().indexOf(Parser.END_CHARSET_TOKEN, s);
284                AttributeTokenizer tokenizer = new AttributeTokenizer(snippet.getBuffer(), s, e, 1, 0);
285                Iterator ti = tokenizer.iterator();
286                while (ti.hasNext()) {
287                    Map.Entry attribute = (Map.Entry) ti.next();
288                    Token key = (Token) attribute.getKey();
289                    //check the attribute name, we are only interested
290                    //in "charset"
291                    if (key != null && key.getRawText() != null
292                        && key.getRawText().equalsIgnoreCase("charset")) {
293                        Token value = (Token) attribute.getValue();
294 
295                        //look for the value of the charset attribute
296                        if (value != null && value.getRawText() != null) {
297                           // if it is supported, use the value for the encoding
298                           if (Charset.isSupported(value.getRawText())) {
299                               enc = value.getRawText();
300                           } else {
301                               log.error(messages.getMessage("template.encoding.notsupported",
302                                       new Object[] { value.getRawText() }));
303                           }
304                        }
305                    }
306                }
307             }
308 
309 
310         } finally {
311             if (in != null) {
312                in.close();
313             }
314             if (snippet != null) {
315                snippet.close();
316             }
317         }
318 
319         if (enc == null) {
320             enc = getConfig().getServletContext().getInitParameter(Globals.CLAY_HTML_CHARSET);
321             if (enc != null) {
322                if (!Charset.isSupported(enc)) {
323                   log.error(messages.getMessage("template.encoding.notsupported",
324                            new Object[] { enc }));
325                   enc = System.getProperty("file.encoding");
326                }
327             } else {
328                enc = System.getProperty("file.encoding");
329             }
330         }
331 
332         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         return BuilderFactory.getRenderer(node);
345     }
346 
347 }