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:
  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:

// 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.

Tags: java api library


Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob