Saturday, May 18, 2013

Oracle SOA Outbound SocketAdapter with custom Java translation

Create Composite

some points should be noted about messaging adapter: transport media (protocol), message format, content.
in this post, we'll use media: socket. format: let's say its a String.
content: several fields from the input.

input several fields, but it musts send only 1 field of String. it receives multi-fields, send a String, get the response in a String, and send multi-fields reply.

let's say, this is the model we save as /xsd/sabModel.xsd:

 <?xml version= '1.0' encoding= 'UTF-8' ?> 
 <schema xmlns="http://www.w3.org/2001/XMLSchema"  
   targetNamespace="http://www.oracle.com/ns/sabModel"  
   xmlns:model="http://www.oracle.com/ns/sabModel" 
   elementFormDefault="qualified"> 
   <element name="MultiFieldRequest" type="model:MultiFieldsObjRequest"/> 
   <element name="MultiFieldResponse" type="model:MultiFieldsObjResponse"/> 
   <complexType name="MultiFieldsObjRequest"> 
     <sequence> 
       <element name="reqField1" type="string"/> 
       <element name="reqField2" type="string"/> 
       <element name="reqField3" type="string"/> 
     </sequence> 
   </complexType> 
   <complexType name="MultiFieldsObjResponse"> 
     <sequence> 
       <element name="resField1" type="string"/> 
       <element name="resField2" type="string"/> 
     </sequence> 
   </complexType> 
 </schema> 
add socket adapter reference: drag socket adapter from component palette to the external
references area.
service name = sendMsgSocket. next.
adapter interface = specified later. next.
operation type = outbound synchronous request/reply, operation name = sendAndReply.
next.
socket connection jndi name = eis/socket/SocketAdapterBlog (we'll create this in
console later),
hostname (socket server) = localhost (you can change it to the suitable hostname of your
socket server), port (socket server) = 50505.
next.
request message schema = xsd/sabModel.xsd (element = MultiFieldRequest),
response message schema = xsd/sabModel.xsd (element = MultiFieldResponse).
next.
select radio button use custom java code...
java class = com.nostratech.backingbean.socket.SocketTranslator (we'll create this class later).
finish.

add bpel process: drag bpel process from component palette to components area. and a
window will popup,
finish.
wire bpelProcess1 to sendMsgSocket, your composite.xml should look like this:
double click bpelProcess1, drag and drop Invoke between receiveInput and replyOutput. then drag its right arrow to sendMsgSocket partner links.
 
name it invokeSocketAdapter, click the plus sign in variables. let the value being default.
your BPELProcess1.bpel should look like this:
double click the receiveInput, create new variable, let the value being default. OK.
do the same for replyOutput.
then drag and drop Assign activity between receiveInput and invokeSocketAdapter
double click Assign1.
in Copy Rules tab, wire MultiFieldRequest in receiveInput_process_InputVariable to MultiFieldRequest in invokeSocketAdapter_sendAndReply_InputVariable.
in General tab, set the name Assign_fromInput_toInvokeSocket.
OK.
again drag Assign activities, put it between invokeSocketAdapter and replyOutput. do as before, but wire MultiFieldResponse in invokeSocketAdapter_sendAndReply_OutputVariable to MultiFieldResponse in replyOutput_process_OutputVariable, and name it AssignfromInvokeSocket_toOutput.
now your BPELProcess1.bpel will look like this:
save all your work,
now we will do the next step

Create Socket Connection JNDI

from your weblogic console application, in domain structure click deployments.
in Summary of Deployments, tab Control, in list of Deployments click SocketAdapter.
in Settings for SocketAdapter, click tab Configuration - Outbound Connection Pools.
in Outbound Connection Pool Configuration Table, click button New to create new connection
pool.
select radio button javax.resource.cci.ConnectionFactory. next.
enter JNDI Name = eis/socket/SocketAdapterBlog. <- should match with what we set in composite.
finish.
back to Outbound Connection Pool Configuration Table, expand javax.resource.cci.ConnectionFactory.
click eis/socket/SocketAdapterBlog, set host = localhost and port = 50505 (they should match to what we set in composite).
save.
click again the Deployments in Domain Structure, check SocketAdapter
 and click update
page will redirect to
next.
and finish.
now we'll do the next step

Create Java Custom Implement Class

in another java project, create class com.nostratech.backingbean.socket.SocketTranslator (should match what we set in composite),
the class should implements oracle.tip.pc.services.translation.util.ICustomParser.
we can get this interface from bpm-infra.jar file. This jar file is available in the
directory $SOA_ORACLE_HOME/soa/modules/oracle.soa.fabric_11.1.1/ 
(http://docs.oracle.com/cd/E21764_01/integration.1111/e10231/adptr_sock.htm).

for outbound request-reply, we implement executeOutbound method, for example:

 public Element executeOutbound(InputStream inputStream, OutputStream outputStream, Element payload) throws Exception {  
     Element response = null;  
     try {  

       BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));  
       PrintStream out = new PrintStream(outputStream);  
       
       //converting Element to String  
       Element elementModel = (Element)getSpecificNodeByName(payload, "MultiFieldRequest");  
       String messageToSend = packToString(elementModel, "ns1");  
       //end of converting Element to String  
       
       out.println(messageToSend); //send message  
       String reply = in.readLine(); //retrieve reply  
       
       //converting message to suitable Element response model  
       response = unpackFromString(reply, "ns1");  
     
     } catch (IOException e) {  
       throw new Exception("exception di executeOutbound", e);  
     }  
     return response;  
   }  
   private Node getSpecificNodeByName(Element someElement, String nodeName){  
     Node result = null;  
     if(someElement.getNodeName().equals(nodeName)){  
       result = someElement;  
     }  
     NodeList nodelist = someElement.getChildNodes();  
     int i = 0;  
     while(i<nodelist.getLength() && result==null){  
       Node node = nodelist.item(i);  
       if(node.getNodeName().equals(nodeName)){  
         result = node;  
       }else if(node.hasChildNodes()){  
         result = getSpecificNodeByName((Element)node, nodeName);  
       }  
       i++;  
     }  
     return result;  
   }  

