////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2005-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package mx.rpc.soap
{
import flash.xml.XMLDocument;
import flash.xml.XMLNode;
import mx.core.mx_internal;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.rpc.soap.types.ICustomSOAPType;
import mx.rpc.wsdl.WSDLConstants;
import mx.rpc.wsdl.WSDLEncoding;
import mx.rpc.wsdl.WSDLOperation;
import mx.rpc.wsdl.WSDLMessage;
import mx.rpc.wsdl.WSDLMessagePart;
import mx.rpc.xml.Schema;
import mx.rpc.xml.SchemaConstants;
import mx.rpc.xml.SchemaDatatypes;
import mx.rpc.xml.SchemaMarshaller;
import mx.rpc.xml.XMLEncoder;
[ResourceBundle("rpc")]
[ExcludeClass]
/**
* A SOAPEncoder is used to create SOAP 1.1 formatted requests for a web service
* operation. A WSDLOperation provides the definition of how SOAP request should
* be formatted and thus must be set before a call is made to encode().
*
* TODO: Create a SOAP 1.2 specific subclass of this encoder.
*
* @private
*/
public class SOAPEncoder extends XMLEncoder implements ISOAPEncoder
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function SOAPEncoder()
{
super();
log = Log.getLogger("mx.rpc.soap.SOAPEncoder");
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* Determines whether the encoder should ignore whitespace when
* constructing an XML representation of a SOAP request.
* The default is true and thus whitespace is not preserved.
* If an XML Schema type definition specifies a whiteSpace
* restriction set to preserve then ignoreWhitespace must
* first be set to false. Conversely, if a type whiteSpace
* restriction is set to replace or collapse then
* that setting will be honored even if ignoreWhitespace is set to false.
*/
public function get ignoreWhitespace():Boolean
{
return _ignoreWhitespace;
}
public function set ignoreWhitespace(value:Boolean):void
{
_ignoreWhitespace = value;
}
/**
* @private
*/
protected function get inputEncoding():WSDLEncoding
{
return _wsdlOperation.inputMessage.encoding;
}
public function get schemaConstants():SchemaConstants
{
return schemaManager.schemaConstants;
}
public function get soapConstants():SOAPConstants
{
return wsdlOperation.soapConstants;
}
public function get wsdlOperation():WSDLOperation
{
return _wsdlOperation;
}
public function set wsdlOperation(value:WSDLOperation):void
{
_wsdlOperation = value;
schemaManager = _wsdlOperation.schemaManager;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Creates a SOAP encodes request to an operation from the given input
* parameters and headers.
*/
public function encodeRequest(args:* = null, headers:Array = null):XML
{
// FIXME: Some level of validation should occur here
// to check we're ready to start encoding.
reset();
var envelopeXML:XML;
// Keep track of the previous ignoreWhitespace setting (as it is
// unfortunately a static API on the intrinsic XML type) so we
// can set it back to its original state once we're finished.
var oldIgnoreWhitespace:Boolean = XML.ignoreWhitespace;
var oldPrettyPrinting:Boolean = XML.prettyPrinting;
try
{
XML.ignoreWhitespace = ignoreWhitespace;
XML.prettyPrinting = false;
envelopeXML = encodeEnvelope(args, headers);
}
finally
{
XML.ignoreWhitespace = oldIgnoreWhitespace;
XML.prettyPrinting = oldPrettyPrinting;
}
return envelopeXML;
}
/**
* A SOAP Envelope element is the root element of a SOAP message. It
* must specify the SOAP namespace.
*/
protected function encodeEnvelope(args:*, headers:Array):XML
{
log.debug("Encoding SOAP request envelope");
var envelopeXML:XML = <{soapConstants.envelopeQName.localName}/>;
envelopeXML.setNamespace(soapConstants.envelopeNamespace);
envelopeXML.addNamespace(schemaConstants.xsdNamespace);
envelopeXML.addNamespace(schemaConstants.xsiNamespace);
// Make sure the envelope namespace is registered with the schemaManager
// so that its prefix can be looked up when encoding header attributes.
schemaManager.namespaces[soapConstants.envelopeNamespace.prefix] =
soapConstants.envelopeNamespace;
encodeHeaders(headers, envelopeXML);
encodeBody(args, envelopeXML);
return envelopeXML;
}
/**
* Appends SOAP Header to the SOAP Envelope
*/
protected function encodeHeaders(headers:Array, envelopeXML:XML):void
{
if (headers != null)
{
var count:uint = headers.length;
if (count > 0)
{
var headersXML:XML = <{soapConstants.headerQName.localName}/>
headersXML.setNamespace(soapConstants.envelopeNamespace);
envelopeXML.appendChild(headersXML);
for (var i:uint = 0; i < count; i++)
{
encodeHeaderElement(headers[i], headersXML);
}
}
}
}
/**
* Appends a header element to top SOAP Header tag
*/
protected function encodeHeaderElement(header:Object, headersXML:XML):void
{
var preEncodedNode:* = preEncodedCheck(header);
if (preEncodedNode != null)
{
headersXML.appendChild(header);
}
else
{
var headerElement:XMLList = new XMLList();
// header value might contain a mapping
if (header.content != null && header.content.hasOwnProperty(header.qname.localName))
{
header.content = header.content[header.qname.localName];
}
// check the content of the header for pre-encoded values
preEncodedNode = preEncodedCheck(header.content);
if (preEncodedNode != null)
{
// We can't set namespaces or attributes on XMLList, so we
// just append it and return...
if (preEncodedNode is XMLList)
{
headerElement = preEncodedNode as XMLList;
}
else
{
headerElement = new XMLList(preEncodedNode);
}
}
else
{
// If not pre-encoded, we encode the header value. If there is a
// definition in the schema for this header QName, we will use it,
// otherwise encode with anyType. This is handled by XMLEncoder.
headerElement = encode(header.content, header.qname);
}
// Typically headerElement will be an XMLList of length 1, but we
// loop through it anyway, in case we were given a pre-encoded XMLList,
// or an array value for the header.content
for each (var headerElementNode:XML in headerElement)
{
// add namespace to the header, if not already set by encoding.
if (header.qname.uri != null && header.qname.uri.length > 0
&& headerElementNode.namespace().uri != header.qname.uri)
{
var prefix:String = schemaManager.getOrCreatePrefix(header.qname.uri);
var ns:Namespace = new Namespace(prefix, header.qname.uri);
headerElementNode.setNamespace(ns);
}
// add header attributes
var attrStr:String;
if (header.mustUnderstand)
{
attrStr = schemaManager.getOrCreatePrefix(soapConstants.mustUnderstandQName.uri);
headerElementNode.@[attrStr + ":" + soapConstants.mustUnderstandQName.localName] = "1"; // Use "1" form for WS-I compatibility
}
if (header.role != null)
{
attrStr = schemaManager.getOrCreatePrefix(soapConstants.actorQName.uri);
headerElementNode.@[attrStr + ":" + soapConstants.actorQName.localName] = header.role;
}
}
// finally, add header list to SOAP Header node
headersXML.appendChild(headerElement);
}
}
/**
* Encodes the SOAP Body. Currently assumes only one operation sub-element.
*/
protected function encodeBody(inputParams:*, envelopeXML:XML):void
{
log.debug("Encoding SOAP request body");
// Create SOAP Body element
var bodyXML:XML = <{soapConstants.bodyQName.localName}/>;
bodyXML.setNamespace(soapConstants.envelopeNamespace);
// FIXME: Should we continue to support pre-encoded XML fragments here?
// How would we tell the difference between an XML type input and
// a pre-encoded message?
// Special case handling for input params that are pre-encoded SOAP
// body contents.
var preEncoded:Object = preEncodedCheck(inputParams);
if (preEncoded != null)
{
bodyXML.appendChild(preEncoded);
}
else
{
// Encode the operation and append it to the body.
if (wsdlOperation.style == SOAPConstants.DOC_STYLE)
{
if (inputEncoding.useStyle == SOAPConstants.USE_LITERAL)
{
encodeOperationAsDocumentLiteral(inputParams, bodyXML);
}
else
{
//Note: document-encoded not support by WSDL 1.1
throw new Error("WSDL 1.1 supports operations with binding style 'document' only if use style is 'literal'.");
}
}
else if (wsdlOperation.style == SOAPConstants.RPC_STYLE)
{
if (inputEncoding.useStyle == SOAPConstants.USE_LITERAL)
{
encodeOperationAsRPCLiteral(inputParams, bodyXML);
}
else if (inputEncoding.useStyle == SOAPConstants.USE_ENCODED)
{
encodeOperationAsRPCEncoded(inputParams, bodyXML);
}
else
{
throw new Error("WSDL 1.1 does not support operations with binding style 'rpc' and use style " + inputEncoding.useStyle + ".");
}
}
else
{
throw new Error("Unrecognized binding style '" + wsdlOperation.style + "'. Only 'document' and 'rpc' styles are supported.");
}
}
// Add the fully encoded body to the envelope.
envelopeXML.appendChild(bodyXML);
}
/**
* Encodes a WSDL operation using document literal format.
* There's no need to generate an operation element so advance directly
* to encoding the message.
*
* From the WSDL 1.1 specification: *
*
* "If use is literal, then each part references
* a concrete schema definition using either the element or
* type attribute. In the first case, the element referenced
* by the part will appear directly under the Body element (for document
* style bindings)... In the second, the type referenced by the part
* becomes the schema type of the enclosing element (Body for document
* style...)."
*
* From the WSDL 1.1 specification: *
*
* "If the operation style is rpc each part is a parameter
* or a return value and appears inside a wrapper element within the body
* (following Section 7.1 of the SOAP specification). The wrapper element
* is named identically to the operation name and its namespace is the
* value of the namespace attribute. Each message part (parameter) appears
* under the wrapper, represented by an accessor named identically to the
* corresponding parameter of the call. Parts are arranged in the same
* order as the parameters of the call."
*
* "If use is literal, then each part references
* a concrete schema definition using either the element or
* type attribute. In the first case, the element referenced
* by the part will appear ... under an accessor element named after the
* message part (in rpc style). In the second, the type referenced by the
* part becomes the schema type of the enclosing element ( ... part accessor
* element for rpc style)."
*
* From the WSDL 1.1 specification: *
*
* "If the operation style is rpc each part is a parameter
* or a return value and appears inside a wrapper element within the body
* (following Section 7.1 of the SOAP specification). The wrapper element
* is named identically to the operation name and its namespace is the
* value of the namespace attribute. Each message part (parameter) appears
* under the wrapper, represented by an accessor named identically to the
* corresponding parameter of the call. Parts are arranged in the same
* order as the parameters of the call."
*
* "If use is encoded, then each message part
* references an abstract type using the type attribute. These
* abstract types are used to produce a concrete message by applying an
* encoding specified by the encodingStyle attribute. The part
* names, types and value of the namespace attribute are all inputs to the
* encoding, although the namespace attribute only applies to content not
* explicitly defined by the abstract types. If the referenced encoding
* style allows variations in it's format (such as the SOAP encoding does),
* then all variations MUST be supported ("reader makes right")."
*
encoded.
*
* @private
*/
protected override function deriveXSIType(parent:XML, type:QName, value:*):void
{
// Add XSI type if the SOAP message has use style as "encoded"
if (isSOAPEncoding)
{
var datatypes:SchemaDatatypes = schemaManager.schemaDatatypes;
var soapType:QName;
// HACK: For anyType and anySimpleType, try to guess the schema
// type for simple values (note we can't guess for complex values).
if (type == datatypes.anyTypeQName || type == datatypes.anySimpleTypeQName)
{
if (isSimpleValue(value) || type == datatypes.anySimpleTypeQName)
{
var localName:String = SchemaMarshaller.guessSimpleType(value);
soapType = new QName(schemaConstants.xsdURI, localName);
}
}
else
{
soapType = type;
}
if (soapType != null)
{
// FIXME: should be OK to use setXSIType
// setXSIType(parent, soapType);
parent.@[schemaConstants.getXSIToken(schemaConstants.typeAttrQName)] = schemaConstants.getXSDToken(soapType);
}
}
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
private var _ignoreWhitespace:Boolean = true;
private var isSOAPEncoding:Boolean = false;
private var log:ILogger;
private var _wsdlOperation:WSDLOperation;
}
}