/*
* Copyright (C) 2000 - 2008 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://www.openbluedragon.org/
*/
package com.naryx.tagfusion.cfm.xml.ws.javaplatform;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.wsdl.Operation;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.extensions.soap.SOAPAddress;
import org.apache.axis.client.Stub;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.wsdl.gen.Parser;
import org.apache.axis.wsdl.symbolTable.BindingEntry;
import org.apache.axis.wsdl.symbolTable.ElementDecl;
import org.apache.axis.wsdl.symbolTable.Parameter;
import org.apache.axis.wsdl.symbolTable.ServiceEntry;
import org.apache.axis.wsdl.symbolTable.SymTabEntry;
import org.apache.axis.wsdl.symbolTable.SymbolTable;
import org.apache.axis.wsdl.toJava.Utils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.aw20.security.MD5;
import com.naryx.tagfusion.cfm.engine.catchDataFactory;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.xml.ws.CallParameters;
import com.naryx.tagfusion.cfm.xml.ws.dynws.DynamicCacheClassLoader;
import com.naryx.tagfusion.cfm.xml.ws.dynws.DynamicCacheClassLoaderFactory;
import com.naryx.tagfusion.cfm.xml.ws.dynws.DynamicWebServiceStubGeneratorInterface;
import com.naryx.tagfusion.cfm.xml.ws.dynws.StubInfo;
import com.naryx.tagfusion.cfm.xml.ws.dynws.WSDL2Java;
public class DynamicWebServiceStubGenerator implements DynamicWebServiceStubGeneratorInterface {
private String javaCacheDir = null;
private StubInfo si = null;
public void setCacheDir(String javaCache){
this.javaCacheDir = javaCache;
}
public Stub generateStub(String wsdlURL, String portName, CallParameters cp) throws cfmRunTimeException {
// Gen a MD5 sum of the wsdl contents
String wsdlContents = getWSDLContents(wsdlURL, cp);
String wsdlSum = MD5.getDigest( wsdlContents );
// Check cache first
DynamicCacheClassLoader cl = null;
synchronized (DynamicCacheClassLoader.STUB_MUTEX) {
cl = checkCacheForStub(wsdlURL, wsdlSum, portName);
if (cl == null)
cl = buildClientClasses(wsdlURL, portName, wsdlContents, wsdlSum);
}
// Create and return a stub
return createStubInstance(cl, wsdlURL);
}
private Stub createStubInstance(DynamicCacheClassLoader cl, String wsdlURL) throws cfmRunTimeException {
Stub stub = null;
Object locator = null;
// Get the locator
Class[] klasses = cl.findAllClasses();
for (int i = 0; i < klasses.length; i++) {
if (klasses[i].getName().endsWith(this.si.getLocatorName()) && org.apache.axis.client.Service.class.isAssignableFrom(klasses[i])) {
try {
// Create the service
locator = klasses[i].newInstance();
break;
} catch (InstantiationException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". Cannot create locator. " + ex.getMessage()));
} catch (IllegalAccessException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". Cannot access locator. " + ex.getMessage()));
}
}
}
// Make sure we got it.
if (locator == null)
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". Cannot find locator: " + this.si.getLocatorName()));
// Get a stub from the locator
try {
Method stubMethod = locator.getClass().getDeclaredMethod("get" + this.si.getStubName(), new Class[0]);
stub = (Stub) stubMethod.invoke(locator, new Object[0]);
} catch (NoSuchMethodException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". No method: " + "get" + this.si.getStubName() + " not found."));
} catch (IllegalAccessException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". Cannot access method: " + "get" + this.si.getStubName()));
} catch (InvocationTargetException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid service locator generated for " + wsdlURL + ". Cannot invoke method: " + "get" + this.si.getStubName() + ". " + ex.getMessage() + " " + ex.getTargetException().getMessage()));
}
return stub;
}
private DynamicCacheClassLoader checkCacheForStub(String wsdlLocation, String newWSDLSum, String portName) throws cfmRunTimeException {
// Look for it by WSDL hash in our java class cache
File dir = this.genWSDLClassPath(wsdlLocation);
if (dir.exists()) {
// Now look for the serialized StubInfo
File siFile = genStubInfoPath(dir);
if (siFile.exists()) {
try {
StubInfo stubInfo = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(siFile);
ois = new ObjectInputStream(fis);
stubInfo = (StubInfo) ois.readObject();
} finally {
if (fis != null)
fis.close();
if (ois != null)
ois.close();
}
// Get the class loader for the gen'd stubs
DynamicCacheClassLoader rtn = DynamicCacheClassLoader.findClassLoader(dir.getCanonicalPath(), DynamicCacheClassLoader.STUB_CLASSES);
if (rtn == null)
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "No class loader found for dynamic cache dir: " + dir.getCanonicalPath()));
// Compare the WSDL sums to see if the WSDL has changed. Then check
// that the portName
// we need is the same as the one used to generate the stub's
// operations initially.
if (newWSDLSum.equals(stubInfo.getWSDLSum())) {
if (portName == null || (portName.equalsIgnoreCase(stubInfo.getPortName()))) {
// Nothing's changed, lets go
this.si = stubInfo;
return rtn;
}
}
// Otherwise, clear the existing (now invalid) classes and return null
// (...below)
rtn.invalidate();
} catch (IOException ex) {
// Just ignore and assume we cannot use the cache
com.nary.Debug.printStackTrace(ex);
} catch (ClassNotFoundException ex) {
// Just ignore and assume we cannot use the cache
com.nary.Debug.printStackTrace(ex);
}
}
}
// OK, didn't find it
return null;
}
@SuppressWarnings("deprecation")
private String getWSDLContents(String wsdlURL, CallParameters cp) throws cfmRunTimeException {
try {
String wsdlL = wsdlURL.toLowerCase();
String contents = null;
if (wsdlL.startsWith("<?xml version")) {
// The location is the WSDL itself (unexpected)
contents = wsdlURL;
} else {
InputStream is = null;
InputStreamReader isr = null;
StringBuilder buffy = null;
HttpGet method = null;
try {
if (wsdlL.startsWith("http:") || wsdlL.startsWith("https:")) {
// Read from network
DefaultHttpClient client = new DefaultHttpClient();
// Set the timeout
int timeout = cp.getTimeout();
client.getParams().setParameter( "http.connection.timeout", timeout );
client.getParams().setParameter( "http.socket.timeout", timeout );
if (cp.getUsername() != null || cp.getPassword() != null) {
// Set any credentials
client.getCredentialsProvider().setCredentials( AuthScope.ANY, new UsernamePasswordCredentials( cp.getUsername(), cp.getPassword() ) );
}
if (cp.getProxyServer() != null) {
// Set the proxy
HttpHost proxy = new HttpHost( cp.getProxyServer(), (cp.getProxyPort() == -1) ? cp.getDefaultProxyPort() : cp.getProxyPort() );
client.getParams().setParameter( ConnRoutePNames.DEFAULT_PROXY, proxy );
if (cp.getProxyUser() != null || cp.getProxyPassword() != null) {
// Set the proxy credentials
client.getCredentialsProvider().setCredentials( new AuthScope( cp.getProxyServer() , cp.getProxyPort() ),
new UsernamePasswordCredentials( cp.getProxyUser(), cp.getProxyPassword() ) );
}
}
// Create the method and get the response
method = new HttpGet(wsdlURL);
client.getParams().setParameter( "http.protocol.handle-redirects", true );
HttpResponse response = client.execute(method);
switch ( response.getStatusLine().getStatusCode()) {
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
throw new cfmRunTimeException(catchDataFactory.extendedException("errorCode.runtimeError", "Failed to access WSDL: " + wsdlURL + ". Proxy authentication is required.", response.getStatusLine().toString()));
case HttpStatus.SC_UNAUTHORIZED:
throw new cfmRunTimeException(catchDataFactory.extendedException("errorCode.runtimeError", "Failed to access WSDL: " + wsdlURL + ". Authentication is required.", response.getStatusLine().toString()));
case HttpStatus.SC_USE_PROXY:
throw new cfmRunTimeException(catchDataFactory.extendedException("errorCode.runtimeError", "Failed to access WSDL: " + wsdlURL + ". The use of a proxy is required.", response.getStatusLine().toString()));
}
if ( response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
throw new cfmRunTimeException(catchDataFactory.extendedException("errorCode.runtimeError", "Failed to access WSDL: " + wsdlURL, response.getStatusLine().toString()));
is = response.getEntity().getContent();
} else {
// Just try to read off disk
File f = new File(wsdlURL);
is = new FileInputStream(f);
}
// Read the data
char[] buf = new char[4096];
int read = -1;
buffy = new StringBuilder();
isr = new InputStreamReader(is);
while ((read = isr.read(buf, 0, buf.length)) != -1)
buffy.append(buf, 0, read);
contents = buffy.toString();
} finally {
if (isr != null)
isr.close();
if (is != null)
is.close();
if (method != null)
method.releaseConnection();
}
}
// Calc the sum and return
return contents;
} catch (IOException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Failed to access WSDL located at " + wsdlURL + ". There may be an error in the target WSDL. " + ex.getMessage()));
}
}
private File genWSDLClassPath(String wsdlLocation) {
return new File(new File(this.javaCacheDir), MD5.getDigest(wsdlLocation) );
}
private File genStubInfoPath(File wsdlClassPath) {
return new File(wsdlClassPath, wsdlClassPath.getName() + ".ser");
}
@SuppressWarnings("static-access")
private DynamicCacheClassLoader buildClientClasses(String wsdlLocation, String portName, String wsdlContents, String newWSDLSum) throws cfmRunTimeException {
// Generate the source file(s)
WSDL2Java w2j = new WSDL2Java();
File outputDir = genWSDLClassPath(wsdlLocation);
if (outputDir.exists())
recursiveDelete(outputDir);
outputDir.mkdirs();
WSDL2Java.GenResults results = w2j.genClientClasses(wsdlLocation, wsdlContents, outputDir.getAbsolutePath());
try {
// Compile into classes
java.io.ByteArrayOutputStream javacOut = new java.io.ByteArrayOutputStream();
if (!w2j.compileOutput(w2j.getOutputDir(), javacOut)) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Failed to compile web service generated client classes for " + wsdlLocation + "." + " The compiler error messages follow: " + javacOut.toString()));
}
} catch (IOException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Failed to compile web service generated client classes for " + wsdlLocation + "." + " The compiler error messages follow: " + ex.getMessage()));
}
// Delete the java files
// w2j.cleanUpSource();
// Create the StubInfo
Parser wsdlParser = w2j.getWSDLParser();
Service service = getWSDLService(wsdlParser);
Port port = getWSDLPort(service, portName);
this.si = new StubInfo(findLocatorName(wsdlParser, service), findStubName(port), newWSDLSum, portName, findOperations(wsdlParser, port));
// Save the StubInfo
File siFile = genStubInfoPath(outputDir);
OutputStream fos = null;
ObjectOutputStream oos = null;
try {
try {
if (siFile.exists())
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid web service generated for " + wsdlLocation + ". Cannot serialize stub info. File: " + siFile.getAbsolutePath() + " already exists."));
else
siFile.createNewFile();
fos = cfEngine.thisPlatform.getFileIO().getFileOutputStream(siFile);
oos = new ObjectOutputStream(fos);
oos.writeObject(this.si);
} finally {
if (fos != null)
fos.close();
if (oos != null)
oos.close();
}
// Create the DynamicCacheClassLoader
DynamicCacheClassLoader rtn = DynamicCacheClassLoaderFactory.newClassLoader(outputDir.getCanonicalPath(), DynamicCacheClassLoader.STUB_CLASSES);
// Register the IComplexObject impls
Iterator itr = results.iComplexObjects.keySet().iterator();
while (itr.hasNext()) {
String cfcName = (String) itr.next();
rtn.setIComplexObject(cfcName, (String) results.iComplexObjects.get(cfcName));
}
// Register the IQueryBean impl
rtn.setIQueryBean(results.iQueryBean);
// Register the IStructMap impl
rtn.setIQueryBean(results.iStructMap);
// Return the class loader
return rtn;
} catch (IOException ex) {
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid web service generated for " + wsdlLocation + ". Cannot serialize stub info: " + siFile.getAbsolutePath() + ". " + ex.getMessage()));
}
}
private String findLocatorName(Parser wsdlParser, Service service) {
SymbolTable symbolTable = wsdlParser.getSymbolTable();
ServiceEntry sEntry = symbolTable.getServiceEntry(service.getQName());
return sEntry.getName() + "Locator";
}
private String findStubName(Port port) {
String portName = port.getName();
if (!JavaUtils.isJavaId(portName))
portName = Utils.xmlNameToJavaClass(portName);
return portName;
}
private StubInfo.Operation[] findOperations(Parser wsdlParser, Port port) {
SymbolTable symbolTable = wsdlParser.getSymbolTable();
BindingEntry bEntry = symbolTable.getBindingEntry(port.getBinding().getQName());
Set opSet = bEntry.getParameters().keySet();
Iterator itr = opSet.iterator();
StubInfo.Operation[] siOps = new StubInfo.Operation[opSet.size()];
for (int i = 0; itr.hasNext(); i++) {
// Get the operation and add the parameters
Operation op = (Operation) itr.next();
Vector parms = bEntry.getParameters(op).list;
// Populate the parms
StubInfo.Parameter[] siParms = new StubInfo.Parameter[parms.size()];
StubInfo.Operation siOp = new StubInfo.Operation(op.getName(), siParms);
siOps[i] = siOp;
Iterator tmpItr = parms.iterator();
for (int j = 0; tmpItr.hasNext(); j++) {
Parameter p = (Parameter) tmpItr.next();
siParms[j] = new StubInfo.Parameter(p.getName(), p.isNillable(), p.isOmittable());
}
// If there is only 1 parameter and it's a complex object,
// gather parameter information for its properties.
if (parms.size() == 1) {
Vector elems = ((Parameter) parms.get(0)).getType().getContainedElements();
if (elems != null && !elems.isEmpty()) {
StubInfo.Parameter[] siSubParms = new StubInfo.Parameter[elems.size()];
tmpItr = elems.iterator();
for (int j = 0; tmpItr.hasNext(); j++) {
ElementDecl e = (ElementDecl) tmpItr.next();
siSubParms[j] = new StubInfo.Parameter(e.getName(), e.getNillable(), e.getMinOccursIs0());
}
siOp.setSubParameters(siSubParms);
}
}
}
// Return the operations
return siOps;
}
private Service getWSDLService(Parser wsdlParser) throws cfmRunTimeException {
SymTabEntry symTabEntry = null;
Map.Entry entry = null;
Vector v = null;
// Iterate through all the entries in the WSDL until we
// find the first service entry. Assume it's that one.
Iterator iterator = wsdlParser.getSymbolTable().getHashMap().entrySet().iterator();
while (iterator.hasNext()) {
entry = (Map.Entry) iterator.next();
v = (Vector) entry.getValue();
for (int i = 0; i < v.size(); ++i) {
if (ServiceEntry.class.isInstance(v.elementAt(i))) {
symTabEntry = (SymTabEntry) v.elementAt(i);
break;
}
}
}
if (symTabEntry == null)
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid web service operation. Cannot locate service entry in WSDL"));
// Return the service
return ((ServiceEntry) symTabEntry).getService();
}
/**
* Returns the Port binding for the specified Service.
*
* @param service
* Service to get a Port binding from
* @param portName
* name of the port binding to get (or null)
* @return Port binding for the specified Service
* @throws cfmRunTimeException
*/
private Port getWSDLPort(Service service, String portName) throws cfmRunTimeException {
Port port = null;
Map ports = service.getPorts();
if (portName != null) {
Iterator itr = ports.keySet().iterator();
while (itr.hasNext()) {
// Get the one that has a localpart that matches the specified portName
port = (Port) ports.get((String) itr.next());
if (isValidPort(port) && port.getBinding().getPortType().getQName().getLocalPart().equals(portName))
return port;
}
} else {
Iterator itr = ports.keySet().iterator();
while (itr.hasNext()) {
// Pick the first.
port = (Port) ports.get((String) itr.next());
if (isValidPort(port))
return port;
}
}
throw new cfmRunTimeException(catchDataFactory.generalException("errorCode.runtimeError", "Invalid web service operation. Cannot locate port entry for service " + service.getQName().toString() + " in WSDL" + (portName == null ? "" : " with port name: " + portName)));
}
/**
* Returns true if the specified Port instance is valid, false otherwise.
*
* @param port
* Port instance to validate
* @return true if the specified Port instance is valid, false otherwise
* @throws cfmRunTimeException
*/
private boolean isValidPort(Port port) throws cfmRunTimeException {
if (port != null) {
List list = null;
// There appears to be a bug in JRocket 1.5 such that calling
// "port.getExtensibilityElements()" causes the JVM to throw a
// NPE even when the variable is indeed initialized. Using
// reflection seems to get around the problem.
// list = port.getExtensibilityElements();
try {
Class impl = port.getClass();
Method getter = impl.getMethod("getExtensibilityElements", new Class[0]);
list = (List) getter.invoke(port, new Object[0]);
} catch (NoSuchMethodException ex) {
com.nary.Debug.printStackTrace(ex);
} catch (IllegalAccessException ex) {
com.nary.Debug.printStackTrace(ex);
} catch (InvocationTargetException ex) {
com.nary.Debug.printStackTrace(ex);
}
if (list != null) {
Iterator itr = list.iterator();
while (itr.hasNext()) {
if (itr.next() instanceof SOAPAddress)
return true;
}
}
}
return false;
}
private void recursiveDelete(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
files[i].delete();
} else {
recursiveDelete(files[i]);
files[i].delete();
}
}
}
dir.delete();
}
public StubInfo getStubInfo() {
return this.si;
}
}