package railo.runtime.type; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Iterator; import java.util.Map.Entry; import javax.servlet.jsp.tagext.BodyContent; import railo.commons.io.cache.Cache; import railo.commons.lang.CFTypes; import railo.commons.lang.SizeOf; import railo.runtime.Component; import railo.runtime.ComponentImpl; import railo.runtime.PageContext; import railo.runtime.PageContextImpl; import railo.runtime.PageSource; import railo.runtime.cache.ram.RamCache; import railo.runtime.component.MemberSupport; import railo.runtime.config.ConfigImpl; import railo.runtime.config.NullSupportHelper; import railo.runtime.dump.DumpData; import railo.runtime.dump.DumpProperties; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.exp.UDFCasterException; import railo.runtime.functions.cache.Util; import railo.runtime.listener.ApplicationContextSupport; import railo.runtime.op.Caster; import railo.runtime.op.Decision; import railo.runtime.op.Duplicator; import railo.runtime.type.Collection.Key; import railo.runtime.type.scope.Argument; import railo.runtime.type.scope.ArgumentIntKey; import railo.runtime.type.scope.Local; import railo.runtime.type.scope.LocalImpl; import railo.runtime.type.scope.Undefined; import railo.runtime.type.udf.UDFCacheEntry; import railo.runtime.type.util.ComponentUtil; import railo.runtime.type.util.UDFUtil; import railo.runtime.writer.BodyContentUtil; /** * defines a abstract class for a User defined Functions */ public class UDFImpl extends MemberSupport implements UDFPlus,Sizeable,Externalizable { private static final RamCache DEFAULT_CACHE=new RamCache(); private static final long serialVersionUID = -7288148349256615519L; // do not change protected ComponentImpl ownerComponent; protected UDFPropertiesImpl properties; /** * DO NOT USE THIS CONSTRUCTOR! * this constructor is only for deserialize process */ public UDFImpl(){ super(0); } public UDFImpl(UDFProperties properties) { super(properties.getAccess()); this.properties= (UDFPropertiesImpl) properties; } @Override public long sizeOf() { return SizeOf.size(properties); } public UDF duplicate(ComponentImpl cfc) { UDFImpl udf = new UDFImpl(properties); udf.ownerComponent=cfc; udf.setAccess(getAccess()); return udf; } @Override public UDF duplicate(boolean deepCopy) { return duplicate(ownerComponent); } @Override public UDF duplicate() { return duplicate(ownerComponent); } @Override public Object implementation(PageContext pageContext) throws Throwable { return ComponentUtil.getPage(pageContext, properties.pageSource).udfCall(pageContext,this,properties.index); } private final Object castToAndClone(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException { //if(value instanceof Array)print.out(count++); if(Decision.isCastableTo(arg.getType(),arg.getTypeAsString(),value)) return arg.isPassByReference()?value:Duplicator.duplicate(value,false); throw new UDFCasterException(this,arg,value,index); //REALCAST return Caster.castTo(pc,arg.getType(),arg.getTypeAsString(),value); } private final Object castTo(FunctionArgument arg,Object value, int index) throws PageException { if(Decision.isCastableTo(arg.getType(),arg.getTypeAsString(),value)) return value; throw new UDFCasterException(this,arg,value,index); } private void defineArguments(PageContext pc,FunctionArgument[] funcArgs, Object[] args,Argument newArgs) throws PageException { // define argument scope for(int i=0;i<funcArgs.length;i++) { // argument defined if(args.length>i) { newArgs.setEL(funcArgs[i].getName(),castToAndClone(pc,funcArgs[i], args[i],i+1)); } // argument not defined else { Object d=getDefaultValue(pc,i,NullSupportHelper.NULL()); if(d==NullSupportHelper.NULL()) { if(funcArgs[i].isRequired()) { throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in."); } if(!NullSupportHelper.full()) newArgs.setEL(funcArgs[i].getName(),Argument.NULL); } else { newArgs.setEL(funcArgs[i].getName(),castTo(funcArgs[i],d,i+1)); } } } for(int i=funcArgs.length;i<args.length;i++) { newArgs.setEL(ArgumentIntKey.init(i+1),args[i]); } } private void defineArguments(PageContext pageContext, FunctionArgument[] funcArgs, Struct values, Argument newArgs) throws PageException { // argumentCollection UDFUtil.argumentCollection(values,funcArgs); //print.out(values.size()); Object value; Collection.Key name; for(int i=0;i<funcArgs.length;i++) { // argument defined name=funcArgs[i].getName(); value=values.removeEL(name); if(value!=null) { newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1)); continue; } value=values.removeEL(ArgumentIntKey.init(i+1)); if(value!=null) { newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1)); continue; } // default argument or exception Object defaultValue=getDefaultValue(pageContext,i,NullSupportHelper.NULL());//funcArgs[i].getDefaultValue(); if(defaultValue==NullSupportHelper.NULL()) { if(funcArgs[i].isRequired()) { throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in."); } newArgs.set(name,Argument.NULL); } else newArgs.set(name,castTo(funcArgs[i],defaultValue,i+1)); } Iterator<Entry<Key, Object>> it = values.entryIterator(); Entry<Key, Object> e; while(it.hasNext()) { e = it.next(); newArgs.set(e.getKey(),e.getValue()); } } public static Collection.Key toKey(Object obj) { if(obj==null) return null; if(obj instanceof Collection.Key) return (Collection.Key) obj; String str = Caster.toString(obj,null); if(str==null) return KeyImpl.init(obj.toString()); return KeyImpl.init(str); } @Override public Object callWithNamedValues(PageContext pc, Struct values,boolean doIncludePath) throws PageException { return this.properties.cachedWithin>0? _callCachedWithin(pc,null, null, values, doIncludePath): _call(pc,null, null, values, doIncludePath); } public Object callWithNamedValues(PageContext pc,Collection.Key calledName, Struct values,boolean doIncludePath) throws PageException { return this.properties.cachedWithin>0? _callCachedWithin(pc,calledName, null, values, doIncludePath): _call(pc,calledName, null, values, doIncludePath); } @Override public Object call(PageContext pc, Object[] args, boolean doIncludePath) throws PageException { return this.properties.cachedWithin>0? _callCachedWithin(pc,null, args,null, doIncludePath): _call(pc,null, args,null, doIncludePath); } public Object call(PageContext pc,Collection.Key calledName, Object[] args, boolean doIncludePath) throws PageException { return this.properties.cachedWithin>0? _callCachedWithin(pc,calledName, args,null, doIncludePath): _call(pc,calledName, args,null, doIncludePath); } // private static int count=0; private Object _callCachedWithin(PageContext pc,Collection.Key calledName, Object[] args, Struct values,boolean doIncludePath) throws PageException { PageContextImpl pci=(PageContextImpl) pc; String id = UDFUtil.callerHash(this,args,values); Cache cache = Util.getDefault(pc,ConfigImpl.CACHE_DEFAULT_FUNCTION,DEFAULT_CACHE); Object o = cache.getValue(id,null); // get from cache if(o instanceof UDFCacheEntry ) { UDFCacheEntry entry = (UDFCacheEntry)o; //if(entry.creationdate+properties.cachedWithin>=System.currentTimeMillis()) { try { pc.write(entry.output); } catch (IOException e) { throw Caster.toPageException(e); } return entry.returnValue; //} //cache.remove(id); } // execute the function BodyContent bc = pci.pushBody(); try { Object rtn = _call(pci,calledName, args, values, doIncludePath); String out = bc.getString(); cache.put(id, new UDFCacheEntry(out, rtn),properties.cachedWithin,properties.cachedWithin); return rtn; } finally { BodyContentUtil.flushAndPop(pc,bc); } } private Object _call(PageContext pc,Collection.Key calledName, Object[] args, Struct values,boolean doIncludePath) throws PageException { //print.out(count++); PageContextImpl pci=(PageContextImpl) pc; Argument newArgs= pci.getScopeFactory().getArgumentInstance(); newArgs.setFunctionArgumentNames(properties.argumentsSet); LocalImpl newLocal=pci.getScopeFactory().getLocalInstance(); Undefined undefined=pc.undefinedScope(); Argument oldArgs=pc.argumentsScope(); Local oldLocal=pc.localScope(); Collection.Key oldCalledName=pci.getActiveUDFCalledName(); pc.setFunctionScopes(newLocal,newArgs); pci.setActiveUDFCalledName(calledName); int oldCheckArgs=undefined.setMode(properties.localMode==null?pc.getApplicationContext().getLocalMode():properties.localMode.intValue()); PageSource psInc=null; try { PageSource ps = getPageSource(); if(doIncludePath)psInc = ps; //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.e(getPageSource().getDisplayPath()); if(doIncludePath && getOwnerComponent()!=null) { //if(!ps.getDisplayPath().endsWith("Dump.cfc"))print.ds(ps.getDisplayPath()); psInc=ComponentUtil.getPageSource(getOwnerComponent()); if(psInc==pci.getCurrentTemplatePageSource()) { psInc=null; } } pci.addPageSource(ps,psInc); pci.addUDF(this); ////////////////////////////////////////// BodyContent bc=null; Boolean wasSilent=null; boolean bufferOutput=getBufferOutput(pci); if(!getOutput()) { if(bufferOutput) bc = pci.pushBody(); else wasSilent=pc.setSilent()?Boolean.TRUE:Boolean.FALSE; } UDF parent=null; if(ownerComponent!=null) { parent=pci.getActiveUDF(); pci.setActiveUDF(this); } Object returnValue = null; try { if(args!=null) defineArguments(pc,getFunctionArguments(),args,newArgs); else defineArguments(pc,getFunctionArguments(),values,newArgs); returnValue=implementation(pci); if(ownerComponent!=null)pci.setActiveUDF(parent); } catch(Throwable t) { if(ownerComponent!=null)pci.setActiveUDF(parent); if(!getOutput()) { if(bufferOutput)BodyContentUtil.flushAndPop(pc,bc); else if(!wasSilent)pc.unsetSilent(); } //BodyContentUtil.flushAndPop(pc,bc); throw Caster.toPageException(t); } if(!getOutput()) { if(bufferOutput)BodyContentUtil.clearAndPop(pc,bc); else if(!wasSilent)pc.unsetSilent(); } //BodyContentUtil.clearAndPop(pc,bc); if(properties.returnType==CFTypes.TYPE_ANY) return returnValue; else if(Decision.isCastableTo(properties.strReturnType,returnValue,false,false,-1)) return returnValue; else throw new UDFCasterException(this,properties.strReturnType,returnValue); //REALCAST return Caster.castTo(pageContext,returnType,returnValue,false); ////////////////////////////////////////// } finally { pc.removeLastPageSource(psInc!=null); pci.removeUDF(); pci.setFunctionScopes(oldLocal,oldArgs); pci.setActiveUDFCalledName(oldCalledName); undefined.setMode(oldCheckArgs); pci.getScopeFactory().recycle(newArgs); pci.getScopeFactory().recycle(newLocal); } } @Override public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { return UDFUtil.toDumpData(pageContext, maxlevel, dp,this,false); } @Override public String getDisplayName() { return properties.displayName; } @Override public String getHint() { return properties.hint; } @Override public PageSource getPageSource() { return properties.pageSource; } public Struct getMeta() { return properties.meta; } @Override public Struct getMetaData(PageContext pc) throws PageException { return ComponentUtil.getMetaData(pc, properties); //return getMetaData(pc, this); } @Override public Object getValue() { return this; } /** * @param componentImpl the componentImpl to set * @param injected */ public void setOwnerComponent(ComponentImpl component) { this.ownerComponent = component; } @Override public Component getOwnerComponent() { return ownerComponent;//+++ } @Override public String toString() { StringBuffer sb=new StringBuffer(properties.functionName); sb.append("("); int optCount=0; for(int i=0;i<properties.arguments.length;i++) { if(i>0)sb.append(", "); if(!properties.arguments[i].isRequired()){ sb.append("["); optCount++; } sb.append(properties.arguments[i].getTypeAsString()); sb.append(" "); sb.append(properties.arguments[i].getName()); } for(int i=0;i<optCount;i++){ sb.append("]"); } sb.append(")"); return sb.toString(); } @Override public Boolean getSecureJson() { return properties.secureJson; } @Override public Boolean getVerifyClient() { return properties.verifyClient; } @Override public Object clone() { return duplicate(); } @Override public FunctionArgument[] getFunctionArguments() { return properties.arguments; } @Override public Object getDefaultValue(PageContext pc,int index) throws PageException { return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,null); } @Override public Object getDefaultValue(PageContext pc,int index, Object defaultValue) throws PageException { return UDFUtil.getDefaultValue(pc,properties.pageSource,properties.index,index,defaultValue); } // public abstract Object getDefaultValue(PageContext pc,int index) throws PageException; @Override public String getFunctionName() { return properties.functionName; } @Override public boolean getOutput() { return properties.output; } public Boolean getBufferOutput() { return properties.bufferOutput; } private boolean getBufferOutput(PageContextImpl pc) {// FUTURE move to interface if(properties.bufferOutput!=null) return properties.bufferOutput.booleanValue(); return ((ApplicationContextSupport)pc.getApplicationContext()).getBufferOutput(); } @Override public int getReturnType() { return properties.returnType; } @Override public String getReturnTypeAsString() { return properties.strReturnType; } @Override public String getDescription() { return properties.description; } @Override public int getReturnFormat() { if(properties.returnFormat<0) return UDF.RETURN_FORMAT_WDDX; return properties.returnFormat; } @Override public int getReturnFormat(int defaultValue) { if(properties.returnFormat<0) return defaultValue; return properties.returnFormat; } public final String getReturnFormatAsString() { return properties.strReturnFormat; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // access setAccess(in.readInt()); // properties properties=(UDFPropertiesImpl) in.readObject(); } @Override public void writeExternal(ObjectOutput out) throws IOException { // access out.writeInt(getAccess()); // properties out.writeObject(properties); } @Override public boolean equals(Object obj){ if(!(obj instanceof UDF)) return false; return equals(this,(UDF)obj); } public static boolean equals(UDF left, UDF right){ if( !left.getPageSource().equals(right.getPageSource()) || !_eq(left.getFunctionName(),right.getFunctionName()) || left.getAccess()!=right.getAccess() || !_eq(left.getFunctionName(),right.getFunctionName()) || left.getOutput()!=right.getOutput() || left.getReturnFormat()!=right.getReturnFormat() || left.getReturnType()!=right.getReturnType() || !_eq(left.getReturnTypeAsString(),right.getReturnTypeAsString()) || !_eq(left.getSecureJson(),right.getSecureJson()) || !_eq(left.getVerifyClient(),right.getVerifyClient()) ) return false; // Arguments FunctionArgument[] largs = left.getFunctionArguments(); FunctionArgument[] rargs = right.getFunctionArguments(); if(largs.length!=rargs.length) return false; for(int i=0;i<largs.length;i++){ if(!largs[i].equals(rargs[i]))return false; } return true; } private static boolean _eq(Object left, Object right) { if(left==null) return right==null; return left.equals(right); } public int getIndex(){ return properties.index; } }