Integrating Business Partners the RESTful Way > Exchanging Data Using E-Business Protocols

27.4. Exchanging Data Using E-Business Protocols

Something that was new to me on this project was the use of standard e-business protocols. When the distributor informed me of the requirement to exchange requests and responses using the Rosettanet standard, I had to do a little research. I started by going to the Rosettanet web site (http://www.rosettanet.org) and downloading the specific standards I was interested in. I found a diagram detailing a typical exchange between business partners, along with a specification for the XML request and response.

Since I had a lot of trial-and-error type work to do, the first thing I did was set up a test that I could run myself to simulate an interaction with the distributor without having to coordinate testing with their staff for each iteration of development. I used the Apache Commons HttpClient to manage the HTTP exchanges:

	public class TestHotKeyService {

	    public static void main (String[] args) throws Exception {

	        String strURL = "http://xxxxxxxxxxx/HotKey/HotKeyService";
	        String strXMLFilename = "SampleXMLRequest.xml";
	        File input = new File(strXMLFilename);

	        PostMethod post = new PostMethod(strURL);
	        post.setRequestBody(new FileInputStream(input));
	        if (input.length( ) < Integer.MAX_VALUE) {
	            post.setRequestContentLength((int)input.length());
	        } else {
	              post.setRequestContentLength(
	                  EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);
	        }

	        post.setRequestHeader("Content-type", "text/xml; charset=ISO-8859-1");

	        HttpClient httpclient = new HttpClient();
	        System.out.println("[Response status code]: " +
	               httpclient.executeMethod(post));
	        System.out.println("\n[Response body]: ");
	        System.out.println("\n" + post.getResponseBodyAsString( ));

	        post.releaseConnection();
	    }
	}


					    

This allowed me to accelerate my learning curve as I tried out several different types of requests and examined the results. I'm a firm believer in diving right into coding as soon as possible. You can only learn so much from a book, an article on a web site, or a set of API documents. By getting your hands dirty early on in the process, you'll uncover a lot of things you may not have thought about by simply studying the problem.

The Rosettanet standard, like many others, is very detailed and complete. Chances are, you'll end up needing and using only a small fraction of it to accomplish any given task. For this project, I only needed to set a few standard identification fields, along with a product number and availability date for pricing inquiries, or an order number for order status inquiries.

27.4.1. Parsing the XML Using XPath

The XML request data was far from simple XML. As mentioned earlier, the Rosettanet standard is very detailed and thorough. Parsing such a document could have proved to be quite a nightmare if it were not for XPath. Using XPath mappings, I was able to define the exact path to each node that I was interested in and easily pull out the necessary data. I chose to implement these mappings as a HashMap, which I later iterated over, grabbing the specified nodes and creating a new HashMap with the values. These values were then used later in both the executeQuery and getResponseXML methods that I'll describe next:

	public class HotkeyAdaptorRosProdAvailImpl implements HotkeyAdaptor {

	    String inputFile;           // request XML
	    HashMap requestValues;       // stores parsed XML values from request
	    HashMap as400response;       // stores return parameter from RPG call

	    /* Declare XPath mappings and populate with a static initialization block */
	    public static HashMap xpathmappings = new HashMap();
	    static {
	        xpathmappings.put("from_ContactName",
	"//Pip3A2PriceAndAvailabilityRequest/fromRole/PartnerRoleDescription/
	ContactInformation/contactName/FreeFormText");
	        xpathmappings.put("from_EmailAddress", "//Pip3A2PriceAndAvailabilityRequest/
	fromRole/PartnerRoleDescription/ContactInformation/EmailAddress");
	    }
	       // Remaining xpath mappings omitted for brevity...

	    public HotkeyAdaptorRosProdAvailImpl() {
	        this.requestValues = new HashMap();
	        this.as400response = new HashMap();
	    }

	    public void setXML(String _xml) {
	        this.inputFile = _xml;

	    }

	    public boolean parseXML() {

	        try {
	            Document doc = null;
	            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	            DocumentBuilder db = dbf.newDocumentBuilder();
	            StringReader r = new StringReader(this.inputFile);
	            org.xml.sax.InputSource is = new org.xml.sax.InputSource(r);
	            doc = db.parse(is);

	            Element root = doc.getDocumentElement();

	            Node node = null;

	            Iterator xpathvals = xpathmappings.values().iterator( );
	            Iterator xpathvars = xpathmappings.keySet().iterator( );
	            while (xpathvals.hasNext() && xpathvars.hasNext( )) {
	                node = XPathAPI.selectSingleNode(root, String)xpathvals.next( ));
	                requestValues.put((String)xpathvars.next( ),
	                        node.getChildNodes().item(0).getNodeValue( ));
	            }

	        }
	        catch (Exception e) {
	            System.out.println(e.toString( ));
	        }

	        return true;
	    }
	    public boolean executeQuery() {
	         // Code omitted...
	    }

	    public String getResponseXML() {
	        // Code omitted...
	    }

	}


					    

The executeQuery method contains all of the code necessary to access the RPG code running on the AS/400 systems, in order to get the necessary response data we'll use later to construct the response XML document. Many years ago, I worked on a project that integrated a MAPICS system (RPG on the AS/400) with a new system that I wrote using Visual Basic. I had written code for both sides of the exchange, in RPG and CL on the AS/400, and Visual Basic on the PC. This led to several speaking engagements where I attempted to show legions of RPG programmers how to integrate their legacy systems with modern client/server software. At the time, it really was a complicated and almost mystical thing to do.

Since then, IBM has made it very easy and provided us with a library of Java functions that do all the work for us. (So much for all the consulting gigs and book deals I could have had with that one!) Here's the code, using the much better Java library from IBM:

	public boolean executeQuery() {
	    StringBuffer sb = new StringBuffer();

	    sb.append(requestValues.get("from_ContactName")).append("|");
	    sb.append(requestValues.get("from_EmailAddress")).append("|");
	    sb.append(requestValues.get("from_TelephoneNumber")).append("|");
	    sb.append(requestValues.get("from_BusinessIdentifier")).append("|");
	    sb.append(requestValues.get("prod_BeginAvailDate")).append("|");
	    sb.append(requestValues.get("prod_EndAvailDate")).append("|");
	    sb.append(requestValues.get("prod_Quantity")).append("|");
	    sb.append(requestValues.get("prod_ProductIdentifier")).append("|");

	    try {
	        AS400 sys = new AS400("SS100044", "ACME", "HOUSE123");

	        CharConverter ch = new CharConverter();
	        byte[] as = ch.stringToByteArray(sb.toString( ));

	        ProgramParameter[] parmList = new ProgramParameter[2];
	        parmList[0] = new ProgramParameter(as);
	        parmList[1] = new ProgramParameter(255);

	        ProgramCall pgm = new ProgramCall(sys,
	               "/QSYS.LIB/DEVOBJ.LIB/J551231.PGM", parmList);
	        if (pgm.run( ) != true) {
	            AS400Message[] msgList = pgm.getMessageList();	
	            for (int i=0; i < msgList.length; i++) {
	                System.out.println(msgList[i].getID( ) + " : " +
	                         msgList[i].getText());
	            }
	                   }
	        else {
	            CharConverter chconv = new CharConverter();
	            String response =
	                    chconv.byteArrayToString(parmList[1].getOutputData( ));

	            StringTokenizer st = new StringTokenizer(response, "|");

	            String status = (String) st.nextToken().trim( );
	            as400response.put("Status", status);
	            String error = (String) st.nextToken().trim( );
	            as400response.put("ErrorCode", error);
	            String quantity = (String) st.nextToken().trim( );
	            as400response.put("Quantity",
	                    String.valueOf(Integer.parseInt(quantity)));

	            if (status.toUpperCase( ).equals("ER")) {
	                if (error.equals("1")) {
	                    as400response.put("ErrorMsg",
	                             "Account not authorized for item availability.");
	                }
	                if (error.equals("2")) {
	                    as400response.put("ErrorMsg", "Item not found.");
	                }
	                if (error.equals("3")) {
	                    as400response.put("ErrorMsg", "Item is obsolete.");
	                    as400response.put("Replacement",
	                             (String) st.nextToken().trim( ));
	                }
	                if (error.equals("4")) {
	                    as400response.put("ErrorMsg",
	                             "Invalid quantity amount.");
	                }
	                if (error.equals("5")) {
	                as400response.put("ErrorMsg",
	                         "Preference profile processing error.");
	                }
	                if (error.equals("6")) {
	                    as400response.put("ErrorMsg",
	                             "ATP processing error.");
	                }
	            }
	        }
	    }
	    catch (Exception e) {
	        System.out.println(e.toString( ));
	    }

	    return true;
	}


					    

This method begins by assembling a parameter string (pipe-delimited) that gets passed into the AS/400 program, where it parses the string, retrieves the requested data, and returns a pipe-delimited string with a status and error code as well as the result of the operation.

Assuming there isn't an error, the results of this AS/400 interaction get stored in another HashMap, which we'll use when constructing the XML response document. If there is an error, then that gets written to the response instead.

27.4.2. Assembling the XML Response

I've always enjoyed seeing the many ways people have tried to create XML documents programmatically. What I always tell people is that XML documents are just big text strings. Therefore, it's usually easier to just write one out using a StringBuffer rather than trying to build a DOM (Document Object Model) or using a special XML generator library.

For this project, I simply created a StringBuffer object and appended each individual line of the XML document following the Rosettanet standard. In the following code example, I omitted several lines of code, but this should give you an idea of how the response was constructed:

	   public String getResponseXML() {
	       StringBuffer response = new StringBuffer();
	       response.append("<Pip3A2PriceAndAvailabilityResponse>").append("\n");
	       response.append("    <ProductAvailability>").append("\n");
	       response.append("   <ProductQuantity>").append(as400response.get("Quantity")).
	append("</ProductQuantity>").append("\n");
	        response.append("   </ProductAvailability>").append("\n");
	        response.append("   <ProductIdentification>").append("\n");
	        response.append("     <PartnerProductIdentification>").append("\n");
	        response.append("       <GlobalPartnerClassificationCode>Manufacturer</
	GlobalPartnerClassificationCode>").append("\n");
	        response.append("       <ProprietaryProductIdentifier>").append(requestValues.
	get("prod_ProductIdentifier")).append("</ProprietaryProductIdentifier>").append("\n");
	        response.append("      </PartnerProductIdentification>").append("\n");
	        response.append("    </ProductIdentification>").append("\n");
	        response.append("  </ProductPriceAndAvailabilityLineItem>").append("\n");
	        response.append("</Pip3A2PriceAndAvailabilityResponse>").append("\n");

	        return response.toString();
	   }