the parameter nodeName should match the element name we provide in xsd schema.
you can create your own parser method to replace/implement the method packToString which
should parse the Element to a String.

later, the method can implement the iso8583 parser, or a normal fixed-length string, or whatever, depends on the technical integration needs.

watch the namespace, attributes, field names, they should match the name you specified in xsd schema. for example:

 public String packToString(Element elementModel, String prefix){ 
     prefix = (prefix == null ? "" : (prefix.equals("") ? prefix : prefix+":"));  
     String reqField1 = getSpecificNodeValue(elementModel, prefix+"reqField1");  
     String reqField2 = getSpecificNodeValue(elementModel, prefix+"reqField2");  
     String reqField3 = getSpecificNodeValue(elementModel, prefix+"reqField3");  
     String toSend = padding(reqField1, 20, " ", true)  
       + padding(reqField2, 15, " ", true)  
       + padding(reqField3, 8, "0", false);  
     return toSend;  
   }  

   private String getSpecificNodeValue(Element someElement, String nodeName){  
     String result = null;  
     Node node = getSpecificNodeByName(someElement, nodeName);  
     if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()){  
       result = node.getFirstChild().getNodeValue();  
     }  
     return result;  
   }  

   private String padding(String value, int length, String spacer, boolean leftAlign){  
     value = value == null ? "" : value;  
     String result = null;
     if(value.length()==length){
       result = value;
     else if(value.length()>length){
       result = leftAlign ? value+loopSpacer(spacer, length-value.length()) : loopSpacer(spacer, length-value.length())+value;
     }else{
       result = leftAlign ? value.substring(0, length) : value.substring(length-value.length(), length);
     }
     return result; 
   }  

   private String loopSpacer(int length, String spacer){  
     String result = "";  
     for(int i=0; i<length; i++){  
       result += spacer;  
     }  
     return result;  
   }  

then replace/implement the method unpackFromString to parse a String back to Element. again, watch the namespace, attributes, field names to match the name you specified in xsd schema. for example:

 private Element unpackFromString(String reply, String prefix) throws XMLParseException, SAXException, IOException {  
     Map attributes = new HashMap();  
     attributes.put("xmlns", "http://www.oracle.com/ns/sabModel");  
     if(prefix!=null && !prefix.equals("")){  
       attributes.put("xmlns:"+prefix, "http://www.oracle.com/ns/sabModel");  
     }  
     Map properties = new HashMap();  
     properties.put("resField1", unpadding(reply.substring(0, 20), " ", true));  
     properties.put("resField2", unpadding(reply.substring(20, 35), " ", true));  
     
     //create xml String of response
     String xml = packToXml("MultiFieldResponse", prefix, attributes, properties);  

     //parse xml String to Element
     DOMParser parser = new DOMParser();  
     parser.setValidationMode(DOMParser.NONVALIDATING);  
     parser.parse(new InputSource(new StringReader(xml)));  
     Element message = (Element)parser.getDocument().getElementsByTagName("MultiFieldResponse").item(0);  
     return message;  
   }  

   private String packToXml(String rootTag, String prefix, Map attributes, Map properties){  
     StringBuffer str = new StringBuffer();  
     str.append("<"+rootTag+" ");  
     packEachLoop(TYPE_ATTRIBUTES, str, attributes, null);  
     str.append(">\n");  
     packEachLoop(TYPE_PROPERTIES, str, properties, prefix);  
     str.append("</"+rootTag+">");     
     return str.toString();  
   }  

   private void packEachLoop(int packType, StringBuffer str, Map values, String prefix){  
     Iterator it = values.keySet().iterator();  
     while(it.hasNext()){  
       String key = (String)it.next();  
       if(packType == TYPE_ATTRIBUTES){   
         str.append(key+"=\""+values.get(key)+"\" ");  
       }else if(packType == TYPE_PROPERTIES){   
         String tmpPrefix = (prefix == null ? "" : (prefix.equals("") ? prefix : prefix+":"));  
         str.append("<" + tmpPrefix + key + ">");  
         str.append(values.get(key));  
         str.append("</" + tmpPrefix + key + ">\n");  
         tmpPrefix = null;  
       }  
     }  
   }  

   private String unpadding(String value, String spacer, boolean leftAlign){  
     String result = null;  
     if(leftAlign && spacer.equals(" ")){  
       result = value.trim();  
     }else if(!leftAlign && spacer.equals("0")){  
       int i = 0;  
       int count = 0;  
       while(i<value.length() && result==null){  
         if(value.charAt(i)=='0'){  
           count++;  
         }else{  
           result = value.substring(count, value.length());  
         }  
         i++;  
       }  
     }  
     return result;  
   }  

Element -> org.w3c.dom.Element
you can use oracle.xml.parser.v2.DOMParser to parse xml String to xml Element. here's the jar:
http://mirrors.ibiblio.org/pub/mirrors/maven/mule/dependencies/maven1/oracle-xdb/jars/xmlparserv2.jar
   
when you're done creating the class, compile it to jar and place it in folder /SCA-INF/lib/ in your soa project.

next: Build, Deploy and Test

don't forget to add the custom java jar in your soa project properties - library.
rebuild the soa project, deploy.

before you test it, you should have the socket server to receive the message and send reply. you can use this class for that purpose:

 public class SocketServerTest extends Thread {  

   private ServerSocket socketAgent;  
   private Socket connection;  
   private PrintStream out;  
   private BufferedReader in;  
   private String message;  
   private final int PORT = 50505;  
   private boolean started;  
   private boolean done;  

   public SocketServerTest() {  
     started = false;  
     done = false;  
   }  

   @Override  
   public void run() {  
     started = true;  
     while (!done) {  
       try {  
         socketAgent = new ServerSocket(PORT);  
         System.out.println("listens port " + PORT + "..");  
         connection = socketAgent.accept();  
         System.out.println("Connection received from " + connection.getInetAddress().getHostName());  
         out = new PrintStream(connection.getOutputStream());  
         in = new BufferedReader(new InputStreamReader(connection.getInputStream()));  
         while ((message = in.readLine()) != null) {  
           System.out.println("received = "+message);  
           sendResponse(message);  
         }  
       } catch (IOException ex) {  
         ex.printStackTrace();  
       } finally {  
         try {  
           in.close();  
           out.close();  
           socketAgent.close();  
         } catch (IOException ex) {  
           ex.printStackTrace();  
         }  
       }  
     }  
     System.out.println("stops listen port " + PORT + "..");  
     started = false;  
   }  

   private void sendResponse(String reqMsg) {  
     String resField1 = reqMsg.substring(0, 20); //resField1=reqField1  
     String resField2 = padding("RESPONSE", 15, " ", true);  
     String reply = resField1 + resField2;  
     out.println(reply);  
     System.out.println("reply = "+reply);  
     message = null;  
   }  

   private String padding(String value, int length, String spacer, boolean leftAlign){  
     value = value == null ? "" : value;  
     String result = null;
     if(value.length()==length){
       result = value;
     else if(value.length()>length){
       result = leftAlign ? value+loopSpacer(spacer, length-value.length()) : loopSpacer(spacer, length-value.length())+value;
     }else{
       result = leftAlign ? value.substring(0, length) : value.substring(length-value.length(), length);
     }
     return result;  
   }  

   private String loopSpacer(int length, String spacer){  
     String result = "";  
     for(int i=0; i<length; i++){  
       result += spacer;  
     }  
     return result;  
   }  

   public static void main(String[] args) {  
     SocketServerTest server = new SocketServerTest();  
     server.run();  
   }  
 }  

run the above code from your server,
then test your deployed composite from your enterprise manager.
click Test Web Service button.
check in your server socket console,
and the result in enterprise manager:
done!

okay, that's the concept and some sample.
for further purpose implementation, if there's something wrong in the translation, you can:
1. check the prefix in Flow Trace, is it match prefix in executeOutbound method,
2. check the length of each field in request and response message (padding-unpadding coupling),
3. use remote debugging to check the java custom code.

good luck!

No comments:

Post a Comment