/*
* 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;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.wsdl.OperationType;
import javax.xml.namespace.QName;
import javax.xml.rpc.holders.Holder;
import org.apache.axis.AxisEngine;
import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.MessageContext;
import org.apache.axis.constants.Style;
import org.apache.axis.description.JavaServiceDesc;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.ParameterDesc;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.message.RPCElement;
import org.apache.axis.message.RPCHeaderParam;
import org.apache.axis.message.RPCParam;
import org.apache.axis.message.SOAPBodyElement;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.providers.java.RPCProvider;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.fromJava.Emitter;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import com.naryx.tagfusion.cfm.engine.cfComponentData;
import com.naryx.tagfusion.cfm.engine.cfEngine;
public class cfcProvider extends RPCProvider {
private static final long serialVersionUID = 1L;
public static final String OPTION_WSDL_BINDINGNAME = "wsdlBindingName";
//private cfComponentData cfc = null;
public cfcProvider(cfComponentData cfc) {
super();
//this.cfc = cfc;
}
/**
* Generate the WSDL for this service.
*
* Put in the "WSDL" property of the message context as a org.w3c.dom.Document
*
* @param msgContext
* MessageContext in which to place the WSDL Document.
* @throws AxisFault
*/
public void generateWSDL(MessageContext msgContext) throws AxisFault {
if (log.isDebugEnabled())
log.debug("Enter: BasicProvider::generateWSDL (" + this + ")");
/* Find the service we're invoking so we can grab it's options */
/** ************************************************************ */
SOAPService service = msgContext.getService();
ServiceDesc serviceDesc = service.getInitializedServiceDesc(msgContext);
// Calculate the appropriate namespaces for the WSDL we're going
// to put out.
//
// If we've been explicitly told which namespaces to use, respect
// that. If not:
//
// The "interface namespace" should be either:
// 1) The namespace of the ServiceDesc
// 2) The transport URL (if there's no ServiceDesc ns)
try {
// Location URL is whatever is explicitly set in the MC
String locationUrl = msgContext.getStrProp(MessageContext.WSDLGEN_SERV_LOC_URL);
if (locationUrl == null) {
// If nothing, try what's explicitly set in the ServiceDesc
locationUrl = serviceDesc.getEndpointURL();
}
if (locationUrl == null) {
// If nothing, use the actual transport URL
locationUrl = msgContext.getStrProp(MessageContext.TRANS_URL);
}
// Interface namespace is whatever is explicitly set
String interfaceNamespace = msgContext.getStrProp(MessageContext.WSDLGEN_INTFNAMESPACE);
if (interfaceNamespace == null) {
// If nothing, use the default namespace of the ServiceDesc
interfaceNamespace = serviceDesc.getDefaultNamespace();
}
if (interfaceNamespace == null) {
// If nothing still, use the location URL determined above
interfaceNamespace = locationUrl;
}
// Do we want to do this?
//
// if (locationUrl == null) {
// locationUrl = url;
// } else {
// try {
// URL urlURL = new URL(url);
// URL locationURL = new URL(locationUrl);
// URL urlTemp = new URL(urlURL.getProtocol(),
// locationURL.getHost(),
// locationURL.getPort(),
// urlURL.getFile());
// interfaceNamespace += urlURL.getFile();
// locationUrl = urlTemp.toString();
// } catch (Exception e) {
// locationUrl = url;
// interfaceNamespace = url;
// }
// }
Emitter emitter = new Emitter();
// This seems like a good idea, but in fact isn't because the
// emitter will figure out a reasonable name (<classname>Service)
// for the WSDL service element name. We provide the 'alias'
// setting to explicitly set this name. See bug 13262 for more info.
// emitter.setServiceElementName(serviceDesc.getName());
// service alias may be provided if exact naming is required,
// otherwise Axis will name it according to the implementing class name
String alias = (String) service.getOption("alias");
if (alias != null)
emitter.setServiceElementName(alias);
// Set style/use
emitter.setStyle(serviceDesc.getStyle());
emitter.setUse(serviceDesc.getUse());
// Set version info
emitter.setVersionMessage(System.getProperty("line.separator") + "WSDL created by " + cfEngine.PRODUCT_VERSION + System.getProperty("line.separator"));
if (serviceDesc instanceof JavaServiceDesc) {
emitter.setClsSmart(((JavaServiceDesc) serviceDesc).getImplClass(), locationUrl);
}
// If a wsdl target namespace was provided, use the targetNamespace.
// Otherwise use the interfaceNamespace constructed above.
String targetNamespace = (String) service.getOption(OPTION_WSDL_TARGETNAMESPACE);
if (targetNamespace == null || targetNamespace.length() == 0) {
targetNamespace = interfaceNamespace;
}
emitter.setIntfNamespace(targetNamespace);
emitter.setLocationUrl(locationUrl);
emitter.setServiceDesc(serviceDesc);
emitter.setTypeMappingRegistry(msgContext.getTypeMappingRegistry());
String wsdlPortType = (String) service.getOption(OPTION_WSDL_PORTTYPE);
String wsdlServiceElement = (String) service.getOption(OPTION_WSDL_SERVICEELEMENT);
String wsdlServicePort = (String) service.getOption(OPTION_WSDL_SERVICEPORT);
String wsdlInputSchema = (String) service.getOption(OPTION_WSDL_INPUTSCHEMA);
String wsdlSoapActinMode = (String) service.getOption(OPTION_WSDL_SOAPACTION_MODE);
String extraClasses = (String) service.getOption(OPTION_EXTRACLASSES);
String bindingName = (String) service.getOption(OPTION_WSDL_BINDINGNAME);
if (wsdlPortType != null && wsdlPortType.length() > 0) {
emitter.setPortTypeName(wsdlPortType);
}
if (wsdlServiceElement != null && wsdlServiceElement.length() > 0) {
emitter.setServiceElementName(wsdlServiceElement);
}
if (wsdlServicePort != null && wsdlServicePort.length() > 0) {
emitter.setServicePortName(wsdlServicePort);
}
if (wsdlInputSchema != null && wsdlInputSchema.length() > 0) {
emitter.setInputSchema(wsdlInputSchema);
}
if (wsdlSoapActinMode != null && wsdlSoapActinMode.length() > 0) {
emitter.setSoapAction(wsdlSoapActinMode);
}
if (bindingName != null && bindingName.length() > 0) {
emitter.setBindingName(bindingName);
}
if (extraClasses != null && extraClasses.length() > 0) {
emitter.setExtraClasses(extraClasses);
}
if (msgContext.isPropertyTrue(AxisEngine.PROP_EMIT_ALL_TYPES)) {
emitter.setEmitAllTypes(true);
}
Document doc = emitter.emit(Emitter.MODE_ALL);
msgContext.setProperty("WSDL", doc);
} catch (NoClassDefFoundError e) {
entLog.info(Messages.getMessage("toAxisFault00"), e);
throw new AxisFault(e.toString(), e);
} catch (Exception e) {
entLog.info(Messages.getMessage("toAxisFault00"), e);
throw AxisFault.makeFault(e);
}
if (log.isDebugEnabled())
log.debug("Exit: BasicProvider::generateWSDL (" + this + ")");
}
/**
* Fill in a service description with the correct impl class and typemapping
* set. This uses methods that can be overridden by other providers (like the
* EJBProvider) to get the class from the right place.
*
* @param service
* SOAPService wrapper
* @param msgContext
* MessageContext for this SOAPService
* @throws AxisFault
*/
public void initServiceDesc(SOAPService service, MessageContext msgContext) throws AxisFault {
// Normal initialization
super.initServiceDesc(service, msgContext);
// Remove the IComplexObject operations
removeIComplexObjectOps(service.getServiceDescription());
}
/**
* Removes all operations from the ServiceDesc that correspond to the
* IComplexObject interface. We don't want that in the WSDL.
*
* @param sd
* ServiceDesc from which we need to remove OperationDescs.
*/
protected void removeIComplexObjectOps(ServiceDesc sd) {
OperationDesc[] ops = null;
ArrayList opParms = null;
ops = sd.getOperationsByName("bd_setFieldValues");
for (int i = 0; i < ops.length; i++) {
opParms = ops[i].getAllInParams();
if (opParms.size() == 2 && ops[i].getReturnClass().equals(void.class)) {
ParameterDesc p1 = (ParameterDesc) opParms.get(0);
ParameterDesc p2 = (ParameterDesc) opParms.get(1);
if ((p1.getName().equals("data") && p1.getJavaType().equals(Map.class) && p2.getName().equals("missingRequiredFieldNames") && p2.getJavaType().equals(List.class)) || ((p2.getName().equals("data") && p2.getJavaType().equals(Map.class) && p1.getName().equals("missingRequiredFieldNames") && p1.getJavaType().equals(List.class)))) {
sd.removeOperationDesc(ops[i]);
break;
}
}
}
ops = sd.getOperationsByName("bd_getFieldValues");
for (int i = 0; i < ops.length; i++) {
opParms = ops[i].getAllInParams();
if (opParms.size() == 1 && ops[i].getReturnClass().equals(void.class)) {
ParameterDesc p1 = (ParameterDesc) opParms.get(0);
if (p1.getName().equals("data") && p1.getJavaType().equals(Map.class)) {
sd.removeOperationDesc(ops[i]);
break;
}
}
}
ops = sd.getOperationsByName("bd_getFieldTypes");
for (int i = 0; i < ops.length; i++) {
opParms = ops[i].getAllInParams();
if (opParms.size() == 1 && ops[i].getReturnClass().equals(void.class)) {
ParameterDesc p1 = (ParameterDesc) opParms.get(0);
if (p1.getName().equals("data") && p1.getJavaType().equals(Map.class)) {
sd.removeOperationDesc(ops[i]);
break;
}
}
}
ops = sd.getOperationsByName("bd_getCfcName");
for (int i = 0; i < ops.length; i++) {
opParms = ops[i].getAllInParams();
if ((opParms == null || opParms.size() == 0) && ops[i].getReturnClass().equals(String.class)) {
sd.removeOperationDesc(ops[i]);
break;
}
}
}
public void processMessage(MessageContext msgContext, SOAPEnvelope reqEnv, SOAPEnvelope resEnv, Object obj) throws Exception {
if (log.isDebugEnabled()) {
log.debug("Enter: RPCProvider.processMessage()");
}
SOAPService service = msgContext.getService();
ServiceDesc serviceDesc = service.getServiceDescription();
OperationDesc operation = msgContext.getOperation();
Vector bodies = reqEnv.getBodyElements();
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage("bodyElems00", "" + bodies.size()));
if (bodies.size() > 0) {
log.debug(Messages.getMessage("bodyIs00", "" + bodies.get(0)));
}
}
RPCElement body = null;
// Find the first "root" body element, which is the RPC call.
for (int bNum = 0; body == null && bNum < bodies.size(); bNum++) {
// If this is a regular old SOAPBodyElement, and it's a root,
// we're probably a non-wrapped doc/lit service. In this case,
// we deserialize the element, and create an RPCElement "wrapper"
// around it which points to the correct method.
// FIXME : There should be a cleaner way to do this...
if (!(bodies.get(bNum) instanceof RPCElement)) {
SOAPBodyElement bodyEl = (SOAPBodyElement) bodies.get(bNum);
// igors: better check if bodyEl.getID() != null
// to make sure this loop does not step on SOAP-ENC objects
// that follow the parameters! FIXME?
if (bodyEl.isRoot() && operation != null && bodyEl.getID() == null) {
ParameterDesc param = operation.getParameter(bNum);
// at least do not step on non-existent parameters!
if (param != null) {
Object val = bodyEl.getValueAsType(param.getTypeQName());
body = new RPCElement("", operation.getName(), new Object[] { val });
}
}
} else {
body = (RPCElement) bodies.get(bNum);
}
}
// special case code for a document style operation with no
// arguments (which is a strange thing to have, but whatever)
if (body == null) {
// throw an error if this isn't a document style service
if (!(serviceDesc.getStyle().equals(Style.DOCUMENT))) {
throw new Exception(Messages.getMessage("noBody00"));
}
// look for a method in the service that has no arguments,
// use the first one we find.
ArrayList ops = serviceDesc.getOperations();
for (Iterator iterator = ops.iterator(); iterator.hasNext();) {
OperationDesc desc = (OperationDesc) iterator.next();
if (desc.getNumInParams() == 0) {
// found one with no parameters, use it
msgContext.setOperation(desc);
// create an empty element
body = new RPCElement(desc.getName());
// stop looking
break;
}
}
// If we still didn't find anything, report no body error.
if (body == null) {
throw new Exception(Messages.getMessage("noBody00"));
}
}
String methodName = body.getMethodName();
Vector args = null;
try {
args = body.getParams();
} catch (SAXException e) {
if (e.getException() != null)
throw e.getException();
throw e;
}
int numArgs = args.size();
// This may have changed, so get it again...
// FIXME (there should be a cleaner way to do this)
operation = msgContext.getOperation();
if (operation == null) {
QName qname = new QName(body.getNamespaceURI(), body.getName());
try {
// Seems to be a bug in Axis such that the client detects the
// implementation
// class and thus creates different operations indexed by QNames with
// namespaces
// whereas the document style generated WSDL doesn't have namespace
// information.
operation = serviceDesc.getOperationByElementQName(qname);
} catch (Exception ex) {
// Try to find the operation by matching only the local part of the
// QName.
Iterator itr = serviceDesc.getOperations().iterator();
while (itr.hasNext()) {
OperationDesc opDesc = (OperationDesc) itr.next();
if (opDesc.getElementQName().getLocalPart().equals(qname.getLocalPart())) {
// Let's try this
operation = serviceDesc.getOperationByElementQName(opDesc.getElementQName());
break;
}
}
if (operation == null) {
// Didn't find a matching operation. Throw the original exception.
throw ex;
}
}
if (operation == null) {
SOAPConstants soapConstants = msgContext == null ? SOAPConstants.SOAP11_CONSTANTS : msgContext.getSOAPConstants();
if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) {
AxisFault fault = new AxisFault(Constants.FAULT_SOAP12_SENDER, Messages.getMessage("noSuchOperation", methodName), null, null);
fault.addFaultSubCode(Constants.FAULT_SUBCODE_PROC_NOT_PRESENT);
throw new SAXException(fault);
} else {
throw new AxisFault(Constants.FAULT_CLIENT, Messages.getMessage("noSuchOperation", methodName), null, null);
}
} else {
msgContext.setOperation(operation);
}
}
// Create the array we'll use to hold the actual parameter
// values. We know how big to make it from the metadata.
Object[] argValues = new Object[operation.getNumParams()];
// A place to keep track of the out params (INOUTs and OUTs)
ArrayList outs = new ArrayList();
// Put the values contained in the RPCParams into an array
// suitable for passing to java.lang.reflect.Method.invoke()
// Make sure we respect parameter ordering if we know about it
// from metadata, and handle whatever conversions are necessary
// (values -> Holders, etc)
for (int i = 0; i < numArgs; i++) {
RPCParam rpcParam = (RPCParam) args.get(i);
Object value = rpcParam.getObjectValue();
// first check the type on the paramter
ParameterDesc paramDesc = rpcParam.getParamDesc();
// if we found some type info try to make sure the value type is
// correct. For instance, if we deserialized a xsd:dateTime in
// to a Calendar and the service takes a Date, we need to convert
if (paramDesc != null && paramDesc.getJavaType() != null) {
// Get the type in the signature (java type or its holder)
Class sigType = paramDesc.getJavaType();
// Convert the value into the expected type in the signature
value = JavaUtils.convert(value, sigType);
rpcParam.setObjectValue(value);
if (paramDesc.getMode() == ParameterDesc.INOUT) {
outs.add(rpcParam);
}
}
// Put the value (possibly converted) in the argument array
// make sure to use the parameter order if we have it
if (paramDesc == null || paramDesc.getOrder() == -1) {
argValues[i] = value;
} else {
argValues[paramDesc.getOrder()] = value;
}
if (log.isDebugEnabled()) {
log.debug(" " + Messages.getMessage("value00", "" + argValues[i]));
}
}
// See if any subclasses want a crack at faulting on a bad operation
// FIXME : Does this make sense here???
String allowedMethods = (String) service.getOption("allowedMethods");
checkMethodName(msgContext, allowedMethods, operation.getName());
// Now create any out holders we need to pass in
int count = numArgs;
for (int i = 0; i < argValues.length; i++) {
// We are interested only in OUT/INOUT
ParameterDesc param = operation.getParameter(i);
if (param.getMode() == ParameterDesc.IN)
continue;
Class holderClass = param.getJavaType();
if (holderClass != null && Holder.class.isAssignableFrom(holderClass)) {
int index = count;
// Use the parameter order if specified or just stick them to the end.
if (param.getOrder() != -1) {
index = param.getOrder();
} else {
count++;
}
// If it's already filled, don't muck with it
if (argValues[index] != null) {
continue;
}
argValues[index] = holderClass.newInstance();
// Store an RPCParam in the outs collection so we
// have an easy and consistent way to write these
// back to the client below
RPCParam p = new RPCParam(param.getQName(), argValues[index]);
p.setParamDesc(param);
outs.add(p);
} else {
throw new AxisFault(Messages.getMessage("badOutParameter00", "" + param.getQName(), operation.getName()));
}
}
// OK! Now we can invoke the method
Object objRes = null;
try {
objRes = invokeMethod(msgContext, operation.getMethod(), obj, argValues);
} catch (IllegalArgumentException e) {
String methodSig = operation.getMethod().toString();
String argClasses = "";
for (int i = 0; i < argValues.length; i++) {
if (argValues[i] == null) {
argClasses += "null";
} else {
argClasses += argValues[i].getClass().getName();
}
if (i + 1 < argValues.length) {
argClasses += ",";
}
}
log.info(Messages.getMessage("dispatchIAE00", new String[] { methodSig, argClasses }), e);
throw new AxisFault(Messages.getMessage("dispatchIAE00", new String[] { methodSig, argClasses }), e);
}
/**
* If this is a one-way operation, there is nothing more to do.
*/
if (OperationType.ONE_WAY.equals(operation.getMep()))
return;
/* Now put the result in the result SOAPEnvelope */
/** ********************************************** */
RPCElement resBody = new RPCElement(methodName + "Response");
resBody.setPrefix(body.getPrefix());
resBody.setNamespaceURI(body.getNamespaceURI());
resBody.setEncodingStyle(msgContext.getEncodingStyle());
try {
// Return first
if (operation.getMethod().getReturnType() != Void.TYPE) {
QName returnQName = operation.getReturnQName();
if (returnQName == null) {
String nsp = body.getNamespaceURI();
if (nsp == null || nsp.length() == 0) {
nsp = serviceDesc.getDefaultNamespace();
}
returnQName = new QName(msgContext.isEncoded() ? "" : nsp, methodName + "Return");
}
RPCParam param = new RPCParam(returnQName, objRes);
param.setParamDesc(operation.getReturnParamDesc());
if (!operation.isReturnHeader()) {
// For SOAP 1.2 rpc style, add a result
if (msgContext.getSOAPConstants() == SOAPConstants.SOAP12_CONSTANTS && (serviceDesc.getStyle().equals(Style.RPC))) {
RPCParam resultParam = new RPCParam(Constants.QNAME_RPC_RESULT, returnQName);
resultParam.setXSITypeGeneration(Boolean.FALSE);
resBody.addParam(resultParam);
}
resBody.addParam(param);
} else {
resEnv.addHeader(new RPCHeaderParam(param));
}
}
// Then any other out params
if (!outs.isEmpty()) {
for (Iterator i = outs.iterator(); i.hasNext();) {
// We know this has a holder, so just unwrap the value
RPCParam param = (RPCParam) i.next();
Holder holder = (Holder) param.getObjectValue();
Object value = JavaUtils.getHolderValue(holder);
ParameterDesc paramDesc = param.getParamDesc();
param.setObjectValue(value);
if (paramDesc != null && paramDesc.isOutHeader()) {
resEnv.addHeader(new RPCHeaderParam(param));
} else {
resBody.addParam(param);
}
}
}
} catch (Exception e) {
throw e;
}
// Add the responseBody to the envelope
resEnv.addBodyElement(resBody);
// Axis has a bug that swallows all AxisFaults when serializing
// the response message. This results in an empty string response
// if something goes wrong during the serialization. To avoid this,
// we'll serialize the responseBody here/now so that any problems
// will be properly logged and returned as an AxisFault to the client.
resBody.getAsString();
}
/**
* This method encapsulates the method invocation.
*
* @param msgContext
* MessageContext
* @param method
* the target method.
* @param obj
* the target object
* @param argValues
* the method arguments
*/
protected Object invokeMethod(MessageContext msgContext, Method method, Object obj, Object[] argValues) throws Exception {
return (method.invoke(obj, argValues));
}
/**
* Throw an AxisFault if the requested method is not allowed.
*
* @param msgContext
* MessageContext
* @param allowedMethods
* list of allowed methods
* @param methodName
* name of target method
*/
protected void checkMethodName(MessageContext msgContext, String allowedMethods, String methodName) throws Exception {
// Our version doesn't need to do anything, though inherited
// ones might.
}
}