001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.ext.jsp;
054:
055: import freemarker.ext.beans.BeansWrapper;
056: import freemarker.log.Logger;
057: import freemarker.template.ObjectWrapper;
058: import freemarker.template.TemplateModel;
059: import freemarker.template.TemplateModelException;
060: import freemarker.template.TemplateTransformModel;
061: import freemarker.template.TransformControl;
062: import freemarker.template.utility.SecurityUtilities;
063: import freemarker.template.utility.StringUtil;
064:
065: import javax.servlet.jsp.JspException;
066: import javax.servlet.jsp.JspWriter;
067: import javax.servlet.jsp.tagext.BodyContent;
068: import javax.servlet.jsp.tagext.BodyTag;
069: import javax.servlet.jsp.tagext.IterationTag;
070: import javax.servlet.jsp.tagext.Tag;
071: import javax.servlet.jsp.tagext.TryCatchFinally;
072: import java.beans.BeanInfo;
073: import java.beans.IntrospectionException;
074: import java.beans.Introspector;
075: import java.beans.PropertyDescriptor;
076: import java.io.CharArrayReader;
077: import java.io.CharArrayWriter;
078: import java.io.IOException;
079: import java.io.Reader;
080: import java.io.Writer;
081: import java.lang.reflect.InvocationTargetException;
082: import java.lang.reflect.Method;
083: import java.util.HashMap;
084: import java.util.Iterator;
085: import java.util.Map;
086:
087: /**
088: * @version $Id: TagTransformModel.java,v 1.17.2.2 2006/07/08 14:45:34 ddekany Exp $
089: * @author Attila Szegedi
090: */
091: class TagTransformModel implements TemplateTransformModel {
092: private static final char[] NEWLINE = SecurityUtilities
093: .getSystemProperty("line.separator").toCharArray();
094:
095: private static final Logger logger = Logger
096: .getLogger("freemarker.servlet");
097:
098: private final Class tagClass;
099: private final Method dynaSetter;
100: private final Map propertySetters = new HashMap();
101: private final boolean isBodyTag;
102: private final boolean isIterationTag;
103: private final boolean isTryCatchFinally;
104:
105: public TagTransformModel(Class tagClass)
106: throws IntrospectionException {
107: if (!Tag.class.isAssignableFrom(tagClass)) {
108: throw new IllegalArgumentException(tagClass.getName()
109: + " does not implement the " + Tag.class.getName()
110: + " interface.");
111: }
112: isIterationTag = IterationTag.class.isAssignableFrom(tagClass);
113: isBodyTag = isIterationTag
114: && BodyTag.class.isAssignableFrom(tagClass);
115: isTryCatchFinally = TryCatchFinally.class
116: .isAssignableFrom(tagClass);
117:
118: this .tagClass = tagClass;
119: BeanInfo bi = Introspector.getBeanInfo(tagClass);
120: PropertyDescriptor[] pda = bi.getPropertyDescriptors();
121: for (int i = 0; i < pda.length; i++) {
122: PropertyDescriptor pd = pda[i];
123: Method m = pd.getWriteMethod();
124: if (m != null) {
125: propertySetters.put(pd.getName(), m);
126: }
127: }
128: // Check to see if the tag implements the JSP2.0 DynamicAttributes
129: // interface, to allow setting of arbitrary attributes
130: Method dynaSetter;
131: try {
132: dynaSetter = tagClass.getMethod("setDynamicAttribute",
133: new Class[] { String.class, String.class,
134: Object.class });
135: } catch (NoSuchMethodException nsme) {
136: dynaSetter = null;
137: }
138: this .dynaSetter = dynaSetter;
139: }
140:
141: public Writer getWriter(Writer out, Map args)
142: throws TemplateModelException {
143: try {
144: Tag tag = getTagInstance();
145: FreeMarkerPageContext pageContext = PageContextFactory
146: .getCurrentPageContext();
147: Tag parentTag = pageContext.peekTopTag();
148: tag.setParent(parentTag);
149: tag.setPageContext(pageContext);
150: setupTag(tag, args, pageContext.getObjectWrapper());
151: // If the parent of this writer is not a JspWriter itself, use
152: // a little Writer-to-JspWriter adapter...
153: boolean usesAdapter;
154: if (out instanceof JspWriter) {
155: // This is just a sanity check. If it were JDK 1.4-only,
156: // we'd use an assert.
157: if (out != pageContext.getOut()) {
158: throw new TemplateModelException(
159: "out != pageContext.getOut(). Out is "
160: + out + " pageContext.getOut() is "
161: + pageContext.getOut());
162: }
163: usesAdapter = false;
164: } else {
165: out = new JspWriterAdapter(out);
166: pageContext.pushWriter((JspWriter) out);
167: usesAdapter = true;
168: }
169: JspWriter w = new TagWriter(out, tag, pageContext,
170: usesAdapter);
171: pageContext.pushTopTag(tag);
172: pageContext.pushWriter(w);
173: return w;
174: } catch (TemplateModelException e) {
175: throw e;
176: } catch (Exception e) {
177: throw new TemplateModelException(e);
178: }
179: }
180:
181: private Tag getTagInstance() throws IllegalAccessException,
182: InstantiationException {
183: return (Tag) tagClass.newInstance();
184: }
185:
186: private void setupTag(Object tag, Map args, ObjectWrapper wrapper)
187: throws TemplateModelException, InvocationTargetException,
188: IllegalAccessException {
189: BeansWrapper bwrapper = wrapper instanceof BeansWrapper ? (BeansWrapper) wrapper
190: : BeansWrapper.getDefaultInstance();
191: if (args != null && !args.isEmpty()) {
192: Object[] aarg = new Object[1];
193: for (Iterator iter = args.entrySet().iterator(); iter
194: .hasNext();) {
195: Map.Entry entry = (Map.Entry) iter.next();
196: aarg[0] = bwrapper.unwrap((TemplateModel) entry
197: .getValue());
198: Method m = (Method) propertySetters.get(entry.getKey());
199: if (m == null) {
200: if (dynaSetter == null) {
201: throw new TemplateModelException(
202: "Unknown property "
203: + StringUtil.jQuote(entry
204: .getKey().toString())
205: + " on instance of "
206: + tagClass.getName());
207: } else {
208: dynaSetter.invoke(tag, new Object[] { null,
209: entry.getKey(), aarg[0] });
210: }
211: } else {
212: BeansWrapper.coerceBigDecimals(m, aarg);
213: m.invoke(tag, aarg);
214: }
215: }
216: }
217: }
218:
219: /**
220: * An implementation of BodyContent that buffers it's input to a char[].
221: */
222: static class BodyContentImpl extends BodyContent {
223: private CharArrayWriter buf;
224:
225: BodyContentImpl(JspWriter out, boolean buffer) {
226: super (out);
227: if (buffer)
228: initBuffer();
229: }
230:
231: void initBuffer() {
232: buf = new CharArrayWriter();
233: }
234:
235: public void flush() throws IOException {
236: if (buf == null) {
237: getEnclosingWriter().flush();
238: }
239: }
240:
241: public void clear() throws IOException {
242: if (buf != null) {
243: buf = new CharArrayWriter();
244: } else {
245: throw new IOException("Can't clear");
246: }
247: }
248:
249: public void clearBuffer() throws IOException {
250: if (buf != null) {
251: buf = new CharArrayWriter();
252: } else {
253: throw new IOException("Can't clear");
254: }
255: }
256:
257: public int getRemaining() {
258: return Integer.MAX_VALUE;
259: }
260:
261: public void newLine() throws IOException {
262: write(NEWLINE);
263: }
264:
265: public void close() throws IOException {
266: }
267:
268: public void print(boolean arg0) throws IOException {
269: write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE
270: .toString());
271: }
272:
273: public void print(char arg0) throws IOException {
274: write(arg0);
275: }
276:
277: public void print(char[] arg0) throws IOException {
278: write(arg0);
279: }
280:
281: public void print(double arg0) throws IOException {
282: write(Double.toString(arg0));
283: }
284:
285: public void print(float arg0) throws IOException {
286: write(Float.toString(arg0));
287: }
288:
289: public void print(int arg0) throws IOException {
290: write(Integer.toString(arg0));
291: }
292:
293: public void print(long arg0) throws IOException {
294: write(Long.toString(arg0));
295: }
296:
297: public void print(Object arg0) throws IOException {
298: write(arg0 == null ? "null" : arg0.toString());
299: }
300:
301: public void print(String arg0) throws IOException {
302: write(arg0);
303: }
304:
305: public void println() throws IOException {
306: newLine();
307: }
308:
309: public void println(boolean arg0) throws IOException {
310: print(arg0);
311: newLine();
312: }
313:
314: public void println(char arg0) throws IOException {
315: print(arg0);
316: newLine();
317: }
318:
319: public void println(char[] arg0) throws IOException {
320: print(arg0);
321: newLine();
322: }
323:
324: public void println(double arg0) throws IOException {
325: print(arg0);
326: newLine();
327: }
328:
329: public void println(float arg0) throws IOException {
330: print(arg0);
331: newLine();
332: }
333:
334: public void println(int arg0) throws IOException {
335: print(arg0);
336: newLine();
337: }
338:
339: public void println(long arg0) throws IOException {
340: print(arg0);
341: newLine();
342: }
343:
344: public void println(Object arg0) throws IOException {
345: print(arg0);
346: newLine();
347: }
348:
349: public void println(String arg0) throws IOException {
350: print(arg0);
351: newLine();
352: }
353:
354: public void write(int c) throws IOException {
355: if (buf != null) {
356: buf.write(c);
357: } else {
358: getEnclosingWriter().write(c);
359: }
360: }
361:
362: public void write(char[] cbuf, int off, int len)
363: throws IOException {
364: if (buf != null) {
365: buf.write(cbuf, off, len);
366: } else {
367: getEnclosingWriter().write(cbuf, off, len);
368: }
369: }
370:
371: public String getString() {
372: return buf.toString();
373: }
374:
375: public Reader getReader() {
376: return new CharArrayReader(buf.toCharArray());
377: }
378:
379: public void writeOut(Writer out) throws IOException {
380: buf.writeTo(out);
381: }
382:
383: }
384:
385: class TagWriter extends BodyContentImpl implements TransformControl {
386: private final Tag tag;
387: private final FreeMarkerPageContext pageContext;
388: private boolean needPop = true;
389: private final boolean needDoublePop;
390:
391: TagWriter(Writer out, Tag tag,
392: FreeMarkerPageContext pageContext, boolean needDoublePop) {
393: super ((JspWriter) out, false);
394: this .needDoublePop = needDoublePop;
395: this .tag = tag;
396: this .pageContext = pageContext;
397: }
398:
399: public String toString() {
400: return "TagWriter for " + tag.getClass().getName()
401: + " wrapping a " + getEnclosingWriter().toString();
402: }
403:
404: Tag getTag() {
405: return tag;
406: }
407:
408: FreeMarkerPageContext getPageContext() {
409: return pageContext;
410: }
411:
412: public int onStart() throws TemplateModelException {
413: try {
414: int dst = tag.doStartTag();
415: switch (dst) {
416: case Tag.SKIP_BODY:
417: // EVAL_PAGE is illegal actually, but some taglibs out there
418: // use it, and it seems most JSP compilers allow them to and
419: // treat it identically to SKIP_BODY, so we're going with
420: // the flow and we allow it too, altough strictly speaking
421: // it is in violation of the spec.
422: case Tag.EVAL_PAGE: {
423: endEvaluation();
424: return TransformControl.SKIP_BODY;
425: }
426: case BodyTag.EVAL_BODY_BUFFERED: {
427: if (isBodyTag) {
428: initBuffer();
429: BodyTag btag = (BodyTag) tag;
430: btag.setBodyContent(this );
431: btag.doInitBody();
432: } else {
433: throw new TemplateModelException(
434: "Can't buffer body since "
435: + tag.getClass().getName()
436: + " does not implement BodyTag.");
437: }
438: // Intentional fall-through
439: }
440: case Tag.EVAL_BODY_INCLUDE: {
441: return TransformControl.EVALUATE_BODY;
442: }
443: default: {
444: throw new RuntimeException("Illegal return value "
445: + dst + " from " + tag.getClass().getName()
446: + ".doStartTag()");
447: }
448: }
449: } catch (JspException e) {
450: throw new TemplateModelException(e.getMessage(), e);
451: }
452: }
453:
454: public int afterBody() throws TemplateModelException {
455: try {
456: if (isIterationTag) {
457: int dab = ((IterationTag) tag).doAfterBody();
458: switch (dab) {
459: case Tag.SKIP_BODY: {
460: endEvaluation();
461: return END_EVALUATION;
462: }
463: case IterationTag.EVAL_BODY_AGAIN: {
464: return REPEAT_EVALUATION;
465: }
466: default: {
467: throw new TemplateModelException(
468: "Unexpected return value " + dab
469: + "from "
470: + tag.getClass().getName()
471: + ".doAfterBody()");
472: }
473: }
474: }
475: endEvaluation();
476: return END_EVALUATION;
477: } catch (JspException e) {
478: throw new TemplateModelException(e);
479: }
480: }
481:
482: private void endEvaluation() throws JspException {
483: if (needPop) {
484: pageContext.popWriter();
485: needPop = false;
486: }
487: if (tag.doEndTag() == Tag.SKIP_PAGE) {
488: logger.warn("Tag.SKIP_PAGE was ignored from a "
489: + tag.getClass().getName() + " tag.");
490: }
491: }
492:
493: public void onError(Throwable t) throws Throwable {
494: if (isTryCatchFinally) {
495: ((TryCatchFinally) tag).doCatch(t);
496: } else {
497: throw t;
498: }
499: }
500:
501: public void close() {
502: if (needPop) {
503: pageContext.popWriter();
504: }
505: pageContext.popTopTag();
506: try {
507: if (isTryCatchFinally) {
508: ((TryCatchFinally) tag).doFinally();
509: }
510: // No pooling yet
511: tag.release();
512: } finally {
513: if (needDoublePop) {
514: pageContext.popWriter();
515: }
516: }
517: }
518:
519: }
520:
521: static class JspWriterAdapter extends JspWriter {
522: private final Writer out;
523:
524: JspWriterAdapter(Writer out) {
525: super (0, true);
526: this .out = out;
527: }
528:
529: public String toString() {
530: return "JspWriterAdapter wrapping a " + out.toString();
531: }
532:
533: public void clear() throws IOException {
534: throw new IOException("Can't clear");
535: }
536:
537: public void clearBuffer() throws IOException {
538: throw new IOException("Can't clear");
539: }
540:
541: public void close() throws IOException {
542: throw new IOException("Close not permitted.");
543: }
544:
545: public void flush() throws IOException {
546: out.flush();
547: }
548:
549: public int getRemaining() {
550: return 0;
551: }
552:
553: public void newLine() throws IOException {
554: out.write(NEWLINE);
555: }
556:
557: public void print(boolean arg0) throws IOException {
558: out.write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE
559: .toString());
560: }
561:
562: public void print(char arg0) throws IOException {
563: out.write(arg0);
564: }
565:
566: public void print(char[] arg0) throws IOException {
567: out.write(arg0);
568: }
569:
570: public void print(double arg0) throws IOException {
571: out.write(Double.toString(arg0));
572: }
573:
574: public void print(float arg0) throws IOException {
575: out.write(Float.toString(arg0));
576: }
577:
578: public void print(int arg0) throws IOException {
579: out.write(Integer.toString(arg0));
580: }
581:
582: public void print(long arg0) throws IOException {
583: out.write(Long.toString(arg0));
584: }
585:
586: public void print(Object arg0) throws IOException {
587: out.write(arg0 == null ? "null" : arg0.toString());
588: }
589:
590: public void print(String arg0) throws IOException {
591: out.write(arg0);
592: }
593:
594: public void println() throws IOException {
595: newLine();
596: }
597:
598: public void println(boolean arg0) throws IOException {
599: print(arg0);
600: newLine();
601: }
602:
603: public void println(char arg0) throws IOException {
604: print(arg0);
605: newLine();
606: }
607:
608: public void println(char[] arg0) throws IOException {
609: print(arg0);
610: newLine();
611: }
612:
613: public void println(double arg0) throws IOException {
614: print(arg0);
615: newLine();
616: }
617:
618: public void println(float arg0) throws IOException {
619: print(arg0);
620: newLine();
621: }
622:
623: public void println(int arg0) throws IOException {
624: print(arg0);
625: newLine();
626: }
627:
628: public void println(long arg0) throws IOException {
629: print(arg0);
630: newLine();
631: }
632:
633: public void println(Object arg0) throws IOException {
634: print(arg0);
635: newLine();
636: }
637:
638: public void println(String arg0) throws IOException {
639: print(arg0);
640: newLine();
641: }
642:
643: public void write(int c) throws IOException {
644: out.write(c);
645: }
646:
647: public void write(char[] arg0, int arg1, int arg2)
648: throws IOException {
649: out.write(arg0, arg1, arg2);
650: }
651: }
652: }
|