001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.type; 020 021import java.io.Externalizable; 022import java.io.IOException; 023import java.io.ObjectInput; 024import java.io.ObjectOutput; 025import java.util.Iterator; 026import java.util.Map.Entry; 027 028import javax.servlet.jsp.tagext.BodyContent; 029 030import lucee.commons.lang.CFTypes; 031import lucee.commons.lang.ExceptionUtil; 032import lucee.commons.lang.SizeOf; 033import lucee.runtime.Component; 034import lucee.runtime.ComponentImpl; 035import lucee.runtime.PageContext; 036import lucee.runtime.PageContextImpl; 037import lucee.runtime.PageSource; 038import lucee.runtime.cache.tag.CacheHandler; 039import lucee.runtime.cache.tag.CacheHandlerFactory; 040import lucee.runtime.cache.tag.CacheItem; 041import lucee.runtime.cache.tag.udf.UDFCacheItem; 042import lucee.runtime.component.MemberSupport; 043import lucee.runtime.config.ConfigImpl; 044import lucee.runtime.config.ConfigWebUtil; 045import lucee.runtime.config.NullSupportHelper; 046import lucee.runtime.dump.DumpData; 047import lucee.runtime.dump.DumpProperties; 048import lucee.runtime.exp.ExpressionException; 049import lucee.runtime.exp.PageException; 050import lucee.runtime.exp.UDFCasterException; 051import lucee.runtime.listener.ApplicationContextSupport; 052import lucee.runtime.op.Caster; 053import lucee.runtime.op.Decision; 054import lucee.runtime.op.Duplicator; 055import lucee.runtime.type.Collection.Key; 056import lucee.runtime.type.scope.Argument; 057import lucee.runtime.type.scope.ArgumentIntKey; 058import lucee.runtime.type.scope.Local; 059import lucee.runtime.type.scope.LocalImpl; 060import lucee.runtime.type.scope.Undefined; 061import lucee.runtime.type.util.ComponentUtil; 062import lucee.runtime.type.util.UDFUtil; 063import lucee.runtime.writer.BodyContentUtil; 064 065/** 066 * defines a abstract class for a User defined Functions 067 */ 068public class UDFImpl extends MemberSupport implements UDFPlus,Sizeable,Externalizable { 069 070 private static final long serialVersionUID = -7288148349256615519L; // do not change 071 072 protected ComponentImpl ownerComponent; 073 protected UDFPropertiesImpl properties; 074 075 /** 076 * DO NOT USE THIS CONSTRUCTOR! 077 * this constructor is only for deserialize process 078 */ 079 public UDFImpl(){ 080 super(0); 081 } 082 083 public UDFImpl(UDFProperties properties) { 084 super(properties.getAccess()); 085 this.properties= (UDFPropertiesImpl) properties; 086 } 087 088 @Override 089 public long sizeOf() { 090 return SizeOf.size(properties); 091 } 092 093 public UDF duplicate(ComponentImpl cfc) { 094 UDFImpl udf = new UDFImpl(properties); 095 udf.ownerComponent=cfc; 096 udf.setAccess(getAccess()); 097 return udf; 098 } 099 100 @Override 101 public UDF duplicate(boolean deepCopy) { 102 return duplicate(ownerComponent); 103 } 104 105 @Override 106 public UDF duplicate() { 107 return duplicate(ownerComponent); 108 } 109 110 @Override 111 public Object implementation(PageContext pageContext) throws Throwable { 112 return ComponentUtil.getPage(pageContext, properties.pageSource).udfCall(pageContext,this,properties.index); 113 } 114 115 private final Object castToAndClone(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException { 116 if(!((PageContextImpl)pc).getTypeChecking() || Decision.isCastableTo(pc,arg.getType(),arg.getTypeAsString(),value)) 117 return arg.isPassByReference()?value:Duplicator.duplicate(value,false); 118 throw new UDFCasterException(this,arg,value,index); 119 } 120 private final Object castTo(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException { 121 if(Decision.isCastableTo(pc,arg.getType(),arg.getTypeAsString(),value)) return value; 122 throw new UDFCasterException(this,arg,value,index); 123 } 124 125 private void defineArguments(PageContext pc,FunctionArgument[] funcArgs, Object[] args,Argument newArgs) throws PageException { 126 // define argument scope 127 boolean fns = ((ConfigImpl)pc.getConfig()).getFullNullSupport(); 128 for(int i=0;i<funcArgs.length;i++) { 129 // argument defined 130 if(args.length>i && (args[i]!=null || fns)) { 131 newArgs.setEL(funcArgs[i].getName(),castToAndClone(pc,funcArgs[i], args[i],i+1)); 132 } 133 // argument not defined 134 else { 135 Object d=getDefaultValue(pc,i,NullSupportHelper.NULL()); 136 if(d==NullSupportHelper.NULL()) { 137 if(funcArgs[i].isRequired()) { 138 throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in."); 139 } 140 if(!NullSupportHelper.full()) newArgs.setEL(funcArgs[i].getName(),Argument.NULL); 141 } 142 else { 143 newArgs.setEL(funcArgs[i].getName(),castTo(pc,funcArgs[i],d,i+1)); 144 } 145 } 146 } 147 for(int i=funcArgs.length;i<args.length;i++) { 148 newArgs.setEL(ArgumentIntKey.init(i+1),args[i]); 149 } 150 } 151 152 153 private void defineArguments(PageContext pageContext, FunctionArgument[] funcArgs, Struct values, Argument newArgs) throws PageException { 154 StructImpl _values=(StructImpl) values; 155 // argumentCollection 156 UDFUtil.argumentCollection(values,funcArgs); 157 //print.out(values.size()); 158 Object value; 159 Collection.Key name; 160 161 for(int i=0;i<funcArgs.length;i++) { 162 // argument defined 163 name=funcArgs[i].getName(); 164 value=_values.remove(name,NullSupportHelper.NULL()); 165 if(value!=NullSupportHelper.NULL()) { 166 newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1)); 167 continue; 168 } 169 value=_values.remove(ArgumentIntKey.init(i+1),NullSupportHelper.NULL()); 170 if(value!=NullSupportHelper.NULL()) { 171 newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1)); 172 continue; 173 } 174 175 176 // default argument or exception 177 Object defaultValue=getDefaultValue(pageContext,i,NullSupportHelper.NULL());//funcArgs[i].getDefaultValue(); 178 if(defaultValue==NullSupportHelper.NULL()) { 179 if(funcArgs[i].isRequired()) { 180 throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in."); 181 } 182 if(!((ConfigImpl)pageContext.getConfig()).getFullNullSupport()) 183 newArgs.set(name,Argument.NULL); 184 } 185 else newArgs.set(name,castTo(pageContext,funcArgs[i],defaultValue,i+1)); 186 } 187 188 189 Iterator<Entry<Key, Object>> it = values.entryIterator(); 190 Entry<Key, Object> e; 191 while(it.hasNext()) { 192 e = it.next(); 193 newArgs.set(e.getKey(),e.getValue()); 194 } 195 } 196 197 198 199 200 public static Collection.Key toKey(Object obj) { 201 if(obj==null) return null; 202 if(obj instanceof Collection.Key) return (Collection.Key) obj; 203 String str = Caster.toString(obj,null); 204 if(str==null) return KeyImpl.init(obj.toString()); 205 return KeyImpl.init(str); 206 } 207 208 @Override 209 public Object callWithNamedValues(PageContext pc, Struct values,boolean doIncludePath) throws PageException { 210 return this.properties.cachedWithin!=null? 211 _callCachedWithin(pc,null, null, values, doIncludePath): 212 _call(pc,null, null, values, doIncludePath); 213 } 214 public Object callWithNamedValues(PageContext pc,Collection.Key calledName, Struct values,boolean doIncludePath) throws PageException { 215 return this.properties.cachedWithin!=null? 216 _callCachedWithin(pc,calledName, null, values, doIncludePath): 217 _call(pc,calledName, null, values, doIncludePath); 218 } 219 220 @Override 221 public Object call(PageContext pc, Object[] args, boolean doIncludePath) throws PageException { 222 return this.properties.cachedWithin!=null? 223 _callCachedWithin(pc,null, args,null, doIncludePath): 224 _call(pc,null, args,null, doIncludePath); 225 } 226 227 public Object call(PageContext pc,Collection.Key calledName, Object[] args, boolean doIncludePath) throws PageException { 228 return this.properties.cachedWithin!=null? 229 _callCachedWithin(pc,calledName, args,null, doIncludePath): 230 _call(pc,calledName, args,null, doIncludePath); 231 } 232 // private static int count=0; 233 234 235 236 private Object _callCachedWithin(PageContext pc,Collection.Key calledName, Object[] args, Struct values,boolean doIncludePath) throws PageException { 237 PageContextImpl pci=(PageContextImpl) pc; 238 String id=CacheHandlerFactory.createId(this,args,values); 239 CacheHandler ch = ConfigWebUtil.getCacheHandlerFactories(pc.getConfig()).function.getInstance(pc.getConfig(), properties.cachedWithin); 240 CacheItem ci=ch.get(pc, id); 241 242 // get from cache 243 if(ci instanceof UDFCacheItem ) { 244 UDFCacheItem entry = (UDFCacheItem)ci; 245 //if(entry.creationdate+properties.cachedWithin>=System.currentTimeMillis()) { 246 try { 247 pc.write(entry.output); 248 } catch (IOException e) { 249 throw Caster.toPageException(e); 250 } 251 return entry.returnValue; 252 //} 253 254 //cache.remove(id); 255 } 256 257 long start = System.nanoTime(); 258 259 // execute the function 260 BodyContent bc = pci.pushBody(); 261 262 try { 263 Object rtn = _call(pci,calledName, args, values, doIncludePath); 264 String out = bc.getString(); 265 266 ch.set(pc, id,properties.cachedWithin,new UDFCacheItem(out, rtn,getFunctionName(),getPageSource().getDisplayPath(),System.nanoTime()-start)); 267 // cache.put(id, new UDFCacheEntry(out, rtn),properties.cachedWithin,properties.cachedWithin); 268 return rtn; 269 } 270 finally { 271 BodyContentUtil.flushAndPop(pc,bc); 272 } 273 } 274 275 private Object _call(PageContext pc,Collection.Key calledName, Object[] args, Struct values,boolean doIncludePath) throws PageException { 276 277 //print.out(count++); 278 PageContextImpl pci=(PageContextImpl) pc; 279 Argument newArgs= pci.getScopeFactory().getArgumentInstance(); 280 newArgs.setFunctionArgumentNames(properties.argumentsSet); 281 LocalImpl newLocal=pci.getScopeFactory().getLocalInstance(); 282 283 Undefined undefined=pc.undefinedScope(); 284 Argument oldArgs=pc.argumentsScope(); 285 Local oldLocal=pc.localScope(); 286 Collection.Key oldCalledName=pci.getActiveUDFCalledName(); 287 288 pc.setFunctionScopes(newLocal,newArgs); 289 pci.setActiveUDFCalledName(calledName); 290 291 int oldCheckArgs=undefined.setMode(properties.localMode==null?pc.getApplicationContext().getLocalMode():properties.localMode.intValue()); 292 PageSource psInc=null; 293 try { 294 PageSource ps = getPageSource(); 295 if(doIncludePath)psInc = ps; 296 //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.e(getPageSource().getDisplayPath()); 297 if(doIncludePath && getOwnerComponent()!=null) { 298 //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.ds(ps.getDisplayPath()); 299 psInc=ComponentUtil.getPageSource(getOwnerComponent()); 300 if(psInc==pci.getCurrentTemplatePageSource()) { 301 psInc=null; 302 } 303 304 } 305 pci.addPageSource(ps,psInc); 306 pci.addUDF(this); 307 308////////////////////////////////////////// 309 BodyContent bc=null; 310 Boolean wasSilent=null; 311 boolean bufferOutput=getBufferOutput(pci); 312 if(!getOutput()) { 313 if(bufferOutput) bc = pci.pushBody(); 314 else wasSilent=pc.setSilent()?Boolean.TRUE:Boolean.FALSE; 315 } 316 317 UDF parent=null; 318 if(ownerComponent!=null) { 319 parent=pci.getActiveUDF(); 320 pci.setActiveUDF(this); 321 } 322 Object returnValue = null; 323 324 try { 325 326 if(args!=null) defineArguments(pc,getFunctionArguments(),args,newArgs); 327 else defineArguments(pc,getFunctionArguments(),values,newArgs); 328 329 returnValue=implementation(pci); 330 if(ownerComponent!=null)pci.setActiveUDF(parent); 331 } 332 catch(Throwable t) { 333 ExceptionUtil.rethrowIfNecessary(t); 334 if(ownerComponent!=null)pci.setActiveUDF(parent); 335 if(!getOutput()) { 336 if(bufferOutput)BodyContentUtil.flushAndPop(pc,bc); 337 else if(!wasSilent)pc.unsetSilent(); 338 } 339 //BodyContentUtil.flushAndPop(pc,bc); 340 throw Caster.toPageException(t); 341 } 342 if(!getOutput()) { 343 if(bufferOutput)BodyContentUtil.clearAndPop(pc,bc); 344 else if(!wasSilent)pc.unsetSilent(); 345 } 346 //BodyContentUtil.clearAndPop(pc,bc); 347 348 349 350 351 if(properties.returnType==CFTypes.TYPE_ANY || !((PageContextImpl)pc).getTypeChecking()) return returnValue; 352 else if(Decision.isCastableTo(properties.strReturnType,returnValue,false,false,-1)) return returnValue; 353 else throw new UDFCasterException(this,properties.strReturnType,returnValue); 354 //REALCAST return Caster.castTo(pageContext,returnType,returnValue,false); 355////////////////////////////////////////// 356 357 } 358 finally { 359 pc.removeLastPageSource(psInc!=null); 360 pci.removeUDF(); 361 pci.setFunctionScopes(oldLocal,oldArgs); 362 pci.setActiveUDFCalledName(oldCalledName); 363 undefined.setMode(oldCheckArgs); 364 pci.getScopeFactory().recycle(newArgs); 365 pci.getScopeFactory().recycle(newLocal); 366 } 367 } 368 369 @Override 370 public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { 371 return UDFUtil.toDumpData(pageContext, maxlevel, dp,this,UDFUtil.TYPE_UDF); 372 } 373 374 @Override 375 public String getDisplayName() { 376 return properties.displayName==null?"":properties.displayName; 377 } 378 379 @Override 380 public String getHint() { 381 return properties.hint==null?"":properties.hint; 382 } 383 384 @Override 385 public PageSource getPageSource() { 386 return properties.pageSource; 387 } 388 389 public Struct getMeta() { 390 return properties.meta; 391 } 392 393 @Override 394 public Struct getMetaData(PageContext pc) throws PageException { 395 return ComponentUtil.getMetaData(pc, properties); 396 //return getMetaData(pc, this); 397 } 398 399 @Override 400 public Object getValue() { 401 return this; 402 } 403 404 405 /** 406 * @param componentImpl the componentImpl to set 407 * @param injected 408 */ 409 public void setOwnerComponent(ComponentImpl component) { 410 this.ownerComponent = component; 411 } 412 413 @Override 414 public Component getOwnerComponent() { 415 return ownerComponent;//+++ 416 } 417 418 @Override 419 public String toString() { 420 StringBuffer sb=new StringBuffer(properties.functionName); 421 sb.append("("); 422 int optCount=0; 423 for(int i=0;i<properties.arguments.length;i++) { 424 if(i>0)sb.append(", "); 425 if(!properties.arguments[i].isRequired()){ 426 sb.append("["); 427 optCount++; 428 } 429 sb.append(properties.arguments[i].getTypeAsString()); 430 sb.append(" "); 431 sb.append(properties.arguments[i].getName()); 432 } 433 for(int i=0;i<optCount;i++){ 434 sb.append("]"); 435 } 436 sb.append(")"); 437 return sb.toString(); 438 } 439 440 @Override 441 public Boolean getSecureJson() { 442 return properties.secureJson; 443 } 444 445 @Override 446 public Boolean getVerifyClient() { 447 return properties.verifyClient; 448 } 449 450 @Override 451 public Object clone() { 452 return duplicate(); 453 } 454 455 @Override 456 public FunctionArgument[] getFunctionArguments() { 457 return properties.arguments; 458 } 459 460 @Override 461 public Object getDefaultValue(PageContext pc,int index) throws PageException { 462 return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,null); 463 } 464 465 @Override 466 public Object getDefaultValue(PageContext pc,int index, Object defaultValue) throws PageException { 467 return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,defaultValue); 468 } 469 // public abstract Object getDefaultValue(PageContext pc,int index) throws PageException; 470 471 @Override 472 public String getFunctionName() { 473 return properties.functionName; 474 } 475 476 @Override 477 public boolean getOutput() { 478 return properties.output; 479 } 480 481 public Boolean getBufferOutput() { 482 return properties.bufferOutput; 483 } 484 485 private boolean getBufferOutput(PageContextImpl pc) {// FUTURE move to interface 486 if(properties.bufferOutput!=null) 487 return properties.bufferOutput.booleanValue(); 488 return ((ApplicationContextSupport)pc.getApplicationContext()).getBufferOutput(); 489 } 490 491 @Override 492 public int getReturnType() { 493 return properties.returnType; 494 } 495 496 @Override 497 public String getReturnTypeAsString() { 498 return properties.strReturnType; 499 } 500 501 @Override 502 public String getDescription() { 503 return properties.description==null?"":properties.description; 504 } 505 506 @Override 507 public int getReturnFormat() { 508 if(properties.returnFormat<0) return UDF.RETURN_FORMAT_WDDX; 509 return properties.returnFormat; 510 } 511 512 @Override 513 public int getReturnFormat(int defaultValue) { 514 if(properties.returnFormat<0) return defaultValue; 515 return properties.returnFormat; 516 } 517 518 public final String getReturnFormatAsString() { 519 return properties.strReturnFormat; 520 } 521 522 @Override 523 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 524 // access 525 setAccess(in.readInt()); 526 527 // properties 528 properties=(UDFPropertiesImpl) in.readObject(); 529 } 530 531 @Override 532 public void writeExternal(ObjectOutput out) throws IOException { 533 // access 534 out.writeInt(getAccess()); 535 536 // properties 537 out.writeObject(properties); 538 } 539 540 @Override 541 public boolean equals(Object obj){ 542 if(!(obj instanceof UDF)) return false; 543 return equals(this,(UDF)obj); 544 } 545 public static boolean equals(UDF left, UDF right){ 546 if( 547 !left.getPageSource().equals(right.getPageSource()) 548 || !_eq(left.getFunctionName(),right.getFunctionName()) 549 || left.getAccess()!=right.getAccess() 550 || !_eq(left.getFunctionName(),right.getFunctionName()) 551 || left.getOutput()!=right.getOutput() 552 || left.getReturnFormat()!=right.getReturnFormat() 553 || left.getReturnType()!=right.getReturnType() 554 || !_eq(left.getReturnTypeAsString(),right.getReturnTypeAsString()) 555 || !_eq(left.getSecureJson(),right.getSecureJson()) 556 || !_eq(left.getVerifyClient(),right.getVerifyClient()) 557 ) return false; 558 559 // Arguments 560 FunctionArgument[] largs = left.getFunctionArguments(); 561 FunctionArgument[] rargs = right.getFunctionArguments(); 562 if(largs.length!=rargs.length) return false; 563 for(int i=0;i<largs.length;i++){ 564 if(!largs[i].equals(rargs[i]))return false; 565 } 566 567 568 569 570 return true; 571 } 572 573 private static boolean _eq(Object left, Object right) { 574 if(left==null) return right==null; 575 return left.equals(right); 576 } 577 578 public int getIndex(){ 579 return properties.index; 580 } 581 582} 583