Did you know? DZone has great portals for Python, Cloud, NoSQL, and HTML5!

Jakub is a Java EE developer since 2005 and occasionally a project manager, working currently with Iterate AS. He's highly interested in developer productivity (and tools like Maven and AOP/AspectJ), web frameworks, Java portals, testing and performance and works a lot with IBM technologies. A native to Czech Republic, he lives now in Oslo, Norway. Jakub is a DZone MVB and is not an employee of DZone and has posted 90 posts at DZone. You can read more from them at their website. View Full User Profile

Creating JAX-WS Web Service Using Service Data Objects (SDO) Instead of JAXB-bound POJOs

12.30.2010
Email
Views: 5308
  • submit to reddit

If you need to invoke 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:

  1. Use RAD to generate a JAX-RPC webservice from a WSDL with an SDO facade.
  2. 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
  3. Copy the SDO-related classes from the JAX-RPC webservice to the JAX-WS one, exclude just the JAX-RPC webservice interface and implementation
  4. Adjust the generated EMFSOAPElementUtil – change (de)serialize methods to expect/produce a String instead of SOAPElement
  5. Put it all together in the WS implementation class created in #2
  6. Finishing touches – add conversion of org.eclipse.emf.ecore.xml.type.internal.XMLCalendar to javax.xml.datatype.XMLGregorianCalendar

The help of Rational Application Developer (RAD) describes how to generate a JAX-RPC webservice from a WSDL with an SDO facade but provides no clues for how to use SDOs with a JAX-WS based webservice. The trick is simple: similarly as in the JAX-RPC case, create a JAX-WS webservice that accesses its input as XML data (represented by javax.xml.transform.Source) and use the generate SDO facade code from JAX-RPC to convert the XML from/to SDO.

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:That’s it, folks.
// 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);
}

From http://theholyjava.wordpress.com/2010/12/29/creating-jax-ws-webservice-using-service-data-objects-sdo-instead-of-jaxb-bound-pojos/

Tags:
Published at DZone with permission of Jakub Holý, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Josh Marotti replied on Mon, 2011/01/03 - 3:08pm

Keeping your business logic straight forward with objects you expect (DataObject, EDataObject, etc...) and dealing with the soap message with a message handler to 'intercept' the message would probably separate things out a little cleaner than how you have it setup right now with webproviders.

Jakub Holý replied on Thu, 2011/01/06 - 4:48am in response to: fortknox

Hi Josh, I don't really see a difference between converting the input data to SDO in a webservice or in a message handler as the webservice does nothing else than the conversion from/to SDO and delegation of the action itself to (some existing) SDO-based call. But if the WS had more responsabilities then certainly it would be good to split the conversion responsability out as you propose. Thanks!

Jakub Holý replied on Thu, 2011/01/06 - 5:07am in response to: fortknox

@Josh, I'd be very thankful if you'd give me a hint how to convert the SOAP message's body to an Object (an SDO in my case) and how to pass this object on to the target webservice. I've read an article about JAX-WS handlers but as far as I understand it and the LogicalMessage JavaDoc, a handler cannot replace the XML payload with an object, it can only modify the XML (there is no setPayload(Object payload), only setPayload(Object payload, JAXBContext context), which marshalls the object back to XML). So I really don't see how to perform the conversion I need with a handler :-(

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.