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