/*
* 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);
}
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;
}
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();
}
}
}
}
}