/* * Copyright (C) 2000 - 2012 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://openbd.org/ * $Id: DynamicWebServiceTypeGenerator.java 2147 2012-07-02 01:57:34Z alan $ */ /* * Created on Jan 22, 2005 * * To change the template for this generated file go to * Window>Preferences>Java>Code Generation>Code and Comments */ package com.naryx.tagfusion.cfm.xml.ws.dynws; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.beans.SimpleBeanInfo; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.Writer; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import org.apache.axis.AxisProperties; import org.apache.axis.MessageContext; import org.apache.axis.components.compiler.Compiler; import org.apache.axis.components.compiler.CompilerError; import org.apache.axis.components.compiler.CompilerFactory; import com.nary.util.FastMap; import com.nary.util.UUID; import com.nary.util.string; import com.naryx.tagfusion.cfm.engine.cfEngine; import com.naryx.tagfusion.cfm.engine.cfWebServices; import com.naryx.tagfusion.cfm.xml.ws.encoding.ser.IComplexObject; public class DynamicWebServiceTypeGenerator { public static final String NAMESPACE = "na_svr"; private static final String EOL = System.getProperty("line.separator"); private String javaCacheDir; private String dynDirName; private Map<String, CFCSourceInfo> srcMap; private Map<String, String> srcBeanInfoMap; private List<DynamicCacheClassLoader> clList; private Map<String, String> typeList; private CFCDescriptor desc; public DynamicWebServiceTypeGenerator(String javaCache) { this.javaCacheDir = javaCache; } public String generateType(CFCDescriptor d, MessageContext msgContext) throws IOException { Map<String, CFCSourceInfo> sMap = new FastMap<String, CFCSourceInfo>(); Map<String, String> sBeanInfoMap = new FastMap<String, String>(); List<DynamicCacheClassLoader> clList = new ArrayList<DynamicCacheClassLoader>(); String name = UUID.generateKey(); // Populate the necessary lists String dynClsName = prepareType(d, name, new FastMap<String, String>(false), sMap, sBeanInfoMap, clList); // Build the classes (if necessary) if (buildClasses(name, sMap, sBeanInfoMap, clList)) { // Link the DynamicCacheClassLoaders together (as dependents) linkClassLoaders(getClassLoader(dynClsName)); } // Return the class name return dynClsName; } public String prepareType(CFCDescriptor d, String dynName, Map<String, String> typeNames, Map<String, CFCSourceInfo> srcMap, Map<String, String> srcBeanInfoMap, List<DynamicCacheClassLoader> clList) { this.dynDirName = dynName; this.srcMap = srcMap; this.srcBeanInfoMap = srcBeanInfoMap; this.clList = clList; this.typeList = typeNames; String fqName = getFQName(d.getName()); this.typeList.put(fqName, fqName); this.desc = d; return buildType(); } public String getKnownType(String fullTypeName) { return this.typeList.get(fullTypeName); } public void addType(CFCDescriptor d) { DynamicWebServiceTypeGenerator g = new DynamicWebServiceTypeGenerator(this.javaCacheDir); g.prepareType(d, this.dynDirName, this.typeList, this.srcMap, this.srcBeanInfoMap, this.clList); } public static String getFQName(String cfcName) { String rtn = NAMESPACE + "." + cfcName.trim(); String name = rtn.substring(rtn.lastIndexOf(".") + 1); rtn = rtn.substring(0, rtn.lastIndexOf(".")); name = (Character.isDigit(name.charAt(0)) ? "n" + name : name); rtn = prefixNSDigits(rtn); return rtn + "." + name; } private String buildType() { // Create the full class name String name = getFQName(this.desc.getName()); name = name.replaceAll("-", ""); // Look for the generated type first DynamicCacheClassLoader cl = null; Class<?> cls = DynamicCacheClassLoader.findLoadedClass(name, DynamicCacheClassLoader.SKEL_CLASSES); if (cls != null) cl = (DynamicCacheClassLoader) cls.getClassLoader(); // If it's been generated already, get the class path to include. // Otherwise, generate the java source code directly. if (cl == null) genSource(); else this.clList.add(cl); // Return the class name return name; } private void genSource() { // Determine the package name for the generated source String prefix = this.desc.getName().trim(); String pkgName = NAMESPACE; if (prefix.lastIndexOf(".") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf(".")); prefix = prefixNSDigits(prefix); pkgName += "." + prefix; pkgName = pkgName.replaceAll("-", ""); } // Determine the class name for the generated source String className = this.desc.getName().trim().substring(this.desc.getName().trim().lastIndexOf(".") + 1); if (Character.isDigit(className.charAt(0))) className = "n" + className; // Start the java source StringBuilder buffy = startClass(pkgName, className); // Add WS methods for each CFC function addOperations(buffy); // Add WS properties for each CFC property addProperties(buffy); // Add the IComplexObject interface. addIComplexObjectInterface(buffy); // End the java source endClass(buffy); // Add the java source this.srcMap.put(pkgName + "." + className, new CFCSourceInfo(buffy.toString(), this.desc.getFile(), this.desc.getName(), pkgName + "." + className)); // Start the BeanInfo source buffy = startBeanInfoClass(pkgName, className + "BeanInfo"); // Add WS properties for each CFC property addBeanInfoProperties(pkgName + "." + className, buffy); // End the java source endClass(buffy); // Add the BeanInfo source this.srcBeanInfoMap.put(pkgName + "." + className + "BeanInfo", buffy.toString()); } private StringBuilder startClass(String pkgName, String clsName) { StringBuilder buffy = new StringBuilder(); buffy.append("/* This java file was dynamically generated by OpenBlueDragon */" + EOL); buffy.append("package " + pkgName + ";" + EOL); buffy.append(EOL); buffy.append("public class " + clsName + " implements "); buffy.append(Serializable.class.getName() + ", " + IComplexObject.class.getName() ); buffy.append(" {" + EOL); return buffy; } private StringBuilder startBeanInfoClass(String pkgName, String clsName) { StringBuilder buffy = new StringBuilder(); buffy.append("/* This java file was dynamically generated by OpenBlueDragon */" + EOL); buffy.append("package " + pkgName + ";" + EOL); buffy.append(EOL); buffy.append("public class " + clsName + " extends "); buffy.append(SimpleBeanInfo.class.getName()); buffy.append(" {" + EOL); return buffy; } private void endClass(StringBuilder buffy) { buffy.append("/* End of dynamically generated class. */" + EOL); buffy.append("}" + EOL); } private void addOperations(StringBuilder buffy) { for (int i = 0; i < this.desc.getFunctionCount(); i++) { String rtnType = desc.getFunctionReturnType(i, this); String rtnTypeKlass = null; if (rtnType == null) { rtnType = "void"; rtnTypeKlass = "null"; } else { rtnTypeKlass = rtnType + ".class"; } buffy.append(" public " + rtnType + " " + desc.getFunctionName(i) + "("); for (int x = 0; x < this.desc.getFunctionParameterCount(i); x++) { if (x > 0) buffy.append(", "); buffy.append(this.desc.getFunctionParameterType(i, x, this) + " " + this.desc.getFunctionParameterName(i, x)); } buffy.append(") throws java.lang.Exception {" + EOL); // Have all operations look for a CFCInvoker in the Request context. // Then use it to do the actual invocation. buffy.append(" " + CFCInvoker.class.getName() + " cfcInvoker = " + CFCInvoker.class.getName() + ".getCFCInvoker(Thread.currentThread());" + EOL); buffy.append(" if (cfcInvoker == null)" + EOL); buffy.append(" throw new Exception(\"Cannot find a CFCInvoker for current thread: \" + Thread.currentThread() + \". Perhaps " + desc.getFunctionName(i) + " is being invoked a second time.\");" + EOL); buffy.append(" Class rtnType = " + rtnTypeKlass + ";" + EOL); buffy.append(" String[] argNames = new String[" + this.desc.getFunctionParameterCount(i) + "];" + EOL); buffy.append(" Object[] argValues = new Object[" + this.desc.getFunctionParameterCount(i) + "];" + EOL); for (int x = 0; x < this.desc.getFunctionParameterCount(i); x++) { buffy.append(" argNames[" + x + "] = \"" + this.desc.getFunctionParameterName(i, x) + "\";" + EOL); buffy.append(" argValues[" + x + "] = " + this.desc.getFunctionParameterName(i, x) + ";" + EOL); } if (rtnType.equals("void")) buffy.append(" cfcInvoker.invoke(\"" + this.desc.getFunctionName(i) + "\", argNames, argValues, rtnType, this.getClass().getClassLoader());" + EOL); else buffy.append(" return (" + rtnType + ")cfcInvoker.invoke(\"" + this.desc.getFunctionName(i) + "\", argNames, argValues, rtnType, this.getClass().getClassLoader());" + EOL); buffy.append(" }" + EOL); buffy.append(EOL); } } private void addProperties(StringBuilder buffy) { for (int i = 0; i < this.desc.getPropertyCount(); i++) { String typ = this.desc.getPropertyType(i, this); String name = this.desc.getPropertyName(i); buffy.append(" private " + typ + " " + name + ";" + EOL); buffy.append(" public " + typ + " get" + name + "(){" + EOL); buffy.append(" return " + name + ";" + EOL); buffy.append(" }" + EOL); buffy.append(" public void set" + name + "(" + typ + " pVal){" + EOL); buffy.append(" " + name + "=pVal;" + EOL); buffy.append(" }" + EOL); } buffy.append(EOL); } private void addBeanInfoProperties(String fullClassName, StringBuilder buffy) { buffy.append(" public " + PropertyDescriptor.class.getName() + "[] getPropertyDescriptors(){" + EOL); if (this.desc.getPropertyCount() > 0) { buffy.append(" try{" + EOL); buffy.append(" " + PropertyDescriptor.class.getName() + "[] rtn;" + EOL); buffy.append(" rtn = new " + PropertyDescriptor.class.getName() + "[" + this.desc.getPropertyCount() + "];" + EOL); for (int i = 0; i < this.desc.getPropertyCount(); i++) { String name = this.desc.getPropertyName(i); String readMethodName = "get" + name; String writeMethodName = "set" + name; buffy.append(" rtn[" + i + "] = new " + PropertyDescriptor.class.getName() + "(\"" + name + "\", " + fullClassName + ".class, \"" + readMethodName + "\", \"" + writeMethodName + "\");" + EOL); } buffy.append(" return rtn;" + EOL); buffy.append(" }" + EOL); buffy.append(" catch (" + IntrospectionException.class.getName() + " ex) {" + EOL); buffy.append(" throw new " + RuntimeException.class.getName() + "(\"Cannot create PropertyDescriptor for " + fullClassName + ". \" + ex.getMessage(), ex);" + EOL); buffy.append(" }" + EOL); } else { buffy.append(" return null;" + EOL); } buffy.append(" }" + EOL); } private void addIComplexObjectInterface(StringBuilder buffy) { // Add the bd_setFieldValues method. buffy.append(" public void bd_setFieldValues("); buffy.append(Map.class.getName() + " data, " + List.class.getName() + " missingRequiredFieldNames){" + EOL); // Now one for each defined property for (int i = 0; i < desc.getPropertyCount(); i++) { /* * Add some code that looks like this: * * if (data.containsKey("field1")) this.field1 = * (MyFieldClass)data.get("field1"); * * Notice that this implementation doesn't add any names to the * missingRequiredFieldNames List. That's because this is the server side * IComplexObject. In practice this method should never actually be called * (i.e. the conversion routines should never have a need to populate an * instance of this Type). */ buffy.append(" if (data.containsKey(\"" + desc.getPropertyName(i) + "\"))" + EOL); buffy.append(" this." + desc.getPropertyName(i) + " = (" + this.desc.getPropertyType(i, this) + ")data.get(\"" + desc.getPropertyName(i) + "\");" + EOL); } buffy.append(" }" + EOL); buffy.append(EOL); // Add the bd_getFieldValues method. buffy.append(" public void bd_getFieldValues(" + Map.class.getName() + " data){" + EOL); // Now one for each defined property for (int i = 0; i < desc.getPropertyCount(); i++) { /* * Add some code that looks like this. * * data.put("field1", this.field1); */ buffy.append(" data.put(\"" + desc.getPropertyName(i) + "\", this." + desc.getPropertyName(i) + ");" + EOL); } buffy.append(" }" + EOL); buffy.append(EOL); // Add the bd_getFieldTypes method. buffy.append(" public void bd_getFieldTypes(" + Map.class.getName() + " data){" + EOL); // Now one for each defined property for (int i = 0; i < desc.getPropertyCount(); i++) { /* * Add some code that looks like this. * * data.put("field1", MyFieldClass.class); */ buffy.append(" data.put(\"" + desc.getPropertyName(i) + "\", " + desc.getPropertyType(i, this) + ".class);" + EOL); } buffy.append(" }" + EOL); buffy.append(EOL); // Add the bd_getCfcName method. buffy.append(" public String bd_getCfcName(){" + EOL); /* * Add some code that looks like this. * * return "MyCfcPackage.MyCfcName"; */ buffy.append(" return \"" + desc.getName() + "\";" + EOL); buffy.append(" }" + EOL); buffy.append(EOL); } private boolean buildClasses(String name, Map<String, CFCSourceInfo> sMap, Map<String, String> sBeanInfoMap, List<DynamicCacheClassLoader> clList) throws IOException { if (sMap.size() > 0) { // Setup the classpath StringBuilder buffy = getDefaultClasspath(); Iterator<DynamicCacheClassLoader> itr = clList.iterator(); while (itr.hasNext()) buffy.append(File.pathSeparator + itr.next().getCacheDir()); // Write the files File rootDir = new File(genClassPath(name)); File[] srcFiles = new File[sMap.size() + sBeanInfoMap.size()]; CFCSourceInfo[] cfcInfo = new CFCSourceInfo[sMap.size()]; Iterator<String> keys = sMap.keySet().iterator(); for (int i = 0; keys.hasNext(); i++) { String tmp = keys.next(); srcFiles[i] = writeFile(rootDir, tmp, sMap.get(tmp).source); // Keep the underlying cfc file reference, name, and IComplexObject // impl class name handy cfcInfo[i] = sMap.get(tmp); } keys = sBeanInfoMap.keySet().iterator(); for (int i = sMap.size(); keys.hasNext(); i++) { String tmp = keys.next(); srcFiles[i] = writeFile(rootDir, tmp, sBeanInfoMap.get(tmp)); } // Compile the src Compiler compiler = CompilerFactory.getCompiler(); compiler.setClasspath(buffy.toString()); compiler.setDestination(rootDir.getAbsolutePath()); for (int i = 0; i < srcFiles.length; i++) { compiler.addFile(srcFiles[i].getAbsolutePath()); } boolean result = compiler.compile(); // Problem encountered if (!result) { for (int i = 0; i < srcFiles.length; i++) new File(srcFiles[i].getAbsolutePath().substring(0, srcFiles[i].getAbsolutePath().length() - 5) + ".class").delete(); // Build compile errors StringBuilder message = new StringBuilder("Error compiling: " + EOL); for (int i = 0; i < srcFiles.length; i++) message.append(srcFiles[i].getAbsolutePath() + EOL); message.append(":" + EOL); List<CompilerError> errors = compiler.getErrors(); int count = errors.size(); for (int i = 0; i < count; i++) { CompilerError error = errors.get(i); if (i > 0) message.append(EOL); message.append("Line "); message.append(error.getStartLine()); message.append(", column "); message.append(error.getStartColumn()); message.append(": "); message.append(error.getMessage()); } message.append(EOL + "Classpath: " + EOL); message.append(buffy.toString() + EOL); throw new IOException("Server compileError: " + EOL + message.toString()); } // Delete the temporary *.java file and check return code // for (int i=0; i<srcFiles.length; i++) // srcFiles[i].delete(); // Add an addition to the class path for this new cache DynamicCacheClassLoader dcl = DynamicCacheClassLoaderFactory.newClassLoader(rootDir.getCanonicalPath(), DynamicCacheClassLoader.SKEL_CLASSES); clList.add(dcl); // Associate the generated classes with their cfc file // equivalents for this DynamicCacheClassLoader for (int i = 0; i < cfcInfo.length; i++) { dcl.associateCFC(cfcInfo[i].file); dcl.setIComplexObject(cfcInfo[i].name, cfcInfo[i].impl); } // Return true, we did create a new DynamicCacheClassLoader return true; } // Return false, we had no source to compile return false; } /** * Writes the specified source out to a physical file in the rootDir. Returns * the File that was written. * * @param rootDir * root directory in which to write the files * @param name * name of the file to write * @param source * source of the file to write * @return File created on disk * @throws IOException */ private File writeFile(File rootDir, String name, String source) throws IOException { File f = new File(rootDir, name.replace('.', File.separatorChar) + ".java"); if (!f.getParentFile().exists()) f.getParentFile().mkdirs(); f.createNewFile(); if (!f.exists() && f.getAbsolutePath().length() < 256) throw new IOException("Could not create file: " + f.getAbsolutePath()); // Write out the file Writer fw = null; try { fw = cfEngine.thisPlatform.getFileIO().getFileWriter(f); fw.write(source); fw.flush(); } finally { if (fw != null) fw.close(); } return f; } /** * Link together the dependent DynamicCacheClassLoaders as associated * instances to the primary DynamicCacheClassLoader (the one responsible for * the type/class being generated). All DynamicCacheClassLoaders in the list * at this point are needed to realize the requested type/class. * * @param primaryCl */ private void linkClassLoaders(DynamicCacheClassLoader primaryCl) { for (int i = 0; i < this.clList.size(); i++) primaryCl.associateDynamicCacheClassLoader(this.clList.get(i)); } public DynamicCacheClassLoader getClassLoader(String clsName) throws IOException { Class<?> dynClass = DynamicCacheClassLoader.findLoadedClass(clsName, DynamicCacheClassLoader.SKEL_CLASSES); if (dynClass == null) throw new IOException("Dynamic class: " + clsName + " not found. Perhaps there was an error compiling it."); return (DynamicCacheClassLoader) dynClass.getClassLoader(); } private String genClassPath(String file) { File dir = new File(this.javaCacheDir); return new File(dir, file).getAbsolutePath(); } private StringBuilder getDefaultClasspath() throws IOException { StringBuilder classpath = new StringBuilder(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); while (cl != null) { if (cl instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) cl).getURLs(); for (int i = 0; (urls != null) && i < urls.length; i++) addPathToBuffer(urls[i], classpath); } cl = cl.getParent(); } // Add the BD JARs to the java compiler classpath String ps = System.getProperty("path.separator"); String fs = System.getProperty("file.separator"); String dir = null; String libDir = null; String bootClassPath = AxisProperties.getProperty("sun.boot.class.path"); // The JARs are in the WEB-INF lib folder. dir = cfWebServices.getDocRootDir(); if (dir != null && !dir.endsWith(fs)) dir += fs; dir = dir + "WEB-INF" + fs; libDir = dir + "lib" + fs; String altLibDir = cfEngine.getAltLibPath(); if (!bootClassPath.contains("webservices.jar") && classpath.indexOf("webservices.jar") < 0) { classpath.append( ps ); classpath.append( getJarPath(libDir, altLibDir, "webservices.jar") ); } if (!bootClassPath.contains("wsdl4j.jar") && classpath.indexOf("wsdl4j.jar") < 0) { classpath.append( ps ); classpath.append( getJarPath(libDir, altLibDir, "wsdl4j.jar") ); } if (!bootClassPath.contains("saaj.jar") && classpath.indexOf("saaj.jar") < 0) { classpath.append( ps ); classpath.append( getJarPath(libDir, altLibDir, "saaj.jar") ); } if (!bootClassPath.contains("jaxrpc.jar") && classpath.indexOf("jaxrpc.jar") < 0) { classpath.append( ps ); classpath.append( getJarPath(libDir, altLibDir, "jaxrpc.jar") ); } classpath.append(ps + dir + "classes"); // Add the J2EE specific jars (not required as classes, may be in the classes dir) classpath.append( ps ); classpath.append( getJarPath(libDir, altLibDir, "OpenBlueDragon.jar") ); // boot classpath isn't found in above search if (bootClassPath != null) classpath.append(ps + bootClassPath); return classpath; } public static String getJarPath(String libDir, String altPath, String jarfile ) throws IOException { File f = new File( libDir + jarfile ); if ( f.exists() ) return f.getAbsolutePath(); if ( altPath != null ){ f = new File( altPath + jarfile ); if ( f.exists() ) return f.getAbsolutePath(); } throw new IOException("JAR: " + jarfile + " was not found in either [" + libDir + "] or [" + altPath + "]"); } @SuppressWarnings("deprecation") private void addPathToBuffer(URL url, StringBuilder classpath) { String path = url.getPath(); // The path is URL encoded so we need to URL decode it // before adding it to the classpath. path = java.net.URLDecoder.decode(path); // If it is a drive letter, adjust accordingly. if (path.length() >= 3 && path.charAt(0) == '/' && path.charAt(2) == ':') path = path.substring(1); classpath.append(path); classpath.append(File.pathSeparatorChar); // if its a jar extract Class-Path entries from manifest File file = new File(url.getFile()); if (file.isFile()) { FileInputStream fis = null; try { fis = new FileInputStream(file); if (isJar(fis)) { JarFile jar = new JarFile(file); Manifest manifest = jar.getManifest(); if (manifest != null) { Attributes attributes = manifest.getMainAttributes(); if (attributes != null) { String s = attributes.getValue(java.util.jar.Attributes.Name.CLASS_PATH); String base = file.getParent(); if (s != null) { List<String> tokens = string.split(s, " "); for (int i = 0; i < tokens.size(); i++) { String t = tokens.get(i).toString(); classpath.append(base + File.separatorChar + t); classpath.append(File.pathSeparatorChar); } } } } } } catch (IOException ioe) { if (fis != null) { try { fis.close(); } catch (IOException ioe2) { } } } } } // an exception or emptiness signifies not a jar public static boolean isJar(InputStream is) { JarInputStream jis= null; try { jis = new JarInputStream(is); if (jis.getNextEntry() != null) { return true; } } catch (IOException ioe) { }finally{ org.aw20.io.StreamUtil.closeStream(jis); } return false; } public static String prefixNSDigits(String str) { if (str != null) { str = str.trim(); StringBuilder buffy = new StringBuilder(); List<String> tokens = string.split(str, "."); for (int i = 0; i < tokens.size(); i++) { String t = tokens.get(i).toString(); if (Character.isDigit(t.charAt(0))) t = "ns" + t; buffy.append("." + t); } if (str.endsWith(".")) buffy.append("."); str = (str.startsWith(".") ? buffy.toString() : buffy.toString().substring(1)); } return str; } private class CFCSourceInfo { public String source = null; public String name = null; public String impl = null; public File file = null; public CFCSourceInfo(String javaSource, File file, String name, String impl) { this.source = javaSource; this.file = file; this.name = name; this.impl = impl; } } }