• Home  / 
  • Retro
  •  /  SOAP messaging in Java — the hardcore way

Buy me a coffee »

SOAP messaging in Java — the hardcore way

SOAP is out, REST is in. However there are some Interfaces out there which are using the SOA Protocol — and they are very-very old.

And those old Interfaces are the main topic here.

Imagine you get a new task at your job to create an application which gets the Data from a remote service. There is no problem, this is a very common scenario. And it is common too that this service uses SOAP. However it is unusual to get such an old version of WSDL that you cannot handle it with actual tools.

I will not talk about SOAP. If you are interested what SOAP is, please refer to Wikipedia.

So, I’ve got a WSDL to use. Because I worked with WSDL and SOAP at the beginning of my professional career (some 6-7 years ago) I located my early memories and tried to generate some Java classes from the Schema.

Unfortunately I’ve got my hands tied because we had to implement our application with Google App Engine (GAE). And if you do not know it already: the GAE has a white list containing the java packages and classes which you are allowed to use. So some Web Service functionality is not allowed but this is not a big problem.

The easiest way would be to use wsimport. This is currently the best method if you need a fast access to generate a Java endpoint from WSDL.

I called out the command

wsimport "the URL of the WSDL"

but I’ve got the following error message:

[ERROR] "Use of SOAP Encoding is not supported.
SOAP extension element on line 2 in "the URL of the WSDL" has use="encoded" ".

          Failed to parse the WSDL.

Bad-bad problem but as a Pro I do not give up. I looked at the web to find out what is behind this error message. The suggested solution was to generate the classes with Apache Axis version 1.4. Well I thought about this too but I resigned from this solution because the 1.4.Final version of Apache Axis was released at 22th April 2006. And you need a lot of dependencies to get your classes, and because of the tool’s age you will end up with “aged” implementations: you get 4 Java files generated, 2 interfaces, 2 classes, more than 400 lines of code and a lot of dependencies to Axis. (If you ask yourself how do I know it: I created the classes with Axis 1.4. If you do not mind I won’t include the calling with all JARs as the -cp parameter when calling the org.apache.axis.wsdl.WSDL2Java.)

So after Axis I searched for another option. Because this is a one-time class generation (the interface is as old as Axis itself) I do not want any generated code at building time (in this case with Maven).

Because the interface has only two callable endpoints (search data and get data by id) I thought I can call the interface directly with SoapConnections and return the result message. This was a lean solution for a concrete purpose.

You could argue that this is not reusable but I prefer concrete solutions over generic ones if I know it won’t be reused in any other way.

After this I’ve been informed that things are not as easy as they seemed because the interface endpoint waited for messages with a custom header (indicated nowhere) which has an authentication parameter too — with the password. Fortunately my solution was capable to extend the server call with custom header parameters. After some refactoring (making things modular for further bug corrections is always good) I ended up with a utility class containing 107 lines (with proper comments):

package omitted.package.name;

import java.io.PrintWriter;
import java.util.logging.Logger;

import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;

/**
 * Custom SOAP communicator which adds the password to the header of the request
 * and returns the required data.
 *
 * @author GHajba
 *
 */
public class SoapCommunicator {

    private static Logger LOG = Logger.getLogger(SoapCommunicator.class.getName());

    /**
     * Sends an outgoing request with a text-query to the remote system.
     *
     * @param password
     *            the authentication password
     * @param text
     *            the text to search for
     *
     * @return a response from the server, null if something went wrong.
     */
    public static String requestResultList(String password, String text) {
        return requestMessage(password, "searchEntriesByText", "text", text);
    }

    /**
     * Sends an outgoing request with an ID-query to the remote system
     *
     * @param password
     *            to authenticate
     * @param id
     *            of the entry
     *
     * @return a response from the server, null if something went wrong
     */
    public static String requestEntryData(String password, String id) {
        return requestMessage(password, "createResult", "entryId", id);
    }

    private static String requestMessage(String password, String method, String key, String value) {
        String message = null;

        try {
            SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
            SOAPConnection soapConnection = soapConnectionFactory.createConnection();

            // Send SOAP Message to SOAP Server
            String url = "URL of the service endpoint";
            SOAPMessage soapResponse = soapConnection.call(createSOAPRequest(password, method, key, value), url);

            message = soapResponse.getSOAPBody().getTextContent();

            soapConnection.close();
        }
        catch (Exception e) {
            LOG.severe(e.getLocalizedMessage());
            e.printStackTrace(new PrintWriter(System.err));
        }
        return message;
    }

    private static SOAPMessage createSOAPRequest(String password, String method, String key, String value)
            throws Exception {
        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();

        String serverURI = "URI of the service endpoint";

        // SOAP Envelope
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.addNamespaceDeclaration("nsd", serverURI);

        SOAPHeader soapHeader = envelope.getHeader();
        SOAPElement headerElem = soapHeader.addChildElement("authenticate", "nsd");
        headerElem.addTextNode(password);
        // SOAP Body
        SOAPBody soapBody = envelope.getBody();
        SOAPElement soapBodyElem = soapBody.addChildElement(method, "nsd");
        SOAPElement soapBodyElem1 = soapBodyElem.addChildElement(key, "ned");
        soapBodyElem1.addTextNode(value);

        MimeHeaders headers = soapMessage.getMimeHeaders();
        headers.addHeader("SOAPAction", serverURI + method);

        soapMessage.saveChanges();

        return soapMessage;
    }
}

Note that I’ve removed the WSDL URL and altered the code at some points to hide confidential things.

So this was the hard-code(d) way to call a SOAP endpoint from Java.

And because I mentioned that this app was deployed at GAE: you should not use GWT as GUI framework for your application because SOAP cannot be translated to JS and you end up with a blank page in your browser without any error messages in the logs.

About the author

GHajba

Senior developer, consultant, author, mentor, apprentice. I love to share my knowledge and insights what I achieve through my daily work which is not trivial -- at least not for me.

Leave a comment: