| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package freemarker.ext.jsp; |
| |
| import java.beans.IntrospectionException; |
| import java.io.CharArrayReader; |
| import java.io.CharArrayWriter; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.util.Map; |
| |
| import javax.servlet.jsp.JspException; |
| import javax.servlet.jsp.JspWriter; |
| import javax.servlet.jsp.tagext.BodyContent; |
| import javax.servlet.jsp.tagext.BodyTag; |
| import javax.servlet.jsp.tagext.IterationTag; |
| import javax.servlet.jsp.tagext.SimpleTag; |
| import javax.servlet.jsp.tagext.Tag; |
| import javax.servlet.jsp.tagext.TryCatchFinally; |
| |
| import freemarker.log.Logger; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.TemplateTransformModel; |
| import freemarker.template.TransformControl; |
| |
| /** |
| * Adapts a {@link Tag}-based custom JSP tag to be a value that's callable in templates as an user-defined directive. |
| * For {@link SimpleTag}-based custom JSP tags {@link SimpleTagDirectiveModel} is used instead. |
| */ |
| class TagTransformModel extends JspTagModelBase implements TemplateTransformModel { |
| private static final Logger LOG = Logger.getLogger("freemarker.jsp"); |
| |
| private final boolean isBodyTag; |
| private final boolean isIterationTag; |
| private final boolean isTryCatchFinally; |
| |
| public TagTransformModel(String tagName, Class tagClass) throws IntrospectionException { |
| super(tagName, tagClass); |
| isIterationTag = IterationTag.class.isAssignableFrom(tagClass); |
| isBodyTag = isIterationTag && BodyTag.class.isAssignableFrom(tagClass); |
| isTryCatchFinally = TryCatchFinally.class.isAssignableFrom(tagClass); |
| } |
| |
| @Override |
| public Writer getWriter(Writer out, Map args) throws TemplateModelException { |
| try { |
| Tag tag = (Tag) getTagInstance(); |
| FreeMarkerPageContext pageContext = PageContextFactory.getCurrentPageContext(); |
| Tag parentTag = (Tag) pageContext.peekTopTag(Tag.class); |
| tag.setParent(parentTag); |
| tag.setPageContext(pageContext); |
| setupTag(tag, args, pageContext.getObjectWrapper()); |
| // If the parent of this writer is not a JspWriter itself, use |
| // a little Writer-to-JspWriter adapter... |
| boolean usesAdapter; |
| if (out instanceof JspWriter) { |
| // This is just a sanity check. If it were JDK 1.4-only, |
| // we'd use an assert. |
| if (out != pageContext.getOut()) { |
| throw new TemplateModelException( |
| "out != pageContext.getOut(). Out is " + |
| out + " pageContext.getOut() is " + |
| pageContext.getOut()); |
| } |
| usesAdapter = false; |
| } else { |
| out = new JspWriterAdapter(out); |
| pageContext.pushWriter((JspWriter) out); |
| usesAdapter = true; |
| } |
| JspWriter w = new TagWriter(out, tag, pageContext, usesAdapter); |
| pageContext.pushTopTag(tag); |
| pageContext.pushWriter(w); |
| return w; |
| } catch (Exception e) { |
| throw toTemplateModelExceptionOrRethrow(e); |
| } |
| } |
| |
| /** |
| * An implementation of BodyContent that buffers it's input to a char[]. |
| */ |
| static class BodyContentImpl extends BodyContent { |
| private CharArrayWriter buf; |
| |
| BodyContentImpl(JspWriter out, boolean buffer) { |
| super(out); |
| if (buffer) initBuffer(); |
| } |
| |
| void initBuffer() { |
| buf = new CharArrayWriter(); |
| } |
| |
| @Override |
| public void flush() throws IOException { |
| if (buf == null) { |
| getEnclosingWriter().flush(); |
| } |
| } |
| |
| @Override |
| public void clear() throws IOException { |
| if (buf != null) { |
| buf = new CharArrayWriter(); |
| } else { |
| throw new IOException("Can't clear"); |
| } |
| } |
| |
| @Override |
| public void clearBuffer() throws IOException { |
| if (buf != null) { |
| buf = new CharArrayWriter(); |
| } else { |
| throw new IOException("Can't clear"); |
| } |
| } |
| |
| @Override |
| public int getRemaining() { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public void newLine() throws IOException { |
| write(JspWriterAdapter.NEWLINE); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| } |
| |
| @Override |
| public void print(boolean arg0) throws IOException { |
| write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); |
| } |
| |
| @Override |
| public void print(char arg0) throws IOException { |
| write(arg0); |
| } |
| |
| @Override |
| public void print(char[] arg0) throws IOException { |
| write(arg0); |
| } |
| |
| @Override |
| public void print(double arg0) throws IOException { |
| write(Double.toString(arg0)); |
| } |
| |
| @Override |
| public void print(float arg0) throws IOException { |
| write(Float.toString(arg0)); |
| } |
| |
| @Override |
| public void print(int arg0) throws IOException { |
| write(Integer.toString(arg0)); |
| } |
| |
| @Override |
| public void print(long arg0) throws IOException { |
| write(Long.toString(arg0)); |
| } |
| |
| @Override |
| public void print(Object arg0) throws IOException { |
| write(arg0 == null ? "null" : arg0.toString()); |
| } |
| |
| @Override |
| public void print(String arg0) throws IOException { |
| write(arg0); |
| } |
| |
| @Override |
| public void println() throws IOException { |
| newLine(); |
| } |
| |
| @Override |
| public void println(boolean arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(char arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(char[] arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(double arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(float arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(int arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(long arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(Object arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void println(String arg0) throws IOException { |
| print(arg0); |
| newLine(); |
| } |
| |
| @Override |
| public void write(int c) throws IOException { |
| if (buf != null) { |
| buf.write(c); |
| } else { |
| getEnclosingWriter().write(c); |
| } |
| } |
| |
| @Override |
| public void write(char[] cbuf, int off, int len) throws IOException { |
| if (buf != null) { |
| buf.write(cbuf, off, len); |
| } else { |
| getEnclosingWriter().write(cbuf, off, len); |
| } |
| } |
| |
| @Override |
| public String getString() { |
| return buf.toString(); |
| } |
| |
| @Override |
| public Reader getReader() { |
| return new CharArrayReader(buf.toCharArray()); |
| } |
| |
| @Override |
| public void writeOut(Writer out) throws IOException { |
| buf.writeTo(out); |
| } |
| |
| } |
| |
| class TagWriter extends BodyContentImpl implements TransformControl { |
| private final Tag tag; |
| private final FreeMarkerPageContext pageContext; |
| private boolean needPop = true; |
| private final boolean needDoublePop; |
| private boolean closed = false; |
| |
| TagWriter(Writer out, Tag tag, FreeMarkerPageContext pageContext, boolean needDoublePop) { |
| super((JspWriter) out, false); |
| this.needDoublePop = needDoublePop; |
| this.tag = tag; |
| this.pageContext = pageContext; |
| } |
| |
| @Override |
| public String toString() { |
| return "TagWriter for " + tag.getClass().getName() + " wrapping a " + getEnclosingWriter().toString(); |
| } |
| |
| Tag getTag() { |
| return tag; |
| } |
| |
| FreeMarkerPageContext getPageContext() { |
| return pageContext; |
| } |
| |
| @Override |
| public int onStart() |
| throws TemplateModelException { |
| try { |
| int dst = tag.doStartTag(); |
| switch(dst) { |
| case Tag.SKIP_BODY: |
| // EVAL_PAGE is illegal actually, but some taglibs out there |
| // use it, and it seems most JSP compilers allow them to and |
| // treat it identically to SKIP_BODY, so we're going with |
| // the flow and we allow it too, altough strictly speaking |
| // it's in violation of the spec. |
| case Tag.EVAL_PAGE: { |
| endEvaluation(); |
| return TransformControl.SKIP_BODY; |
| } |
| case BodyTag.EVAL_BODY_BUFFERED: { |
| if (isBodyTag) { |
| initBuffer(); |
| BodyTag btag = (BodyTag) tag; |
| btag.setBodyContent(this); |
| btag.doInitBody(); |
| } else { |
| throw new TemplateModelException("Can't buffer body since " + tag.getClass().getName() + " does not implement BodyTag."); |
| } |
| // Intentional fall-through |
| } |
| case Tag.EVAL_BODY_INCLUDE: { |
| return TransformControl.EVALUATE_BODY; |
| } |
| default: { |
| throw new RuntimeException("Illegal return value " + dst + " from " + tag.getClass().getName() + ".doStartTag()"); |
| } |
| } |
| } catch (Exception e) { |
| throw toTemplateModelExceptionOrRethrow(e); |
| } |
| } |
| |
| @Override |
| public int afterBody() |
| throws TemplateModelException { |
| try { |
| if (isIterationTag) { |
| int dab = ((IterationTag) tag).doAfterBody(); |
| switch(dab) { |
| case Tag.SKIP_BODY: |
| endEvaluation(); |
| return END_EVALUATION; |
| case IterationTag.EVAL_BODY_AGAIN: |
| return REPEAT_EVALUATION; |
| default: |
| throw new TemplateModelException("Unexpected return value " + dab + "from " + tag.getClass().getName() + ".doAfterBody()"); |
| } |
| } |
| endEvaluation(); |
| return END_EVALUATION; |
| } catch (Exception e) { |
| throw toTemplateModelExceptionOrRethrow(e); |
| } |
| } |
| |
| private void endEvaluation() throws JspException { |
| if (needPop) { |
| pageContext.popWriter(); |
| needPop = false; |
| } |
| if (tag.doEndTag() == Tag.SKIP_PAGE) { |
| LOG.warn("Tag.SKIP_PAGE was ignored from a " + tag.getClass().getName() + " tag."); |
| } |
| } |
| |
| @Override |
| public void onError(Throwable t) throws Throwable { |
| if (isTryCatchFinally) { |
| ((TryCatchFinally) tag).doCatch(t); |
| } else { |
| throw t; |
| } |
| } |
| |
| @Override |
| public void close() { |
| if (closed) { |
| return; |
| } |
| closed = true; |
| |
| if (needPop) { |
| pageContext.popWriter(); |
| } |
| pageContext.popTopTag(); |
| try { |
| if (isTryCatchFinally) { |
| ((TryCatchFinally) tag).doFinally(); |
| } |
| // No pooling yet |
| tag.release(); |
| } finally { |
| if (needDoublePop) { |
| pageContext.popWriter(); |
| } |
| } |
| } |
| |
| } |
| } |