Creating JAX-WS webservice using Service Data Objects (SDO) instead of JAXB-bound POJOs
If you need to invoke a logic using Service Data Objects (SDOs) from a JAX-WS webservice under Websphere 7 without the SCA Feature Pack, it is possible to do it similarly to the old approach of generating a JAX-RPC webservice from a WSDL with an SDO facade (actually building on it).
The steps are:
Notice that it generates POJO interfaces for the data objects, their implementations that actually implement the required SDO's commonj.sdo.DataObject, and some factories for instantiating them.
At the end, the code should look like this: LearningActivityRawXmlServiceImpl.java, part 1:
The classLaSOAPElementUtilhas been created by the JAX-RPC generator. LearningActivityHttpBindingImplSDO represents the code that accepts and produces an SDO.
To transform the Source to XML and DataObject:
The classes com.ibm.w3.xmlns.ibmww.hr.learning.lms.br.la.DocumentRoot and ...la.UpdateLearningActivityType have been created by the JAX-RPC generator based on the WSDL and XSDs. laUtil_.deserialize(..) only invokes the (also generated) EMFSOAPElementUtil.deserialize(..), which we will adjust later on. Notice that we need to cast from the generated pure java interface to the implementation class (UpdateLearningActivityTypeImpl) because only it does implement DataObject.
To transform DataObject to XML and Source:
Again, laUtil_.serialize(..) only invokes the EMFSOAPElementUtil.serialize(..).
EMFSOAPElementUtil.java (part):
Don't worry about the XMLResourceImpl and similar stuff, it has been generated by the JAX-RPC tool.
Of course this is necessary only if any of the JAX-RPC generated data objects use XMLGregorianCalendar.
The conversion utility ConversionUtils.java:
Adding the conversion to each of the affected data objects' eSet method:
The steps are:
- Use RAD to generate a JAX-RPC webservice from a WSDL with an SDO facade.
- Implement a JAX-WS webservice accessing directly its input as XML data (i.e. implement is as a WebServiceProvider for message payload)
- Use Transformer and StreamSource/Result to convert from/to String containing XML
- Copy the SDO-related classes from the JAX-RPC webservice to the JAX-WS one, exclude just the JAX-RPC webservice interface and implementation
- Adjust the generated EMFSOAPElementUtil - change (de)serialize methods to expect/produce a String instead of SOAPElement
- Put it all together in the WS implementation class created in #2
- Finishing touches - add conversion of org.eclipse.emf.ecore.xml.type.internal.XMLCalendar to javax.xml.datatype.XMLGregorianCalendar
Implementation steps in detail
1. Generating JAX-RPC webservice with an SDO facade
Follow the link above.Notice that it generates POJO interfaces for the data objects, their implementations that actually implement the required SDO's commonj.sdo.DataObject, and some factories for instantiating them.
2. Implementing a JAX-WS webservice accessing its data as XML (javax.xml.transform.Source)
Again, follow the corresponding link above.At the end, the code should look like this: LearningActivityRawXmlServiceImpl.java, part 1:
javax.xml.ws.ServiceMode(value=javax.xml.ws.Service.Mode.PAYLOAD)
@javax.xml.ws.WebServiceProvider(...)
public class LearningActivityRawXmlServiceImpl implements Provider<Source> {
private LearningActivityHttpBindingImplSDO sdoInstance_ = new LearningActivityHttpBindingImplSDO();
private LaSOAPElementUtil laUtil_ = new LaSOAPElementUtil();
@Override
public Source invoke(final Source request) {
final DataObject requestSDO = convertRequest(request);
final DataObject responseSDO = sdoInstance_.updateLearningActivity(requestSDO);
final Source response = convertResponse(responseSDO);
return response;
}
// ... other methods omitted ...
}
The classLaSOAPElementUtilhas been created by the JAX-RPC generator. LearningActivityHttpBindingImplSDO represents the code that accepts and produces an SDO.
To transform the Source to XML and DataObject:
private DataObject convertRequest(final Source request) {
final StringWriter requestXmlWriter = new StringWriter();
try {
final Transformer trans = TransformerFactory.newInstance().newTransformer();
trans.transform(request, new StreamResult(requestXmlWriter));
final String requestXml = requestXmlWriter.toString();
final DocumentRoot laRoot = (DocumentRoot) laUtil_.deserialize(requestXml);
final UpdateLearningActivityType updateLearningActivityParametersSDO = laRoot.getUpdateLearningActivity();
return (UpdateLearningActivityTypeImpl) updateLearningActivityParametersSDO;
} catch (Exception e) {
// TransformerException, IOException, SAXException
throw new RuntimeException("Conversion failed: " + e + ", in: " + request, e);
} catch (TransformerFactoryConfigurationError e) {
throw new RuntimeException("Transformation during conversion failed: " + e + ", in: " + request, e);
}
}
The classes com.ibm.w3.xmlns.ibmww.hr.learning.lms.br.la.DocumentRoot and ...la.UpdateLearningActivityType have been created by the JAX-RPC generator based on the WSDL and XSDs. laUtil_.deserialize(..) only invokes the (also generated) EMFSOAPElementUtil.deserialize(..), which we will adjust later on. Notice that we need to cast from the generated pure java interface to the implementation class (UpdateLearningActivityTypeImpl) because only it does implement DataObject.
To transform DataObject to XML and Source:
private Source convertResponse(final DataObject responseSDO) {
try {
final String responseXml = laUtil_.serialize((EDataObjectImpl) responseSDO);
final Source response = new StreamSource(new StringReader(responseXml));
return response;
} catch (IOException e) {
throw new RuntimeException("Conversion failed: " + e + ", in: " + responseSDO, e);
} catch (SOAPException e) {
throw new RuntimeException("Conversion failed: " + e + ", in: " + responseSDO, e);
}
}
Again, laUtil_.serialize(..) only invokes the EMFSOAPElementUtil.serialize(..).
4. Adjust the generated EMFSOAPElementUtil to use XML String instead of SOAPElement
While the JAX-RPC generated EMFSOAPElementUtil uses SOAPElement, we need to use Strings containing XML and therefore will adjust the signature and bodies of the (de)serialization methods slightly:EMFSOAPElementUtil.java (part):
public EDataObjectImpl deserialize (final String xml)
throws IOException, SAXException
{
// Change: the inputStream is created from a String and not from a SOAPElement
final XMLResourceImpl res = (XMLResourceImpl)factory.createResource(URI.createURI("*.xml"));
final InputStream inputStream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
res.load(inputStream, null);
final EDataObjectImpl document = (EDataObjectImpl)res.getContents().get(0);
return document;
}
public String serialize ( EDataObjectImpl document )
throws IOException, SOAPException
{
XMLResourceImpl res = (XMLResourceImpl)factory.createResource(URI.createURI("*.xml"));
res.getContents().add(document);
res.getDefaultSaveOptions().put(XMLResource.OPTION_DECLARE_XML,Boolean.FALSE);
res.setEncoding("UTF-8");
// Changed below - save into a StringWriter
final StringWriter outputXmlWriter = new StringWriter();
res.save(outputXmlWriter,null);
return outputXmlWriter.toString();
}
Don't worry about the XMLResourceImpl and similar stuff, it has been generated by the JAX-RPC tool.
6. Finishing touches - add conversion of XML Calendar
While JAX-RPC uses javax.xml.datatype.XMLGregorianCalendar, the EMF-based SDO implementation uses an incompatible org.eclipse.emf.ecore.xml.type.internal.XMLCalendar and it's therefore necessary to convert the former to the latter in each eSet(..) method of the generated data objects.Of course this is necessary only if any of the JAX-RPC generated data objects use XMLGregorianCalendar.
The conversion utility ConversionUtils.java:
import java.util.GregorianCalendar;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.eclipse.emf.ecore.xml.type.internal.XMLCalendar;
public class ConversionUtils {
public static XMLGregorianCalendar convertEmfToXmlCalendar(final XMLCalendar emfCalendar) {
final GregorianCalendar gregCal = new GregorianCalendar();
gregCal.setTime(emfCalendar.getDate());
gregCal.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
final XMLGregorianCalendar xmlGregCal = DatatypeFactory
.newInstance().newXMLGregorianCalendar(gregCal);
return xmlGregCal;
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
return null;
}
}
Adding the conversion to each of the affected data objects' eSet method:
// In a generated data class extending org.eclipse.emf.ecore.sdo.impl.EDataObjectImpl
public void eSet(int featureID, Object newValue)
{
if (newValue instanceof XMLCalendar) {
newValue = ConversionUtils.convertEmfToXmlCalendar((XMLCalendar) newValue);
}
switch (featureID)
{
...
}
super.eSet(featureID, newValue);
}
That's it, folks.