001    package railo.runtime;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.io.OutputStream;
006    
007    import javax.servlet.ServletException;
008    import javax.servlet.http.HttpServletRequest;
009    import javax.servlet.http.HttpServletResponse;
010    
011    import railo.commons.io.IOUtil;
012    import railo.commons.io.res.Resource;
013    import railo.commons.io.res.util.ResourceUtil;
014    import railo.commons.lang.CFTypes;
015    import railo.commons.lang.StringUtil;
016    import railo.runtime.config.ConfigImpl;
017    import railo.runtime.config.ConfigWebImpl;
018    import railo.runtime.converter.ConverterException;
019    import railo.runtime.converter.JSONConverter;
020    import railo.runtime.converter.ScriptConverter;
021    import railo.runtime.converter.WDDXConverter;
022    import railo.runtime.dump.DumpUtil;
023    import railo.runtime.exp.ApplicationException;
024    import railo.runtime.exp.ExpressionException;
025    import railo.runtime.exp.PageException;
026    import railo.runtime.gateway.GatewayEngineImpl;
027    import railo.runtime.interpreter.JSONExpressionInterpreter;
028    import railo.runtime.net.rpc.server.ComponentController;
029    import railo.runtime.net.rpc.server.RPCServer;
030    import railo.runtime.op.Caster;
031    import railo.runtime.op.Decision;
032    import railo.runtime.type.Array;
033    import railo.runtime.type.Collection.Key;
034    import railo.runtime.type.FunctionArgument;
035    import railo.runtime.type.KeyImpl;
036    import railo.runtime.type.List;
037    import railo.runtime.type.Scope;
038    import railo.runtime.type.Struct;
039    import railo.runtime.type.UDF;
040    import railo.runtime.type.UDFImpl;
041    import railo.runtime.type.util.ComponentUtil;
042    import railo.runtime.type.util.StructUtil;
043    
044    /**
045     * A Page that can produce Components
046     */
047    public abstract class ComponentPage extends PagePlus  {
048            
049            private static final long serialVersionUID = -3483642653131058030L;
050            
051            public static final railo.runtime.type.Collection.Key METHOD = KeyImpl.intern("method");
052            public static final railo.runtime.type.Collection.Key QUERY_FORMAT = KeyImpl.intern("queryFormat");
053            //public static final railo.runtime.type.Collection.Key REMOTE_PERSISTENT = KeyImpl.intern("remotePersistent");
054            public static final railo.runtime.type.Collection.Key REMOTE_PERSISTENT_ID = KeyImpl.intern("Id16hohohh");
055    
056            //public static final short REMOTE_PERSISTENT_REQUEST = 1;
057            //public static final short REMOTE_PERSISTENT_SESSION = 2; FUTURE
058            //public static final short REMOTE_PERSISTENT_APPLICATION = 4; FUTURE
059            //public static final short REMOTE_PERSISTENT_SERVER = 8;
060            
061            
062            private long lastCheck=-1;
063            
064            
065            public abstract ComponentImpl newInstance(PageContext pc,String callPath,boolean isRealPath)
066                    throws railo.runtime.exp.PageException; 
067            
068            /**
069             * @see railo.runtime.Page#call(railo.runtime.PageContext)
070             */
071            public void call(PageContext pc) throws PageException {
072                    
073                    // remote persistent (only type server is supported)
074                    String strRemotePersisId = Caster.toString(pc.urlFormScope().get(REMOTE_PERSISTENT_ID,null),null);
075                    if(!StringUtil.isEmpty(strRemotePersisId,true)) {
076                            strRemotePersisId=strRemotePersisId.trim();
077                    }
078                    else strRemotePersisId=null;
079                    
080                    HttpServletRequest req = pc.getHttpServletRequest();
081                    // client
082                    String client = Caster.toString(req.getAttribute("client"),null);
083                    // call type (invocation, store-only)
084                    String callType = Caster.toString(req.getAttribute("call-type"),null);
085                    boolean fromGateway="railo-gateway-1-0".equals(client);
086                    ComponentPro component;
087            try {
088                pc.setSilent();
089                // load the cfc
090                try {
091                        if(fromGateway && strRemotePersisId!=null) {
092                            ConfigWebImpl config=(ConfigWebImpl) pc.getConfig();
093                            GatewayEngineImpl engine = config.getGatewayEngine();
094                            component=(ComponentPro) engine.getPersistentRemoteCFC(strRemotePersisId);
095                            
096                            if(component==null) {
097                                    component=newInstance(pc,getPageSource().getComponentName(),false);
098                                    if(!fromGateway)component=ComponentWrap.toComponentWrap(Component.ACCESS_REMOTE,component);
099                                    
100                                    engine.setPersistentRemoteCFC(strRemotePersisId,component);
101                            }
102                            
103                        }
104                        else {
105                            component=newInstance(pc,getPageSource().getComponentName(),false);
106                            if(!fromGateway)component=ComponentWrap.toComponentWrap(Component.ACCESS_REMOTE,component);
107                        }
108                }
109                finally {
110                    pc.unsetSilent();
111                }
112                
113                // Only get the Component, no invocation
114                if("store-only".equals(callType)) {
115                    req.setAttribute("component", component);
116                    return;
117                }
118                
119                
120                
121                // METHOD INVOCATION
122                            String qs=pc.getHttpServletRequest().getQueryString();
123                if(pc.getBasePageSource()==this.getPageSource())
124                    pc.getDebugger().setOutput(false);
125                boolean isPost=pc. getHttpServletRequest().getMethod().equalsIgnoreCase("POST");
126                Object method;
127                
128                boolean suppressContent = ((ConfigImpl)pc.getConfig()).isSuppressContent();
129                if(suppressContent)pc.clear();
130                
131                // POST
132                if(isPost) {
133                    // Soap
134                    if(isSoap(pc)) { 
135                            callWebservice(pc,component);
136                            //close(pc);
137                        return;
138                    }
139                            // WDDX
140                    else if((method=pc.urlFormScope().get("method",null))!=null) {
141                        callWDDX(pc,component,Caster.toString(method),suppressContent);
142                            //close(pc);
143                        return;
144                    }
145                    
146                }
147                
148                // GET
149                else {
150                    // WSDL
151                    if(qs!=null && qs.trim().equalsIgnoreCase("wsdl")) {
152                        callWSDL(pc,component);
153                            //close(pc);
154                        return;
155                    } 
156                            // WDDX
157                    else if((method=pc.urlFormScope().get("method",null))!=null) {
158                        callWDDX(pc,component,Caster.toString(method),suppressContent);
159                        //close(pc); 
160                        return;
161                    } 
162                }
163                
164                
165                // Include MUST
166                Array path = pc.getTemplatePath();
167                //if(path.size()>1 ) {
168                if(path.size()>1 && !(path.size()==3 && List.last(path.getE(2).toString(),"/\\",true).equalsIgnoreCase("application.cfc")) ) {// MUSTMUST bad impl -> check with and without application.cfc
169                    
170                    ComponentWrap c = ComponentWrap.toComponentWrap(Component.ACCESS_PRIVATE,ComponentUtil.toComponentAccess(component));
171                    Key[] keys = c.keys();
172                    Object el;
173                    Scope var = pc.variablesScope();
174                    for(int i=0;i<keys.length;i++) {
175                            el=c.get(keys[i],null);
176                            if(el instanceof UDF) 
177                                    var.set(keys[i], el);
178                            
179                    }
180                    
181                    return;
182                }
183               
184                
185                            // DUMP
186                            //TODO component.setAccess(pc,Component.ACCESS_PUBLIC);
187                            String cdf = pc.getConfig().getComponentDumpTemplate();
188                            
189                            if(cdf!=null && cdf.trim().length()>0) {
190                                pc.variablesScope().set("component",component);
191                                pc.doInclude(cdf);
192                            }
193                            else pc.write(pc.getConfig().getDefaultDumpWriter().toString(pc,component.toDumpData(pc,9999,DumpUtil.toDumpProperties() ),true));
194                            
195                    }
196                    catch(Throwable t) {
197                            throw Caster.toPageException(t);//Exception Handler.castAnd Stack(t, this, pc);
198                    }
199            }
200            
201            /*private void close(PageContext pc) {
202                    //pc.close();
203            }*/
204    
205            public static  boolean isSoap(PageContext pc) {
206                    HttpServletRequest req = pc.getHttpServletRequest();
207                    InputStream is=null;
208                    try {
209                            is=req.getInputStream();
210                            
211                            String input = IOUtil.toString(is,"iso-8859-1");
212                            return 
213                            StringUtil.indexOfIgnoreCase(input, "soap:Envelope")!=-1 || 
214                            StringUtil.indexOfIgnoreCase(input, "soapenv:Envelope")!=-1 || 
215                                    StringUtil.indexOfIgnoreCase(input, "SOAP-ENV:Envelope")!=-1;
216                    } 
217                    catch (IOException e) {
218                            return false;
219                    }
220                    finally {
221                            IOUtil.closeEL(is);
222                    }
223            }
224            
225            
226        private void callWDDX(PageContext pc, Component component, String methodName, boolean suppressContent) throws IOException, ConverterException, PageException {
227            Struct url = StructUtil.duplicate(pc.urlFormScope(),true);
228    
229            // define args
230            url.removeEL(KeyImpl.FIELD_NAMES);
231            url.removeEL(METHOD);
232            Object args=url.get(KeyImpl.ARGUMENT_COLLECTION,null);
233            Object returnFormat=url.get(KeyImpl.RETURN_FORMAT,null);
234            Object queryFormat=url.get(QUERY_FORMAT,null);
235            
236            if(args==null){
237                    args=pc.getHttpServletRequest().getAttribute("argumentCollection");
238            }
239            
240          //content-type
241            Object o = component.get(KeyImpl.init(methodName),null);
242            Props props = getProps(pc, o, returnFormat);
243            HttpServletResponse rsp = pc.getHttpServletResponse();
244            if(!props.output) {
245                    switch(props.format){
246                    case UDF.RETURN_FORMAT_WDDX:
247                            rsp.setContentType("text/xml; charset=UTF-8");
248                            rsp.setHeader("Return-Format", "wddx");
249                    break;
250                    case UDF.RETURN_FORMAT_JSON:
251                            rsp.setContentType("application/json");
252                            rsp.setHeader("Return-Format", "json");
253                    break;
254                    case UDF.RETURN_FORMAT_PLAIN:
255                            rsp.setContentType("text/plain; charset=UTF-8");
256                            rsp.setHeader("Return-Format", "plain");
257                    break;
258                    case UDF.RETURN_FORMAT_SERIALIZE:
259                            rsp.setContentType("text/plain; charset=UTF-8");
260                            rsp.setHeader("Return-Format", "serialize");
261                    break;
262                    }
263            }
264            
265            Object rtn=null;
266            try{
267                    if(suppressContent)pc.setSilent();
268            
269                    
270                    if(args==null){
271                            url=translate(component,methodName,url);
272                            rtn = component.callWithNamedValues(pc, methodName, url);
273                    }
274                    else if(args instanceof String){
275                            try {
276                                            args=new JSONExpressionInterpreter().interpret(pc, (String)args);
277                                            
278                                    } catch (PageException e) {}
279                    }
280                    
281                    // call
282                    if(args!=null) {
283                            if(Decision.isCastableToStruct(args)){
284                                    rtn = component.callWithNamedValues(pc, methodName, Caster.toStruct(args,false));
285                            }
286                            else if(Decision.isCastableToArray(args)){
287                                    rtn = component.call(pc, methodName, Caster.toNativeArray(args));
288                            }
289                            else {
290                                    Object[] ac=new Object[1];
291                                    ac[0]=args;
292                                    rtn = component.call(pc, methodName, ac);
293                            }
294                    }
295            }
296            finally {
297                    if(suppressContent)pc.unsetSilent();
298            }
299            // convert result
300            if(rtn!=null){
301                    if(pc.getHttpServletRequest().getHeader("AMF-Forward")!=null) {
302                            pc.variablesScope().setEL("AMF-Forward", rtn);
303                            //ThreadLocalWDDXResult.set(rtn);
304                    }
305                    else {
306                            pc.forceWrite(convertResult(pc, props, queryFormat, rtn));
307                    }
308            }
309            //pc.setSilent();
310            
311        }
312        
313        private static Props getProps(PageContext pc, Object o,Object returnFormat) throws PageException {
314            Props props = new Props();
315            
316                    props.strType="any";
317                    props.secureJson=pc.getApplicationContext().getSecureJson();
318                    if(o instanceof UDF) {
319                            UDF udf = ((UDF)o);
320                            props.format=udf.getReturnFormat();
321                            props.type=udf.getReturnType();
322                            props.strType=udf.getReturnTypeAsString();
323                            props.output=udf.getOutput();
324                            if(udf.getSecureJson()!=null)props.secureJson=udf.getSecureJson().booleanValue();
325                    }
326                    if(!StringUtil.isEmpty(returnFormat)){
327                            props.format=UDFImpl.toReturnFormat(Caster.toString(returnFormat));
328                    }
329            
330                    // return type XML ignore WDDX
331                    if(props.type==CFTypes.TYPE_XML) {
332                            if(UDF.RETURN_FORMAT_WDDX==props.format)
333                                    props.format=UDF.RETURN_FORMAT_PLAIN;
334                    }
335            
336            
337            
338            return props;
339        }
340        
341        public static String convertResult(PageContext pc,Component component, String methodName,Object returnFormat,Object queryFormat,Object rtn) throws ConverterException, PageException {
342            Object o = component.get(methodName,null);
343            Props p = getProps(pc, o, returnFormat);
344            return convertResult(pc, p, queryFormat, rtn);
345        }
346        
347        private static String convertResult(PageContext pc,Props props,Object queryFormat,Object rtn) throws ConverterException, PageException {
348    
349            /*Object o = component.get(methodName,null);
350                    int format=UDF.RETURN_FORMAT_WDDX;
351                    int type=CFTypes.TYPE_ANY;
352                    String strType="any";
353                    boolean secureJson=pc.getApplicationContext().getSecureJson();
354                    if(o instanceof UDF) {
355                            UDF udf = ((UDF)o);
356                            format=udf.getReturnFormat();
357                            type=udf.getReturnType();
358                            strType=udf.getReturnTypeAsString();
359                            if(udf.getSecureJson()!=null)secureJson=udf.getSecureJson().booleanValue();
360                    }
361                    if(!StringUtil.isEmpty(returnFormat)){
362                            format=UDFImpl.toReturnFormat(Caster.toString(returnFormat));
363                    }*/
364            
365            
366                    // return type XML ignore WDDX
367                    if(props.type==CFTypes.TYPE_XML) {
368                            //if(UDF.RETURN_FORMAT_WDDX==format) format=UDF.RETURN_FORMAT_PLAIN;
369                            rtn=Caster.toString(Caster.toXML(rtn));
370                    }
371                    // function does no real cast, only check it
372                    else rtn=Caster.castTo(pc, (short)props.type, props.strType, rtn);
373            
374            // WDDX
375                    if(UDF.RETURN_FORMAT_WDDX==props.format) {
376                            WDDXConverter converter = new WDDXConverter(pc.getTimeZone(),false,false);
377                converter.setTimeZone(pc.getTimeZone());
378                    return converter.serialize(rtn);
379                    }
380                    // JSON
381                    else if(UDF.RETURN_FORMAT_JSON==props.format) {
382                            boolean byColumn = false;
383                    if(queryFormat instanceof String){
384                            String strQF=((String) queryFormat).trim();
385                            if(strQF.equalsIgnoreCase("row"));
386                            else if(strQF.equalsIgnoreCase("column"))byColumn=true;
387                            else throw new ApplicationException("invalid queryformat definition ["+strQF+"], valid formats are [row,column]");
388                    }
389                    JSONConverter converter = new JSONConverter(false);
390                    String prefix="";
391                    if(props.secureJson) {
392                            prefix=pc.getApplicationContext().getSecureJsonPrefix();
393                            if(prefix==null)prefix="";
394                    }
395                return prefix+converter.serialize(pc,rtn,byColumn);
396                    }
397                    // Serialize
398                    else if(UDF.RETURN_FORMAT_SERIALIZE==props.format) {
399                            ScriptConverter converter = new ScriptConverter(false);
400                            return converter.serialize(rtn);
401                    }
402                    // Plain
403                    else if(UDF.RETURN_FORMAT_PLAIN==props.format) {
404                    return Caster.toString(rtn);
405                    }
406                    return null;
407            }
408    
409            public static Struct translate(Component c, String strMethod, Struct params) {
410                    Key[] keys = params.keys();
411                    FunctionArgument[] args=null;
412                    int index=-1;
413                    Object value;
414            for(int i=0;i<keys.length;i++){
415                    index=Caster.toIntValue(keys[i].getString(),0);
416                    if(index>0)  {
417                            if(args==null)args=_getArgs(c,strMethod);
418                            if(args!=null && index<=args.length) {
419                                    value=params.removeEL(keys[i]);
420                                    if(value!=null)params.setEL(args[index-1].getName(), value);
421                            }
422                    }
423                    
424            }
425            return params;
426            }
427    
428            private static FunctionArgument[] _getArgs(Component c, String strMethod) {
429                    Object o=c.get(strMethod,null);
430                    if(o instanceof UDF) return ((UDF) o).getFunctionArguments();
431                    return null;
432            }
433    
434            private void callWSDL(PageContext pc, ComponentPro component) throws ServletException, IOException, ExpressionException {
435            // take wsdl file defined by user
436            String wsdl = component.getWSDLFile();
437            if(!StringUtil.isEmpty(wsdl)) {
438                    
439                    OutputStream os=null;// FUTURE add to interface
440                    Resource input = ResourceUtil.toResourceExisting(pc, wsdl);
441                    try {
442                            os=((PageContextImpl)pc).getResponseStream();
443                                    pc.getResponse().setContentType("text/xml; charset=utf-8");
444                            IOUtil.copy(input, os, false);
445                            
446                    }
447                    finally {
448                            IOUtil.flushEL(os);
449                    IOUtil.closeEL(os);
450                    ((PageContextImpl)pc).getRootOut().setClosed(true);
451                    }
452            }
453            // create a wsdl file
454            else {
455                    RPCServer.getInstance(pc.getId(),pc.getServletContext())
456                            .doGet(pc.getHttpServletRequest(), pc. getHttpServletResponse(), component);
457            }
458        }
459        
460        private void callWebservice(PageContext pc, Component component) throws IOException, ServletException {
461            ComponentController.set(pc, component);
462            try {
463                    RPCServer.getInstance(pc.getId(),pc.getServletContext())
464                            .doPost(pc.getHttpServletRequest(), pc. getHttpServletResponse(), component);
465            }
466            finally {
467                    ComponentController.release();
468            }
469        }
470        
471    
472        public abstract void initComponent(PageContext pc,ComponentImpl c) throws PageException;
473    
474            public void ckecked() {
475                    lastCheck=System.currentTimeMillis();
476            }
477    
478            public long lastCheck() {
479                    return lastCheck;
480            }
481            
482    }
483            class Props {
484    
485                    public String strType="any";
486                    public boolean secureJson;
487                    public int type=CFTypes.TYPE_ANY;
488                    public int format=UDF.RETURN_FORMAT_WDDX;
489                    public boolean output=true;
490                    
491            }