/***************************************************************************
* Copyright (C) 2006-2012 by Fabrizio Montesi <famontesi@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program 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 Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.net;
import com.ibm.wsdl.extensions.schema.SchemaImpl;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.soap.Detail;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import jolie.lang.Constants;
import jolie.Interpreter;
import jolie.runtime.FaultException;
import jolie.runtime.Value;
import jolie.runtime.ValueVector;
import jolie.runtime.VariablePath;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.sun.xml.xsom.XSAttributeDecl;
import com.sun.xml.xsom.XSAttributeUse;
import com.sun.xml.xsom.XSComplexType;
import com.sun.xml.xsom.XSContentType;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSModelGroup;
import com.sun.xml.xsom.XSModelGroupDecl;
import com.sun.xml.xsom.XSParticle;
import com.sun.xml.xsom.XSSchema;
import com.sun.xml.xsom.XSSchemaSet;
import com.sun.xml.xsom.XSTerm;
import com.sun.xml.xsom.XSType;
import com.sun.xml.xsom.parser.XSOMParser;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Definition;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import jolie.net.http.HttpMessage;
import jolie.net.http.HttpParser;
import jolie.net.http.HttpUtils;
import jolie.net.ports.Interface;
import jolie.net.protocols.SequentialCommProtocol;
import jolie.net.soap.WSDLCache;
import jolie.runtime.typing.OneWayTypeDescription;
import jolie.runtime.typing.RequestResponseTypeDescription;
import jolie.runtime.typing.Type;
import jolie.runtime.typing.TypeCastingException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
/**
* Implements the SOAP over HTTP protocol.
*
* @author Fabrizio Montesi
*
* 2006 - Fabrizio Montesi, Mauro Silvagni: first write. 2007 - Fabrizio
* Montesi: rewritten from scratch, exploiting new JOLIE capabilities. 2008 -
* Fabrizio Montesi: initial support for schemas. 2008 - Claudio Guidi: initial
* support for WS-Addressing. 2010 - Fabrizio Montesi: initial support for WSDL
* documents.
*
*/
public class SoapProtocol extends SequentialCommProtocol
{
private String inputId = null;
private final Interpreter interpreter;
private final MessageFactory messageFactory;
private XSSchemaSet schemaSet = null;
private URI uri = null;
private Definition wsdlDefinition = null;
private Port wsdlPort = null;
private final TransformerFactory transformerFactory;
private final Map< String, String > namespacePrefixMap = new HashMap< String, String >();
private boolean received = false;
private final static String CRLF = new String( new char[]{13, 10} );
private static class Parameters {
private static final String WRAPPED = "wrapped";
private static final String INHERITED_TYPE = "__soap_inherited_type";
private static final String ADD_ATTRIBUTE = "add_attribute";
private static final String ENVELOPE = "envelope";
private static final String OPERATION = "operation";
private static final String STYLE = "style";
}
/*
* it forced the insertion of namespaces within the soap message
*
*
* type Attribute: void {
* .name: string
* .value: string
* }
*
* parameter add_attribute: void {
* .envelope: void {
* .attribute*: Attribute
* }
* .operation*: void {
* .operation_name: string
* .attribute: Attribute
* }
* }
*/
public String name()
{
return "soap";
}
public SoapProtocol( VariablePath configurationPath, URI uri, Interpreter interpreter )
throws SOAPException
{
super( configurationPath );
this.uri = uri;
this.transformerFactory = TransformerFactory.newInstance();
this.interpreter = interpreter;
this.messageFactory = MessageFactory.newInstance( SOAPConstants.SOAP_1_1_PROTOCOL );
}
private void parseSchemaElement( Definition definition, Element element, XSOMParser schemaParser )
throws IOException
{
try {
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty( "indent", "yes" );
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult( sw );
DOMSource source = new DOMSource( element );
transformer.transform( source, result );
InputSource schemaSource = new InputSource( new StringReader( sw.toString() ) );
schemaSource.setSystemId( definition.getDocumentBaseURI() );
schemaParser.parse( schemaSource );
} catch( TransformerConfigurationException e ) {
throw new IOException( e );
} catch( TransformerException e ) {
throw new IOException( e );
} catch( SAXException e ) {
throw new IOException( e );
}
}
private void parseWSDLTypes( XSOMParser schemaParser )
throws IOException
{
Definition definition = getWSDLDefinition();
if ( definition != null ) {
Types types = definition.getTypes();
if ( types != null ) {
List<ExtensibilityElement> list = types.getExtensibilityElements();
for( ExtensibilityElement element : list ) {
if ( element instanceof SchemaImpl ) {
Element schemaElement = ((SchemaImpl) element).getElement();
Map<String, String> namespaces = definition.getNamespaces();
for( Entry<String, String> entry : namespaces.entrySet() ) {
if ( entry.getKey().equals( "xmlns" ) || entry.getKey().trim().isEmpty() ) {
continue;
}
if ( schemaElement.getAttribute( "xmlns:" + entry.getKey() ).isEmpty() ) {
schemaElement.setAttribute( "xmlns:" + entry.getKey(), entry.getValue() );
}
}
parseSchemaElement( definition, schemaElement, schemaParser );
}
}
}
}
}
private XSSchemaSet getSchemaSet()
throws IOException, SAXException
{
if ( schemaSet == null ) {
XSOMParser schemaParser = new XSOMParser();
ValueVector vec = getParameterVector( "schema" );
if ( vec.size() > 0 ) {
for( Value v : vec ) {
schemaParser.parse( new File( v.strValue() ) );
}
}
parseWSDLTypes( schemaParser );
schemaSet = schemaParser.getResult();
String nsPrefix = "jolie";
int i = 1;
for( XSSchema schema : schemaSet.getSchemas() ) {
if ( !schema.getTargetNamespace().equals( XMLConstants.W3C_XML_SCHEMA_NS_URI ) ) {
namespacePrefixMap.put( schema.getTargetNamespace(), nsPrefix + i++ );
}
}
}
return schemaSet;
}
private boolean convertAttributes()
{
boolean ret = false;
if ( hasParameter( "convertAttributes" ) ) {
ret = checkBooleanParameter( "convertAttributes" );
}
return ret;
}
private void initNamespacePrefixes( SOAPElement element )
throws SOAPException
{
for( Entry<String, String> entry : namespacePrefixMap.entrySet() ) {
element.addNamespaceDeclaration( entry.getValue(), entry.getKey() );
}
}
private void valueToSOAPElement(
Value value,
SOAPElement element,
SOAPEnvelope soapEnvelope )
throws SOAPException
{
String type = "any";
if ( value.isDefined() ) {
if ( value.isInt() ) {
type = "int";
} else if ( value.isString() ) {
type = "string";
} else if ( value.isDouble() ) {
type = "double";
}
element.addAttribute( soapEnvelope.createName( "type", "xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI ), "xsd:" + type );
element.addTextNode( value.strValue() );
}
if ( convertAttributes() ) {
Map<String, ValueVector> attrs = getAttributesOrNull( value );
if ( attrs != null ) {
for( Entry<String, ValueVector> attrEntry : attrs.entrySet() ) {
element.addAttribute(
soapEnvelope.createName( attrEntry.getKey() ),
attrEntry.getValue().first().strValue() );
}
}
}
for( Entry<String, ValueVector> entry : value.children().entrySet() ) {
if ( !entry.getKey().startsWith( "@" ) ) {
for( Value val : entry.getValue() ) {
valueToSOAPElement(
val,
element.addChildElement( entry.getKey() ),
soapEnvelope );
}
}
}
}
private static Map<String, ValueVector> getAttributesOrNull( Value value )
{
Map<String, ValueVector> ret = null;
ValueVector vec = value.children().get( Constants.Predefined.ATTRIBUTES.token().content() );
if ( vec != null && vec.size() > 0 ) {
ret = vec.first().children();
}
if ( ret == null ) {
ret = new HashMap<String, ValueVector>();
}
return ret;
}
private static Value getAttributeOrNull( Value value, String attrName )
{
Value ret = null;
Map<String, ValueVector> attrs = getAttributesOrNull( value );
if ( attrs != null ) {
ValueVector vec = attrs.get( attrName );
if ( vec != null && vec.size() > 0 ) {
ret = vec.first();
}
}
return ret;
}
private static Value getAttribute( Value value, String attrName )
{
return value.getChildren( Constants.Predefined.ATTRIBUTES.token().content() ).first().getChildren( attrName ).first();
}
private String getPrefixOrNull( XSAttributeDecl decl )
{
if ( decl.getOwnerSchema().attributeFormDefault() ) {
return namespacePrefixMap.get( decl.getOwnerSchema().getTargetNamespace() );
}
return null;
}
private String getPrefixOrNull( XSElementDecl decl )
{
if ( decl.getOwnerSchema().elementFormDefault() ) {
return namespacePrefixMap.get( decl.getOwnerSchema().getTargetNamespace() );
}
return null;
}
private String getPrefix( XSElementDecl decl )
{
return namespacePrefixMap.get( decl.getOwnerSchema().getTargetNamespace() );
}
private String getPrefix( XSComplexType compType ) {
return namespacePrefixMap.get( compType.getOwnerSchema().getTargetNamespace() );
}
private void termProcessing( Value value, SOAPElement element, SOAPEnvelope envelope, boolean first,
XSTerm currTerm, int getMaxOccur,
XSSchemaSet sSet, String messageNamespace)
throws SOAPException
{
if ( currTerm.isElementDecl() ) {
ValueVector vec;
XSElementDecl currElementDecl = currTerm.asElementDecl();
String name = currElementDecl.getName();
String prefix = (first) ? getPrefix( currElementDecl ) : getPrefixOrNull( currElementDecl );
SOAPElement childElement = null;
if ( (vec = value.children().get( name )) != null ) {
int k = 0;
while( vec.size() > 0 && (getMaxOccur > k || getMaxOccur == XSParticle.UNBOUNDED) ) {
if ( prefix == null ) {
childElement = element.addChildElement( name );
} else {
childElement = element.addChildElement( name, prefix );
}
Value v = vec.remove( 0 );
valueToTypedSOAP(
v,
currElementDecl,
childElement,
envelope,
false,
sSet,
messageNamespace);
k++;
}
}
}
}
private void groupProcessing(
Value value,
XSElementDecl xsDecl,
SOAPElement element,
SOAPEnvelope envelope,
boolean first,
XSModelGroup modelGroup,
XSSchemaSet sSet,
String messageNamespace)
throws SOAPException
{
XSParticle[] children = modelGroup.getChildren();
XSTerm currTerm;
for( int i = 0; i < children.length; i++ ) {
currTerm = children[i].getTerm();
if ( currTerm.isModelGroup() ) {
groupProcessing( value, xsDecl, element, envelope, first, currTerm.asModelGroup(), sSet, messageNamespace );
} else {
termProcessing( value, element, envelope, first, currTerm, children[i].getMaxOccurs(), sSet, messageNamespace );
}
}
}
private void valueToTypedSOAP(
Value value,
XSElementDecl xsDecl,
SOAPElement element,
SOAPEnvelope envelope,
boolean first,// Ugly fix! This should be removed as soon as another option arises.
XSSchemaSet sSet,
String messageNamespace
)
throws SOAPException
{
XSType currType = xsDecl.getType();
if ( currType.isSimpleType() ) {
element.addTextNode( value.strValue() );
} else if ( currType.isComplexType() ) {
XSType type = currType;
if ( currType.asComplexType().isAbstract() ) {
// if the complex type is abstract search for the inherited type defined into the jolie value
// under the node __soap_inherited_type
if ( value.hasChildren( Parameters.INHERITED_TYPE ) ) {
String inheritedType = value.getFirstChild( Parameters.INHERITED_TYPE ).strValue();
XSComplexType xsInheritedType = sSet.getComplexType( messageNamespace, inheritedType );
if ( xsInheritedType == null ) {
System.out.println( "WARNING: Type " + inheritedType + " not found in the schema set");
} else {
type = xsInheritedType;
String nameType = "type";
String prefixType = "xsi";
QName attrName = envelope.createQName( nameType, prefixType );
element.addAttribute( attrName, getPrefix( xsInheritedType ) + ":" + inheritedType );
}
}
}
String name;
Value currValue;
XSComplexType complexT = type.asComplexType();
XSParticle particle;
XSContentType contentT;
//end new stuff
// Iterate over attributes
Collection<? extends XSAttributeUse> attributeUses = complexT.getAttributeUses();
for( XSAttributeUse attrUse : attributeUses ) {
name = attrUse.getDecl().getName();
if ( (currValue = getAttributeOrNull( value, name )) != null ) {
QName attrName = envelope.createQName( name, getPrefixOrNull( attrUse.getDecl() ) );
element.addAttribute( attrName, currValue.strValue() );
}
}
// processing content (no base type parent )
contentT = complexT.getContentType();
if ( contentT.asSimpleType() != null ) {
element.addTextNode( value.strValue() );
} else if ( (particle = contentT.asParticle()) != null ) {
XSTerm term = particle.getTerm();
XSModelGroupDecl modelGroupDecl;
XSModelGroup modelGroup = null;
if ( (modelGroupDecl = term.asModelGroupDecl()) != null ) {
modelGroup = modelGroupDecl.getModelGroup();
} else if ( term.isModelGroup() ) {
modelGroup = term.asModelGroup();
}
if ( modelGroup != null ) {
XSModelGroup.Compositor compositor = modelGroup.getCompositor();
if ( compositor.equals( XSModelGroup.SEQUENCE ) ) {
groupProcessing( value, xsDecl, element, envelope, first, modelGroup, sSet, messageNamespace );
}
}
}
}
}
private Definition getWSDLDefinition()
throws IOException
{
if ( wsdlDefinition == null && hasParameter( "wsdl" ) ) {
String wsdlUrl = getStringParameter( "wsdl" );
try {
wsdlDefinition = WSDLCache.getInstance().get( wsdlUrl );
} catch( WSDLException e ) {
throw new IOException( e );
}
}
return wsdlDefinition;
}
private String getSoapActionForOperation( String operationName )
throws IOException
{
String soapAction = null;
Port port = getWSDLPort();
if ( port != null ) {
BindingOperation bindingOperation = port.getBinding().getBindingOperation( operationName, null, null );
for( ExtensibilityElement element : (List<ExtensibilityElement>) bindingOperation.getExtensibilityElements() ) {
if ( element instanceof SOAPOperation ) {
soapAction = ((SOAPOperation) element).getSoapActionURI();
}
}
}
if ( soapAction == null ) {
soapAction = getStringParameter( "namespace" ) + "/" + operationName;
}
return soapAction;
}
private Port getWSDLPort()
throws IOException
{
Port port = wsdlPort;
if ( port == null && hasParameter( "wsdl" ) && getParameterFirstValue( "wsdl" ).hasChildren( "port" ) ) {
String portName = getParameterFirstValue( "wsdl" ).getFirstChild( "port" ).strValue();
Definition definition = getWSDLDefinition();
if ( definition != null ) {
Map<QName, Service> services = definition.getServices();
Iterator<Entry<QName, Service>> it = services.entrySet().iterator();
while( port == null && it.hasNext() ) {
port = it.next().getValue().getPort( portName );
}
}
if ( port != null ) {
wsdlPort = port;
}
}
return port;
}
private String getOutputMessageRootElementName( String operationName )
throws IOException
{
String elementName = operationName + ((received) ? "Response" : "");
Port port = getWSDLPort();
if ( port != null ) {
try {
Operation operation = port.getBinding().getPortType().getOperation( operationName, null, null );
Part part = null;
if ( received ) {
// We are sending a response
part = ((Entry<String, Part>) operation.getOutput().getMessage().getParts().entrySet().iterator().next()).getValue();
} else {
// We are sending a request
part = ((Entry<String, Part>) operation.getInput().getMessage().getParts().entrySet().iterator().next()).getValue();
}
elementName = part.getElementName().getLocalPart();
} catch( Exception e ) {
}
}
return elementName;
}
private String getOutputMessageNamespace( String operationName )
throws IOException
{
String messageNamespace = "";
Port port = getWSDLPort();
if ( port == null ) {
if ( hasParameter( "namespace" ) ) {
messageNamespace = getStringParameter( "namespace" );
}
} else {
Operation operation = port.getBinding().getPortType().getOperation( operationName, null, null );
if ( operation != null ) {
Map<String, Part> parts = operation.getOutput().getMessage().getParts();
if ( parts.size() > 0 ) {
Part part = parts.entrySet().iterator().next().getValue();
if ( part.getElementName() == null ) {
messageNamespace = operation.getOutput().getMessage().getQName().getNamespaceURI();
} else {
messageNamespace = part.getElementName().getNamespaceURI();
}
}
}
}
return messageNamespace;
}
private String[] getParameterOrder( String operationName )
throws IOException
{
List<String> parameters = null;
Port port = getWSDLPort();
if ( port != null ) {
Operation operation = port.getBinding().getPortType().getOperation( operationName, null, null );
if ( operation != null ) {
parameters = operation.getParameterOrdering();
}
}
return (parameters == null) ? null : parameters.toArray( new String[0] );
}
private void setOutputEncodingStyle( SOAPEnvelope soapEnvelope, String operationName )
throws IOException, SOAPException
{
Port port = getWSDLPort();
if ( port != null ) {
BindingOperation bindingOperation = port.getBinding().getBindingOperation( operationName, null, null );
if ( bindingOperation == null ) {
return;
}
BindingOutput output = bindingOperation.getBindingOutput();
if ( output == null ) {
return;
}
for( ExtensibilityElement element : (List<ExtensibilityElement>) output.getExtensibilityElements() ) {
if ( element instanceof javax.wsdl.extensions.soap.SOAPBody ) {
List<String> list = ((javax.wsdl.extensions.soap.SOAPBody) element).getEncodingStyles();
if ( list != null && list.isEmpty() == false ) {
soapEnvelope.setEncodingStyle( list.get( 0 ) );
soapEnvelope.addNamespaceDeclaration( "enc", list.get( 0 ) );
}
}
}
}
}
public void send( OutputStream ostream, CommMessage message, InputStream istream )
throws IOException
{
try {
inputId = message.operationName();
String messageNamespace = getOutputMessageNamespace( message.operationName() );
if ( received ) {
// We're responding to a request
inputId += "Response";
}
SOAPMessage soapMessage = messageFactory.createMessage();
SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
setOutputEncodingStyle( soapEnvelope, message.operationName() );
SOAPBody soapBody = soapEnvelope.getBody();
if ( checkBooleanParameter( "wsAddressing" ) ) {
SOAPHeader soapHeader = soapEnvelope.getHeader();
// WS-Addressing namespace
soapHeader.addNamespaceDeclaration( "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing" );
// Message ID
Name messageIdName = soapEnvelope.createName( "MessageID", "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing" );
SOAPHeaderElement messageIdElement = soapHeader.addHeaderElement( messageIdName );
if ( received ) {
// TODO: remove this after we implement a mechanism for being sure message.id() is the one received before.
messageIdElement.setValue( "uuid:1" );
} else {
messageIdElement.setValue( "uuid:" + message.id() );
}
// Action element
Name actionName = soapEnvelope.createName( "Action", "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing" );
SOAPHeaderElement actionElement = soapHeader.addHeaderElement( actionName );
/*
* TODO: the action element could be specified within the
* parameter. Perhaps wsAddressing.action ? We could also allow
* for giving a prefix or a suffix to the operation name, like
* wsAddressing.action.prefix, wsAddressing.action.suffix
*/
actionElement.setValue( message.operationName() );
// From element
Name fromName = soapEnvelope.createName( "From", "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing" );
SOAPHeaderElement fromElement = soapHeader.addHeaderElement( fromName );
Name addressName = soapEnvelope.createName( "Address", "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing" );
SOAPElement addressElement = fromElement.addChildElement( addressName );
addressElement.setValue( "http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous" );
// To element
/*
* if ( operation == null ) { // we are sending a Notification
* or a Solicit Name toName = soapEnvelope.createName("To",
* "wsa", "http://schemas.xmlsoap.org/ws/2004/03/addressing");
* SOAPHeaderElement
* toElement=soapHeader.addHeaderElement(toName);
* toElement.setValue(getURI().getHost()); }
*/
}
if ( message.isFault() ) {
FaultException f = message.fault();
SOAPFault soapFault = soapBody.addFault();
soapFault.setFaultCode( soapEnvelope.createQName( "Server", soapEnvelope.getPrefix() ) );
soapFault.setFaultString( f.getMessage() );
Detail detail = soapFault.addDetail();
DetailEntry de = detail.addDetailEntry( soapEnvelope.createName( f.faultName(), null, messageNamespace ) );
valueToSOAPElement( f.value(), de, soapEnvelope );
} else {
XSSchemaSet sSet = getSchemaSet();
XSElementDecl elementDecl;
String messageRootElementName = getOutputMessageRootElementName( message.operationName() );
if ( sSet == null
|| (elementDecl = sSet.getElementDecl( messageNamespace, messageRootElementName )) == null ) {
Name operationName = null;
soapEnvelope.addNamespaceDeclaration( "xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI );
soapEnvelope.addNamespaceDeclaration( "xsd", XMLConstants.W3C_XML_SCHEMA_NS_URI );
if ( messageNamespace.isEmpty() ) {
operationName = soapEnvelope.createName( messageRootElementName );
} else {
soapEnvelope.addNamespaceDeclaration( "jolieMessage", messageNamespace );
operationName = soapEnvelope.createName( messageRootElementName, "jolieMessage", messageNamespace );
}
SOAPBodyElement opBody = soapBody.addBodyElement( operationName );
String[] parameters = getParameterOrder( message.operationName() );
if ( parameters == null ) {
valueToSOAPElement( message.value(), opBody, soapEnvelope );
} else {
for( String parameterName : parameters ) {
valueToSOAPElement( message.value().getFirstChild( parameterName ), opBody.addChildElement( parameterName ), soapEnvelope );
}
}
} else {
initNamespacePrefixes( soapEnvelope );
if ( hasParameter( Parameters.ADD_ATTRIBUTE ) ) {
Value add_parameter = getParameterFirstValue( Parameters.ADD_ATTRIBUTE );
if ( add_parameter.hasChildren( Parameters.ENVELOPE ) ) {
// attributes must be added to the envelope
ValueVector attributes = add_parameter.getFirstChild( Parameters.ENVELOPE ).getChildren( "attribute" );
for( Value att : attributes ) {
soapEnvelope.addNamespaceDeclaration( att.getFirstChild( "name" ).strValue(), att.getFirstChild( "value" ).strValue() );
}
}
}
boolean wrapped = true;
Value vStyle = getParameterVector( Parameters.STYLE ).first();
if ( "document".equals( vStyle.strValue() ) ) {
wrapped = vStyle.getFirstChild( Parameters.WRAPPED ).boolValue();
}
SOAPElement opBody = soapBody;
if ( wrapped ) {
opBody = soapBody.addBodyElement(
soapEnvelope.createName( messageRootElementName, namespacePrefixMap.get( elementDecl.getOwnerSchema().getTargetNamespace() ), null ) );
// adding forced attributes to operation
if ( hasParameter( Parameters.ADD_ATTRIBUTE ) ) {
Value add_parameter = getParameterFirstValue( Parameters.ADD_ATTRIBUTE );
if ( add_parameter.hasChildren( Parameters.OPERATION ) ) {
ValueVector operations = add_parameter.getChildren( Parameters.OPERATION );
for( Value op : operations ) {
if ( op.getFirstChild( "operation_name" ).strValue().equals( message.operationName() ) ) {
// attributes must be added to the envelope
Value attribute = op.getFirstChild( "attribute" );
QName attrName;
if ( attribute.hasChildren( "prefix" ) ) {
attrName = opBody.createQName( attribute.getFirstChild( "name" ).strValue(), attribute.getFirstChild( "prefix" ).strValue() );
} else {
attrName = opBody.createQName( attribute.getFirstChild( "name" ).strValue(), null );
}
opBody.addAttribute( attrName, attribute.getFirstChild( "value" ).strValue() );
}
}
}
}
}
valueToTypedSOAP( message.value(), elementDecl, opBody, soapEnvelope, !wrapped, sSet, messageNamespace );
}
}
if ( soapEnvelope.getHeader().hasChildNodes() == false ) {
// Some service implementations do not like empty headers
soapEnvelope.getHeader().detachNode();
}
ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
soapMessage.writeTo( tmpStream );
String soapString = CRLF + "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ new String( tmpStream.toByteArray() );
String messageString = "";
String soapAction = null;
if ( received ) {
// We're responding to a request
messageString += "HTTP/1.1 200 OK" + CRLF;
received = false;
} else {
// We're sending a notification or a solicit
String path = uri.getPath(); // TODO: fix this to consider resourcePaths
if ( path == null || path.length() == 0 ) {
path = "*";
}
messageString += "POST " + path + " HTTP/1.1" + CRLF;
messageString += "Host: " + uri.getHost() + CRLF;
/*
* soapAction = "SOAPAction: \"" + messageNamespace + "/" +
* message.operationName() + '\"' + CRLF;
*/
soapAction = "SOAPAction: \"" + getSoapActionForOperation( message.operationName() ) + '\"' + CRLF;
}
if ( getParameterVector( "keepAlive" ).first().intValue() != 1 ) {
channel().setToBeClosed( true );
messageString += "Connection: close" + CRLF;
}
//messageString += "Content-Type: application/soap+xml; charset=\"utf-8\"\n";
messageString += "Content-Type: text/xml; charset=\"utf-8\"" + CRLF;
messageString += "Content-Length: " + soapString.length() + CRLF;
if ( soapAction != null ) {
messageString += soapAction;
}
messageString += soapString + CRLF;
if ( getParameterVector( "debug" ).first().intValue() > 0 ) {
interpreter.logInfo( "[SOAP debug] Sending:\n" + tmpStream.toString() );
}
inputId = message.operationName();
Writer writer = new OutputStreamWriter( ostream );
writer.write( messageString );
writer.flush();
} catch( SOAPException se ) {
throw new IOException( se );
} catch( SAXException saxe ) {
throw new IOException( saxe );
}
}
private void xmlNodeToValue( Value value, Node node )
{
String type = "xsd:string";
Node currNode;
// Set attributes
NamedNodeMap attributes = node.getAttributes();
if ( attributes != null ) {
for( int i = 0; i < attributes.getLength(); i++ ) {
currNode = attributes.item( i );
if ( "type".equals( currNode.getNodeName() ) == false && convertAttributes() ) {
getAttribute( value, currNode.getNodeName() ).setValue( currNode.getNodeValue() );
} else {
type = currNode.getNodeValue();
}
}
}
// Set children
NodeList list = node.getChildNodes();
Value childValue;
for( int i = 0; i < list.getLength(); i++ ) {
currNode = list.item( i );
switch( currNode.getNodeType() ) {
case Node.ELEMENT_NODE:
childValue = value.getNewChild( currNode.getLocalName() );
xmlNodeToValue( childValue, currNode );
break;
case Node.TEXT_NODE:
value.setValue( currNode.getNodeValue() );
break;
}
}
if ( "xsd:int".equals( type ) ) {
value.setValue( value.intValue() );
} else if ( "xsd:double".equals( type ) ) {
value.setValue( value.doubleValue() );
} else if ( "xsd:boolean".equals( type ) ) {
value.setValue( value.boolValue() );
}
}
private static Element getFirstElement( Node node )
{
NodeList nodes = node.getChildNodes();
for( int i = 0; i < nodes.getLength(); i++ ) {
if ( nodes.item( i ).getNodeType() == Node.ELEMENT_NODE ) {
return (Element) nodes.item( i );
}
}
return null;
}
/*
* private Schema getRecvMessageValidationSchema() throws IOException {
* List< Source > sources = new ArrayList< Source >(); Definition definition
* = getWSDLDefinition(); if ( definition != null ) { Types types =
* definition.getTypes(); if ( types != null ) { List< ExtensibilityElement
* > list = types.getExtensibilityElements(); for( ExtensibilityElement
* element : list ) { if ( element instanceof SchemaImpl ) { sources.add(
* new DOMSource( ((SchemaImpl)element).getElement() ) ); } } } }
* SchemaFactory schemaFactory = SchemaFactory.newInstance(
* XMLConstants.W3C_XML_SCHEMA_NS_URI ); try { return
* schemaFactory.newSchema( sources.toArray( new Source[sources.size()] ) );
* } catch( SAXException e ) { throw new IOException( e ); } }
*/
public CommMessage recv( InputStream istream, OutputStream ostream )
throws IOException
{
HttpParser parser = new HttpParser( istream );
HttpMessage message = parser.parse();
HttpUtils.recv_checkForChannelClosing( message, channel() );
CommMessage retVal = null;
String messageId = message.getPropertyOrEmptyString( "soapaction" );
FaultException fault = null;
Value value = Value.create();
try {
if ( message.content() != null && message.content().length > 0 ) {
if ( checkBooleanParameter( "debug" ) ) {
interpreter.logInfo( "[SOAP debug] Receiving:\n" + new String( message.content(), "UTF8" ) );
}
SOAPMessage soapMessage = messageFactory.createMessage();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
/*
* Schema messageSchema = getRecvMessageValidationSchema(); if (
* messageSchema != null ) {
* factory.setIgnoringElementContentWhitespace( true );
* factory.setSchema( messageSchema ); }
*/
factory.setNamespaceAware( true );
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource src = new InputSource( new ByteArrayInputStream( message.content() ) );
Document doc = builder.parse( src );
DOMSource dom = new DOMSource( doc );
soapMessage.getSOAPPart().setContent( dom );
/*
* if ( checkBooleanParameter( "debugAfter" ) ) {
* ByteArrayOutputStream tmpStream = new
* ByteArrayOutputStream(); soapMessage.writeTo( tmpStream );
* interpreter.logInfo( "[SOAP debug] Receiving:\n" +
* tmpStream.toString() ); }
*/
SOAPFault soapFault = soapMessage.getSOAPBody().getFault();
if ( soapFault == null ) {
Element soapValueElement = getFirstElement( soapMessage.getSOAPBody() );
messageId = soapValueElement.getLocalName();
xmlNodeToValue( value, soapValueElement );
ValueVector schemaPaths = getParameterVector( "schema" );
if ( schemaPaths.size() > 0 ) {
List<Source> sources = new LinkedList<Source>();
Value schemaPath;
for( int i = 0; i < schemaPaths.size(); i++ ) {
schemaPath = schemaPaths.get( i );
if ( schemaPath.getChildren( "validate" ).first().intValue() > 0 ) {
sources.add( new StreamSource( new File( schemaPaths.get( i ).strValue() ) ) );
}
}
if ( !sources.isEmpty() ) {
Schema schema =
SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ).newSchema( sources.toArray( new Source[0] ) );
schema.newValidator().validate( new DOMSource( soapMessage.getSOAPBody().getFirstChild() ) );
}
}
} else {
String faultName = "UnknownFault";
Value faultValue = Value.create();
Detail d = soapFault.getDetail();
if ( d != null ) {
Node n = d.getFirstChild();
if ( n != null ) {
faultName = n.getLocalName();
xmlNodeToValue(
faultValue, n );
} else {
faultValue.setValue( soapFault.getFaultString() );
}
}
fault = new FaultException( faultName, faultValue );
}
}
String resourcePath = recv_getResourcePath( message );
if ( message.isResponse() ) {
if ( fault != null && message.statusCode() == 500 ) {
fault = new FaultException( "InternalServerError", "" );
}
retVal = new CommMessage( CommMessage.GENERIC_ID, inputId, resourcePath, value, fault );
} else if ( !message.isError() ) {
if ( messageId.isEmpty() ) {
throw new IOException( "Received SOAP Message without a specified operation" );
}
retVal = new CommMessage( CommMessage.GENERIC_ID, messageId, resourcePath, value, fault );
}
} catch( SOAPException e ) {
throw new IOException( e );
} catch( ParserConfigurationException e ) {
throw new IOException( e );
} catch( SAXException e ) {
//TODO support resourcePath
retVal = new CommMessage( CommMessage.GENERIC_ID, messageId, "/", value, new FaultException( "TypeMismatch", e ) );
}
received = true;
if ( "/".equals( retVal.resourcePath() ) && channel().parentPort() != null
&& channel().parentPort().getInterface().containsOperation( retVal.operationName() ) ) {
try {
// The message is for this service
Interface iface = channel().parentPort().getInterface();
OneWayTypeDescription oneWayTypeDescription = iface.oneWayOperations().get( retVal.operationName() );
if ( oneWayTypeDescription != null && message.isResponse() == false ) {
// We are receiving a One-Way message
oneWayTypeDescription.requestType().cast( retVal.value() );
} else {
RequestResponseTypeDescription rrTypeDescription = iface.requestResponseOperations().get( retVal.operationName() );
if ( retVal.isFault() ) {
Type faultType = rrTypeDescription.faults().get( retVal.fault().faultName() );
if ( faultType != null ) {
faultType.cast( retVal.value() );
}
} else {
if ( message.isResponse() ) {
rrTypeDescription.responseType().cast( retVal.value() );
} else {
rrTypeDescription.requestType().cast( retVal.value() );
}
}
}
} catch( TypeCastingException e ) {
// TODO: do something here?
}
}
return retVal;
}
private String recv_getResourcePath( HttpMessage message )
{
String ret = "/";
if ( checkBooleanParameter( "interpretResource" ) ) {
ret = message.requestPath();
}
return ret;
}
}