/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * **/ package lucee.transformer.bytecode.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import lucee.commons.io.IOUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.KeyGenerator; import lucee.commons.lang.PhysicalClassLoader; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.runtime.Component; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; import lucee.runtime.config.ConfigWeb; import lucee.runtime.exp.PageException; import lucee.runtime.op.Caster; import lucee.runtime.reflection.Reflector; import lucee.runtime.util.JavaProxyUtil; import lucee.transformer.bytecode.visitor.ArrayVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; /** * creates a Java Proxy for components, so you can use componets as java classes following a certain interface or class */ public class JavaProxyFactory { private static final String COMPONENT_NAME="L"+Types.COMPONENT.getInternalName()+";"; private static final String CONFIG_WEB_NAME="L"+Types.CONFIG_WEB.getInternalName()+";"; //private static final Type JAVA_PROXY = Type.getType(JavaProxy.class); private static final Type CFML_ENGINE_FACTORY = Type.getType(CFMLEngineFactory.class); private static final Type CFML_ENGINE = Type.getType(CFMLEngine.class); private static final Type JAVA_PROXY_UTIL = Type.getType(JavaProxyUtil.class); private static final org.objectweb.asm.commons.Method CALL = new org.objectweb.asm.commons.Method( "call", Types.OBJECT, new Type[]{Types.CONFIG_WEB,Types.COMPONENT,Types.STRING,Types.OBJECT_ARRAY}); private static final org.objectweb.asm.commons.Method CONSTRUCTOR = new org.objectweb.asm.commons.Method( "<init>", Types.VOID, new Type[]{ Types.CONFIG_WEB, Types.COMPONENT } ); private static final org.objectweb.asm.commons.Method SUPER_CONSTRUCTOR = new org.objectweb.asm.commons.Method( "<init>", Types.VOID, new Type[]{} ); private static final org.objectweb.asm.commons.Method TO_BOOLEAN = new org.objectweb.asm.commons.Method( "toBoolean", Types.BOOLEAN_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_FLOAT = new org.objectweb.asm.commons.Method( "toFloat", Types.FLOAT_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_INT = new org.objectweb.asm.commons.Method( "toInt", Types.INT_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_DOUBLE = new org.objectweb.asm.commons.Method( "toDouble", Types.DOUBLE_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_LONG = new org.objectweb.asm.commons.Method( "toLong", Types.LONG_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_CHAR = new org.objectweb.asm.commons.Method( "toChar", Types.CHAR, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_BYTE = new org.objectweb.asm.commons.Method( "toByte", Types.BYTE_VALUE, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_SHORT = new org.objectweb.asm.commons.Method( "toShort", Types.SHORT, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_STRING = new org.objectweb.asm.commons.Method( "toString", Types.STRING, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method TO_ = new org.objectweb.asm.commons.Method( "to", Types.OBJECT, new Type[]{Types.OBJECT,Types.CLASS}); private static final org.objectweb.asm.commons.Method _BOOLEAN = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.BOOLEAN_VALUE}); private static final org.objectweb.asm.commons.Method _FLOAT = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.FLOAT_VALUE}); private static final org.objectweb.asm.commons.Method _INT = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.INT_VALUE}); private static final org.objectweb.asm.commons.Method _DOUBLE = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.DOUBLE_VALUE}); private static final org.objectweb.asm.commons.Method _LONG = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.LONG_VALUE}); private static final org.objectweb.asm.commons.Method _CHAR = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.CHAR}); private static final org.objectweb.asm.commons.Method _BYTE = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.BYTE_VALUE}); private static final org.objectweb.asm.commons.Method _SHORT = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.SHORT}); private static final org.objectweb.asm.commons.Method _OBJECT = new org.objectweb.asm.commons.Method( "toCFML", Types.OBJECT, new Type[]{Types.OBJECT}); private static final org.objectweb.asm.commons.Method GET_INSTANCE = new org.objectweb.asm.commons.Method( "getInstance", CFML_ENGINE, new Type[]{}); private static final org.objectweb.asm.commons.Method GET_JAVA_PROXY_UTIL = new org.objectweb.asm.commons.Method( "getJavaProxyUtil", Types.OBJECT, // FUTURE change to JavaProxy new Type[]{}); /* public static Object to(Object obj, Class clazz) { return obj; }*/ /*public static Object createProxy(Config config,Component cfc, String className) throws PageException, IOException { return createProxy(cfc, null, ClassUtil.loadClass(config.getClassLoader(), className)); }*/ public static Object createProxy(PageContext pc, Component cfc, Class extendz,Class... interfaces) throws PageException, IOException { PageContextImpl pci=(PageContextImpl) pc; ClassLoader[] parents = extractClassLoaders(interfaces); if(extendz==null) extendz=Object.class; if(interfaces==null) interfaces=new Class[0]; else { for(int i=0;i<interfaces.length;i++){ if(!interfaces[i].isInterface()) throw new IOException("definition ["+interfaces[i].getName()+"] is a class and not a interface"); } } Type typeExtends = Type.getType(extendz); Type[] typeInterfaces = ASMUtil.toTypes(interfaces); String[] strInterfaces=new String[typeInterfaces.length]; for(int i=0;i<strInterfaces.length;i++){ strInterfaces[i]=typeInterfaces[i].getInternalName(); } String className=createClassName(extendz,interfaces); //Mapping mapping = cfc.getPageSource().getMapping(); // get ClassLoader PhysicalClassLoader pcl=null; try { pcl = (PhysicalClassLoader) pci.getRPCClassLoader(false,parents);// mapping.getConfig().getRPCClassLoader(false) } catch (IOException e) { throw Caster.toPageException(e); } Resource classFile = pcl.getDirectory().getRealResource(className.concat(".class")); // check if already exists, if yes return if(classFile.exists()) { try { Object obj=newInstance(pcl,className,pc.getConfig(),cfc); if(obj!=null) return obj; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } /* String classNameOriginal=component.getPageSource().getFullClassName(); String realOriginal=classNameOriginal.replace('.','/'); Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class")); */ ClassWriter cw = ASMUtil.getClassWriter(); cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, className, null, typeExtends.getInternalName(), strInterfaces); //BytecodeContext statConstr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.STATIC_CONSTRUCTOR); //BytecodeContext constr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.CONSTRUCTOR); // field Component FieldVisitor _fv = cw.visitField(Opcodes.ACC_PRIVATE, "cfc", COMPONENT_NAME, null, null); _fv.visitEnd(); _fv = cw.visitField(Opcodes.ACC_PRIVATE, "config", CONFIG_WEB_NAME, null, null); _fv.visitEnd(); // Constructor GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR,null,null,cw); Label begin = new Label(); adapter.visitLabel(begin); adapter.loadThis(); adapter.invokeConstructor(Types.OBJECT, SUPER_CONSTRUCTOR); //adapter.putField(JAVA_PROXY, arg1, arg2) adapter.visitVarInsn(Opcodes.ALOAD, 0); adapter.visitVarInsn(Opcodes.ALOAD, 1); adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "config", CONFIG_WEB_NAME); adapter.visitVarInsn(Opcodes.ALOAD, 0); adapter.visitVarInsn(Opcodes.ALOAD, 2); adapter.visitFieldInsn(Opcodes.PUTFIELD, className, "cfc", COMPONENT_NAME); adapter.visitInsn(Opcodes.RETURN); Label end = new Label(); adapter.visitLabel(end); adapter.visitLocalVariable("config",CONFIG_WEB_NAME, null, begin, end, 1); adapter.visitLocalVariable("cfc",COMPONENT_NAME, null, begin, end, 2); //adapter.returnValue(); adapter.endMethod(); // create methods Set<Class> cDone=new HashSet<Class>(); Map<String,Class> mDone=new HashMap<String,Class>(); for(int i=0;i<interfaces.length;i++){ _createProxy(cw,cDone,mDone, cfc, interfaces[i],className); } cw.visitEnd(); // create class file byte[] barr = cw.toByteArray(); try { ResourceUtil.touch(classFile); IOUtil.copy(new ByteArrayInputStream(barr), classFile,true); pcl = (PhysicalClassLoader) pci.getRPCClassLoader(true,parents); Class<?> clazz = pcl.loadClass(className, barr); return newInstance(clazz, pc.getConfig(),cfc); } catch(Throwable t) { ExceptionUtil.rethrowIfNecessary(t); throw Caster.toPageException(t); } } private static ClassLoader[] extractClassLoaders(Class[] classes) { List<ClassLoader> list=new ArrayList<ClassLoader>(); ClassLoader cl,tmp; Iterator<ClassLoader> it; if(classes!=null)outer:for(int i=0; i<classes.length; i++) { cl=classes[i].getClassLoader(); it = list.iterator(); while(it.hasNext()) { tmp=it.next(); if(tmp==cl) continue outer; } list.add(cl); } return list.toArray(new ClassLoader[list.size()]); } private static void _createProxy(ClassWriter cw, Set<Class> cDone,Map<String,Class> mDone, Component cfc, Class clazz, String className) throws IOException { if(cDone.contains(clazz)) return; cDone.add(clazz); // super class Class superClass = clazz.getSuperclass(); if(superClass!=null)_createProxy(cw, cDone,mDone, cfc, superClass,className); // interfaces Class[] interfaces = clazz.getInterfaces(); if(interfaces!=null)for(int i=0;i<interfaces.length;i++){ _createProxy(cw,cDone,mDone, cfc, interfaces[i],className); } Method[] methods = clazz.getMethods(); if(methods!=null)for(int i=0;i<methods.length;i++){ _createMethod(cw,mDone,methods[i],className); } } private static void _createMethod(ClassWriter cw, Map<String,Class> mDone, Method src, String className) throws IOException { final Class<?>[] classArgs = src.getParameterTypes(); final Class<?> classRtn = src.getReturnType(); String str=src.getName()+"("+Reflector.getDspMethods(classArgs)+")"; Class rtnClass = mDone.get(str); if(rtnClass!=null) { if(rtnClass!=classRtn) throw new IOException("there is a conflict with method ["+str+"], this method is declared more than once with different return types."); return; } mDone.put(str,classRtn); Type[] typeArgs = ASMUtil.toTypes(classArgs); Type typeRtn = Type.getType(classRtn); org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method( src.getName(), typeRtn, typeArgs ); GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL , method, null, null, cw); //BytecodeContext bc = new BytecodeContext(statConstr,constr,null,null,keys,cw,className,adapter,method,writeLog); Label start=adapter.newLabel(); adapter.visitLabel(start); // if the result of "call" need castring, we have to do this here if(needCastring(classRtn)) { adapter.invokeStatic(CFML_ENGINE_FACTORY, GET_INSTANCE); adapter.invokeInterface(CFML_ENGINE, GET_JAVA_PROXY_UTIL); adapter.checkCast(JAVA_PROXY_UTIL); } adapter.invokeStatic(CFML_ENGINE_FACTORY, GET_INSTANCE); adapter.invokeInterface(CFML_ENGINE, GET_JAVA_PROXY_UTIL); adapter.checkCast(JAVA_PROXY_UTIL); //Java Proxy.call(cfc,"add",new Object[]{arg0}) // config (first argument) adapter.visitVarInsn(Opcodes.ALOAD, 0); adapter.visitFieldInsn(Opcodes.GETFIELD, className, "config", CONFIG_WEB_NAME); // cfc (second argument) adapter.visitVarInsn(Opcodes.ALOAD, 0); adapter.visitFieldInsn(Opcodes.GETFIELD, className, "cfc", COMPONENT_NAME); // name (3th argument) adapter.push(src.getName()); // arguments (4th argument) ArrayVisitor av=new ArrayVisitor(); av.visitBegin(adapter,Types.OBJECT,typeArgs.length); for(int y=0;y<typeArgs.length;y++){ av.visitBeginItem(adapter, y); adapter.invokeStatic(CFML_ENGINE_FACTORY, GET_INSTANCE); adapter.invokeInterface(CFML_ENGINE, GET_JAVA_PROXY_UTIL); adapter.checkCast(JAVA_PROXY_UTIL); adapter.loadArg(y); if(classArgs[y]==boolean.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _BOOLEAN); else if(classArgs[y]==byte.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _BYTE); else if(classArgs[y]==char.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _CHAR); else if(classArgs[y]==double.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _DOUBLE); else if(classArgs[y]==float.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _FLOAT); else if(classArgs[y]==int.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _INT); else if(classArgs[y]==long.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _LONG); else if(classArgs[y]==short.class) adapter.invokeInterface(JAVA_PROXY_UTIL, _SHORT); else adapter.invokeInterface(JAVA_PROXY_UTIL, _OBJECT); av.visitEndItem(adapter); } av.visitEnd(); adapter.invokeInterface(JAVA_PROXY_UTIL, CALL); //CFMLEngineFactory.getInstance().getCastUtil().toBooleanValue(o); //Java Proxy.to...(...); int rtn=Opcodes.IRETURN; if(classRtn==boolean.class) adapter.invokeInterface(JAVA_PROXY_UTIL, TO_BOOLEAN); else if(classRtn==byte.class) adapter.invokeInterface(JAVA_PROXY_UTIL, TO_BYTE); else if(classRtn==char.class) adapter.invokeInterface(JAVA_PROXY_UTIL, TO_CHAR); else if(classRtn==double.class){ rtn=Opcodes.DRETURN; adapter.invokeInterface(JAVA_PROXY_UTIL, TO_DOUBLE); } else if(classRtn==float.class) { rtn=Opcodes.FRETURN; adapter.invokeInterface(JAVA_PROXY_UTIL, TO_FLOAT); } else if(classRtn==int.class) adapter.invokeInterface(JAVA_PROXY_UTIL, TO_INT); else if(classRtn==long.class) { rtn=Opcodes.LRETURN; adapter.invokeInterface(JAVA_PROXY_UTIL, TO_LONG); } else if(classRtn==short.class) adapter.invokeInterface(JAVA_PROXY_UTIL, TO_SHORT); else if(classRtn==void.class){ rtn=Opcodes.RETURN; adapter.pop(); } else if(classRtn==String.class){ rtn=Opcodes.ARETURN; adapter.invokeInterface(JAVA_PROXY_UTIL, TO_STRING); } else { rtn=Opcodes.ARETURN; adapter.checkCast(typeRtn); } adapter.visitInsn(rtn); adapter.endMethod(); } private static boolean needCastring(Class<?> classRtn) { return classRtn==boolean.class || classRtn==byte.class || classRtn==char.class || classRtn==double.class || classRtn==float.class || classRtn==int.class || classRtn==long.class || classRtn==short.class || classRtn==String.class; } private static Object newInstance(PhysicalClassLoader cl, String className, ConfigWeb config,Component cfc) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException { return newInstance(cl.loadClass(className),config,cfc); } private static Object newInstance(Class<?> _clazz,ConfigWeb config, Component cfc) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { Constructor<?> constr = _clazz.getConstructor(new Class[]{ConfigWeb.class,Component.class}); return constr.newInstance(new Object[]{config,cfc}); } private static String createClassName(Class extendz, Class[] interfaces) throws IOException { if(extendz==null) extendz=Object.class; StringBuilder sb=new StringBuilder(extendz.getName()); if(interfaces!=null && interfaces.length>0){ sb.append(';'); String[] arr=new String[interfaces.length]; for(int i=0;i<interfaces.length;i++){ arr[i]=interfaces[i].getName(); } Arrays.sort(arr); sb.append(lucee.runtime.type.util.ListUtil.arrayToList(arr, ";")); } String key = KeyGenerator.createVariable(sb.toString()); return key; } }