1 /* 2 * Copyright (c) 2003 The Visigoth Software Society. All rights 3 * reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * 3. The end-user documentation included with the redistribution, if 18 * any, must include the following acknowledgement: 19 * "This product includes software developed by the 20 * Visigoth Software Society (http://www.visigoths.org/)." 21 * Alternately, this acknowledgement may appear in the software itself, 22 * if and wherever such third-party acknowledgements normally appear. 23 * 24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 25 * project contributors may be used to endorse or promote products derived 26 * from this software without prior written permission. For written 27 * permission, please contact visigoths@visigoths.org. 28 * 29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth" 30 * nor may "FreeMarker" or "Visigoth" appear in their names 31 * without prior written permission of the Visigoth Software Society. 32 * 33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR 37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 44 * SUCH DAMAGE. 45 * ==================================================================== 46 * 47 * This software consists of voluntary contributions made by many 48 * individuals on behalf of the Visigoth Software Society. For more 49 * information on the Visigoth Software Society, please see 50 * http://www.visigoths.org/ 51 */ 52 53 package freemarker.ext.jsp; 54 55 import java.beans.IntrospectionException; 56 import java.io.CharArrayReader; 57 import java.io.CharArrayWriter; 58 import java.io.IOException; 59 import java.io.Reader; 60 import java.io.Writer; 61 import java.util.Map; 62 63 import javax.servlet.jsp.JspException; 64 import javax.servlet.jsp.JspWriter; 65 import javax.servlet.jsp.tagext.BodyContent; 66 import javax.servlet.jsp.tagext.BodyTag; 67 import javax.servlet.jsp.tagext.IterationTag; 68 import javax.servlet.jsp.tagext.Tag; 69 import javax.servlet.jsp.tagext.TryCatchFinally; 70 71 import freemarker.log.Logger; 72 import freemarker.template.TemplateModelException; 73 import freemarker.template.TemplateTransformModel; 74 import freemarker.template.TransformControl; 75 import freemarker.template.utility.SecurityUtilities; 76 77 /** 78 * @version $Id: TagTransformModel.java,v 1.17.2.2 2006/07/08 14:45:34 ddekany Exp $ 79 * @author Attila Szegedi 80 */ 81 class TagTransformModel extends JspTagModelBase implements TemplateTransformModel 82 { 83 private static final Logger logger = Logger.getLogger("freemarker.servlet"); 84 85 private final boolean isBodyTag; 86 private final boolean isIterationTag; 87 private final boolean isTryCatchFinally; 88 89 public TagTransformModel(Class tagClass) throws IntrospectionException { 90 super(tagClass); 91 isIterationTag = IterationTag.class.isAssignableFrom(tagClass); 92 isBodyTag = isIterationTag && BodyTag.class.isAssignableFrom(tagClass); 93 isTryCatchFinally = TryCatchFinally.class.isAssignableFrom(tagClass); 94 } 95 96 public Writer getWriter(Writer out, Map args) throws TemplateModelException 97 { 98 try { 99 Tag tag = (Tag)getTagInstance(); 100 FreeMarkerPageContext pageContext = PageContextFactory.getCurrentPageContext(); 101 Tag parentTag = (Tag)pageContext.peekTopTag(Tag.class); 102 tag.setParent(parentTag); 103 tag.setPageContext(pageContext); 104 setupTag(tag, args, pageContext.getObjectWrapper()); 105 // If the parent of this writer is not a JspWriter itself, use 106 // a little Writer-to-JspWriter adapter... 107 boolean usesAdapter; 108 if(out instanceof JspWriter) { 109 // This is just a sanity check. If it were JDK 1.4-only, 110 // we'd use an assert. 111 if(out != pageContext.getOut()) { 112 throw new TemplateModelException( 113 "out != pageContext.getOut(). Out is " + 114 out + " pageContext.getOut() is " + 115 pageContext.getOut()); 116 } 117 usesAdapter = false; 118 } 119 else { 120 out = new JspWriterAdapter(out); 121 pageContext.pushWriter((JspWriter)out); 122 usesAdapter = true; 123 } 124 JspWriter w = new TagWriter(out, tag, pageContext, usesAdapter); 125 pageContext.pushTopTag(tag); 126 pageContext.pushWriter(w); 127 return w; 128 } 129 catch(TemplateModelException e) { 130 throw e; 131 } 132 catch(RuntimeException e) { 133 throw e; 134 } 135 catch(Exception e) { 136 throw new TemplateModelException(e); 137 } 138 } 139 140 /** 141 * An implementation of BodyContent that buffers it's input to a char[]. 142 */ 143 static class BodyContentImpl extends BodyContent { 144 private CharArrayWriter buf; 145 146 BodyContentImpl(JspWriter out, boolean buffer) { 147 super(out); 148 if (buffer) initBuffer(); 149 } 150 151 void initBuffer() { 152 buf = new CharArrayWriter(); 153 } 154 155 public void flush() throws IOException { 156 if(buf == null) { 157 getEnclosingWriter().flush(); 158 } 159 } 160 161 public void clear() throws IOException { 162 if(buf != null) { 163 buf = new CharArrayWriter(); 164 } 165 else { 166 throw new IOException("Can't clear"); 167 } 168 } 169 170 public void clearBuffer() throws IOException { 171 if(buf != null) { 172 buf = new CharArrayWriter(); 173 } 174 else { 175 throw new IOException("Can't clear"); 176 } 177 } 178 179 public int getRemaining() { 180 return Integer.MAX_VALUE; 181 } 182 183 public void newLine() throws IOException { 184 write(JspWriterAdapter.NEWLINE); 185 } 186 187 public void close() throws IOException { 188 } 189 190 public void print(boolean arg0) throws IOException { 191 write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); 192 } 193 194 public void print(char arg0) throws IOException 195 { 196 write(arg0); 197 } 198 199 public void print(char[] arg0) throws IOException 200 { 201 write(arg0); 202 } 203 204 public void print(double arg0) throws IOException 205 { 206 write(Double.toString(arg0)); 207 } 208 209 public void print(float arg0) throws IOException 210 { 211 write(Float.toString(arg0)); 212 } 213 214 public void print(int arg0) throws IOException 215 { 216 write(Integer.toString(arg0)); 217 } 218 219 public void print(long arg0) throws IOException 220 { 221 write(Long.toString(arg0)); 222 } 223 224 public void print(Object arg0) throws IOException 225 { 226 write(arg0 == null ? "null" : arg0.toString()); 227 } 228 229 public void print(String arg0) throws IOException 230 { 231 write(arg0); 232 } 233 234 public void println() throws IOException 235 { 236 newLine(); 237 } 238 239 public void println(boolean arg0) throws IOException 240 { 241 print(arg0); 242 newLine(); 243 } 244 245 public void println(char arg0) throws IOException 246 { 247 print(arg0); 248 newLine(); 249 } 250 251 public void println(char[] arg0) throws IOException 252 { 253 print(arg0); 254 newLine(); 255 } 256 257 public void println(double arg0) throws IOException 258 { 259 print(arg0); 260 newLine(); 261 } 262 263 public void println(float arg0) throws IOException 264 { 265 print(arg0); 266 newLine(); 267 } 268 269 public void println(int arg0) throws IOException 270 { 271 print(arg0); 272 newLine(); 273 } 274 275 public void println(long arg0) throws IOException 276 { 277 print(arg0); 278 newLine(); 279 } 280 281 public void println(Object arg0) throws IOException 282 { 283 print(arg0); 284 newLine(); 285 } 286 287 public void println(String arg0) throws IOException 288 { 289 print(arg0); 290 newLine(); 291 } 292 293 public void write(int c) throws IOException 294 { 295 if(buf != null) { 296 buf.write(c); 297 } 298 else { 299 getEnclosingWriter().write(c); 300 } 301 } 302 303 public void write(char[] cbuf, int off, int len) throws IOException 304 { 305 if(buf != null) { 306 buf.write(cbuf, off, len); 307 } 308 else { 309 getEnclosingWriter().write(cbuf, off, len); 310 } 311 } 312 313 public String getString() { 314 return buf.toString(); 315 } 316 317 public Reader getReader() { 318 return new CharArrayReader(buf.toCharArray()); 319 } 320 321 public void writeOut(Writer out) throws IOException { 322 buf.writeTo(out); 323 } 324 325 } 326 327 class TagWriter extends BodyContentImpl implements TransformControl 328 { 329 private final Tag tag; 330 private final FreeMarkerPageContext pageContext; 331 private boolean needPop = true; 332 private final boolean needDoublePop; 333 334 TagWriter(Writer out, Tag tag, FreeMarkerPageContext pageContext, boolean needDoublePop) 335 { 336 super((JspWriter)out, false); 337 this.needDoublePop = needDoublePop; 338 this.tag = tag; 339 this.pageContext = pageContext; 340 } 341 342 public String toString() { 343 return "TagWriter for " + tag.getClass().getName() + " wrapping a " + getEnclosingWriter().toString(); 344 } 345 346 Tag getTag() 347 { 348 return tag; 349 } 350 351 FreeMarkerPageContext getPageContext() 352 { 353 return pageContext; 354 } 355 356 public int onStart() 357 throws 358 TemplateModelException 359 { 360 try { 361 int dst = tag.doStartTag(); 362 switch(dst) { 363 case Tag.SKIP_BODY: 364 // EVAL_PAGE is illegal actually, but some taglibs out there 365 // use it, and it seems most JSP compilers allow them to and 366 // treat it identically to SKIP_BODY, so we're going with 367 // the flow and we allow it too, altough strictly speaking 368 // it is in violation of the spec. 369 case Tag.EVAL_PAGE: { 370 endEvaluation(); 371 return TransformControl.SKIP_BODY; 372 } 373 case BodyTag.EVAL_BODY_BUFFERED: { 374 if(isBodyTag) { 375 initBuffer(); 376 BodyTag btag = (BodyTag)tag; 377 btag.setBodyContent(this); 378 btag.doInitBody(); 379 } 380 else { 381 throw new TemplateModelException("Can't buffer body since " + tag.getClass().getName() + " does not implement BodyTag."); 382 } 383 // Intentional fall-through 384 } 385 case Tag.EVAL_BODY_INCLUDE: { 386 return TransformControl.EVALUATE_BODY; 387 } 388 default: { 389 throw new RuntimeException("Illegal return value " + dst + " from " + tag.getClass().getName() + ".doStartTag()"); 390 } 391 } 392 } 393 catch(JspException e) { 394 throw new TemplateModelException(e.getMessage(), e); 395 } 396 } 397 398 public int afterBody() 399 throws 400 TemplateModelException 401 { 402 try { 403 if(isIterationTag) { 404 int dab = ((IterationTag)tag).doAfterBody(); 405 switch(dab) { 406 case Tag.SKIP_BODY: { 407 endEvaluation(); 408 return END_EVALUATION; 409 } 410 case IterationTag.EVAL_BODY_AGAIN: { 411 return REPEAT_EVALUATION; 412 } 413 default: { 414 throw new TemplateModelException("Unexpected return value " + dab + "from " + tag.getClass().getName() + ".doAfterBody()"); 415 } 416 } 417 } 418 endEvaluation(); 419 return END_EVALUATION; 420 } 421 catch(JspException e) { 422 throw new TemplateModelException(e); 423 } 424 } 425 426 private void endEvaluation() throws JspException { 427 if(needPop) { 428 pageContext.popWriter(); 429 needPop = false; 430 } 431 if(tag.doEndTag() == Tag.SKIP_PAGE) { 432 logger.warn("Tag.SKIP_PAGE was ignored from a " + tag.getClass().getName() + " tag."); 433 } 434 } 435 436 public void onError(Throwable t) throws Throwable { 437 if(isTryCatchFinally) { 438 ((TryCatchFinally)tag).doCatch(t); 439 } 440 else { 441 throw t; 442 } 443 } 444 445 public void close() { 446 if(needPop) { 447 pageContext.popWriter(); 448 } 449 pageContext.popTopTag(); 450 try { 451 if(isTryCatchFinally) { 452 ((TryCatchFinally)tag).doFinally(); 453 } 454 // No pooling yet 455 tag.release(); 456 } 457 finally { 458 if(needDoublePop) { 459 pageContext.popWriter(); 460 } 461 } 462 } 463 464 } 465 }