Monday, January 25, 2010

Using javax.xml.soap to access OTRS SOAP service without WSDL

For most of us who are frequently using WSDL to generate stub classes for accessing webservices, dealing with SOAP services without WSDL is painful. OTRS (Open source Ticket Request System) is one of them. One attempt by OpenNMS team has been quite successful on integrating OTRS by writing its own webservice as an OTRS plugin. However, this is not satisfactory in my opinion. We need a better way of doing it.

I have tried to find out the pattern of the SOAP messages being exchanged by the rpc-example.pl with my local installation of OTRS. Here is the process of the investigation and this attempt has successfully execute RPC on the OTRS and I have been able to retrieve the result.

First, I create a sample perl script to make a simple request to retrieve Ticket by TicketID. Here is the script:
#!/usr/bin/perl -w
use SOAP::Lite ( 'autodispatch', proxy => 'http://localhost/otrs/rpc.pl', trace => 'all');
my $User = 'some_user';
my $Pw   = 'some_pass';

my $RPC = Core->new();
my %Ticket = $RPC->Dispatch($User,$Pw,'TicketObject','TicketGet', TicketID=>1);
print "$Ticket{TicketNumber}\n";
Note: I set 'trace' property to 'all' to enable printing out every possible debugging message for SOAP::Lite API

Observing the output of the debug message, I found the SOAP request has the following pattern:

<SOAP:Envelope xmlns ... dst..dst>
    <soap:Body>
          <Dispatch xmlns="/Core">
                  <Username xsi:type="xsd:string">some_user</Username>
                  <Password xsi:type="xsd:string">some_pass</Password>
                  <Object xsi:type="xsd:string">TicketObject or other object </Object>
                  <Method xsi:type="xsd:string">TicketGet or other method</Method>
                  <Param1_Name xsi:type="xsd:string">fill in the param name</Param1_Name>
                  <Param1_Value xsi:type="xsd:string/int">fill in the param value</Param1_Value>
...
... other params
          </Dispatch>
    </soap:Body>
</SOAP:Envelope>


Note: I have modified the element name to make it easier to understand what the content supposed to be

Having looked at the pattern, I then wrote a sample Java code to access it. Here is my attempt:

import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;


