I never went into the Java-craze back in around the 2000's. I thought it was slow and I liked C/C++. But things have changed. PC's are getting faster and the "slowness" is almost gone. But knowing something about Java is a neccessity. The market for Java is enormous, and it's used almost everywhere. Still not a fan, but feel I have to get through this after I started this series of "Talking about SOAP .."-articles. So here I go again: Objective is to find/and use an API for Java that downloads and creates code in Java for communicating with a SOAP-server.

My developement tool/code-editor is Eclipse. I downloaded the latest one(2019-03). Platform is Windows 10-x64.

I kind of started in the wrong end when I was seeking for a way to make this work with a deprecated API called Axis/Axis2 developed by The Apache Foundation. Not sure if Axis2 for Java is deprecated, but I could not make it work with Eclipse. I could not install the plugins and I got alot of errors when compiling/when trying to use the libraries. I gave that up eventually. I tried a couple of other things also, but there were always something that did not work or was missing. You could probably/maybe make it work if you downgraded either Eclipse or your jdk. I found out that I did not want to go that way.

But I did come by another API by The Apache Foundation called Apache CXF. As their web-site says: 

Apache CXF™ is an open source services framework. CXF helps you build and develop services using frontend programming APIs, like JAX-WS and JAX-RS. These services can speak a variety of protocols such as SOAP, XML/HTTP, RESTful HTTP, or CORBA and work over a variety of transports such as HTTP, JMS or JBI.

And it seemed like the site was still maintained. I looked at the source-developement at github, and the latest change was done some days ago. So I took a chance to go through the README's. And read the how-to's at their main site http://cxf.apache.org/index.html.

This article has three parts:

1. Installing Apache CXF 
2. Creating a simple project
3. Get order-info from the generated wsdl-code
4. Create an Order from an XML-file

1. Installing Apache CXF 

1. I downloaded the newest Binary Distribution from here: http://cxf.apache.org/download.html
2. I Unzipped it at an appropriate folder/destination.
3. Open Eclipse: Add the CXF-binary path here: Window->Preferences->Web Services->CXF 2.x Preferences. Choose the CXF Runtime Tab, and press "Add.."


There are other ways according to their website, but I'm trying to keep the amount of instructions and downloads to a minimum.

4. Go to Help -> Install New Software -> Install

    - Install software from these two locations:
      1. http://eclipse-cs.sourceforge.net/update
 
     2. http://sourceforge.net/projects/pmd/files/pmd-eclipse/update-site/

You can find the complete users guide here: http://cxf.apache.org/docs/index.html


2. Creating a simple Project

1. Here I'm going to create a "Java Project". This is the same as a "Console application" one might say.
 

2. After pressing next you'll come to "Java Settings". Press the Liraries - tab and press "Add Library". A new Dialog-box will appear:

3. Press Next and then Finish. After pressing next you can choose the runtime you want use. It should be the binary-version that was downloaded earlier on.

4. We're NOT going to use Eclipse to create the wsdl source-files. We're going to use a utility called wsdl2java which came with the Apache CXF - distribution. It's located in the "./bin"-directory. Open a command-prompt and navigate to the bin-directory

5. I had some name-conflicts when I first ran the command so I had to add the parameter "-autoNameResolution". There are other ways to configure this, but I'm not going to try to find out anything more about it. So, my command became like this:

D:\Users\Frode Meek\Downloads\apache-cxf-3.3.1\apache-cxf-3.3.1\bin>wsdl2java -client -autoNameResolution https://path_to_the_wsdl_uri?WSDL

D:\Users\Frode Meek\Downloads\apache-cxf-3.3.1\apache-cxf-3.3.1\bin>

I did not get any feedback, which is positive. the "-client" parameter says to wsdl2java to create a simple client application with an entry-point. See http://cxf.apache.org/docs/wsdl-to-java.html for more options.

6. I then moved the sources that wsdl2java had created to my projects' source-location and updated the project in the project-navigator in Eclipse. See pic below:

 

Now we're ready to test the code. I could immidiately see that Eclipse had no objections to the "imported" code.(I have the "Build automatically"-option set, so it tells me immidiately if something is wrong.

3. Get Order-info from the generated wsdl-code

There wasn't much I had to do since the generator also had created a simple client that tested all(not sure about all, but I would think so) methods. It was also difficult to find. All methods gets their owb class/file, but the entry-point-file ended with "*_Client.java". Look for it. The client-file/main-entry-point looked like this:

package se.ongoingsystems.wsi;

/**
 * Please modify this class to meet your needs
 * This class is not complete
 */

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

/**
 * This class was generated by Apache CXF 3.3.1
 * 2019-04-22T20:17:38.736+02:00
 * Generated source version: 3.3.1
 *
 */
public final class ServiceSoap_ServiceSoap12_Client {

    private static final QName SERVICE_NAME = new QName("http://ongoingsystems.se/WSI", "Service");

    private ServiceSoap_ServiceSoap12_Client() {
    }

    public static void main(String args[]) throws java.lang.Exception {
        URL wsdlURL = Service.WSDL_LOCATION;
        if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
            File wsdlFile = new File(args[0]);
            try {
                if (wsdlFile.exists()) {
                    wsdlURL = wsdlFile.toURI().toURL();
                } else {
                    wsdlURL = new URL(args[0]);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }

        Service ss = new Service(wsdlURL, SERVICE_NAME);
        ServiceSoap port = ss.getServiceSoap12();

        {
        System.out.println("Invoking getInventoryItems...");
        int _getInventoryItems_goodsOwnerId = 0;
        java.lang.String _getInventoryItems_userName = "";
        java.lang.String _getInventoryItems_password = "";
        se.ongoingsystems.wsi.InventoryItemsQuery _getInventoryItems_inventoryItemsQuery = null;
        se.ongoingsystems.wsi.InventoryItemsResult _getInventoryItems__return = port.getInventoryItems(_getInventoryItems_goodsOwnerId, _getInventoryItems_userName, _getInventoryItems_password, _getInventoryItems_inventoryItemsQuery);
        System.out.println("getInventoryItems.result=" + _getInventoryItems__return);


        }
        {
        System.out.println("Invoking getShipmentsByQuery...");
        java.lang.String _getShipmentsByQuery_goodsOwnerCode = "";
        java.lang.String _getShipmentsByQuery_userName = "";
        java.lang.String _getShipmentsByQuery_password = "";
        se.ongoingsystems.wsi.ShipmentFilters _getShipmentsByQuery_query = null;
        se.ongoingsystems.wsi.GetShipmentsResult _getShipmentsByQuery__return = port.getShipmentsByQuery(_getShipmentsByQuery_goodsOwnerCode, _getShipmentsByQuery_userName, _getShipmentsByQuery_password, _getShipmentsByQuery_query);
        System.out.println("getShipmentsByQuery.result=" + _getShipmentsByQuery__return);


        }
        {
        System.out.println("Invoking getOrderStatuses...");
        java.lang.String _getOrderStatuses_userName = "";
        java.lang.String _getOrderStatuses_password = "";
        java.lang.String _getOrderStatuses_goodsOwnerCode = "";
        se.ongoingsystems.wsi.GetOrderStatusesResult _getOrderStatuses__return = port.getOrderStatuses(_getOrderStatuses_userName, _getOrderStatuses_password, _getOrderStatuses_goodsOwnerCode);
        System.out.println("getOrderStatuses.result=" + _getOrderStatuses__return);
----------------------------- etc etc -------------------------------------------

The naming-convention is really messy, but it's understandable. In controversy the client-example is very usefull. You can start coding immidiately, and not waste any more time. I rewrote, testing ONE method just to see if the code works. ...and it did..Here's the main class with the main-entrypoint:

public final class ServiceSoap_ServiceSoap12_Client {

    private static final QName SERVICE_NAME = new QName("http://ongoingsystems.se/WSI", "Service");
    private final String _getOrderByOrderNumber_goodsOwnerCode = "ownercode";
    private final String _getOrderByOrderNumber_userName = "username";
    private final String _getOrderByOrderNumber_password = "passord";
    private final String _getOrderByOrderNumber_orderNumber = "201904112046-test";
    private Order _getOrderNumberResult;
    private ServiceSoap_ServiceSoap12_Client() 
    {
    }
    public String getOrderNumber()
    {
    	return this._getOrderByOrderNumber_orderNumber;
    }
    public String getPassword()
    {
    	return this._getOrderByOrderNumber_password;
    }
    public String getUserName()
    {
    	return this._getOrderByOrderNumber_userName;
    }
    public String getGoodsOwnerCode()
    {
    	return this._getOrderByOrderNumber_goodsOwnerCode;
    }
    public void SetGetOrderNumberResult(Order _result)
    {
    	this._getOrderNumberResult = _result;
    }
    public Order getOrderNumberResult()
    {
    	return this._getOrderNumberResult;
    }
    public static void main(String args[]) throws java.lang.Exception {
        URL wsdlURL = Service.WSDL_LOCATION;
        if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
            File wsdlFile = new File(args[0]);
            try {
                if (wsdlFile.exists()) {
                    wsdlURL = wsdlFile.toURI().toURL();
                } else {
                    wsdlURL = new URL(args[0]);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }

        Service ss = new Service(wsdlURL, SERVICE_NAME);
        ServiceSoap port = ss.getServiceSoap12();
        ServiceSoap_ServiceSoap12_Client login= new ServiceSoap_ServiceSoap12_Client();
        {
            System.out.println("Invoking getOrderByOrderNumber...");
            
            login.SetGetOrderNumberResult(port.getOrderByOrderNumber(login.getGoodsOwnerCode(), login.getUserName(), login.getPassword(), login.getOrderNumber()));
            if(login.getOrderNumberResult().success == true)
            {
            	System.out.println("GetOrderNumberByNumber was a success.\n");
            
            	System.out.println("Consignee->Name: " + login.getOrderNumberResult().consignee.name);
            	System.out.println("GoodsOwner->Name: " + login.getOrderNumberResult().goodsOwnerInfo.name);
            	System.out.println("Orderinfo->CreatedDate: " + login.getOrderNumberResult().orderInfo.createdDate);
            }
            else
            {
            	System.out.println("login.getOrderNumberResult().success: " + login.getOrderNumberResult().success);
            	System.out.println("Getting order was not a success. exiting with errornumber 1");
            	System.exit(1);
            }
         }
        System.exit(0);

Result of this is the following:

apr 22, 2019 8:31:20 PM org.apache.cxf.wsdl.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://ongoingsystems.se/WSI}Service from WSDL: https://becolager.ongoingsystems.se/BecoLager/service.asmx?WSDL
Invoking getOrderByOrderNumber...
GetOrderNumberByNumber was a success.

Consignee->Name: Test Name Snilstveit Nipedalen 167
GoodsOwner->Name: profag test
Orderinfo->CreatedDate: 2019-04-11T20:51:54.67

4. Creating and Order from an XML-file

All of the "Talking SOAP ..." articles have a chapter that reads an xml-file and assign the data to an object. The XML-file has always had the same data except for the "remark" and the "customer's order number". This is kind of interesting since all of the API's tackle the data (a little) differently. This time I definitely made a note of it since it was such a small "thing". I'm going to explain later.

1. I'm using JAXB to bind XML data(Apache CXF is..). JAXB uses the Unmarshaller object to read or create data to and from an object. It's like in C# (or other's) where you serialize and deserialize an object.
2. My create_order.xml is a file that has complex objects. (objects within objects).

Let's look at my function SetOrderFromXml()

public int SetOrderFromXml()
    {
    	File fis = new File(this._xmlPath);
    	this._xmlOrder = new CustomerOrder();// = new CustomerOrder();
    	try
    	{
    		System.out.println("Getting data from xml-file: \n");
	        JAXBContext jbc = JAXBContext.newInstance(CustomerOrder.class);
	        Unmarshaller unm = jbc.createUnmarshaller();
	        this._xmlOrder = (CustomerOrder)unm.unmarshal(fis);
    	}
    	catch(Exception e)
    	{
    		e.printStackTrace();
    		return 1;//not sure if this is neccesary..but it's a habit
    	}
        
        return 0;
    }

Line 4: I initialize my Object that the SOAP-server expects.
Line 8: Create a new instance of the JAXBContext giving it our Objects'(CustomerOrder) skeleton.
Line 10: Typecast the return-value from the umarshal-function.

That's actually it. I thought...

The CustomerOrder object _xmlOrder contained nothing. Since my XML has bin stripped for namespaces I started with inserting namespaces in the file.

Nothing...

I looked at other examples on the net and finally found my solution. At first I thought that it might have something to do with the order of the data which it was read, but I could NOT see anything in the class declaration that this was the case. But as I was looking at examples and looking at my own decleration I saw that I was missing one string-decleration: @XMLRootElement. The fact of it all is that you have to have this set to start reading the data from the XML-file:

@XmlRootElement(name="CustomerOrder")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "CustomerOrder", propOrder = {
    "orderInfo",
    "goodsOwner",
    "customer",
    "communicationInfo",
    "transporterContract",
    "customerOrderLines",
    "customerOrderTextLines",
    "transportServices",
    "returnTransporterContract"
})

Heavenly!

So from then on I thought that this would be a piece of cake:
1. Create the Service-object
2. Create the ServiceSoap-object
3. Initialize my own(local) class
4. Read File
5. Process the Order--->"System.InvalidOperationException: There is an error in XML document (1, 631). ---> System.FormatException: The string '' is not a valid AllXsd value

That's what I got. So I started debugging, trying to see what was wrong. I googled the error-string and saw that some people has had problems with the format of the dataTime value. I looked at mine which actually was in the form of yyyy-mm-dd. He he. So I googled xml datetime format and got an answer which i just had to try becuase the actual format is yyyy-mm-ddThh-mm-ss . So i changed my date-time format from this value: 2019-04-26 to 2019-04-26T15:00:00.

And so it worked!.

In all the other API's that I've tried they have never complained about that, so I must admit that I'm a little bewildered. But ok..if that's what it wants..then that's what it'll get. 

Here's the code I made..testing some output to see with my own eyes that it worked:

Service ss = new Service(wsdlURL, SERVICE_NAME);
ServiceSoap port = ss.getServiceSoap12();
        
ogCreateOrderFromFile login= new ogCreateOrderFromFile();
        //see if it's been imported correctly
	   if(login.SetOrderFromXml() == 0)
	   {
	        System.out.println("getCustomerOrderObj().orderInfo.orderRemark: \n" + login.getCustomerOrderObj().orderInfo.orderRemark);
		System.out.println("getCustomerOrderObj().goodsOwner.goodsOwnerCode: \n" + login.getCustomerOrderObj().goodsOwner.goodsOwnerCode);
		System.out.println("getCustomerOrderObj().transporterContract.transporterCode: \n" + login.getCustomerOrderObj().transporterContract.transporterCode);
		System.out.println("getCustomerOrderObj().orderInfo.deliveryDate: \n" + login.getCustomerOrderObj().orderInfo.deliveryDate);
		System.out.println("getCustomerOrderObj().customerOrderLines.getCustomerOrderLine().size(): \n" + login.getCustomerOrderObj().customerOrderLines.getCustomerOrderLine().size());
		//Since everything is ok. We can process the order by using the method ProcessOrder
		FileResultClass res = new FileResultClass();
		res = port.processOrder(login.getGoodsOwnerCode(), login.getUserName(), login.getPassword(), login.getCustomerOrderObj());
		if(res.success == true)
		{
				System.out.println("Processing the order was a success..let's check some values:\n");
				System.out.println("res.message: " + res.message + "\n");
				System.out.println("res.goodsOwnerOrderNumber: " + res.goodsOwnerOrderNumber + "\n");
				System.out.println("res.orderId: " + res.orderId + "\n");
				
		}
		else
		{
			System.out.println("res.success return false..\n");
			System.out.println("res.errorMessage..\n" + res.errorMessage);
			System.exit(1);
		}
	    
	}

Line 15: processOrder-method is called.

Output of this:

apr 24, 2019 6:41:27 PM org.apache.cxf.wsdl.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://ongoingsystems.se/WSI}Service from WSDL: https://becolager.ongoingsystems.se/BecoLager/service.asmx?WSDL
Getting data from xml-file: 

getCustomerOrderObj().orderInfo.orderRemark: 
SOAP/WSDL test using Eclipse/Java w/API-Apache CXF - CreateOrder----FM
getCustomerOrderObj().goodsOwner.goodsOwnerCode: 
null
getCustomerOrderObj().transporterContract.transporterCode: 
DTPG
getCustomerOrderObj().orderInfo.deliveryDate: 
2019-04-26T15:00:00
getCustomerOrderObj().customerOrderLines.getCustomerOrderLine().size(): 
4
Processing the order was a success..let's check some values:

res.message: Customer with customer number '1604' and ID '27554' created. <br/> <br/>Order number 201904231656-test created/updated

res.goodsOwnerOrderNumber: 201904231656-test

res.orderId: 21820

That was fun.

If you're like me and not making your own classes these "problems" will occur. But it's totally worth it to set your mind into it. If there are problems debug, analyze error codes and look at what other people has done. It helps.