Request Response Binding
OASIS
There is now a resolved issue relating to databinding and operation selection in the OASIS assembly TC
http://www.osoa.org/jira/browse/ASSEMBLY-79
http://www.osoa.org/jira/browse/BINDINGS-27
http://www.osoa.org/jira/browse/BINDINGS-43
http://www.osoa.org/jira/browse/BINDINGS-44
http://www.osoa.org/jira/browse/BINDINGS-45
The bindings TC has yet to define precisely what wire formats and operation selectors will be supported by each binding but we can imagine the kind of thing that will be required in order to investigate the implementation implications. Here I'm using JMS as an exemplar based on the notes from Ant and Raymond.
Binding invocation chain proposal
Binding provider proposal
JMS Exemplar
Wire Format Scenarios
Based on the message types that JMS supports and the mapping from the SCA JMS binding specification. Note the wire formats here are just guesses based on what Ant came up with. I've seen no spec activity on this yet.
# | JMS Message Type | JMS Message Body | Service interface | ImplemenationInterface | WireFormat | OperationSelection | Notes |
---|---|---|---|---|---|---|---|
1 | JMS Text | XML | WSDL | doSomething(SDO/POJO) | <wireFormat.jmsTextXML/> | operationSelection.jmsDefault | From the spec |
2 | JMS Bytes | XML | WSDL | doSomething(SDO/POJO) | <wireFormat.jmsBytesXML/> | operationSelection.jmsDefault | From the spec |
3 | Any JMS Message | any | doSomething(javax.jms.Message) | doSomething(javax.jms.Message) | <wireFormat.jmsTextXML/> | operationSelection.jmsDefault | From the spec |
4 | Any JMS Message | any | Custom | doSomething(SDO/POJO) | <wireFormat.someCustomFormat /> | operationSelection.jmsDefault | need a custom body extractor/inserter in this case |
5 | JMS Text | characters | doSomething(String) | doSomething(String) | <wireFormat.jmsText/> | operationSelection.jmsDefault |
|
6 | JMS Bytes | bytes | doSomething(byte[]) | doSomething(byte[]) | <wireFormat.jmsBytes/> | operationSelection.jmsDefault |
|
7 | JMS Object | Java Object | doSomething(SDO or POJO) | doSomething(SDO or POJO) | <wireFormat.jmsObject/> | operationSelection.jmsDefault |
|
8 | JMS Map | ? | ? | ? | <wireFormat.jms.map/> |
|
|
9 | JMS Stream | ? | ? | ? | <wireFormat.jms.stream/> |
|
|
wireFormat.jmsTextXML
This is the default format, you don't need to specify it.
Composite
A composite using the default wire format (JMS text messages containing XML).
Code Block |
---|
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" targetNamespace="http://helloworld" xmlns:hw="http://helloworld" name="helloworld"> <component name="HelloWorldReferenceComponent"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldReferenceImpl" /> <reference name="helloWorldService1" > <binding.jms> <destination name="HelloWorldService1"/> <wireFormat.jmsTextXML/> </binding.jms> </reference> </component> <component name="HelloWorldServiceComponent1"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldServiceImpl" /> <service name="HelloWorldService"> <binding.jms> <destination name="HelloWorldService1"/> </binding.jms> </service> </component> </composite> |
Java service interface
Code Block |
---|
@Remotable public interface HelloWorldService { public String getGreetings(String name); public String getPersonGreetings(Person person); } |
Code Block |
---|
public class Person { String firstName; String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } |
WSDL Service Interface
Code Block |
---|
<wsdl:types> <schema elementFormDefault="qualified" targetNamespace="http://helloworld" xmlns="http://www.w3.org/2001/XMLSchema"> <xsd:complexType name="PersonType"> <xsd:sequence> <xsd:element name="firstName" type="xsd:string"/> <xsd:element name="lastName" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <element name="getPersonGreetings"> <complexType> <sequence> <element name="person" type="PersonType"/> </sequence> </complexType> </element> <element name="getPersonGreetingsResponse"> <complexType> <sequence> <element name="getPersonGreetingsReturn" type="xsd:string"/> </sequence> </complexType> </element> <element name="getGreetings"> <complexType> <sequence> <element name="name" type="xsd:string"/> </sequence> </complexType> </element> <element name="getGreetingsResponse"> <complexType> <sequence> <element name="getGreetingsReturn" type="xsd:string"/> </sequence> </complexType> </element> </schema> </wsdl:types> <wsdl:message name="getGreetingsRequest"> <wsdl:part element="tns:getGreetings" name="parameters"/> </wsdl:message> <wsdl:message name="getGreetingsResponse"> <wsdl:part element="tns:getGreetingsResponse" name="parameters"/> </wsdl:message> <wsdl:message name="getPersonGreetingsRequest"> <wsdl:part element="tns:getPersonGreetings" name="parameters"/> </wsdl:message> <wsdl:message name="getPersonGreetingsResponse"> <wsdl:part element="tns:getPersonGreetingsResponse" name="parameters"/> </wsdl:message> <wsdl:portType name="HelloWorld"> <wsdl:operation name="getGreetings"> <wsdl:input message="tns:getGreetingsRequest" name="getGreetingsRequest"/> <wsdl:output message="tns:getGreetingsResponse" name="getGreetingsResponse"/> </wsdl:operation> <wsdl:operation name="getPersonGreetings"> <wsdl:input message="tns:getPersonGreetingsRequest" name="getPersonGreetingsRequest"/> <wsdl:output message="tns:getPersonGreetingsResponse" name="getPersonGreetingsResponse"/> </wsdl:operation> </wsdl:portType> |
getGreetings message body
<name>Fred Bloggs</name>
<getGreetingsReturn>Hello Fred Bloggs</getGreetingsReturn>
For consistency you would imagine the following but this is not what I think the spec says.
<getGreetings>
<name>Fred Bloggs</name>
</getGreetings>
<getGreetingsResponse>
<getGreetingsReturn>Hello Fred Bloggs</getGreetingsReturn>
</getGreetingsReponse>
getPersonGreetings message body
<getPersonGreetings>
<firstName>Fred</firstName>
<lastName>Bloggs</lastName>
</getPersonGreetings>
<getPersonGreetingsResponse>
<getPersonGreetingsReturn>Hello Fred Bloggs</getPersonGreetingsReturn>
</getPeronGreetingsReponse>
Resulting runtime configuration
Interceptor
Interceptors implement the Invoker interface which is as follows:
Code Block |
---|
public interface Invoker { /** * Process a synchronous wire * * @param msg The request Message for the wire * @return The response Message from the wire */ Message invoke(Message msg); } |
Interceptors are placed on a wire in the order dictated by their phase
WireFormatJMSTextXMLReferenceInterceptor
Code Block |
---|
public class WireFormatJMSTextXMLReferenceInterceptor implements Interceptor { private Invoker next; private RuntimeWire runtimeWire; private JMSResourceFactory jmsResourceFactory; private JMSBinding jmsBinding; private JMSMessageProcessor requestMessageProcessor; private JMSMessageProcessor responseMessageProcessor; public WireFormatJMSTextXMLReferenceInterceptor(JMSBinding jmsBinding, JMSResourceFactory jmsResourceFactory, RuntimeWire runtimeWire) { super(); this.jmsBinding = jmsBinding; this.runtimeWire = runtimeWire; this.jmsResourceFactory = jmsResourceFactory; this.requestMessageProcessor = JMSMessageProcessorUtil.getRequestMessageProcessor(jmsBinding); this.responseMessageProcessor = JMSMessageProcessorUtil.getResponseMessageProcessor(jmsBinding); } public Message invoke(Message msg) { if (jmsBinding.getRequestWireFormat() instanceof WireFormatJMSTextXML){ msg = invokeRequest(msg); } msg = getNext().invoke(msg); if (jmsBinding.getResponseWireFormat() instanceof WireFormatJMSTextXML){ msg = invokeResponse(msg); } return msg; } public Message invokeRequest(Message msg) { try { // get the jms context JMSBindingContext context = (JMSBindingContext)msg.getHeaders().get(JMSBindingConstants.MSG_CTXT_POSITION); Session session = context.getJmsSession(); javax.jms.Message requestMsg = requestMessageProcessor.insertPayloadIntoJMSMessage(session, msg.getBody()); msg.setBody(requestMsg); requestMsg.setJMSReplyTo(context.getReplyToDestination()); return msg; } catch (JMSException e) { throw new JMSBindingException(e); } } public Message invokeResponse(Message msg) { if (msg.getBody() != null){ Object[] response = (Object[])responseMessageProcessor.extractPayloadFromJMSMessage((javax.jms.Message)msg.getBody()); if (response != null && response.length > 0){ msg.setBody(response[0]); } else { msg.setBody(null); } } return msg; } public Invoker getNext() { return next; } public void setNext(Invoker next) { this.next = next; } } |
WireFormatJMSTextXMLServiceInterceptor
Code Block |
---|
public class WireFormatJMSTextXMLServiceInterceptor implements Interceptor { private Invoker next; private RuntimeWire runtimeWire; private JMSResourceFactory jmsResourceFactory; private JMSBinding jmsBinding; private JMSMessageProcessor requestMessageProcessor; private JMSMessageProcessor responseMessageProcessor; public WireFormatJMSTextXMLServiceInterceptor(JMSBinding jmsBinding, JMSResourceFactory jmsResourceFactory, RuntimeWire runtimeWire) { super(); this.jmsBinding = jmsBinding; this.runtimeWire = runtimeWire; this.jmsResourceFactory = jmsResourceFactory; this.requestMessageProcessor = JMSMessageProcessorUtil.getRequestMessageProcessor(jmsBinding); this.responseMessageProcessor = JMSMessageProcessorUtil.getResponseMessageProcessor(jmsBinding); } public Message invoke(Message msg) { if (jmsBinding.getRequestWireFormat() instanceof WireFormatJMSTextXML){ msg = invokeRequest(msg); } msg = getNext().invoke(msg); if (jmsBinding.getResponseWireFormat() instanceof WireFormatJMSTextXML){ msg = invokeResponse(msg); } return msg; } public Message invokeRequest(Message msg) { // get the jms context JMSBindingContext context = (JMSBindingContext)msg.getHeaders().get(JMSBindingConstants.MSG_CTXT_POSITION); javax.jms.Message jmsMsg = context.getJmsMsg(); if ("onMessage".equals(msg.getOperation().getName())) { msg.setBody(new Object[]{jmsMsg}); } else { Object requestPayload = requestMessageProcessor.extractPayloadFromJMSMessage(jmsMsg); msg.setBody(requestPayload); } return msg; } public Message invokeResponse(Message msg) { // get the jms context JMSBindingContext context = (JMSBindingContext)msg.getHeaders().get(JMSBindingConstants.MSG_CTXT_POSITION); javax.jms.Message requestJMSMsg = context.getJmsMsg(); Session session = context.getJmsSession(); javax.jms.Message responseJMSMsg; if (msg.isFault()) { responseJMSMsg = responseMessageProcessor.createFaultMessage(session, (Throwable)msg.getBody()); } else { responseJMSMsg = responseMessageProcessor.insertPayloadIntoJMSMessage(session, msg.getBody()); } msg.setBody(responseJMSMsg); return msg; } public Invoker getNext() { return next; } public void setNext(Invoker next) { this.next = next; } } |
3 - wireFormat.? with onMessage(javax.jms.Message msg) style interface
This format can be derived from the service interface so again you don't need to specify it.
Composite
A composite using any form of JMSMessage end to end.
Code Block |
---|
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" targetNamespace="http://helloworld" xmlns:hw="http://helloworld" name="helloworld"> <component name="HelloWorldReferenceComponent"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldReferenceImpl" /> <reference name="helloWorldService1" > <binding.jms> <destination name="HelloWorldService1"/> <wireFormat.jms/> </binding.jms> </reference> </component> <component name="HelloWorldServiceComponent1"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldServiceImpl" /> <service name="HelloWorldService"> <binding.jms> <destination name="HelloWorldService1"/> </binding.jms> </service> </component> </composite> |
Java service interface
Code Block |
---|
@Remotable public interface HelloWorldService { public void setGreetings(javax.jms.Message msg); } |
Problem with this is that it's an interface and JAXB doesn't work with interfaces.
WSDL service interface
Code Block |
---|
<wsdl:types> <schema elementFormDefault="qualified" targetNamespace="http://helloworld" xmlns="http://www.w3.org/2001/XMLSchema"> <element name="setGreetings"> <complexType> <sequence> <any/> </sequence> </complexType> </element> </schema> </wsdl:types> <wsdl:message name="setGreetingsRequest"> <wsdl:part element="tns:setGreetings" name="parameters"/> </wsdl:message> <wsdl:portType name="HelloWorld"> <wsdl:operation name="setGreetings"> <wsdl:input message="tns:setGreetingsRequest" name="setGreetingsRequest"/> </wsdl:operation> </wsdl:portType> |
getGreetings message body
Fred Bloggs
Resulting runtime configuration
TBD
JMSReferenceInterceptor
A pass though. JMS message is delivered from the implementation to the binding.
JMSServiceInterceptor
A pass through. JMS message is delivered from the binding to the implementation.
4 - wireFormat.someCustomFormat
Composite
A composite using any form of JMS message on the wire but with a non-JMS message business interface
Code Block |
---|
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" targetNamespace="http://helloworld" xmlns:hw="http://helloworld" name="helloworld"> <component name="HelloWorldReferenceComponent"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldReferenceImpl" /> <reference name="helloWorldService1" > <binding.jms> <destination name="HelloWorldService1"/> <wireFormat.someCustomFormat/> </binding.jms> </reference> </component> <component name="HelloWorldServiceComponent1"> <implementation.java class="org.apache.tuscany.sca.binding.jms.format.jmstextxml.helloworld.HelloWorldServiceImpl" /> <service name="HelloWorldService"> <binding.jms> <destination name="HelloWorldService1"/> <wireFormat.someCustomFormat/> </binding.jms> </service> </component> </composite> |
Java service interface
Code Block |
---|
@Remotable public interface HelloWorldService { public String getGreetings(String name); public String getPersonGreetings(Person person); } |
Code Block |
---|
public class Person { String firstName; String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } |
Resulting runtime configuration
TBD
MyMessageReferenceInterceptor(Would be custom name)
Take service interface arguments from tuscany message and perform custom transformation to create JMSmessage
MyMessageServiceInterceptor
Take JMSTextMessage body and perform custom transformation create service interface arguments in tuscany message
Background Material
Current Situation
Message Formats
Plugability Options
Note | ||
---|---|---|
| ||
I guess the key is to define the extensibility on the edges of invocation chains. For reference binding or component implementation, the invoker is Reference Binding and Implementation Service Binding One idea is that we could wrap the native message into SCA message with the body set to the native message. This way, we can just contribute Inteceptors ---------------------- native JMS age ----------------- --------------------- SCA message with normalized payload ---------------- — SCA Message — ---------------------- native JMS Message ---------------- |
Ant's JMS examples
Scenarios
The JMS binding needs to support sending/receiving different types of JMS messages and payloads, for example,
1- JMS Text messages containing XML (this is the default type of messages)
2- JMS Object messages containing the actual request/response Objects
3- JMS Text messages containing a String, how the payload String is obtained could be a default (toString on the args) or a user plugable function
4- Arbitrary JMS message types with arbitrary payloads and a user customizable handler to convert to/from the JMS message
Note, that the latest spec drafts say the default may be either a JMS Text message or a JMS Bytes message. The response should probably match what was received so the response and callback handler need some way of knowing what type of request message was received.
For the non-default cases we need a way of defining this somewhere. Currently it uses a Tuscany specific attribute on the JMS binding - "messageProcessor", latest spec discussions are for a new wireFormat SCDL element - http://lists.oasis-open.org/archives/sca-bindings/200808/msg00036.html
We could have the following wireFormat extensions to support those above 4 cases:
1- <wireFormat.jmstextxml/> (the default, you wouldn't need to specify it)
2- <wireFormat.jmsobject/>
3- <wireFormat.jmstext/> and <wireFormat.jmstext handler="some.user.Class"/>
4- <wireFormat.jmscustom handler="some.user.Class"/>
A problem with 3 and 4 would be what is the interface for the handler class? To create a JMS Message requires a JMS session so the handler cannot easily create the JMS message itself so its probably easier if the type of message can be determined from the wireFormat SCDL.
A complete example of using JMS Object messages could be:
Code Block |
---|
<composite ...> <component name="Client"> <implementation.java class="..."/> <reference name="serviceA"> <binding.jms uri="jms:TheService"> <wireFormat.jmsobject/> </binding.jms> </reference> </component> <component name="Service"> <implementation.java class="..."/> <service name="TheService"> <binding.jms /> <wireFormat.jmsobject/> </binding.jms> </service> </component> </composite> |