/**
 *
 * @author masjoko
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        // Create the connection
 SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
 SOAPConnection conn = scf.createConnection();

 // Create message
 MessageFactory mf = MessageFactory.newInstance();
 SOAPMessage msg = mf.createMessage();

 // Object for message parts
 SOAPPart sp = msg.getSOAPPart();

 SOAPEnvelope env = sp.getEnvelope();
 env.addNamespaceDeclaration("xsd","http://www.w3.org/2001/XMLSchema");
 env.addNamespaceDeclaration("xsi","http://www.w3.org/2001/XMLSchema-instance");
 env.addNamespaceDeclaration("enc","http://schemas.xmlsoap.org/soap/encoding/");
 env.addNamespaceDeclaration("env","http://schemas.xmlsoap.org/soap/envelop/");
 env.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");

        SOAPBody body = env.getBody();
        SOAPBodyElement dispatch = body.addBodyElement(new QName("/Core","Dispatch"));
        dispatch.addChildElement("Username").addTextNode("some_user").setAttribute("xsi:type", "xsd:string");
        dispatch.addChildElement("Password").addTextNode("some_pass").setAttribute("xsi:type", "xsd:string");
        dispatch.addChildElement("Object").addTextNode("TicketObject").setAttribute("xsi:type", "xsd:string");
        dispatch.addChildElement("Method").addTextNode("TicketGet").setAttribute("xsi:type", "xsd:string");
        dispatch.addChildElement("Param1_Name").addTextNode("TicketID").setAttribute("xsi:type", "xsd:string");
        dispatch.addChildElement("Param1_Value").addTextNode("1").setAttribute("xsi:type", "xsd:int");
        URL url = new URL("http://localhost/otrs/rpc.pl");
        SOAPMessage resp = conn.call(msg, url);
        Document doc = resp.getSOAPPart().getEnvelope().getBody().extractContentAsDocument();
        Element el = doc.getDocumentElement();
        NodeList nl = el.getChildNodes();
        for (int i = 0 ; i<(nl.getLength()/2); i++)
        {
            System.out.println(nl.item(i*2).getTextContent().trim()+":"+nl.item(i*2+1).getTextContent().trim());
        }
    }
}
The following output was shown:
Age:91060917
PriorityID:3
ServiceID:
TicketFreeText11:
TicketFreeTime4:
TicketFreeTime1:
TicketFreeText6:
StateID:6
TicketFreeTime5:
EscalationTime:0
TicketFreeTime6:
TicketFreeKey9:
OwnerID:1
Owner:root@localhost
TicketFreeText7:
TicketFreeKey11:
Created:2007-03-08 16:26:39
TicketFreeText4:
QueueID:4
TicketFreeText2:
TicketFreeKey6:
TicketID:1
TicketFreeKey5:
TicketFreeText12:
UnlockTimeout:1264399466
EscalationResponseTime:0
TicketFreeTime3:
TicketFreeText3:
CustomerUserID:
TicketFreeText8:
TicketFreeText9:
Type:default
TicketFreeKey7:
Responsible:root@localhost
TicketFreeText10:
ResponsibleID:1
TicketFreeKey16:
TicketFreeKey3:
RealTillTimeNotUsed:1264485840
GroupID:1
TicketFreeKey13:
CustomerID:
TicketFreeKey1:
TypeID:1
Priority:3 normal
TicketFreeKey12:
TicketFreeKey10:
TicketFreeKey8:
UntilTime:78924
TicketFreeText1:
EscalationUpdateTime:0
TicketFreeTime2:
Queue:Misc
TicketFreeText13:
State:pending reminder
Title:Welcome to OTRS!
TicketFreeText5:
TicketFreeText15:
TicketFreeText14:
StateType:pending reminder
EscalationSolutionTime:0
LockID:1
TicketFreeKey2:
TicketNumber:1010001
TicketFreeKey14:
Lock:unlock
CreateTimeUnix:1173345999
TicketFreeKey4:
SLAID:
TicketFreeKey15:
TicketFreeText16:

Now, I believe someone should try to write a general purpose method/utility to call OTRS method using Java. I hope this would help anyone who encountered similar problem.

Friday, January 15, 2010

Accessing WS-Security protected (UsernameToken) WebService using PHP5 Soap

After battling for several hour trying to figure out on how to send WS-Security header using PHP5 Soap API, I finally managed to discover the work of the good people at University of Toronto . Using their example and the PHP code they posted on the website: soap-wsse.php and xmlseclibs.php, I have been able to test my CXF-powered webservice.

I have a web service running at http://localhost:9090/WS with WSDL url of http://localhost:9090/WS?wsdl. Using the following PHP script and following the direction from the University of Toronto (putting the two PHP scripts above in the same folder as my script), I have been able to call my webservice correctly.
<?php
require('soap-wsse.php');

class mySoap extends SoapClient {
   function __doRequest($request, $location, $saction, $version) {
    $doc = new DOMDocument('1.0');
    $doc->loadXML($request);
    $objWSSE = new WSSESoap($doc);
    $objWSSE->addUserToken("admin", "admin", false);
    return parent::__doRequest($objWSSE->saveXML(), $location, $saction, $version);
   }
}

$wsdl = 'http://localhost:9090/WS?wsdl';
$sClient = new mySoap($wsdl, array('trace'=>1));
$wrapper->word = new SoapVar("Echo testing", XSD_STRING);
$result = $sClient->echo($wrapper);
print_r($result->return);
?>


The resulting print out on the web is "Echo Testing". I hope this would help anyone that encountered similar problem.

Monday, January 11, 2010

Mono WebClient Basic Authorization pre-authenticate problems

For the last few week I have been trying to set Mono WebClient to send (.. yeah.. simple... just send the fricking..) Basic Authorization header. Apparently, somebody is just too smart to decide not sending the header on the first request for security reason (sending user credential).

Well, the problem is that Acegi/Spring security does not necessarily send 401 header since authentication can be done using Authorization header as well as form. I am using code that works perfectly on Microsoft .NET 2.0/3.5. I have not been able to get it to work on Mono on FC11.

I wish somebody really looked at the problem and have it fixed soon otherwise I have to use Java and Apache Commons HTTP.