/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.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 lucee.commons.lang.CFTypes;
import lucee.commons.lang.ExceptionUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.Component;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.PageSource;
import lucee.runtime.cache.tag.CacheHandler;
import lucee.runtime.cache.tag.CacheHandlerCollectionImpl;
import lucee.runtime.cache.tag.CacheItem;
import lucee.runtime.cache.tag.udf.UDFCacheItem;
import lucee.runtime.component.MemberSupport;
import lucee.runtime.config.Config;
import lucee.runtime.config.NullSupportHelper;
import lucee.runtime.dump.DumpData;
import lucee.runtime.dump.DumpProperties;
import lucee.runtime.exp.ExpressionException;
import lucee.runtime.exp.PageException;
import lucee.runtime.exp.UDFCasterException;
import lucee.runtime.listener.ApplicationContextSupport;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Decision;
import lucee.runtime.op.Duplicator;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.scope.Argument;
import lucee.runtime.type.scope.ArgumentIntKey;
import lucee.runtime.type.scope.Local;
import lucee.runtime.type.scope.LocalImpl;
import lucee.runtime.type.scope.Undefined;
import lucee.runtime.type.util.ComponentUtil;
import lucee.runtime.type.util.UDFUtil;
import lucee.runtime.writer.BodyContentUtil;
/**
* defines an abstract class for a User defined Functions
*/
public class UDFImpl extends MemberSupport implements UDFPlus,Externalizable {
private static final long serialVersionUID = -7288148349256615519L; // do not change
protected Component ownerComponent;
protected UDFPropertiesBase properties;
/**
* DO NOT USE THIS CONSTRUCTOR!
* this constructor is only for deserialize process
*/
public UDFImpl(){
super(0);
}
public UDFImpl(UDFProperties properties) {
super(properties.getAccess(),properties.getModifier());
this.properties= (UDFPropertiesBase) properties;
}
public UDF duplicate(Component 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 properties.getPage(pageContext).udfCall(pageContext,this,properties.getIndex());
}
private final Object castToAndClone(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException {
if(!((PageContextImpl)pc).getTypeChecking() || Decision.isCastableTo(pc,arg.getType(),arg.getTypeAsString(),value))
return arg.isPassByReference()?value:Duplicator.duplicate(value,false);
throw new UDFCasterException(this,arg,value,index);
}
private final Object castTo(PageContext pc,FunctionArgument arg,Object value, int index) throws PageException {
if(Decision.isCastableTo(pc,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
boolean fns = pc.getCurrentTemplateDialect()!=CFMLEngine.DIALECT_CFML || pc.getConfig().getFullNullSupport();
for(int i=0;i<funcArgs.length;i++) {
// argument defined
if(args.length>i && (args[i]!=null || fns)) {
newArgs.setEL(funcArgs[i].getName(),castToAndClone(pc,funcArgs[i], args[i],i+1));
}
// argument not defined
else {
Object d=getDefaultValue(pc,i,NullSupportHelper.NULL(pc));
if(d==NullSupportHelper.NULL(pc)) {
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(pc,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.remove(name,NullSupportHelper.NULL(pageContext));
if(value!=NullSupportHelper.NULL(pageContext)) {
newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1));
continue;
}
value=values.remove(ArgumentIntKey.init(i+1),NullSupportHelper.NULL(pageContext));
if(value!=NullSupportHelper.NULL(pageContext)) {
newArgs.set(name,castToAndClone(pageContext,funcArgs[i], value,i+1));
continue;
}
// default argument or exception
Object defaultValue=getDefaultValue(pageContext,i,NullSupportHelper.NULL(pageContext));//funcArgs[i].getDefaultValue();
if(defaultValue==NullSupportHelper.NULL(pageContext)) {
if(funcArgs[i].isRequired()) {
throw new ExpressionException("The parameter "+funcArgs[i].getName()+" to function "+getFunctionName()+" is required but was not passed in.");
}
if(pageContext.getCurrentTemplateDialect()==CFMLEngine.DIALECT_CFML && !pageContext.getConfig().getFullNullSupport())
newArgs.set(name,Argument.NULL);
}
else newArgs.set(name,castTo(pageContext,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 hasCachedWithin(pc)?
_callCachedWithin(pc,null, null, values, doIncludePath):
_call(pc,null, null, values, doIncludePath);
}
@Override
public Object callWithNamedValues(PageContext pc,Collection.Key calledName, Struct values,boolean doIncludePath) throws PageException {
return hasCachedWithin(pc)?
_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 hasCachedWithin(pc)?
_callCachedWithin(pc,null, args,null, doIncludePath):
_call(pc,null, args,null, doIncludePath);
}
@Override
public Object call(PageContext pc,Collection.Key calledName, Object[] args, boolean doIncludePath) throws PageException {
return hasCachedWithin(pc)?
_callCachedWithin(pc,calledName, args,null, doIncludePath):
_call(pc,calledName, args,null, doIncludePath);
}
private boolean hasCachedWithin(PageContext pc) {
return this.properties.getCachedWithin()!=null || pc.getCachedWithin(Config.CACHEDWITHIN_FUNCTION)!=null;
// Maybe better return !StringUtil.isEmpty(this.properties.cachedWithin) || !StringUtil.isEmpty(pc.getCachedWithin(Config.CACHEDWITHIN_FUNCTION));
}
private Object getCachedWithin(PageContext pc) {
if(this.properties.getCachedWithin()!=null)
return this.properties.getCachedWithin();
return pc.getCachedWithin(Config.CACHEDWITHIN_FUNCTION);
}
private Object _callCachedWithin(PageContext pc,Collection.Key calledName, Object[] args, Struct values,boolean doIncludePath) throws PageException {
PageContextImpl pci=(PageContextImpl) pc;
String id=CacheHandlerCollectionImpl.createId(this,args,values);
CacheHandler ch = pc.getConfig().getCacheHandlerCollection(Config.CACHE_TYPE_FUNCTION,null).getInstanceMatchingObject(getCachedWithin(pc),null);
CacheItem ci=ch!=null?ch.get(pc, id):null;
// get from cache
if(ci instanceof UDFCacheItem ) {
UDFCacheItem entry = (UDFCacheItem)ci;
//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);
}
long start = System.nanoTime();
// execute the function
BodyContent bc = pci.pushBody();
try {
Object rtn = _call(pci,calledName, args, values, doIncludePath);
if(ch!=null){
String out = bc.getString();
ch.set(pc, id,getCachedWithin(pc),new UDFCacheItem(out, rtn,getFunctionName(),getSource(),System.nanoTime()-start));
}
// 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.getArgumentsSet());
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(
pc.getCurrentTemplateDialect()==CFMLEngine.DIALECT_CFML?
(properties.getLocalMode()==null?pc.getApplicationContext().getLocalMode():properties.getLocalMode().intValue()):
Undefined.MODE_LOCAL_OR_ARGUMENTS_ALWAYS
);
PageSource ps=null;
PageSource psInc=null;
try {
ps = properties.getPageSource();
if(doIncludePath)psInc = ps;
if(doIncludePath && getOwnerComponent()!=null) {
psInc=ComponentUtil.getPageSource(getOwnerComponent());
if(psInc==pci.getCurrentTemplatePageSource()) {
psInc=null;
}
}
if(ps!=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) {
ExceptionUtil.rethrowIfNecessary(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(returnValue==null && pc.getConfig().getFullNullSupport()) return returnValue;
if(properties.getReturnType()==CFTypes.TYPE_ANY || !((PageContextImpl)pc).getTypeChecking()) return returnValue;
if(Decision.isCastableTo(properties.getReturnTypeAsString(),returnValue,false,false,-1)) return returnValue;
throw new UDFCasterException(this,properties.getReturnTypeAsString(),returnValue);
//REALCAST return Caster.castTo(pageContext,returnType,returnValue,false);
//////////////////////////////////////////
}
finally {
if(ps!=null)pc.removeLastPageSource(psInc!=null);
pci.removeUDF();
pci.setFunctionScopes(oldLocal,oldArgs);
pci.setActiveUDFCalledName(oldCalledName);
undefined.setMode(oldCheckArgs);
pci.getScopeFactory().recycle(pci,newArgs);
pci.getScopeFactory().recycle(pci,newLocal);
}
}
@Override
public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
return UDFUtil.toDumpData(pageContext, maxlevel, dp,this,UDFUtil.TYPE_UDF);
}
@Override
public String getDisplayName() {
return properties.getDisplayName();
}
@Override
public String getHint() {
return properties.getHint();
}
/*@Override
public PageSource getPageSource() {
return properties.pageSource;
}*/
@Override
public String getSource() {
return properties.getPageSource()!=null?properties.getPageSource().getDisplayPath():"";
}
public Struct getMeta() {
return properties.getMeta();
}
@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
*/
@Override
public void setOwnerComponent(Component component) {
this.ownerComponent = component;
}
@Override
public Component getOwnerComponent() {
return ownerComponent;//+++
}
@Override
public String toString() {
StringBuffer sb=new StringBuffer(properties.getFunctionName());
sb.append("(");
int optCount=0;
FunctionArgument[] args = properties.getFunctionArguments();
for(int i=0;i<args.length;i++) {
if(i>0)sb.append(", ");
if(!args[i].isRequired()){
sb.append("[");
optCount++;
}
sb.append(args[i].getTypeAsString());
sb.append(" ");
sb.append(args[i].getName());
}
for(int i=0;i<optCount;i++){
sb.append("]");
}
sb.append(")");
return sb.toString();
}
@Override
public Boolean getSecureJson() {
return properties.getSecureJson();
}
@Override
public Boolean getVerifyClient() {
return properties.getVerifyClient();
}
@Override
public Object clone() {
return duplicate();
}
@Override
public FunctionArgument[] getFunctionArguments() {
return properties.getFunctionArguments();
}
@Override
public Object getDefaultValue(PageContext pc,int index) throws PageException {
return getDefaultValue(pc, index, null);
}
@Override
public Object getDefaultValue(PageContext pc,int index, Object defaultValue) throws PageException {
return properties.getPage(pc).udfDefaultValue(pc,properties.getIndex(),index,defaultValue);
}
// public abstract Object getDefaultValue(PageContext pc,int index) throws PageException;
@Override
public String getFunctionName() {
return properties.getFunctionName();
}
@Override
public boolean getOutput() {
return properties.getOutput();
}
public Boolean getBufferOutput() {
return properties.getBufferOutput();
}
@Override
public boolean getBufferOutput(PageContext pc) {
if(properties.getBufferOutput()!=null)
return properties.getBufferOutput().booleanValue();
return ((ApplicationContextSupport)pc.getApplicationContext()).getBufferOutput();
}
@Override
public int getReturnType() {
return properties.getReturnType();
}
@Override
public String getReturnTypeAsString() {
return properties.getReturnTypeAsString();
}
@Override
public String getDescription() {
return properties.getDescription();
}
@Override
public int getReturnFormat() {
if(properties.getReturnFormat()<0) return UDF.RETURN_FORMAT_WDDX;
return properties.getReturnFormat();
}
@Override
public int getReturnFormat(int defaultValue) {
if(properties.getReturnFormat()<0) return defaultValue;
return properties.getReturnFormat();
}
public final String getReturnFormatAsString() {
return properties.getReturnFormatAsString();
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// access
setAccess(in.readInt());
// properties
properties=(UDFPropertiesBase) 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){
//print.e(left.getFunctionName()+":"+right.getFunctionName());
if(
!left.id().equals(right.id())
|| !_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);
}
@Override
public int getIndex(){
return properties.getIndex();
}
@Override
public String id(){
return properties.id();
}
@Override
public PageSource getPageSource() {
return this.properties.getPageSource();
}
}