001    package railo.transformer.bytecode.util;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.IOException;
005    import java.lang.reflect.Constructor;
006    import java.lang.reflect.InvocationTargetException;
007    import java.lang.reflect.Method;
008    import java.util.Arrays;
009    import java.util.HashMap;
010    import java.util.HashSet;
011    import java.util.Map;
012    import java.util.Set;
013    
014    import org.objectweb.asm.ClassWriter;
015    import org.objectweb.asm.FieldVisitor;
016    import org.objectweb.asm.Label;
017    import org.objectweb.asm.Opcodes;
018    import org.objectweb.asm.Type;
019    import org.objectweb.asm.commons.GeneratorAdapter;
020    
021    import railo.commons.io.IOUtil;
022    import railo.commons.io.res.Resource;
023    import railo.commons.io.res.util.ResourceUtil;
024    import railo.commons.lang.KeyGenerator;
025    import railo.commons.lang.PhysicalClassLoader;
026    import railo.runtime.Component;
027    import railo.runtime.Mapping;
028    import railo.runtime.PageContext;
029    import railo.runtime.PageContextImpl;
030    import railo.runtime.config.ConfigWeb;
031    import railo.runtime.exp.PageException;
032    import railo.runtime.java.JavaProxy;
033    import railo.runtime.listener.JavaSettingsImpl;
034    import railo.runtime.op.Caster;
035    import railo.runtime.reflection.Reflector;
036    import railo.transformer.bytecode.visitor.ArrayVisitor;
037    
038    /**
039     * creates a Java Proxy for components, so you can use componets as java classes following a certain interface or class
040     */
041    public class JavaProxyFactory {
042            
043    
044            private static final String COMPONENT_NAME="L"+Types.COMPONENT.getInternalName()+";";
045            private static final String CONFIG_WEB_NAME="L"+Types.CONFIG_WEB.getInternalName()+";";
046    
047            private static final Type JAVA_PROXY = Type.getType(JavaProxy.class);
048    
049            
050            private static final org.objectweb.asm.commons.Method CALL = new org.objectweb.asm.commons.Method(
051                            "call",
052                            Types.OBJECT,
053                            new Type[]{Types.CONFIG_WEB,Types.COMPONENT,Types.STRING,Types.OBJECT_ARRAY});
054            
055            private static final org.objectweb.asm.commons.Method CONSTRUCTOR = new org.objectweb.asm.commons.Method(
056                            "<init>",
057                            Types.VOID,
058                            new Type[]{
059                                            Types.CONFIG_WEB,
060                                            Types.COMPONENT
061                                    }
062                    );
063            private static final org.objectweb.asm.commons.Method SUPER_CONSTRUCTOR = new org.objectweb.asm.commons.Method(
064                            "<init>",
065                            Types.VOID,
066                            new Type[]{}
067                    );
068    
069            private static final org.objectweb.asm.commons.Method TO_BOOLEAN = new org.objectweb.asm.commons.Method(
070                            "toBoolean",
071                            Types.BOOLEAN_VALUE,
072                            new Type[]{Types.OBJECT});
073            private static final org.objectweb.asm.commons.Method TO_FLOAT = new org.objectweb.asm.commons.Method(
074                            "toFloat",
075                            Types.FLOAT_VALUE,
076                            new Type[]{Types.OBJECT});
077            private static final org.objectweb.asm.commons.Method TO_INT = new org.objectweb.asm.commons.Method(
078                            "toInt",
079                            Types.INT_VALUE,
080                            new Type[]{Types.OBJECT});
081            private static final org.objectweb.asm.commons.Method TO_DOUBLE = new org.objectweb.asm.commons.Method(
082                            "toDouble",
083                            Types.DOUBLE_VALUE,
084                            new Type[]{Types.OBJECT});
085            private static final org.objectweb.asm.commons.Method TO_LONG = new org.objectweb.asm.commons.Method(
086                            "toLong",
087                            Types.LONG_VALUE,
088                            new Type[]{Types.OBJECT});
089            private static final org.objectweb.asm.commons.Method TO_CHAR = new org.objectweb.asm.commons.Method(
090                            "toChar",
091                            Types.CHAR,
092                            new Type[]{Types.OBJECT});
093            private static final org.objectweb.asm.commons.Method TO_BYTE = new org.objectweb.asm.commons.Method(
094                            "toByte",
095                            Types.BYTE_VALUE,
096                            new Type[]{Types.OBJECT});
097            private static final org.objectweb.asm.commons.Method TO_SHORT = new org.objectweb.asm.commons.Method(
098                            "toShort",
099                            Types.SHORT,
100                            new Type[]{Types.OBJECT});
101            private static final org.objectweb.asm.commons.Method TO_ = new org.objectweb.asm.commons.Method(
102                            "to",
103                            Types.OBJECT,
104                            new Type[]{Types.OBJECT,Types.CLASS});
105            
106            
107    
108            private static final org.objectweb.asm.commons.Method _BOOLEAN = new org.objectweb.asm.commons.Method(
109                            "toCFML",
110                            Types.OBJECT,
111                            new Type[]{Types.BOOLEAN_VALUE});
112            private static final org.objectweb.asm.commons.Method _FLOAT = new org.objectweb.asm.commons.Method(
113                            "toCFML",
114                            Types.OBJECT,
115                            new Type[]{Types.FLOAT_VALUE});
116            private static final org.objectweb.asm.commons.Method _INT = new org.objectweb.asm.commons.Method(
117                            "toCFML",
118                            Types.OBJECT,
119                            new Type[]{Types.INT_VALUE});
120            private static final org.objectweb.asm.commons.Method _DOUBLE = new org.objectweb.asm.commons.Method(
121                            "toCFML",
122                            Types.OBJECT,
123                            new Type[]{Types.DOUBLE_VALUE});
124            private static final org.objectweb.asm.commons.Method _LONG = new org.objectweb.asm.commons.Method(
125                            "toCFML",
126                            Types.OBJECT,
127                            new Type[]{Types.LONG_VALUE});
128            private static final org.objectweb.asm.commons.Method _CHAR = new org.objectweb.asm.commons.Method(
129                            "toCFML",
130                            Types.OBJECT,
131                            new Type[]{Types.CHAR});
132            private static final org.objectweb.asm.commons.Method _BYTE = new org.objectweb.asm.commons.Method(
133                            "toCFML",
134                            Types.OBJECT,
135                            new Type[]{Types.BYTE_VALUE});
136            private static final org.objectweb.asm.commons.Method _SHORT = new org.objectweb.asm.commons.Method(
137                            "toCFML",
138                            Types.OBJECT,
139                            new Type[]{Types.SHORT});
140            private static final org.objectweb.asm.commons.Method _OBJECT = new org.objectweb.asm.commons.Method(
141                            "toCFML",
142                            Types.OBJECT,
143                            new Type[]{Types.OBJECT});
144    
145            
146            
147            
148            
149            
150    /*
151    
152            public static Object to(Object obj, Class clazz) {
153                    return obj;
154            }*/
155            
156            
157            
158    
159            /*public static Object createProxy(Config config,Component cfc, String className) throws PageException, IOException {
160                    return createProxy(cfc, null, ClassUtil.loadClass(config.getClassLoader(), className));
161            }*/
162    
163            public static Object createProxy(PageContext pc, Component cfc, Class extendz,Class... interfaces) throws PageException, IOException {
164                    PageContextImpl pci=(PageContextImpl) pc;
165                    if(extendz==null) extendz=Object.class;
166                    if(interfaces==null) interfaces=new Class[0];
167                    else {
168                            for(int i=0;i<interfaces.length;i++){
169                                    if(!interfaces[i].isInterface()) 
170                                            throw new IOException("definition ["+interfaces[i].getName()+"] is a class and not a interface");
171                            }
172                    }
173                    
174                    
175                    
176                    Type typeExtends = Type.getType(extendz); 
177                    Type[] typeInterfaces = ASMUtil.toTypes(interfaces); 
178                    String[] strInterfaces=new String[typeInterfaces.length];
179                    for(int i=0;i<strInterfaces.length;i++){
180                            strInterfaces[i]=typeInterfaces[i].getInternalName();
181                    }
182                    
183                    
184                    String className=createClassName(extendz,interfaces);
185            //Mapping mapping = cfc.getPageSource().getMapping();
186                    
187            // get ClassLoader
188            PhysicalClassLoader cl=null;
189                    try {
190                            cl = (PhysicalClassLoader) pci.getRPCClassLoader(false);// mapping.getConfig().getRPCClassLoader(false)
191                    } catch (IOException e) {
192                            throw Caster.toPageException(e);
193                    }
194                    Resource classFile = cl.getDirectory().getRealResource(className.concat(".class"));
195                    
196                    // check if already exists, if yes return
197                    if(classFile.exists()) {
198                            //Object obj=newInstance(cl,className,cfc);
199                            // if(obj!=null) return obj;
200                    }
201                    
202                    /*
203                    String classNameOriginal=component.getPageSource().getFullClassName();
204            String realOriginal=classNameOriginal.replace('.','/');
205                    Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class"));
206                    */      
207                    ClassWriter cw = ASMUtil.getClassWriter();
208                
209                    cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, className, null, typeExtends.getInternalName(), strInterfaces);
210                    //BytecodeContext statConstr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.STATIC_CONSTRUCTOR);
211                    //BytecodeContext constr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.CONSTRUCTOR);
212                    
213                    
214                    // field Component
215                    FieldVisitor _fv = cw.visitField(Opcodes.ACC_PRIVATE, "cfc", COMPONENT_NAME, null, null);
216                    _fv.visitEnd();
217                    _fv = cw.visitField(Opcodes.ACC_PRIVATE, "config", CONFIG_WEB_NAME, null, null);
218                    _fv.visitEnd();
219                    
220                     // Constructor
221            GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR,null,null,cw);
222            Label begin = new Label();
223            adapter.visitLabel(begin);
224                    adapter.loadThis();
225            adapter.invokeConstructor(Types.OBJECT, SUPER_CONSTRUCTOR);
226            
227            //adapter.putField(JAVA_PROXY, arg1, arg2)
228    
229            adapter.visitVarInsn(Opcodes.ALOAD, 0);
230            adapter.visitVarInsn(Opcodes.ALOAD, 1);
231            adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "config", CONFIG_WEB_NAME);
232    
233            adapter.visitVarInsn(Opcodes.ALOAD, 0);
234            adapter.visitVarInsn(Opcodes.ALOAD, 2);
235            adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "cfc", COMPONENT_NAME);
236                    
237            adapter.visitInsn(Opcodes.RETURN);
238                    Label end = new Label();
239                    adapter.visitLabel(end);
240                    adapter.visitLocalVariable("config",CONFIG_WEB_NAME, null, begin, end, 1);
241                    adapter.visitLocalVariable("cfc",COMPONENT_NAME, null, begin, end, 2);
242                    
243            //adapter.returnValue();
244            adapter.endMethod();
245            
246                    
247                    // create methods
248                    Set<Class> cDone=new HashSet<Class>();
249                    Map<String,Class> mDone=new HashMap<String,Class>();
250                    for(int i=0;i<interfaces.length;i++){
251                            _createProxy(cw,cDone,mDone, cfc, interfaces[i],className);
252                    }
253            cw.visitEnd();
254            
255            
256            // create class file
257            byte[] barr = cw.toByteArray();
258            
259            try {
260                    ResourceUtil.touch(classFile);
261                    IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
262                    
263                    cl = (PhysicalClassLoader) pci.getRPCClassLoader(true);
264                    Class<?> clazz = cl.loadClass(className, barr);
265                    return newInstance(clazz, pc.getConfig(),cfc);
266            }
267            catch(Throwable t) {
268                    throw Caster.toPageException(t);
269            }
270            }
271    
272            private static void _createProxy(ClassWriter cw, Set<Class> cDone,Map<String,Class> mDone, Component cfc, Class clazz, String className) throws IOException {
273            if(cDone.contains(clazz)) return;
274                    
275                    cDone.add(clazz);
276                    
277                    // super class
278                    Class superClass = clazz.getSuperclass();
279                    if(superClass!=null)_createProxy(cw, cDone,mDone, cfc, superClass,className);
280                    
281                    // interfaces
282                    Class[] interfaces = clazz.getInterfaces();
283                    if(interfaces!=null)for(int i=0;i<interfaces.length;i++){
284                            _createProxy(cw,cDone,mDone, cfc, interfaces[i],className);
285                    }
286                    
287                    Method[] methods = clazz.getMethods();
288                    if(methods!=null)for(int i=0;i<methods.length;i++){
289                            _createMethod(cw,mDone,methods[i],className);
290                    }  
291            }
292    
293            private static void _createMethod(ClassWriter cw, Map<String,Class> mDone, Method src, String className) throws IOException {
294                    Class<?>[] classArgs = src.getParameterTypes();
295                    Class<?> classRtn = src.getReturnType();
296                    
297                    String str=src.getName()+"("+Reflector.getDspMethods(classArgs)+")";
298                    Class rtnClass = mDone.get(str);
299                    if(rtnClass!=null) {
300                            if(rtnClass!=classRtn) throw new IOException("there is a conflict with method ["+str+"], this method is declared more than once with different return types.");
301                            return;
302                    }
303                    mDone.put(str,classRtn);
304                    
305                    Type[] typeArgs = ASMUtil.toTypes(classArgs); 
306                    Type typeRtn = Type.getType(classRtn); 
307                    
308                    org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method(
309                            src.getName(),
310                            typeRtn,
311                            typeArgs
312                            );
313             GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL , method, null, null, cw);
314             //BytecodeContext bc = new BytecodeContext(statConstr,constr,null,null,keys,cw,className,adapter,method,writeLog);
315             Label start=adapter.newLabel();
316             adapter.visitLabel(start);
317             
318             
319             //JavaProxy.call(cfc,"add",new Object[]{arg0})
320             // config
321             adapter.visitVarInsn(Opcodes.ALOAD, 0);
322             adapter.visitFieldInsn(Opcodes.GETFIELD, className, "config", CONFIG_WEB_NAME);
323             
324             // cfc
325             adapter.visitVarInsn(Opcodes.ALOAD, 0);
326             adapter.visitFieldInsn(Opcodes.GETFIELD, className, "cfc", COMPONENT_NAME);
327             
328             // name
329             adapter.push(src.getName());
330             
331             // arguments
332             ArrayVisitor av=new ArrayVisitor();
333             av.visitBegin(adapter,Types.OBJECT,typeArgs.length);
334             for(int y=0;y<typeArgs.length;y++){
335                            av.visitBeginItem(adapter, y);
336                                    adapter.loadArg(y);
337                                    if(classArgs[y]==boolean.class) adapter.invokeStatic(JAVA_PROXY, _BOOLEAN);
338                                    else if(classArgs[y]==byte.class) adapter.invokeStatic(JAVA_PROXY, _BYTE);
339                                    else if(classArgs[y]==char.class) adapter.invokeStatic(JAVA_PROXY, _CHAR);
340                                    else if(classArgs[y]==double.class) adapter.invokeStatic(JAVA_PROXY, _DOUBLE);
341                                    else if(classArgs[y]==float.class) adapter.invokeStatic(JAVA_PROXY, _FLOAT);
342                                    else if(classArgs[y]==int.class) adapter.invokeStatic(JAVA_PROXY, _INT);
343                                    else if(classArgs[y]==long.class) adapter.invokeStatic(JAVA_PROXY, _LONG);
344                                    else if(classArgs[y]==short.class) adapter.invokeStatic(JAVA_PROXY, _SHORT);
345                                    else {
346                                            adapter.invokeStatic(JAVA_PROXY, _OBJECT);
347                                    }
348                                    
349                                    
350                            av.visitEndItem(adapter);
351             }
352             av.visitEnd();
353             adapter.invokeStatic(JAVA_PROXY, CALL);
354             
355           //JavaProxy.to...(...);
356             int rtn=Opcodes.IRETURN;
357             if(classRtn==boolean.class)    adapter.invokeStatic(JAVA_PROXY, TO_BOOLEAN);
358             else if(classRtn==byte.class)  adapter.invokeStatic(JAVA_PROXY, TO_BYTE);
359             else if(classRtn==char.class)  adapter.invokeStatic(JAVA_PROXY, TO_CHAR);
360             else if(classRtn==double.class){
361                                                                            rtn=Opcodes.DRETURN;
362                                                                            adapter.invokeStatic(JAVA_PROXY, TO_DOUBLE);
363             }
364             else if(classRtn==float.class) {
365                                                                                    rtn=Opcodes.FRETURN;
366                                                                                    adapter.invokeStatic(JAVA_PROXY, TO_FLOAT);
367             }
368             else if(classRtn==int.class)   adapter.invokeStatic(JAVA_PROXY, TO_INT);
369             else if(classRtn==long.class)  {
370                                                                            rtn=Opcodes.LRETURN;
371                                                                            adapter.invokeStatic(JAVA_PROXY, TO_LONG);
372             }
373             else if(classRtn==short.class) adapter.invokeStatic(JAVA_PROXY, TO_SHORT);
374             else if(classRtn==void.class){
375                                                                            rtn=Opcodes.RETURN;
376                                                                            adapter.pop();
377             }
378             else {
379                                                                                    rtn=Opcodes.ARETURN;
380                                                                                    adapter.checkCast(typeRtn);
381             }
382             
383             
384             
385             /*mv = cw.visitMethod(ACC_PUBLIC, "add", "(Ljava/lang/Object;)Z", null, null);
386             mv.visitCode();
387             Label l0 = new Label();
388             mv.visitLabel(l0);
389             mv.visitLineNumber(20, l0);
390             mv.visitVarInsn(ALOAD, 0);
391             mv.visitFieldInsn(GETFIELD, "Test", "cfc", "Ljava/lang/Object;");
392             mv.visitLdcInsn("add");
393             mv.visitInsn(ICONST_1);
394             mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
395             mv.visitInsn(DUP);
396             mv.visitInsn(ICONST_0);
397             mv.visitVarInsn(ALOAD, 1);
398             mv.visitInsn(AASTORE);
399             mv.visitMethodInsn(INVOKESTATIC, "JavaProxy", "call", "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
400             mv.visitMethodInsn(INVOKESTATIC, "JavaProxy", "toBoolean", "(Ljava/lang/Object;)Z");
401             mv.visitInsn(IRETURN);
402             Label l1 = new Label();
403             mv.visitLabel(l1);
404             mv.visitLocalVariable("this", "LTest;", null, l0, l1, 0);
405             mv.visitLocalVariable("arg0", "Ljava/lang/Object;", null, l0, l1, 1);
406             mv.visitMaxs(6, 2);
407             mv.visitEnd();*/
408             
409             
410             
411             adapter.visitInsn(rtn);
412             adapter.endMethod();
413                    
414                    
415            }
416    
417            private static Object newInstance(PhysicalClassLoader cl, String className, ConfigWeb config,Component cfc) 
418                    throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException {
419                            return newInstance(cl.loadClass(className),config,cfc);
420            }
421            private static Object newInstance(Class<?> _clazz,ConfigWeb config, Component cfc) 
422            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
423                    Constructor<?> constr = _clazz.getConstructor(new Class[]{ConfigWeb.class,Component.class});
424                    return constr.newInstance(new Object[]{config,cfc});
425            }
426            
427            private static String createClassName(Class extendz, Class[] interfaces) throws IOException {
428                    if(extendz==null) extendz=Object.class;
429                    
430                    
431                    
432                    StringBuilder sb=new StringBuilder(extendz.getName());
433                    if(interfaces!=null && interfaces.length>0){
434                            sb.append(';');
435                            
436                            String[] arr=new String[interfaces.length];
437                            for(int i=0;i<interfaces.length;i++){
438                                    arr[i]=interfaces[i].getName();
439                            }
440                            Arrays.sort(arr);
441                            
442                            sb.append(railo.runtime.type.util.ListUtil.arrayToList(arr, ";"));
443                    }
444                    
445                    String key = KeyGenerator.createVariable(sb.toString());
446                    
447                    
448                    return key;
449            }
450    
451    
452    
453    }