001 package railo.runtime.tag; 002 003 import java.io.IOException; 004 import java.util.Iterator; 005 import java.util.Map.Entry; 006 007 import javax.servlet.jsp.JspWriter; 008 import javax.servlet.jsp.tagext.Tag; 009 010 import railo.commons.lang.StringUtil; 011 import railo.runtime.Component; 012 import railo.runtime.PageContext; 013 import railo.runtime.PageContextImpl; 014 import railo.runtime.PageSource; 015 import railo.runtime.component.ComponentLoader; 016 import railo.runtime.component.Member; 017 import railo.runtime.customtag.CustomTagUtil; 018 import railo.runtime.customtag.InitFile; 019 import railo.runtime.engine.ThreadLocalPageContext; 020 import railo.runtime.exp.ApplicationException; 021 import railo.runtime.exp.CasterException; 022 import railo.runtime.exp.ExpressionException; 023 import railo.runtime.exp.PageException; 024 import railo.runtime.exp.PageRuntimeException; 025 import railo.runtime.exp.PageServletException; 026 import railo.runtime.ext.tag.AppendixTag; 027 import railo.runtime.ext.tag.BodyTagTryCatchFinallyImpl; 028 import railo.runtime.ext.tag.DynamicAttributes; 029 import railo.runtime.op.Caster; 030 import railo.runtime.op.Decision; 031 import railo.runtime.type.Collection; 032 import railo.runtime.type.Collection.Key; 033 import railo.runtime.type.KeyImpl; 034 import railo.runtime.type.Struct; 035 import railo.runtime.type.StructImpl; 036 import railo.runtime.type.scope.Caller; 037 import railo.runtime.type.scope.CallerImpl; 038 import railo.runtime.type.scope.Undefined; 039 import railo.runtime.type.scope.Variables; 040 import railo.runtime.type.scope.VariablesImpl; 041 import railo.runtime.type.util.ArrayUtil; 042 import railo.runtime.type.util.ComponentUtil; 043 import railo.runtime.type.util.KeyConstants; 044 import railo.runtime.type.util.ListUtil; 045 import railo.runtime.type.util.Type; 046 import railo.runtime.util.QueryStack; 047 import railo.runtime.util.QueryStackImpl; 048 import railo.transformer.library.tag.TagLibTag; 049 import railo.transformer.library.tag.TagLibTagAttr; 050 051 052 /** 053 * Creates a CFML Custom Tag 054 **/ 055 public class CFTag extends BodyTagTryCatchFinallyImpl implements DynamicAttributes,AppendixTag { 056 057 private static Collection.Key GENERATED_CONTENT=KeyImpl.intern("GENERATEDCONTENT"); 058 private static Collection.Key EXECUTION_MODE=KeyImpl.intern("EXECUTIONMODE"); 059 private static Collection.Key EXECUTE_BODY=KeyImpl.intern("EXECUTEBODY"); 060 private static Collection.Key PARENT=KeyImpl.intern("PARENT"); 061 private static Collection.Key CFCATCH=KeyConstants._CFCATCH; 062 private static Collection.Key SOURCE=KeyImpl.intern("SOURCE"); 063 064 private static final Collection.Key ON_ERROR = KeyImpl.intern("onError"); 065 private static final Collection.Key ON_FINALLY = KeyImpl.intern("onFinally"); 066 private static final Collection.Key ON_START_TAG = KeyImpl.intern("onStartTag"); 067 private static final Collection.Key ON_END_TAG = KeyImpl.intern("onEndTag"); 068 069 private static final Collection.Key ATTRIBUTE_TYPE = KeyImpl.intern("attributetype"); 070 private static final Collection.Key RT_EXPR_VALUE = KeyImpl.intern("rtexprvalue"); 071 private static final String MARKER = "2w12801"; 072 073 /** 074 * Field <code>attributesScope</code> 075 */ 076 // new scopes 077 protected StructImpl attributesScope; 078 private Caller callerScope; 079 private StructImpl thistagScope; 080 081 private Variables ctVariablesScope; 082 083 private boolean hasBody; 084 085 /** 086 * Field <code>filename</code> 087 */ 088 //protected String filename; 089 090 /** 091 * Field <code>source</code> 092 */ 093 protected InitFile source; 094 private String appendix; 095 //private boolean doCustomTagDeepSearch; 096 097 private Component cfc; 098 private boolean isEndTag; 099 100 101 102 /** 103 * constructor for the tag class 104 **/ 105 public CFTag() { 106 attributesScope = new StructImpl(); 107 callerScope = new CallerImpl(); 108 //thistagScope = new StructImpl(); 109 } 110 111 @Override 112 public void setDynamicAttribute(String uri, String name, Object value) { 113 TagUtil.setDynamicAttribute(attributesScope,KeyImpl.init(name),value,TagUtil.ORIGINAL_CASE); 114 } 115 116 @Override 117 public void setDynamicAttribute(String uri, Collection.Key name, Object value) { 118 TagUtil.setDynamicAttribute(attributesScope,name,value,TagUtil.ORIGINAL_CASE); 119 } 120 121 @Override 122 public void release() { 123 super.release(); 124 125 hasBody=false; 126 //filename=null; 127 128 attributesScope=new StructImpl();//.clear(); 129 callerScope = new CallerImpl(); 130 if(thistagScope!=null)thistagScope=null; 131 if(ctVariablesScope!=null)ctVariablesScope=null; 132 133 134 isEndTag=false; 135 136 //cfc=null; 137 source=null; 138 } 139 140 /** 141 * sets the appendix of the class 142 * @param appendix 143 */ 144 public void setAppendix(String appendix) { 145 this.appendix=appendix; 146 //filename = appendix+'.'+pageContext.getConfig().getCFMLExtension(); 147 } 148 149 @Override 150 public int doStartTag() throws PageException { 151 PageContextImpl pci=(PageContextImpl) pageContext; 152 boolean old=pci.useSpecialMappings(true); 153 try{ 154 initFile(); 155 callerScope.initialize(pageContext); 156 if(source.isCFC())return cfcStartTag(); 157 return cfmlStartTag(); 158 } 159 finally{ 160 pci.useSpecialMappings(old); 161 } 162 } 163 164 @Override 165 public int doEndTag() { 166 PageContextImpl pci=(PageContextImpl) pageContext; 167 boolean old=pci.useSpecialMappings(true); 168 try{ 169 if(source.isCFC())_doCFCFinally(); 170 return EVAL_PAGE; 171 } 172 finally{ 173 pci.useSpecialMappings(old); 174 } 175 } 176 177 @Override 178 public void doInitBody() { 179 180 } 181 182 @Override 183 public int doAfterBody() throws PageException { 184 if(source.isCFC())return cfcEndTag(); 185 return cfmlEndTag(); 186 } 187 188 189 @Override 190 public void doCatch(Throwable t) throws Throwable { 191 if(source.isCFC()){ 192 String source=isEndTag?"end":"body"; 193 isEndTag=false; 194 _doCFCCatch(t,source); 195 } 196 else super.doCatch(t); 197 } 198 199 void initFile() throws PageException { 200 source=initFile(pageContext); 201 } 202 203 public InitFile initFile(PageContext pageContext) throws PageException { 204 return CustomTagUtil.loadInitFile(pageContext, appendix); 205 } 206 207 private int cfmlStartTag() throws PageException { 208 callerScope.initialize(pageContext); 209 210 // thistag 211 if(thistagScope==null)thistagScope=new StructImpl(StructImpl.TYPE_LINKED); 212 thistagScope.set(GENERATED_CONTENT,""); 213 thistagScope.set(EXECUTION_MODE,"start"); 214 thistagScope.set(EXECUTE_BODY,Boolean.TRUE); 215 thistagScope.set(KeyConstants._HASENDTAG,Caster.toBoolean(hasBody)); 216 217 218 ctVariablesScope=new VariablesImpl(); 219 ctVariablesScope.setEL(KeyConstants._ATTRIBUTES,attributesScope); 220 ctVariablesScope.setEL(KeyConstants._CALLER,callerScope); 221 ctVariablesScope.setEL(KeyConstants._THISTAG,thistagScope); 222 223 224 // include 225 doInclude(); 226 227 return Caster.toBooleanValue(thistagScope.get(EXECUTE_BODY))?EVAL_BODY_BUFFERED:SKIP_BODY; 228 } 229 230 private int cfmlEndTag() throws PageException { 231 // thistag 232 String genConBefore = bodyContent.getString(); 233 thistagScope.set(GENERATED_CONTENT,genConBefore); 234 thistagScope.set(EXECUTION_MODE,"end"); 235 thistagScope.set(EXECUTE_BODY,Boolean.FALSE); 236 writeEL(bodyContent, MARKER); 237 238 // include 239 try{ 240 doInclude(); 241 } 242 catch(Throwable t){ 243 writeOut(genConBefore); 244 throw Caster.toPageException(t); 245 } 246 247 writeOut(genConBefore); 248 249 return Caster.toBooleanValue(thistagScope.get(EXECUTE_BODY))?EVAL_BODY_BUFFERED:SKIP_BODY; 250 } 251 252 253 254 private void writeOut(String genConBefore) throws PageException { 255 String output = bodyContent.getString(); 256 bodyContent.clearBody(); 257 String genConAfter = Caster.toString(thistagScope.get(GENERATED_CONTENT)); 258 259 if(genConBefore!=genConAfter){ 260 if(output.startsWith(genConBefore+MARKER)){ 261 output=output.substring((genConBefore+MARKER).length()); 262 } 263 output=genConAfter+output; 264 } 265 else { 266 if(output.startsWith(genConBefore+MARKER)){ 267 output=output.substring((genConBefore+MARKER).length()); 268 output=genConBefore+output; 269 } 270 } 271 272 273 writeEL(bodyContent.getEnclosingWriter(),output); 274 } 275 276 private void writeEL(JspWriter writer, String str) throws PageException { 277 try { 278 writer.write(str); 279 } catch (IOException e) { 280 throw Caster.toPageException(e); 281 } 282 } 283 284 void doInclude() throws PageException { 285 Variables var=pageContext.variablesScope(); 286 pageContext.setVariablesScope(ctVariablesScope); 287 288 289 QueryStack cs=null; 290 Undefined undefined=pageContext.undefinedScope(); 291 int oldMode=undefined.setMode(Undefined.MODE_NO_LOCAL_AND_ARGUMENTS); 292 if(oldMode!=Undefined.MODE_NO_LOCAL_AND_ARGUMENTS) 293 callerScope.setScope(var,pageContext.localScope(),pageContext.argumentsScope(),true); 294 else 295 callerScope.setScope(var,null,null,false); 296 297 if(pageContext.getConfig().allowImplicidQueryCall()) { 298 cs=undefined.getQueryStack(); 299 undefined.setQueryStack(new QueryStackImpl()); 300 } 301 302 try { 303 pageContext.doInclude(new PageSource[]{source.getPageSource()},false); 304 } 305 catch (Throwable t) { 306 throw Caster.toPageException(t); 307 } 308 finally { 309 undefined.setMode(oldMode); 310 //varScopeData=variablesScope.getMap(); 311 pageContext.setVariablesScope(var); 312 if(pageContext.getConfig().allowImplicidQueryCall()) { 313 undefined.setQueryStack(cs); 314 } 315 } 316 317 } 318 319 320 // CFC 321 322 private int cfcStartTag() throws PageException { 323 324 callerScope.initialize(pageContext); 325 cfc = ComponentLoader.loadComponent(pageContext,null,source.getPageSource(), source.getFilename().substring(0,source.getFilename().length()-(pageContext.getConfig().getCFCExtension().length()+1)), false,true); 326 validateAttributes(cfc,attributesScope,StringUtil.ucFirst(ListUtil.last(source.getPageSource().getComponentName(),'.'))); 327 328 boolean exeBody = false; 329 try { 330 Object rtn=Boolean.TRUE; 331 if(cfc.contains(pageContext, KeyConstants._init)){ 332 Tag parent=getParent(); 333 while(parent!=null && !(parent instanceof CFTag && ((CFTag)parent).isCFCBasedCustomTag())) { 334 parent=parent.getParent(); 335 } 336 Struct args=new StructImpl(StructImpl.TYPE_LINKED); 337 args.set(KeyConstants._HASENDTAG, Caster.toBoolean(hasBody)); 338 if(parent instanceof CFTag) { 339 args.set(PARENT, ((CFTag)parent).getComponent()); 340 } 341 rtn=cfc.callWithNamedValues(pageContext, KeyConstants._init, args); 342 } 343 344 if(cfc.contains(pageContext, ON_START_TAG)){ 345 Struct args=new StructImpl(); 346 args.set(KeyConstants._ATTRIBUTES, attributesScope); 347 setCaller(pageContext,args); 348 349 rtn=cfc.callWithNamedValues(pageContext, ON_START_TAG, args); 350 } 351 exeBody=Caster.toBooleanValue(rtn,true); 352 } 353 catch(Throwable t){ 354 _doCFCCatch(t,"start"); 355 } 356 return exeBody?EVAL_BODY_BUFFERED:SKIP_BODY; 357 } 358 359 private void setCaller(PageContext pageContext, Struct args) throws PageException { 360 callerScope.initialize(pageContext); 361 boolean checkAgs=pageContext.undefinedScope().getCheckArguments(); 362 if(checkAgs) 363 callerScope.setScope(pageContext.variablesScope(),pageContext.localScope(),pageContext.argumentsScope(),true); 364 else 365 callerScope.setScope(pageContext.variablesScope(),null,null,false); 366 367 368 args.set(KeyConstants._CALLER, callerScope); 369 370 371 372 //args.set(KeyConstants._CALLER, Duplicator.duplicate(pageContext.undefinedScope(),false)); 373 } 374 375 private static void validateAttributes(Component cfc,StructImpl attributesScope,String tagName) throws ApplicationException, ExpressionException { 376 377 TagLibTag tag=getAttributeRequirments(cfc,false); 378 if(tag==null) return; 379 380 if(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_FIXED || tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_MIXED){ 381 Iterator<Entry<String, TagLibTagAttr>> it = tag.getAttributes().entrySet().iterator(); 382 int count=0; 383 Collection.Key key; 384 TagLibTagAttr attr; 385 Object value; 386 Entry<String, TagLibTagAttr> entry; 387 // check existing attributes 388 while(it.hasNext()){ 389 entry = it.next(); 390 count++; 391 key=KeyImpl.toKey(entry.getKey(),null); 392 attr=entry.getValue(); 393 value=attributesScope.get(key,null); 394 if(value==null){ 395 if(attr.getDefaultValue()!=null){ 396 value=attr.getDefaultValue(); 397 attributesScope.setEL(key, value); 398 } 399 else if(attr.isRequired()) 400 throw new ApplicationException("attribute ["+key.getString()+"] is required for tag ["+tagName+"]"); 401 } 402 if(value!=null) { 403 if(!Decision.isCastableTo(attr.getType(),value,true,true,-1)) 404 throw new CasterException(createMessage(attr.getType(), value)); 405 406 } 407 } 408 409 // check if there are attributes not supported 410 if(tag.getAttributeType()==TagLibTag.ATTRIBUTE_TYPE_FIXED && count<attributesScope.size()){ 411 Collection.Key[] keys = attributesScope.keys(); 412 for(int i=0;i<keys.length;i++){ 413 if(tag.getAttribute(keys[i].getLowerString())==null) 414 throw new ApplicationException("attribute ["+keys[i].getString()+"] is not supported for tag ["+tagName+"]"); 415 } 416 417 //Attribute susi is not allowed for tag cfmail 418 } 419 } 420 } 421 422 private static String createMessage(String type, Object value) { 423 if(value instanceof String) return "can't cast String ["+value+"] to a value of type ["+type+"]"; 424 else if(value!=null) return "can't cast Object type ["+Type.getName(value)+"] to a value of type ["+type+"]"; 425 else return "can't cast Null value to value of type ["+type+"]"; 426 427 } 428 429 430 private static TagLibTag getAttributeRequirments(Component cfc, boolean runtime) throws ExpressionException { 431 432 Struct meta=null; 433 //try { 434 //meta = Caster.toStruct(cfc.get(Component.ACCESS_PRIVATE, METADATA),null,false); 435 Member mem = ComponentUtil.toComponentAccess(cfc).getMember(Component.ACCESS_PRIVATE, KeyConstants._metadata,true,false); 436 if(mem!=null)meta = Caster.toStruct(mem.getValue(),null,false); 437 //}catch (PageException e) {e.printStackTrace();} 438 if(meta==null) return null; 439 440 TagLibTag tag=new TagLibTag(null); 441 // TAG 442 443 // type 444 String type=Caster.toString(meta.get(ATTRIBUTE_TYPE,"dynamic"),"dynamic"); 445 446 if("fixed".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_FIXED); 447 //else if("mixed".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_MIXED); 448 //else if("noname".equalsIgnoreCase(type))tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_NONAME); 449 else tag.setAttributeType(TagLibTag.ATTRIBUTE_TYPE_DYNAMIC); 450 451 if(!runtime){ 452 // hint 453 String hint=Caster.toString(meta.get(KeyConstants._hint,null),null); 454 if(!StringUtil.isEmpty(hint))tag.setDescription(hint); 455 } 456 457 // ATTRIBUTES 458 Struct attributes=Caster.toStruct(meta.get(KeyConstants._ATTRIBUTES,null),null,false); 459 if(attributes!=null) { 460 Iterator<Entry<Key, Object>> it = attributes.entryIterator(); 461 //Iterator it = attributes.entrySet().iterator(); 462 Entry<Key, Object> entry; 463 TagLibTagAttr attr; 464 Struct sct; 465 String name; 466 Object defaultValue; 467 while(it.hasNext()){ 468 entry=it.next(); 469 name=Caster.toString(entry.getKey(),null); 470 if(StringUtil.isEmpty(name)) continue; 471 attr=new TagLibTagAttr(tag); 472 attr.setName(name); 473 474 sct=Caster.toStruct(entry.getValue(),null,false); 475 if(sct!=null){ 476 attr.setRequired(Caster.toBooleanValue(sct.get(KeyConstants._required,Boolean.FALSE),false)); 477 attr.setType(Caster.toString(sct.get(KeyConstants._type,"any"),"any")); 478 479 defaultValue= sct.get(KeyConstants._default,null); 480 if(defaultValue!=null)attr.setDefaultValue(defaultValue); 481 482 483 if(!runtime){ 484 attr.setDescription(Caster.toString(sct.get(KeyConstants._hint,null),null)); 485 attr.setRtexpr(Caster.toBooleanValue(sct.get(RT_EXPR_VALUE,Boolean.TRUE),true)); 486 } 487 } 488 tag.setAttribute(attr); 489 490 } 491 } 492 return tag; 493 } 494 495 private int cfcEndTag() throws PageException { 496 497 boolean exeAgain = false; 498 try{ 499 String output=null; 500 Object rtn=Boolean.FALSE; 501 502 503 if(cfc.contains(pageContext, ON_END_TAG)){ 504 try { 505 output=bodyContent.getString(); 506 bodyContent.clearBody(); 507 //rtn=cfc.call(pageContext, ON_END_TAG, new Object[]{attributesScope,pageContext.variablesScope(),output}); 508 509 Struct args=new StructImpl(StructImpl.TYPE_LINKED); 510 args.set(KeyConstants._ATTRIBUTES, attributesScope); 511 setCaller(pageContext, args); 512 args.set(GENERATED_CONTENT, output); 513 rtn=cfc.callWithNamedValues(pageContext, ON_END_TAG, args); 514 515 516 517 } 518 finally { 519 writeEnclosingWriter(); 520 } 521 } 522 else writeEnclosingWriter(); 523 524 exeAgain= Caster.toBooleanValue(rtn,false); 525 } 526 catch(Throwable t){ 527 isEndTag=true; 528 throw Caster.toPageException(t); 529 } 530 return exeAgain?EVAL_BODY_BUFFERED:SKIP_BODY; 531 532 } 533 534 public void _doCFCCatch(Throwable t, String source) throws PageException { 535 writeEnclosingWriter(); 536 537 // remove PageServletException wrap 538 if(t instanceof PageServletException) { 539 PageServletException pse=(PageServletException)t; 540 t=pse.getPageException(); 541 } 542 543 // abort 544 try { 545 if(railo.runtime.exp.Abort.isAbort(t)){ 546 if(bodyContent!=null){ 547 bodyContent.writeOut(bodyContent.getEnclosingWriter()); 548 bodyContent.clearBuffer(); 549 } 550 throw Caster.toPageException(t); 551 } 552 } 553 catch(IOException ioe){ 554 throw Caster.toPageException(ioe); 555 } 556 557 558 559 try { 560 if(cfc.contains(pageContext, ON_ERROR)){ 561 PageException pe = Caster.toPageException(t); 562 //Object rtn=cfc.call(pageContext, ON_ERROR, new Object[]{pe.getCatchBlock(pageContext),source}); 563 564 Struct args=new StructImpl(StructImpl.TYPE_LINKED); 565 args.set(CFCATCH, pe.getCatchBlock(ThreadLocalPageContext.getConfig(pageContext))); 566 args.set(SOURCE, source); 567 Object rtn=cfc.callWithNamedValues(pageContext, ON_ERROR, args); 568 569 if(Caster.toBooleanValue(rtn,false)) 570 throw t; 571 } 572 else throw t; 573 } 574 catch(Throwable th) { 575 writeEnclosingWriter(); 576 _doCFCFinally(); 577 throw Caster.toPageException(th); 578 } 579 writeEnclosingWriter(); 580 } 581 582 private void _doCFCFinally() { 583 if(cfc.contains(pageContext, ON_FINALLY)){ 584 try { 585 cfc.call(pageContext, ON_FINALLY, ArrayUtil.OBJECT_EMPTY); 586 } 587 catch (PageException pe) { 588 throw new PageRuntimeException(pe); 589 } 590 finally{ 591 writeEnclosingWriter(); 592 } 593 } 594 } 595 596 597 private void writeEnclosingWriter() { 598 if(bodyContent!=null){ 599 try { 600 String output = bodyContent.getString(); 601 bodyContent.clearBody(); 602 bodyContent.getEnclosingWriter().write(output); 603 } 604 catch (IOException e) { 605 //throw Caster.toPageException(e); 606 } 607 } 608 } 609 610 611 612 /** 613 * sets if tag has a body or not 614 * @param hasBody 615 */ 616 public void hasBody(boolean hasBody) { 617 this.hasBody=hasBody; 618 } 619 620 /** 621 * @return Returns the appendix. 622 */ 623 public String getAppendix() { 624 return appendix; 625 } 626 627 /** 628 * @return return thistag 629 */ 630 public Struct getThis() { 631 if(isCFCBasedCustomTag()){ 632 return cfc; 633 } 634 return thistagScope; 635 } 636 637 /** 638 * @return return thistag 639 */ 640 public Struct getCallerScope() { 641 return callerScope; 642 } 643 644 /** 645 * @return return thistag 646 */ 647 public Struct getAttributesScope() { 648 return attributesScope; 649 } 650 651 /** 652 * @return the ctVariablesScope 653 */ 654 public Struct getVariablesScope() { 655 if(isCFCBasedCustomTag()) { 656 return cfc.getComponentScope(); 657 } 658 return ctVariablesScope; 659 } 660 661 /** 662 * @return the cfc 663 */ 664 public Component getComponent() { 665 return cfc; 666 } 667 668 public boolean isCFCBasedCustomTag() { 669 return getSource().isCFC(); 670 } 671 672 private InitFile getSource() { 673 if(source==null){ 674 try { 675 source=initFile(pageContext); 676 } catch (PageException e) { 677 e.printStackTrace(); 678 } 679 } 680 return source; 681 } 682 683 /*class InitFile { 684 PageSource ps; 685 String filename; 686 boolean isCFC; 687 688 public InitFile(PageSource ps,String filename,boolean isCFC){ 689 this.ps=ps; 690 this.filename=filename; 691 this.isCFC=isCFC; 692 } 693 }*/ 694 695 696 }