/*
 * 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 Tag-based custom JSP tag to be a value that's callable in templates as an user-defined directive. For SimpleTag-based custom JSP tags SimpleTagDirectiveModel is used instead.
/** * 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); } 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[].
/** * 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; } 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); } } 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."); } } 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(); } } } } }