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;
020
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.nio.charset.Charset;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map.Entry;
029
030import javax.servlet.ServletException;
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletResponse;
033
034import lucee.commons.io.CharsetUtil;
035import lucee.commons.io.IOUtil;
036import lucee.commons.io.res.Resource;
037import lucee.commons.io.res.util.ResourceUtil;
038import lucee.commons.lang.CFTypes;
039import lucee.commons.lang.ExceptionUtil;
040import lucee.commons.lang.StringUtil;
041import lucee.commons.lang.mimetype.MimeType;
042import lucee.runtime.config.ConfigWebImpl;
043import lucee.runtime.converter.BinaryConverter;
044import lucee.runtime.converter.ConverterException;
045import lucee.runtime.converter.JSONConverter;
046import lucee.runtime.converter.JavaConverter;
047import lucee.runtime.converter.ScriptConverter;
048import lucee.runtime.converter.WDDXConverter;
049import lucee.runtime.converter.XMLConverter;
050import lucee.runtime.converter.bin.ImageConverter;
051import lucee.runtime.dump.DumpUtil;
052import lucee.runtime.dump.DumpWriter;
053import lucee.runtime.exp.ApplicationException;
054import lucee.runtime.exp.ExpressionException;
055import lucee.runtime.exp.PageException;
056import lucee.runtime.exp.PageExceptionImpl;
057import lucee.runtime.gateway.GatewayEngineImpl;
058import lucee.runtime.interpreter.CFMLExpressionInterpreter;
059import lucee.runtime.interpreter.JSONExpressionInterpreter;
060import lucee.runtime.net.http.ReqRspUtil;
061import lucee.runtime.net.rpc.server.ComponentController;
062import lucee.runtime.net.rpc.server.RPCServer;
063import lucee.runtime.op.Caster;
064import lucee.runtime.op.Constants;
065import lucee.runtime.op.Decision;
066import lucee.runtime.rest.RestUtil;
067import lucee.runtime.rest.Result;
068import lucee.runtime.rest.path.Path;
069import lucee.runtime.type.Array;
070import lucee.runtime.type.ArrayImpl;
071import lucee.runtime.type.Collection;
072import lucee.runtime.type.Collection.Key;
073import lucee.runtime.type.FunctionArgument;
074import lucee.runtime.type.KeyImpl;
075import lucee.runtime.type.Struct;
076import lucee.runtime.type.StructImpl;
077import lucee.runtime.type.UDF;
078import lucee.runtime.type.UDFPlus;
079import lucee.runtime.type.scope.Scope;
080import lucee.runtime.type.util.ArrayUtil;
081import lucee.runtime.type.util.CollectionUtil;
082import lucee.runtime.type.util.KeyConstants;
083import lucee.runtime.type.util.ListUtil;
084import lucee.runtime.type.util.StructUtil;
085import lucee.runtime.type.util.UDFUtil;
086
087/**
088 * A Page that can produce Components
089 */
090public abstract class ComponentPage extends PagePlus  {
091        
092        public static final Collection.Key ACCEPT_ARG_COLL_FORMATS = KeyImpl.getInstance("acceptedArgumentCollectionFormats");
093
094        
095        private static final long serialVersionUID = -3483642653131058030L;
096
097        public static final lucee.runtime.type.Collection.Key REMOTE_PERSISTENT_ID = KeyImpl.intern("Id16hohohh");
098
099        private long lastCheck=-1;
100        
101        
102        public abstract ComponentImpl newInstance(PageContext pc,String callPath,boolean isRelPath)
103                throws lucee.runtime.exp.PageException; 
104        
105        @Override
106        public void call(PageContext pc) throws PageException {
107                // remote persistent (only type server is supported)
108                String strRemotePersisId = Caster.toString(getURLorForm(pc,REMOTE_PERSISTENT_ID,null),null);//Caster.toString(pc.urlFormScope().get(REMOTE_PERSISTENT_ID,null),null);
109                
110                if(!StringUtil.isEmpty(strRemotePersisId,true)) {
111                        strRemotePersisId=strRemotePersisId.trim();
112                }
113                else strRemotePersisId=null;
114                
115                HttpServletRequest req = pc.getHttpServletRequest();
116                // client
117                String client = Caster.toString(req.getAttribute("client"),null);
118                // call type (invocation, store-only)
119                String callType = Caster.toString(req.getAttribute("call-type"),null);
120                boolean fromGateway="lucee-gateway-1-0".equals(client);
121                boolean fromRest="lucee-rest-1-0".equals(client);
122                Component component;
123                try {
124                        pc.setSilent();
125                        // load the cfc
126                        try {
127                    if(fromGateway && strRemotePersisId!=null) {
128                        ConfigWebImpl config=(ConfigWebImpl) pc.getConfig();
129                        GatewayEngineImpl engine = config.getGatewayEngine();
130                        component=engine.getPersistentRemoteCFC(strRemotePersisId);
131                        
132                        if(component==null) {
133                                component=newInstance(pc,getPageSource().getComponentName(),false);
134                                if(!fromGateway)component=ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_REMOTE,component);
135                                
136                                engine.setPersistentRemoteCFC(strRemotePersisId,component);
137                        }
138                        
139                    }
140                    else {
141                        component=newInstance(pc,getPageSource().getComponentName(),false);
142                        if(!fromGateway)component=ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_REMOTE,component);
143                    }
144            }
145            finally {
146                pc.unsetSilent();
147            }
148            
149            // Only get the Component, no invocation
150            if("store-only".equals(callType)) {
151                req.setAttribute("component", component);
152                return;
153            }
154            
155            
156            
157            // METHOD INVOCATION
158                        String qs=ReqRspUtil.getQueryString(pc.getHttpServletRequest());
159            if(pc.getBasePageSource()==this.getPageSource() && pc.getConfig().debug())
160                pc.getDebugger().setOutput(false);
161            boolean isPost=pc.getHttpServletRequest().getMethod().equalsIgnoreCase("POST");
162            
163            boolean suppressContent = ((PageContextImpl)pc).getSuppressContent();
164            if(suppressContent)pc.clear();
165            Object method;
166            
167            if(fromRest){ 
168                
169                callRest(pc,component,Caster.toString(req.getAttribute("rest-path"),""),(Result)req.getAttribute("rest-result"),suppressContent);
170                return;
171            }
172            
173            
174            
175            // POST
176            if(isPost) {
177                // Soap
178                if(isSoap(pc)) { 
179                        callWebservice(pc,component);
180                        //close(pc);
181                    return;
182                }
183                        // WDDX
184                else if((method=getURLorForm(pc, KeyConstants._method, null))!=null) {
185                        callWDDX(pc,component,KeyImpl.toKey(method),suppressContent);
186                        //close(pc);
187                    return;
188                }
189                
190            }
191            
192            // GET
193            else {
194                // WSDL
195                if(qs!=null && (qs.trim().equalsIgnoreCase("wsdl") || qs.trim().startsWith("wsdl&"))) {
196                    callWSDL(pc,component);
197                        //close(pc);
198                    return;
199                } 
200                        // WDDX
201                else if((method=getURLorForm(pc, KeyConstants._method, null))!=null) {
202                    callWDDX(pc,component,KeyImpl.toKey(method),suppressContent);
203                    //close(pc); 
204                    return;
205                }
206                
207                if(qs!=null) {
208                        int rf = UDFUtil.toReturnFormat(qs.trim(),-1);
209                        if(rf!=-1) callCFCMetaData(pc,component,rf);
210                        //close(pc);
211                    return;
212                } 
213            }
214            
215            
216            // Include MUST
217            Array path = pc.getTemplatePath();
218            //if(path.size()>1 ) {
219            if(path.size()>1 && !(path.size()==3 && ListUtil.last(path.getE(2).toString(),"/\\",true).equalsIgnoreCase(lucee.runtime.config.Constants.APP_CFC)) ) {// MUSTMUST bad impl -> check with and without application.cfc
220                
221                ComponentSpecificAccess c = ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_PRIVATE,component);
222                Key[] keys = c.keys();
223                Object el;
224                Scope var = pc.variablesScope();
225                for(int i=0;i<keys.length;i++) {
226                        el=c.get(keys[i],null);
227                        if(el instanceof UDF) 
228                                var.set(keys[i], el);
229                        
230                }
231                
232                return;
233            }
234           
235            
236                        // DUMP
237                        //TODO component.setAccess(pc,Component.ACCESS_PUBLIC);
238                        String cdf = pc.getConfig().getComponentDumpTemplate();
239                        
240                        if(cdf!=null && cdf.trim().length()>0) {
241                            pc.variablesScope().set(KeyConstants._component,component);
242                            pc.doInclude(cdf);
243                        }
244                        else pc.write(pc.getConfig().getDefaultDumpWriter(DumpWriter.DEFAULT_RICH).toString(pc,component.toDumpData(pc,9999,DumpUtil.toDumpProperties() ),true));
245                        
246                }
247                catch(Throwable t) {
248                        throw Caster.toPageException(t);//Exception Handler.castAnd Stack(t, this, pc);
249                }
250        }
251        
252        private Object getURLorForm(PageContext pc, Key key, Object defaultValue) {
253                Object res = pc.formScope().get(key,null);
254                if(res!=null) return res;
255                return pc.urlScope().get(key,defaultValue);
256        }
257
258        private void callRest(PageContext pc, Component component, String path, Result result, boolean suppressContent) throws IOException, ConverterException {
259                String method = pc.getHttpServletRequest().getMethod();
260                String[] subPath = result.getPath();
261                Struct cMeta;
262                try {
263                        cMeta = component.getMetaData(pc);
264                } catch (PageException pe) {
265                        throw ExceptionUtil.toIOException(pe);
266                }
267                
268
269                // Consumes
270                MimeType[] cConsumes=null;
271                String strMimeType = Caster.toString(cMeta.get(KeyConstants._consumes,null),null);
272                if(!StringUtil.isEmpty(strMimeType,true)){
273                        cConsumes = MimeType.getInstances(strMimeType,',');
274                }
275                
276                // Produces
277                MimeType[] cProduces=null;
278                strMimeType = Caster.toString(cMeta.get(KeyConstants._produces,null),null);
279                if(!StringUtil.isEmpty(strMimeType,true)){
280                        cProduces = MimeType.getInstances(strMimeType,',');
281                }
282                
283                
284                
285                Iterator<Entry<Key, Object>> it = component.entryIterator();
286                Entry<Key, Object> e;
287                Object value;
288                UDF udf;
289                Struct meta;
290                int status=404;
291                MimeType bestP,bestC;
292                while(it.hasNext()){
293                        e = it.next();
294                        value=e.getValue();
295                        if(value instanceof UDF){
296                                udf=(UDF)value;
297                                try {
298                                        meta = udf.getMetaData(pc);
299                                        
300                                        // check if http method match
301                                        String httpMethod = Caster.toString(meta.get(KeyConstants._httpmethod,null),null);
302                                        if(StringUtil.isEmpty(httpMethod) || !httpMethod.equalsIgnoreCase(method)) continue;
303                                        
304
305                                        // get consumes mimetype
306                                        MimeType[] consumes;
307                                        strMimeType = Caster.toString(meta.get(KeyConstants._consumes,null),null);
308                                        if(!StringUtil.isEmpty(strMimeType,true)){
309                                                consumes = MimeType.getInstances(strMimeType,',');
310                                        }
311                                        else
312                                                consumes=cConsumes;
313                                        
314                                        
315                                        // get produces mimetype
316                                        MimeType[] produces;
317                                        strMimeType = Caster.toString(meta.get(KeyConstants._produces,null),null);
318                                        if(!StringUtil.isEmpty(strMimeType,true)){
319                                                produces = MimeType.getInstances(strMimeType,',');
320                                        }
321                                        else
322                                                produces=cProduces;
323                                        
324                                        
325                                        
326                                        
327                                        String restPath = Caster.toString(meta.get(KeyConstants._restPath,null),null);
328                                        
329                                        // no rest path
330                                        if(StringUtil.isEmpty(restPath)){
331                                                if(ArrayUtil.isEmpty(subPath)) {
332                                                        bestC = best(consumes,result.getContentType());
333                                                        bestP = best(produces,result.getAccept());
334                                                        if(bestC==null) status=405;
335                                                        else if(bestP==null) status=406;
336                                                        else {
337                                                                status=200;
338                                                                _callRest(pc, component, udf, path, result.getVariables(),result,bestP,produces, suppressContent,e.getKey());
339                                                                break;
340                                                        }
341                                                }
342                                        }
343                                        else {
344                                                Struct var = result.getVariables();
345                                                int index=RestUtil.matchPath(var, Path.init(restPath)/*TODO cache this*/, result.getPath());
346                                                if(index>=0 && index+1==result.getPath().length) {
347                                                        bestC = best(consumes,result.getContentType());
348                                                        bestP = best(produces,result.getAccept());
349                                                        
350                                                        if(bestC==null) status=405;
351                                                        else if(bestP==null) status=406;
352                                                        else {
353                                                                status=200;
354                                                                _callRest(pc, component, udf, path, var,result,bestP,produces, suppressContent,e.getKey());
355                                                                break;
356                                                        }
357                                                }
358                                        }
359                                } 
360                                catch (PageException pe) {}
361                        }
362                }
363                if(status==404)
364                        RestUtil.setStatus(pc,404,"no rest service for ["+path+"] found");
365                else if(status==405)
366                        RestUtil.setStatus(pc,405,"Unsupported Media Type");
367                else if(status==406)
368                        RestUtil.setStatus(pc,406,"Not Acceptable");
369                
370        
371        }
372
373        private MimeType best(MimeType[] produces, MimeType... accept) {
374                if(ArrayUtil.isEmpty(produces)){
375                        if(accept.length>0) return accept[0];
376                        return MimeType.ALL;
377                }
378                
379                MimeType best=null,tmp;
380                
381                for(int a=0;a<accept.length;a++){
382                        tmp=accept[a].bestMatch(produces);
383                        if(tmp!=null && !accept[a].hasWildCards() && tmp.hasWildCards()){
384                                tmp=accept[a];
385                        }
386                        if(tmp!=null && 
387                                        (best==null || 
388                                         best.getQuality()<tmp.getQuality() || 
389                                         (best.getQuality()==tmp.getQuality() && best.hasWildCards() && !tmp.hasWildCards())))
390                                best=tmp;
391                }
392                
393                
394                
395                return best;
396        }
397
398        private void _callRest(PageContext pc, Component component, UDF udf,String path, Struct variables, Result result, MimeType best,MimeType[] produces, boolean suppressContent, Key methodName) throws PageException, IOException, ConverterException {
399                FunctionArgument[] fa=udf.getFunctionArguments();
400                Struct args=new StructImpl(),meta;
401                
402                Key name;
403                String restArgName,restArgSource,value;
404                for(int i=0;i<fa.length;i++){
405                        name = fa[i].getName();
406                        meta=fa[i].getMetaData();
407                        restArgSource=meta==null?"":Caster.toString(meta.get(KeyConstants._restArgSource,""),"");
408                        
409                        if("path".equalsIgnoreCase(restArgSource))
410                                setValue(fa[i],args,name, variables.get(name,null));
411                        if("query".equalsIgnoreCase(restArgSource) || "url".equalsIgnoreCase(restArgSource))
412                                setValue(fa[i],args,name, pc.urlScope().get(name,null));
413                        if("form".equalsIgnoreCase(restArgSource))
414                                setValue(fa[i],args,name, pc.formScope().get(name,null));
415                        if("cookie".equalsIgnoreCase(restArgSource))
416                                setValue(fa[i],args,name, pc.cookieScope().get(name,null));
417                        if("header".equalsIgnoreCase(restArgSource) || "head".equalsIgnoreCase(restArgSource)) {
418                                restArgName=meta==null?"":Caster.toString(meta.get(KeyConstants._restArgName,""),"");
419                                if(StringUtil.isEmpty(restArgName))restArgName=name.getString();
420                                value=ReqRspUtil.getHeaderIgnoreCase(pc, restArgName, null);
421                                setValue(fa[i],args,name,value);
422                        }
423                        if("matrix".equalsIgnoreCase(restArgSource))
424                                setValue(fa[i],args,name, result.getMatrix().get(name,null));
425                        
426                        if("body".equalsIgnoreCase(restArgSource) || StringUtil.isEmpty(restArgSource,true)){
427                                boolean isSimple=CFTypes.isSimpleType(fa[i].getType());
428                                Object body = ReqRspUtil.getRequestBody(pc,true,null);
429                                if(isSimple && !Decision.isSimpleValue(body))
430                                        body= ReqRspUtil.getRequestBody(pc,false,null);
431                                setValue(fa[i],args,name, body);
432                        }
433                        
434                }
435                Object rtn=null;
436                try{
437                if(suppressContent)pc.setSilent();
438                        rtn = component.callWithNamedValues(pc, methodName, args);
439                } 
440                catch (PageException e) {
441                        RestUtil.setStatus(pc, 500, ExceptionUtil.getMessage(e));
442                }
443        finally {
444                if(suppressContent)pc.unsetSilent();
445        }
446        
447        // custom response
448                Struct sct = result.getCustomResponse();
449                boolean hasContent=false;
450        if(sct!=null){
451                        HttpServletResponse rsp = pc.getHttpServletResponse();
452                // status
453                int status = Caster.toIntValue(sct.get(KeyConstants._status,Constants.DOUBLE_ZERO),0);
454                if(status>0)rsp.setStatus(status);
455                        
456                // content
457                Object o=sct.get(KeyConstants._content,null);
458                if(o!=null) {
459                        String content=Caster.toString(o,null);
460                        if(content!=null) {
461                                try {
462                                                pc.forceWrite(content);
463                                                hasContent=true;
464                                        } 
465                                catch (IOException e) {}
466                        }
467                }
468                
469                // headers
470                Struct headers=Caster.toStruct(sct.get(KeyConstants._headers,null),null);
471                if(headers!=null){
472                        //Key[] keys = headers.keys();
473                        Iterator<Entry<Key, Object>> it = headers.entryIterator();
474                        Entry<Key, Object> e;
475                        String n,v;
476                        Object tmp;
477                        while(it.hasNext()){
478                                e = it.next();
479                                n=e.getKey().getString();
480                                tmp=e.getValue();
481                                v=Caster.toString(tmp,null);
482                                if(tmp!=null && v==null) v=tmp.toString();
483                                rsp.setHeader(n, v);
484                        }       
485                }
486                }
487        // convert result
488                if(rtn!=null && !hasContent){
489                        Props props = new Props();
490                props.format=result.getFormat();
491                Charset cs = getCharset(pc);
492                if(result.hasFormatExtension()){
493                        //setFormat(pc.getHttpServletResponse(), props.format,cs);
494                        _writeOut(pc, props, null, rtn,cs,true);
495                }
496                else {
497                        if(best!=null && !MimeType.ALL.same(best)) {
498                        int f = MimeType.toFormat(best, -1);
499                        if(f!=-1) {
500                                props.format=f;
501                                //setFormat(pc.getHttpServletResponse(), f,cs);
502                                _writeOut(pc, props, null, rtn,cs,true);
503                        }
504                        else {
505                                writeOut(pc,props,rtn,best);
506                        }
507                }
508                        else {
509                                _writeOut(pc, props, null, rtn,cs,true);
510                        }
511                }
512                
513                
514        }
515                
516        }
517
518        private void setValue(FunctionArgument fa, Struct args, Key name, Object value) {
519                if(value==null){
520                        Struct meta = fa.getMetaData();
521                        if(meta!=null)value=meta.get(KeyConstants._default,null);
522                }
523                args.setEL(name, value);
524        }
525
526        private void writeOut(PageContext pc, Props props, Object obj, MimeType mt) throws PageException, IOException, ConverterException {
527                // TODO miemtype mapping with converter defintion from external file
528                // Images
529                if(mt.same(MimeType.IMAGE_GIF)) writeOut(pc,obj,mt,new ImageConverter("gif"));
530                else if(mt.same(MimeType.IMAGE_JPG)) writeOut(pc,obj,mt,new ImageConverter("jpeg"));
531                else if(mt.same(MimeType.IMAGE_PNG)) writeOut(pc,obj,mt,new ImageConverter("png"));
532                else if(mt.same(MimeType.IMAGE_TIFF)) writeOut(pc,obj,mt,new ImageConverter("tiff"));
533                else if(mt.same(MimeType.IMAGE_BMP)) writeOut(pc,obj,mt,new ImageConverter("bmp"));
534                else if(mt.same(MimeType.IMAGE_WBMP)) writeOut(pc,obj,mt,new ImageConverter("wbmp"));
535                else if(mt.same(MimeType.IMAGE_FBX)) writeOut(pc,obj,mt,new ImageConverter("fbx"));
536                else if(mt.same(MimeType.IMAGE_FBX)) writeOut(pc,obj,mt,new ImageConverter("fbx"));
537                else if(mt.same(MimeType.IMAGE_PNM)) writeOut(pc,obj,mt,new ImageConverter("pnm"));
538                else if(mt.same(MimeType.IMAGE_PGM)) writeOut(pc,obj,mt,new ImageConverter("pgm"));
539                else if(mt.same(MimeType.IMAGE_PBM)) writeOut(pc,obj,mt,new ImageConverter("pbm"));
540                else if(mt.same(MimeType.IMAGE_ICO)) writeOut(pc,obj,mt,new ImageConverter("ico"));
541                else if(mt.same(MimeType.IMAGE_PSD)) writeOut(pc,obj,mt,new ImageConverter("psd"));
542                else if(mt.same(MimeType.IMAGE_ASTERIX)) writeOut(pc,obj,MimeType.IMAGE_PNG,new ImageConverter("png"));
543                
544                // Application
545                else if(mt.same(MimeType.APPLICATION_JAVA)) writeOut(pc,obj,mt,new JavaConverter());
546                //if("application".equalsIgnoreCase(mt.getType()))
547                
548                
549                else _writeOut(pc, props, null, obj,null,true);
550        }
551
552        private static void writeOut(PageContext pc, Object obj, MimeType mt,BinaryConverter converter) throws ConverterException, IOException {
553                pc.getResponse().setContentType(mt.toString());
554                
555                OutputStream os=null;
556                try{
557                        converter.writeOut(pc, obj, os=pc.getResponseStream());
558                }
559                finally{
560                        IOUtil.closeEL(os);
561                }
562        }
563
564        public static  boolean isSoap(PageContext pc) {
565                HttpServletRequest req = pc.getHttpServletRequest();
566                InputStream is=null;
567                try {
568                        is=req.getInputStream();
569                        
570                        String input = IOUtil.toString(is,CharsetUtil.ISO88591);
571                        return 
572                        StringUtil.indexOfIgnoreCase(input, ":Envelope>")!=-1;
573                        
574                        // soap:Envelope
575                } 
576                catch (IOException e) {
577                        return false;
578                }
579                finally {
580                        IOUtil.closeEL(is);
581                }
582        }
583        
584        
585        private void callWDDX(PageContext pc, Component component, Collection.Key methodName, boolean suppressContent) throws PageException {
586                try{
587                        //Struct url = StructUtil.duplicate(pc.urlFormScope(),true);
588                        Struct url=StructUtil.merge(new Struct[]{pc.formScope(),pc.urlScope()});
589                        // define args
590                        url.removeEL(KeyConstants._fieldnames);
591                        url.removeEL(KeyConstants._method);
592                        Object args=url.get(KeyConstants._argumentCollection,null);
593                        String strArgCollFormat=Caster.toString(url.get("argumentCollectionFormat",null),null);
594                        
595                        
596                        // url.returnFormat
597                        int urlReturnFormat=-1;
598                        Object oReturnFormatFromURL=url.get(KeyConstants._returnFormat,null);
599                        if(oReturnFormatFromURL!=null)urlReturnFormat=UDFUtil.toReturnFormat(Caster.toString(oReturnFormatFromURL,null),-1);
600                        
601                        // request header "accept"
602                        List<MimeType> accept = ReqRspUtil.getAccept(pc);
603                        int headerReturnFormat = MimeType.toFormat(accept,UDF.RETURN_FORMAT_XML, -1);
604                        
605                Object queryFormat=url.get(KeyConstants._queryFormat,null);
606                
607                
608                
609                if(args==null){
610                        args=pc.getHttpServletRequest().getAttribute("argumentCollection");
611                }
612                if(StringUtil.isEmpty(strArgCollFormat)) {
613                        strArgCollFormat=Caster.toString(pc.getHttpServletRequest().getAttribute("argumentCollectionFormat"),null);
614                }
615                
616              //content-type
617                Charset cs = getCharset(pc);
618                Object o = component.get(pc,methodName,null);
619        
620                // onMissingMethod
621                if(o==null) o=component.get(pc,KeyConstants._onmissingmethod,null);
622        
623                
624                Props props = getProps(pc, o, urlReturnFormat,headerReturnFormat);
625                //if(!props.output) 
626                        setFormat(pc.getHttpServletResponse(),props.format,cs);
627                        
628                
629                Object rtn=null;
630                try{
631                        if(suppressContent)pc.setSilent();
632                
633                        
634                        if(args==null){
635                                url=translate(component,methodName.getString(),url);
636                                rtn = component.callWithNamedValues(pc, methodName, url);
637                        }
638                        else if(args instanceof String){
639                                String str=(String)args;
640                                int format = UDFUtil.toReturnFormat(strArgCollFormat,-1);
641                                
642                                        // CFML
643                                        if(UDF.RETURN_FORMAT_SERIALIZE==format) {
644                                                 // do not catch exception when format is defined
645                                                args=new CFMLExpressionInterpreter().interpret(pc, str);
646                                        }
647                                        // JSON
648                                        if(UDF.RETURN_FORMAT_JSON==format)      {
649                                                 // do not catch exception when format is defined
650                                                args=new JSONExpressionInterpreter(false).interpret(pc, str);
651                                        }
652                                        // default 
653                                        else {
654                                                 // catch exception when format is not defined, then in this case the string can also be a simple argument
655                                                try {
656                                                        args=new JSONExpressionInterpreter(false).interpret(pc, str);
657                                                } 
658                                                catch (PageException pe) {
659                                                        try {
660                                                                args=new CFMLExpressionInterpreter().interpret(pc, str);
661                                                        } 
662                                                        catch (PageException _pe) {}
663                                                        }
664                                        }
665                                        }
666        
667                        // call
668                        if(args!=null) {
669                                if(Decision.isCastableToStruct(args)){
670                                        rtn = component.callWithNamedValues(pc, methodName, Caster.toStruct(args,false));
671                                }
672                                else if(Decision.isCastableToArray(args)){
673                                        rtn = component.call(pc, methodName, Caster.toNativeArray(args));
674                                }
675                                else {
676                                        Object[] ac=new Object[1];
677                                        ac[0]=args;
678                                        rtn = component.call(pc, methodName, ac);
679                                }
680                        }
681                }
682                finally {
683                        if(suppressContent)pc.unsetSilent();
684                }
685                // convert result
686                if(rtn!=null){
687                        if(pc.getHttpServletRequest().getHeader("AMF-Forward")!=null) {
688                                pc.variablesScope().setEL("AMF-Forward", rtn);
689                        }
690                        else {
691                                _writeOut(pc, props, queryFormat, rtn,cs,false);
692                        }
693                }
694                }
695        catch(Throwable t){
696                PageException pe = Caster.toPageException(t);
697                if(pe instanceof PageExceptionImpl) ((PageExceptionImpl)pe).setExposeMessage(true);
698                throw pe;
699        }
700    }
701    
702        private static void setFormat(HttpServletResponse rsp, int format, Charset charset) {
703                String strCS;
704                if(charset==null) strCS="";
705        else strCS="; charset="+charset.displayName();
706                
707                switch(format){
708        case UDF.RETURN_FORMAT_WDDX:
709                rsp.setContentType("text/xml"+strCS);
710                rsp.setHeader("Return-Format", "wddx");
711        break;
712        case UDF.RETURN_FORMAT_JSON:
713                rsp.setContentType("application/json"+strCS);
714                rsp.setHeader("Return-Format", "json");
715        break;
716        case UDF.RETURN_FORMAT_PLAIN:
717                rsp.setContentType("text/plain"+strCS);
718                rsp.setHeader("Return-Format", "plain");
719        break;
720        case UDF.RETURN_FORMAT_XML:
721                rsp.setContentType("text/xml"+strCS);
722                rsp.setHeader("Return-Format", "xml");
723        break;
724        case UDF.RETURN_FORMAT_SERIALIZE:
725                rsp.setContentType("application/cfml"+strCS);
726                rsp.setHeader("Return-Format", "cfml");
727        break;
728        case UDFPlus.RETURN_FORMAT_JAVA:
729                rsp.setContentType("application/java"); // no charset this is a binary format
730                rsp.setHeader("Return-Format", "java");
731        break;
732        }
733        }
734
735        private static Props getProps(PageContext pc, Object o,int urlReturnFormat,int headerReturnFormat) {
736        Props props = new Props();
737        
738                props.strType="any";
739                props.secureJson=pc.getApplicationContext().getSecureJson();
740                int udfReturnFormat=-1;
741                if(o instanceof UDFPlus) {
742                        UDFPlus udf = ((UDFPlus)o);
743                        udfReturnFormat=udf.getReturnFormat(-1);
744                        props.type=udf.getReturnType();
745                        props.strType=udf.getReturnTypeAsString();
746                        props.output=udf.getOutput();
747                        if(udf.getSecureJson()!=null)props.secureJson=udf.getSecureJson().booleanValue();
748                }
749
750                // format
751                if(isValid(urlReturnFormat)) props.format=urlReturnFormat;
752                else if(isValid(udfReturnFormat)) props.format=udfReturnFormat;
753                else if(isValid(headerReturnFormat)) props.format=headerReturnFormat;
754                else props.format=UDF.RETURN_FORMAT_WDDX;
755                
756                // return type XML ignore WDDX
757                if(props.type==CFTypes.TYPE_XML) {
758                        if(UDF.RETURN_FORMAT_WDDX==props.format)
759                                props.format=UDF.RETURN_FORMAT_PLAIN;
760                }
761        
762        
763        
764        return props;
765    }
766    
767    private static boolean isValid(int returnFormat) {
768                return returnFormat!=-1 && returnFormat!=UDF.RETURN_FORMAT_XML;
769        }
770
771        public static void writeToResponseStream(PageContext pc,Component component, String methodName,int urlReturnFormat,int headerReturnFormat,Object queryFormat,Object rtn) throws ConverterException, PageException, IOException {
772        Object o = component.get(KeyImpl.init(methodName),null);
773        Props p = getProps(pc, o, urlReturnFormat,headerReturnFormat);
774        _writeOut(pc, p, queryFormat, rtn,null,true);
775    }
776    
777    private static void _writeOut(PageContext pc,Props props,Object queryFormat,Object rtn,Charset cs, boolean setFormat) throws ConverterException, PageException, IOException {
778        // return type XML ignore WDDX
779                if(props.type==CFTypes.TYPE_XML) {
780                        //if(UDF.RETURN_FORMAT_WDDX==format) format=UDF.RETURN_FORMAT_PLAIN;
781                        rtn=Caster.toString(Caster.toXML(rtn));
782                }
783                // function does no real cast, only check it
784                else rtn=Caster.castTo(pc, (short)props.type, props.strType, rtn);
785        if(setFormat)setFormat(pc.getHttpServletResponse(), props.format, cs);
786                
787        // WDDX
788                if(UDF.RETURN_FORMAT_WDDX==props.format) {
789                        WDDXConverter converter = new WDDXConverter(pc.getTimeZone(),false,false);
790            converter.setTimeZone(pc.getTimeZone());
791            pc.forceWrite(converter.serialize(rtn));
792                }
793                // JSON
794                else if(UDF.RETURN_FORMAT_JSON==props.format) {
795                        boolean byColumn = false;
796                if(queryFormat instanceof String){
797                        String strQF=((String) queryFormat).trim();
798                        if(strQF.equalsIgnoreCase("row"));
799                        else if(strQF.equalsIgnoreCase("column"))byColumn=true;
800                        else throw new ApplicationException("invalid queryformat definition ["+strQF+"], valid formats are [row,column]");
801                }
802                JSONConverter converter = new JSONConverter(false,cs);
803                String prefix="";
804                if(props.secureJson) {
805                        prefix=pc.getApplicationContext().getSecureJsonPrefix();
806                        if(prefix==null)prefix="";
807                }
808                pc.forceWrite(prefix+converter.serialize(pc,rtn,byColumn));
809                }
810                // CFML
811                else if(UDF.RETURN_FORMAT_SERIALIZE==props.format) {
812                        ScriptConverter converter = new ScriptConverter(false);
813                        pc.forceWrite(converter.serialize(rtn));
814                }
815        // XML
816                else if(UDF.RETURN_FORMAT_XML==props.format) {
817                        XMLConverter converter = new XMLConverter(pc.getTimeZone(),false);
818            converter.setTimeZone(pc.getTimeZone());
819            pc.forceWrite(converter.serialize(rtn));
820                }
821                // Plain
822                else if(UDF.RETURN_FORMAT_PLAIN==props.format) {
823                        pc.forceWrite(Caster.toString(rtn));
824                }
825
826                // JAVA
827                else if(UDFPlus.RETURN_FORMAT_JAVA==props.format) {
828                        writeOut(pc,rtn,MimeType.APPLICATION_JAVA,new JavaConverter());
829                }
830                else throw new IOException("invalid return format defintion:"+props.format);
831        }
832
833        public static Struct translate(Component c, String strMethodName, Struct params) {
834                Collection.Key methodName=KeyImpl.init(strMethodName);
835                Key[] keys = CollectionUtil.keys(params);
836                FunctionArgument[] args=null;
837                int index=-1;
838                Object value;
839        for(int i=0;i<keys.length;i++){
840                index=Caster.toIntValue(keys[i].getString(),0);
841                if(index>0)  {
842                        if(args==null)args=_getArgs(c,methodName);
843                        if(args!=null && index<=args.length) {
844                                value=params.removeEL(keys[i]);
845                                if(value!=null)params.setEL(args[index-1].getName(), value);
846                        }
847                }
848                
849        }
850        return params;
851        }
852
853        private static FunctionArgument[] _getArgs(Component c, Collection.Key methodName) {
854                Object o=c.get(methodName,null);
855                if(o instanceof UDF) return ((UDF) o).getFunctionArguments();
856                return null;
857        }
858        
859        private void callCFCMetaData(PageContext pc, Component cfc, int format) throws IOException, PageException, ConverterException {
860                ComponentSpecificAccess cw = new ComponentSpecificAccess(Component.ACCESS_REMOTE,cfc);
861                ComponentScope scope = cw.getComponentScope();
862                Struct udfs=new StructImpl(),sctUDF,sctArg;
863                Array arrArg;
864                Iterator<Object> it = scope.valueIterator();
865                Object v;
866                UDF udf;
867                FunctionArgument[] args;
868                while(it.hasNext()){
869                                v=it.next();
870                                        // UDF
871                                if(v instanceof UDF) {
872                                        udf=(UDF) v; 
873                        sctUDF=new StructImpl();
874                        arrArg=new ArrayImpl();
875                        udfs.setEL(udf.getFunctionName(), sctUDF);
876                        args = udf.getFunctionArguments();
877                        for(int i=0;i<args.length;i++){
878                                sctArg=new StructImpl();
879                                arrArg.appendEL(sctArg);
880                                sctArg.setEL(KeyConstants._name, args[i].getName().getString());
881                                sctArg.setEL(KeyConstants._type, args[i].getTypeAsString());
882                                sctArg.setEL(KeyConstants._required, args[i].isRequired());
883                                if(!StringUtil.isEmpty(args[i].getHint()))sctArg.setEL(KeyConstants._hint, args[i].getHint());
884                        } 
885                        sctUDF.set(KeyConstants._arguments, arrArg);
886                        sctUDF.set(KeyConstants._returntype, udf.getReturnTypeAsString());
887                        
888                }
889                }
890                Struct rtn=new StructImpl();
891                rtn.set(KeyConstants._functions, udfs);
892                rtn.set(ACCEPT_ARG_COLL_FORMATS, "cfml,json");
893                
894        
895        InputStream is;
896        Charset cs=null;
897                // WDDX
898                if(UDF.RETURN_FORMAT_WDDX==format) {
899                        WDDXConverter converter = new WDDXConverter(pc.getTimeZone(),false,false);
900            converter.setTimeZone(pc.getTimeZone());
901            String str = converter.serialize(rtn);
902            cs = getCharset(pc);
903            is = new ByteArrayInputStream(str.getBytes(cs));
904                }
905                
906        // JSON
907                else if(UDF.RETURN_FORMAT_JSON==format) {
908                boolean byColumn = false;
909                cs = getCharset(pc);
910            JSONConverter converter = new JSONConverter(false,cs);
911                String str = converter.serialize(pc,rtn,byColumn);
912            is = new ByteArrayInputStream(str.getBytes(cs));
913                
914        }
915        // CFML
916                else if(UDF.RETURN_FORMAT_SERIALIZE==format) {
917                        ScriptConverter converter = new ScriptConverter(false);
918                        String str=converter.serialize(rtn);
919            cs = getCharset(pc);
920            is = new ByteArrayInputStream(str.getBytes(cs));
921                }
922        // XML
923                else if(UDF.RETURN_FORMAT_XML==format) {
924                        XMLConverter converter = new XMLConverter(pc.getTimeZone(),false);
925            converter.setTimeZone(pc.getTimeZone());
926            String str=converter.serialize(rtn);
927            cs = getCharset(pc);
928            is = new ByteArrayInputStream(str.getBytes(cs));
929                }
930                // Plain
931                else if(UDF.RETURN_FORMAT_PLAIN==format) {
932                        String str= Caster.toString(rtn);
933            cs = getCharset(pc);
934            is = new ByteArrayInputStream(str.getBytes(cs));
935                }
936                // Java
937                else if(UDFPlus.RETURN_FORMAT_JAVA==format) {
938                        byte[] bytes = JavaConverter.serializeAsBinary(rtn);
939                        is = new ByteArrayInputStream(bytes);
940                        
941                }
942                else throw new IOException("invalid format defintion:"+format);
943        
944        
945        
946        
947                OutputStream os=null;
948                try {
949                        os=pc.getResponseStream();
950                        setFormat(pc.getHttpServletResponse(), format, cs);
951                        IOUtil.copy(is, os, false,false);
952                        
953                }
954                finally {
955                        IOUtil.flushEL(os);
956            IOUtil.closeEL(os);
957            ((PageContextImpl)pc).getRootOut().setClosed(true);
958                }
959        }
960
961        private Charset getCharset(PageContext pc) {
962                HttpServletResponse rsp = pc.getHttpServletResponse();
963        Charset cs = ReqRspUtil.getCharacterEncoding(pc,rsp);
964        if(cs==null)cs=((PageContextImpl)pc).getWebCharset();
965        return cs;
966        }
967
968        private void callWSDL(PageContext pc, Component component) throws ServletException, IOException, ExpressionException {
969        // take wsdl file defined by user
970        String wsdl = component.getWSDLFile();
971        if(!StringUtil.isEmpty(wsdl)) {
972                
973                OutputStream os=null;
974                Resource input = ResourceUtil.toResourceExisting(pc, wsdl);
975                try {
976                        os=pc.getResponseStream();
977                                pc.getResponse().setContentType("text/xml; charset=utf-8");
978                        IOUtil.copy(input, os, false);
979                        
980                }
981                finally {
982                        IOUtil.flushEL(os);
983                IOUtil.closeEL(os);
984                ((PageContextImpl)pc).getRootOut().setClosed(true);
985                }
986        }
987        // create a wsdl file
988        else {
989                RPCServer.getInstance(pc.getId(),pc.getServletContext())
990                        .doGet(pc.getHttpServletRequest(), pc. getHttpServletResponse(), component);
991        }
992    }
993    
994    private void callWebservice(PageContext pc, Component component) throws IOException, ServletException {
995        ComponentController.set(pc, component);
996        try {
997                RPCServer.getInstance(pc.getId(),pc.getServletContext())
998                        .doPost(pc.getHttpServletRequest(), pc. getHttpServletResponse(), component);
999        }
1000        finally {
1001                ComponentController.release();
1002        }
1003    }
1004    
1005
1006    public abstract void initComponent(PageContext pc,ComponentImpl c) throws PageException;
1007
1008        public void ckecked() {
1009                lastCheck=System.currentTimeMillis();
1010        }
1011
1012        public long lastCheck() {
1013                return lastCheck;
1014        }
1015        
1016}
1017        class Props {
1018
1019                public String strType="any";
1020                public boolean secureJson;
1021                public int type=CFTypes.TYPE_ANY;
1022                public int format=UDF.RETURN_FORMAT_WDDX;
1023                public boolean output=true;
1024                
1025        }