001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings.template.parser;
014:
015: import org.wings.template.FileTemplateSource;
016: import org.wings.template.LabelTagHandler;
017: import org.wings.template.TemplateSource;
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020:
021: import java.io.*;
022: import java.util.*;
023:
024: /**
025: * <CODE>PageParser</CODE>
026: * parses SGML markup'd pages and executes
027: * <em>active</em> Tag. Returns its output
028: * through a HttpServletResponse (given in a ParseContext).
029: * Active Tags are handled with SpecialTagHandlers which
030: * can be registered for a specific Tag.
031: * <p/>
032: * <p><h4>Error handling:</h4>
033: * To simplify error detection and correction,
034: * exceptions thrown by the <CODE>executeTag()</CODE>-methods of the
035: * pluggable handlers (e.g. called servlets) are printed,
036: * enclosed in comments ("<!-- ... -->"), in the HTML output.
037: *
038: * @author <A href="mailto:zeller@think.de">Henner Zeller</A>
039: * @see javax.servlet.http.HttpServlet
040: */
041:
042: public class PageParser {
043: private final static Log log = LogFactory.getLog(PageParser.class);
044:
045: private static PageParser sharedInstance = null;
046:
047: /**
048: * returns a singleton instance of this PageParser.
049: * You usually want to use the singleton instance to make
050: * use of a central cache.
051: */
052: public static PageParser getInstance() {
053: if (sharedInstance == null) {
054: synchronized (PageParser.class) {
055: if (sharedInstance == null)
056: sharedInstance = new PageParser();
057: }
058: }
059:
060: return sharedInstance;
061: }
062:
063: /**
064: * This Map contains the cached parsed
065: * pages, saved in TemplateSourceInfo-Objects.
066: * The key is the canonical name of the Data
067: * Source.
068: */
069: private final Map/*<String, TemplateSourceInfo>*/pages = new HashMap();
070:
071: /**
072: * a Hashtable with key/value=tagname/handlerClass
073: */
074: private final Map/*<String,Class>*/handlerClasses = new HashMap();
075:
076: /**
077: * Constructs a new PageParser.
078: */
079: public PageParser() {
080: }
081:
082: /**
083: * Process a general DataStore representing a Template
084: *
085: * @param source The template TemplateSource
086: * @param context The context used while parsing; contains
087: * at least the HttpServletRequest and
088: * HttpServletResponse.
089: * @see ParseContext
090: * @see TemplateSource
091: */
092: public void process(TemplateSource source, ParseContext context)
093: throws IOException {
094: interpretPage(source, getPageParts(source, context), context);
095: }
096:
097: /**
098: * Processes a file.
099: *
100: * @param file The file containing SGML markup
101: * @param context The context used while parsing; contains
102: * at least the HttpServletRequest and HttpServletResponse.
103: * @see ParseContext
104: */
105: public void process(File file, ParseContext context)
106: throws IOException {
107: process(new FileTemplateSource(file), context);
108: }
109:
110: public Map getLabels(TemplateSource source) {
111: String cName = source.getCanonicalName();
112: if (cName == null)
113: return null;
114:
115: TemplateSourceInfo sourceInfo = (TemplateSourceInfo) pages
116: .get(cName);
117: if (sourceInfo == null)
118: return null;
119:
120: return sourceInfo.labels;
121: }
122:
123: /**
124: * register a handler for a specific tag (Class name).
125: * Tags are case-insensitive.
126: *
127: * @param tagname the name of the tag like 'MYSPECIALTAG' or 'SERVLET'
128: * @param handlerClassName the <em>name of class</em> implementing the
129: * action for this tag. This class must
130: * implement the SpecialTagHandler
131: * interface.
132: * @throws ClassNotFoundException if the class with the specified
133: * name is not found.
134: */
135: public void addTagHandler(String tagname, String handlerClassName)
136: throws ClassNotFoundException {
137: handlerClasses.put(tagname.toUpperCase(), Class
138: .forName(handlerClassName));
139: }
140:
141: /**
142: * register a handler for a specific tag (Class).
143: * Tags are case-insensitive.
144: *
145: * @param tagname the name of the tag like 'MYSPECIALTAG' or 'SERVLET'
146: * @param handlerClass the <em>class</em> implementing the
147: * action for this tag. This class must
148: * implement the SpecialTagHandler
149: * interface.
150: */
151: public void addTagHandler(String tagname, Class handlerClass) {
152: handlerClasses.put(tagname.toUpperCase(), handlerClass);
153: }
154:
155: /**
156: * @return Itearator of all Tags which are special to
157: * this PageParser
158: */
159: public Iterator getRegisteredTags() {
160: return handlerClasses.keySet().iterator();
161: }
162:
163: /**
164: * If TemplateSource has changed or has not yet been loaded, load
165: * it and chop into sections, storing result for future use.
166: * Otherwise, return stored preprocessed page.
167: *
168: * @param source TemplateSource for which we want page section list
169: * @return list of page sections, as described in parsePage().
170: * @see #parsePage
171: */
172: private List getPageParts(TemplateSource source,
173: ParseContext context) throws IOException {
174: // first, check to see if we have cached version
175: String cName = source.getCanonicalName();
176: TemplateSourceInfo sourceInfo = null;
177: if (cName != null)
178: sourceInfo = (TemplateSourceInfo) pages.get(cName);
179:
180: /*
181: * parse the page if it has changed or no cached
182: * information is available.
183: */
184: if (sourceInfo == null
185: || sourceInfo.lastModified != source.lastModified()) {
186: // if no cached version, or modified, load
187: sourceInfo = parsePage(source, context);
188: if (cName != null)
189: pages.put(cName, sourceInfo);
190: }
191: return sourceInfo.parts;
192: }
193:
194: /**
195: * Scan through vector of page sections and build
196: * output.
197: * Read the static areas of the TemplateSource and copy them to the
198: * output until the beginning of the next special tag. Invokes
199: * the <CODE>executeTag()</CODE> Method for the tagand goes on with copying.
200: * or invoking the servlets to which they refer.
201: *
202: * @param parts page sections, as provide by parsePage()
203: * @see #parsePage
204: */
205: private void interpretPage(TemplateSource source, List parts,
206: ParseContext context) throws IOException {
207:
208: OutputStream out = context.getOutputStream();
209: InputStream inStream = null;
210: byte buf[] = null;
211:
212: try {
213: // input
214: inStream = source.getInputStream();
215: long inPos = 0;
216:
217: /*
218: * Get Copy Buffer.
219: * If we allocate it here once and pass it to the
220: * copy()-function we don't have to create and garbage collect
221: * a buffer each time we call copy().
222: *
223: * REVISE: this should use a buffer Manager:
224: * a queue which stores buffers. This
225: * way the JVM doesn't have to garbage collect the buffers
226: * created here, so we may use larger Buffers
227: * here.
228: */
229: buf = new byte[4096]; // Get buffer from Buffer Manager
230:
231: for (int i = 0; i < parts.size(); i++) {
232: /** <critical-path> **/
233: SpecialTagHandler part = (SpecialTagHandler) parts
234: .get(i);
235: // copy TemplateSource content till the beginning of the Tag:
236: copy(inStream, out, part.getTagStart() - inPos, buf);
237:
238: context.startTag(i);
239: try {
240: part.executeTag(context, inStream);
241: }
242: /*
243: * Display any Exceptions or Errors as
244: * comment in the page
245: */catch (Throwable e) {
246: out.flush();
247: PrintWriter pout = new PrintWriter(out);
248: pout.println("<!-- ERROR: ------------");
249: e.printStackTrace(pout);
250: pout.println("-->");
251: pout.flush();
252: }
253: context.doneTag(i);
254:
255: inPos = part.getTagStart() + part.getTagLength();
256: /** </critical-path> **/
257: }
258: // copy rest until end of TemplateSource
259: copy(inStream, out, -1, buf);
260: } finally {
261: // clean up resouce: opened input stream
262: if (inStream != null)
263: inStream.close();
264: buf = null; // return buffer to Buffer Manager
265: }
266: out.flush();
267: }
268:
269: /**
270: * copies an InputStream to an OutputStream. copies max. length
271: * bytes.
272: *
273: * @param in The source stream
274: * @param out The destination stream
275: * @param length number of bytes to copy; -1 for unlimited
276: * @param buf Buffer used as temporary space to copy
277: * block-wise.
278: */
279: private static void copy(InputStream in, OutputStream out,
280: long length, byte buf[]) throws IOException {
281: int len;
282: boolean limited = (length >= 0);
283: int rest = limited ? (int) length : buf.length;
284: while (rest > 0
285: && (len = in.read(buf, 0,
286: (rest > buf.length) ? buf.length : rest)) > 0) {
287: out.write(buf, 0, len);
288: if (limited)
289: rest -= len;
290: }
291: }
292:
293: /**
294: * Open and read source, returning list of contents.
295: * The returned vector will contain a list of
296: * <CODE>SpecialTagHandler</CODE>s, containing the
297: * position/length within the input source they are
298: * responsible for.
299: * This Vector is used within <CODE>interpretPage()</CODE>
300: * to create the output.
301: *
302: * @param source source to open and process
303: * @param context The context used while parsing; contains
304: * at least the HttpServletRequest and HttpServletResponse
305: * @return TemplateSourceInfo containing page elements.
306: * <!-- see private <a href="#interpretPage">interpretPage()</a> -->
307: */
308: private TemplateSourceInfo parsePage(TemplateSource source,
309: ParseContext context) throws IOException {
310: /*
311: * read source contents. The SGMLTag requires
312: * to read from a Reader which supports the
313: * mark() operation so we need a BufferedReader
314: * here.
315: *
316: * The PositionReader may be asked at which Position
317: * it currently is (much like the java.io.LineNumberReader); this
318: * is used to determine the exact position of the Tags in the
319: * page to be able to loop through the fast copy/execute/copy
320: * sequence in interpretPage().
321: *
322: * Since interpreting is operating on an InputStream which
323: * copies and skip()s bytes, any source position count done here
324: * assumes that sizeof(char) == sizeof(byte).
325: * So we force the InputStreamReader to interpret the Stream's content
326: * as ISO8859_1, because the localized default behaviour may
327: * differ (e.g. UTF8 for which sizeof(char) != sizeof (byte)
328: */
329: PositionReader fin = null;
330: // from JDK 1.1.6, the name of the encoding is ISO8859_1, but the old
331: // value is still accepted.
332: fin = new PositionReader(
333: new BufferedReader(new InputStreamReader(source
334: .getInputStream(), "8859_1")));
335: TemplateSourceInfo sourceInfo = new TemplateSourceInfo();
336:
337: try {
338: // scan through page parsing SpecialTag statements
339: sourceInfo.lastModified = source.lastModified();
340: sourceInfo.parts = new ArrayList();
341: sourceInfo.labels = new HashMap();
342: long startPos;
343: SGMLTag tag, endTag;
344: long startTime = System.currentTimeMillis();
345: do {
346: endTag = null;
347: startPos = fin.getPosition();
348: tag = new SGMLTag(fin, false);
349: if (tag.getName() != null) {
350: String upName = tag.getName().toUpperCase();
351: if (handlerClasses.containsKey(upName)) {
352: SpecialTagHandler handler = null;
353: try {
354: Class handlerClass = (Class) handlerClasses
355: .get(upName);
356: handler = (SpecialTagHandler) handlerClass
357: .newInstance();
358:
359: endTag = handler.parseTag(context, fin,
360: startPos, tag);
361: } catch (Exception e) {
362: log.warn("Exception", e);
363: }
364: if (endTag != null) {
365: if ("LABEL".equals(upName)) {
366: LabelTagHandler labelHandler = (LabelTagHandler) handler;
367: sourceInfo.labels.put(labelHandler
368: .getFor(), labelHandler
369: .getContent());
370: }
371: sourceInfo.parts.add(handler);
372: }
373: }
374: }
375: } while (!tag.finished());
376: /***
377: sourceInfo.parseTime = System.currentTimeMillis() - startTime;
378: System.err.println ("PageParser: parsing '" +
379: source.getCanonicalName() + "' took " +
380: sourceInfo.parseTime + "ms for " +
381: sourceInfo.parts.size() + " handlers");
382: ***/
383: } finally {
384: if (fin != null)
385: fin.close();
386: }
387: return sourceInfo;
388: }
389:
390: /**
391: * Source info holds the parse information for
392: * a TemplateSource .. and some statistical stuff which
393: * may be interesting for administrative
394: * frontends
395: */
396: private static final class TemplateSourceInfo {
397: ArrayList parts;
398: Map labels;
399: long lastModified;
400:
401: // long parseTime;
402:
403: public TemplateSourceInfo() {
404: }
405: }
406: }
407:
408: /*
409: * Local variables:
410: * c-basic-offset: 4
411: * compile-command: "ant -emacs -find build.xml"
412: * End:
413: */
|