An example of UTL_DBWS package can be found on steve's blog here. That shows how to set it up at the database level in 11g.
Lets assume we have a Web Service deployed to OAS 10.1.3.x with a class as follows. Basic method which takes a org.w3c.dom.Element parameter for it's only service method testXML.
package pas.au.xml.ws;
import org.w3c.dom.Element;
public class XMLWebService
{
public XMLWebService()
{
}
public String testXML (Element xmlInput)
{
return "success";
}
}
With UTL_DBWS installed we could simply write a PLSQL block as follows to call out Web Service method and the output clearly shows this works fine.
set serveroutput on size 100000
set linesize 130
declare
service_ sys.utl_dbws.SERVICE;
call_ sys.utl_dbws.CALL;
service_qname sys.utl_dbws.QNAME;
port_qname sys.utl_dbws.QNAME;
response sys.XMLTYPE;
request sys.XMLTYPE;
inputXML varchar2(300) := null;
myXML varchar2(100) := 'pas';
begin
dbms_output.put_line('Calling Web Service http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPor');
service_qname := sys.utl_dbws.to_qname(null, 'testXMLElement');
service_ := sys.utl_dbws.create_service(service_qname);
call_ := sys.utl_dbws.create_call(service_);
sys.utl_dbws.set_target_endpoint_address(call_, 'http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPort');
sys.utl_dbws.set_property( call_, 'OPERATION_STYLE', 'document');
dbms_output.put_line('Preparing request');
inputXML := '<ns1:testXMLElement xmlns:ns1="http://pas.au.xml.ws/types/"><ns1:xmlInput><pas>'||
myXML||
'</pas></ns1:xmlInput>'||
'</ns1:testXMLElement>';
request := sys.XMLTYPE(inputXML);
response := sys.utl_dbws.invoke(call_, request);
dbms_output.put_line('** Soap Response **'||chr(10)||response.getStringVal());
dbms_output.put_line('Result = '||
response.extract('//ns0:result/child::text()',
'xmlns:ns0="http://pas.au.xml.ws/types/"').getstringval());
end;
/
show errors;
Output
SCOTT@linux11gr2> @invoke_xmlws.sql Calling Web Service http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPor Preparing request ** Soap Response ** <ns0:testXMLResponseElement xmlns:ns0="http://pas.au.xml.ws/types/"> <ns0:result>success</ns0:result> </ns0:testXMLResponseElement> Result = success PL/SQL procedure successfully completed. No errors. SCOTT@linux11gr2>
The problem here is we can't just assume that the data which we pass into theWeb Service method is not a special character which in the XML world would require that it be escaped. The data we passed in our first test was a simple string such as "pas" as shown below.
myXML varchar2(100) := 'pas';
Lets assume we now want to pass data as follows by introducing a & character. The assumption here is we can't control what is passed to the method so it may be a special character which XML won't accept.
myXML varchar2(100) := 'pas&';
The result when running the PLSQL Block now is a runtime exception as follows.
SCOTT@linux11gr2> @invoke_xmlws.sql
Calling Web Service http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPor
Preparing request
declare
*
ERROR at line 1:
ORA-31011: XML parsing failed
ORA-19202: Error occurred in XML processing
LPX-00242: invalid use of ampersand ('&') character (use &)
Error at line 1
ORA-06512: at "SYS.XMLTYPE", line 310
ORA-06512: at line 29
No errors.
SCOTT@linux11gr2>
In fact if you try this outside of PLSQL / Oracle RDBMS itself, such as a J2SE client you would get a runtime exception as follows. The database error was a lot more useful then this.
java.security.PrivilegedActionException: javax.xml.soap.SOAPException: Unable to get header stream in saveChanges. SOAP exception while trying to externalize: Error parsing envelope: (1, 1) Start of root element expected.. java.io.IOException: SOAP exception while trying to externalize: Error parsing envelope: (1, 1) Start of root element expected.
So the solution around this is to use the package method dbms_xmlgen.CONVERT to convert the XML data into the escaped XML equivalent. So now our SQL is as follows
set serveroutput on size 100000
set linesize 130
call dbms_java.set_output(100000);
declare
service_ sys.utl_dbws.SERVICE;
call_ sys.utl_dbws.CALL;
service_qname sys.utl_dbws.QNAME;
port_qname sys.utl_dbws.QNAME;
response sys.XMLTYPE;
request sys.XMLTYPE;
inputXML varchar2(300) := null;
myXML varchar2(100) := 'pas&';
begin
dbms_output.put_line('Calling Web Service http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPor');
service_qname := sys.utl_dbws.to_qname(null, 'testXMLElement');
service_ := sys.utl_dbws.create_service(service_qname);
call_ := sys.utl_dbws.create_call(service_);
sys.utl_dbws.set_target_endpoint_address(call_, 'http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPort');
sys.utl_dbws.set_property( call_, 'OPERATION_STYLE', 'document');
dbms_output.put_line('Preparing request');
--add to convert XML with special characters correctly escaped
inputXML := '<ns1:testXMLElement xmlns:ns1="http://pas.au.xml.ws/types/"><ns1:xmlInput><pas>'||
dbms_xmlgen.CONVERT(myXML, dbms_xmlgen.ENTITY_ENCODE)||
'</pas></ns1:xmlInput>'||
'</ns1:testXMLElement>';
request := sys.XMLTYPE(inputXML);
response := sys.utl_dbws.invoke(call_, request);
dbms_output.put_line('** Soap Response **'||chr(10)||response.getStringVal());
dbms_output.put_line('Result = '||
response.extract('//ns0:result/child::text()',
'xmlns:ns0="http://pas.au.xml.ws/types/"').getstringval());
end;
/
show errors;
Note: I added a call to call dbms_java.set_output(100000); prior to running the BLOCK so we could see our REQUEST/RESPONSE XML to verify it has done what we needed it to do.
And the output now shows it works correctly.
Output
SCOTT@linux11gr2> @invoke_xmlws2.sql Call completed. Calling Web Service http://beast.au.oracle.com:7777/xmlws/XMLWSSoapHttpPor ServiceFacotory: oracle.j2ee.ws.client.ServiceFactoryImpl@7e6efb86 WSDL: null Service: oracle.j2ee.ws.client.BasicService@917ab4b8 *** Created service: -1261915451 - oracle.jpub.runtime.dbws.DbwsProxy$ServiceProxy@eec8c59c *** ServiceProxy.get(-1261915451) = oracle.jpub.runtime.dbws.DbwsProxy$ServiceProxy@eec8c59c setProperty(javax.xml.rpc.soap.operation.style, document) Preparing request dbwsproxy.add.map: ns1, http://pas.au.xml.ws/types/ Attribute 0: http://pas.au.xml.ws/types/: xmlns:ns1, http://pas.au.xml.ws/types/ dbwsproxy.lookup.map: ns1, http://pas.au.xml.ws/types/ createElement(ns1:testXMLElement,null,http://pas.au.xml.ws/types/) dbwsproxy.add.soap.element.namespace: ns1, http://pas.au.xml.ws/types/ Attribute 0: http://pas.au.xml.ws/types/: xmlns:ns1, http://pas.au.xml.ws/types/ dbwsproxy.element.node.child.0: 1, null dbwsproxy.lookup.map: ns1, http://pas.au.xml.ws/types/ createElement(ns1:xmlInput,null,http://pas.au.xml.ws/types/) dbwsproxy.element.node.child.0: 1, null createElement(pas,null,null) dbwsproxy.text.node.child.0: 3, pas& request: <ns1:testXMLElement xmlns:ns1="http://pas.au.xml.ws/types/"> <ns1:xmlInput> <pas>pas&</pas> </ns1:xmlInput> </ns1:testXMLElement> response: <ns0:testXMLResponseElement xmlns:ns0="http://pas.au.xml.ws/types/"> <ns0:result>success</ns0:result> </ns0:testXMLResponseElement> ** Soap Response ** <ns0:testXMLResponseElement xmlns:ns0="http://pas.au.xml.ws/types/"> <ns0:result>success</ns0:result> </ns0:testXMLResponseElement> Result = success PL/SQL procedure successfully completed. No errors. SCOTT@linux11gr2>
2 comments:
Thanks, Pas. Great post. I struggled a few hours try to get the same problem fixed (I was using utl_http). Your solution dbms_xmlgen.convert() saved me. Thanks again.
Nice and thanks!
Post a Comment