Rewriting the SAP WSDL file
From the service configuration created in PI Directory or transaction soamanager, copy the WSDL url and open it in the browser. As Metro requires the policy information, the WSDL must be rewritten to Policy 1.5 by using an XSLT as described below. XSLT is a generic approach of extracting XML data and rewriting XML documents. An XSLT transformation can be called in different ways. Below you find examples how to invoke it from XSML Spy, a Java application or an ABAP application.
Please ensure only one binding is included in the WSDL. When using soamanager, include a single endpoint in a service. Configuration created by PI Directory will allways include one binding per WSDL.
It is recommended to create WSDL files without import statements. This will avoid authentication issues as otherwise Metro will try to resolve the WSDL during runtime. Using the Url parameters, AS ABAP generates WSDLs with or without import statements. To generate a WSDL without import statments, use the URL containing the allinone directive:
host:port/sap/bc/srt/wsdl/srvc_.../wsdl11/allinone/ws_policy/document?sap-client=000
XSLT
Use the XSLT below for rewriting the WSDL.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:wsp15="http://www.w3.org/ns/ws-policy" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsuOld="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sapsp="http://www.sap.com/webas/630/soap/features/security/policy" xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" version="1.0"> <xsl:output method="xml" media-type="text/xml" indent="no" omit-xml-declaration="yes"/> <xsl:template match="*"> <!-- copy security policy element --> <xsl:choose> <xsl:when test="name()='wsp:UsingPolicy'"> <wsp15:UsingPolicy xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" wsdl:required="true"/> </xsl:when> <xsl:when test="name()='wsp:ExactlyOne'"> <wsp15:ExactlyOne xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702" xmlns:wsp15="http://www.w3.org/ns/ws-policy"> <xsl:call-template name="copy_selected_attributes"/> <xsl:apply-templates/> </wsp15:ExactlyOne> <xsl:comment> <xsl:value-of select="count(*/sp:SecureConversationToken)"/> </xsl:comment> </xsl:when> <xsl:when test="name()='wsp:All'"> <wsp15:All xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702" xmlns:wsp15="http://www.w3.org/ns/ws-policy"> <xsl:call-template name="copy_selected_attributes"/> <xsl:apply-templates/> </wsp15:All> </xsl:when> <xsl:when test="name()='wsp:Policy' and count(./wsp:PolicyReference)>0"> <wsp15:PolicyReference> <xsl:copy-of select="./wsp:PolicyReference/@*"/> </wsp15:PolicyReference> </xsl:when> <xsl:when test="name()='wsp:Policy' and count(./wsp:PolicyReference)=0 and count(./wsp:ExactlyOne)=0"> <wsp15:Policy xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"> <xsl:call-template name="copy_selected_attributes"/> <wsp15:ExactlyOne> <wsp15:All> <xsl:apply-templates/> </wsp15:All> </wsp15:ExactlyOne> </wsp15:Policy> </xsl:when> <xsl:when test="name()='wsp:Policy'"> <wsp15:Policy xmlns:wsp15="http://www.w3.org/ns/ws-policy"> <xsl:call-template name="copy_selected_attributes"/> <xsl:apply-templates/> </wsp15:Policy> </xsl:when> <xsl:when test="name()='saptrnbnd:OptimizedXMLTransfer'"> <!--xsl:comment>skip saptrnbnd:OptimizedXMLTransfer</xsl:comment--> </xsl:when> <!-- add addressing --> <xsl:when test="name()='sp:Trust13'"> <sp:Trust13> <!--copy attributes as well --> <xsl:call-template name="copy_selected_attributes"/> <!--trigger recursion of elements --> <xsl:apply-templates/> </sp:Trust13> <wsaw:UsingAddressing xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" wsp:Optional="true"/> </xsl:when> <xsl:when test="name()='wsdl:binding'"> <wsdl:binding> <xsl:copy-of select="@type"/> <xsl:attribute name="name">binding</xsl:attribute> <xsl:apply-templates/> </wsdl:binding> </xsl:when> <xsl:when test="name()='wsdl:service'"> <wsdl:service> <xsl:attribute name="name">service</xsl:attribute> <xsl:apply-templates/> </wsdl:service> </xsl:when> <xsl:when test="name()='wsdl:port'"> <wsdl:port> <xsl:attribute name="name">binding</xsl:attribute> <xsl:attribute name="binding">tns:binding</xsl:attribute> <xsl:apply-templates/> </wsdl:port> </xsl:when> <xsl:otherwise> <xsl:copy> <!--copy attributes as well --> <xsl:call-template name="copy_selected_attributes"/> <!--trigger recursion of elements --> <xsl:apply-templates/> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="copy_selected_attributes"> <xsl:copy-of select="@*[name() != 'wsp:Optional' and name() != 'wsuOld:Id']"/> <xsl:if test="count(@wsp:Optional)=1"> <xsl:attribute name="wsp15:Optional"><xsl:value-of select="@wsp:Optional"/></xsl:attribute> </xsl:if> <xsl:if test="count(@wsuOld:Id)=1 and string-length(@wsuOld:Id)>0"> <xsl:attribute name="wsu:Id"><xsl:value-of select="@wsuOld:Id"/></xsl:attribute> </xsl:if> </xsl:template> </xsl:stylesheet>
1) The simple way: using XML Spy
A simple approachis to run the transformation in XMLSpy.
To rewrite the WSDL using XMLSpy:
- Download the WSDL and save it as a file
- Open the WSDL file in XMLSpy and select menu XSL->XSL Transformation
- Use the XSLT below and save the result in a file
2) The Java way
To rewrite the WSDL using an Java program:
- Copy the file below into a Java IDE
- Copy the XSLT and save it as "p12-p15.xslt" in the same package
- Adopt the main method an specify:
- wsdl url
- username for WSDL access
- password for WSDL access
- Location to store the SAP-WSDL
- Location to store the rewritten WSDL
package rewriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; 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.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.xml.sax.SAXException; public class WSDLRewriter { private static Transformer transformer12deserialize; public static void rewriteWSDL(String url, String sapWsdlPath, String metroWsdlPath, final String username, final String password) throws MalformedURLException, IOException, TransformerConfigurationException, ParserConfigurationException, SAXException, TransformerException { /* * add authentication */ url += "&sap-user=" + username + "&sap-password=" + password; /* * Read wsdl file and store it */ URL urlObject = new URL(url); HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection(); int responseCode = connection.getResponseCode(); if (responseCode == 200) { InputStream is = connection.getInputStream(); int data; ByteArrayOutputStream bos = new ByteArrayOutputStream(); while ((data = is.read()) != -1) { bos.write(data); } bos.close(); byte[] wsdlData = bos.toByteArray(); FileOutputStream fos = new FileOutputStream(sapWsdlPath); fos.write(wsdlData); fos.close(); /* * Run XSLT */ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder newDocumentBuilder = dbf.newDocumentBuilder(); Document wsdlDocument = newDocumentBuilder.parse(new ByteArrayInputStream(wsdlData)); DOMSource wsdlSource = new DOMSource(wsdlDocument.getDocumentElement()); DOMResult wsdlResult = new DOMResult(); TransformerFactory tf = TransformerFactory.newInstance(); transformer12deserialize = tf.newTransformer( new StreamSource(WSDLRewriter.class.getResourceAsStream("p12-p15.xslt"))); transformer12deserialize.transform(wsdlSource, wsdlResult); fos = new FileOutputStream(metroWsdlPath); write(wsdlResult.getNode(), fos); fos.close(); } else { throw new IOException("Error reading WSDL. HTTP Response code:" + connection.getResponseMessage()); } } static private void write(org.w3c.dom.Node document, OutputStream os) throws TransformerFactoryConfigurationError, TransformerException, IOException { /* Create output container */ StreamResult streamResult = new StreamResult(os); /* Prepare input data */ Source source = new DOMSource(document); TransformerFactory tf = TransformerFactory.newInstance(); Transformer tr = tf.newTransformer(); tr.setOutputProperty(OutputKeys.METHOD, "xml"); tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); tr.setOutputProperty(OutputKeys.STANDALONE, "yes"); tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tr.setOutputProperty(OutputKeys.INDENT, "no"); /* Convert */ tr.transform(source, streamResult); os.close(); } public static void main(String args[]) throws Exception { // WSDL url String wsdlUrl = "..."; // user for WSDL access String username = "WSS_ABAP"; // password for WSDL access String password = "abcd1234"; // location where SAP WSDL will be downloaded String sapWsdlFile = "c:/temp/wsdl/sap.wsdl"; // location where rewritten WSDL will be stored String metroWsdlFile = "c:/temp/wsdl/sap.wsdl"; /* * Rewrite WSDL file */ WSDLRewriter.rewriteWSDL(wsdlUrl,sapWsdlFile, metroWsdlFile, username, password); } }
3) The ABAP way
The ABAP approach is to provide the WSDL by URL. To achive this, a HTTP handler is created that will take the following step to rewrite the WSDL.
- Create an CL_HTTP_CLIENT and read the read the WSDL from the same ABAP system using destination NONE
- Transform it using the XSLT transformation
- Write the transformed WSDL
Implementation: IF_HTTP_EXTENSION
This solution is implemented as an ICF node. First create a class implementing IF_HTTP_EXTENSION. Use the coding below for IF_HTTP_EXTENSION~HANDLE_REQUEST.
SPAN { font-family: "Courier New"; font-size: 10pt; color: #000000; background: #FFFFFF; } .L1S31 { font-style: italic; color: #808080; } .L1S32 { color: #3399FF; } .L1S33 { color: #4DA619; } .L1S52 { color: #0000FF; } method if_http_extension~handle_request. data: lr_http_client type ref to if_http_client, lf_url type string, lf_subrc type int4, lf_errortext type string, lf_wsdl_data type xstring, lf_rewritewsdl type xstring. " " - 1 - get url " server->request->get_form_field( exporting name = 'url' receiving value = lf_url exceptions others = 1 ). do strlen( lf_url ) times. if lf_url+sy-index(1) = '/' and sy-index > 8. lf_url = lf_url+sy-index. exit. endif. enddo. if sy-subrc <> 0 or lf_url is initial. call method server->response->set_cdata( data = 'error: parameter url not set' ). return. endif. " " - 2 - read data " cl_http_client=>create_by_destination( exporting destination = 'NONE' importing client = lr_http_client ). cl_http_utility=>set_request_uri( request = lr_http_client->request uri = lf_url ). * Send lr_http_client->request->set_method( if_http_request=>co_request_method_get ). call method lr_http_client->send exporting timeout = 60 exceptions http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 others = 4. if sy-subrc <> 0. call method lr_http_client->get_last_error importing code = lf_subrc message = lf_errortext. call method server->response->set_cdata( data = 'communication_error( send )' && lf_errortext ). return. exit. endif. * receive call method lr_http_client->receive exceptions http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 others = 4. if sy-subrc <> 0. call method lr_http_client->get_last_error importing code = lf_subrc message = lf_errortext. call method server->response->set_cdata( data = 'communication_error( send )' && lf_errortext ). return. endif. lr_http_client->response->get_data( receiving data = lf_wsdl_data exceptions others = 1 ). if sy-subrc <> 0. call method server->response->set_data( data = lf_wsdl_data ). return. endif. * close call method lr_http_client->close exceptions http_invalid_state = 1 others = 2. if sy-subrc <> 0. call method lr_http_client->get_last_error importing code = lf_subrc message = lf_errortext. call method server->response->set_cdata( data = 'communication_error( close )' && lf_errortext ). return. endif. " " - 3 - call transformation " call transformation z_p12_p15 source xml lf_wsdl_data result xml lf_rewritewsdl. server->response->set_content_type('text/xml'). server->response->set_data( exporting data = lf_rewritewsdl ). endmethod.
Implementation: XSLT transformation
Using transaction STRANS to create a new XSLT transformation object and add the XSLT above. To match the coding above, use name z_p12_p15 as transformation name.
Implementation: SICF node
Finally, create an ICF node using the ABAP class as handler.
Rewriting WSDLs
To rewrite a WSDL, call the ICF handler by passing the wsdl-url as parameter url. The url will depend on the name of the created ICF node, i.e.
NetBeans is not able to handle WSDL files requiring authentication. There are multiple solutions to this:
- Download the WSDL and specify the file
- Add &sap-user=<username>&sap-password=<password> to the url
- Add a service user in the icf node and select service user authentication