Create a Java Callback Listener in Eclipse

Overview

For Wireless Network Services API service requests that require significant processing time, such as provisioning service for a device or changing a service plan, the ThingSpace Platform replies first with a synchronous response that acknowledges the request and allows your application to continue processing. Later, the platform sends an asynchronous callback message, reporting the results of the request.

The synchronous response contains a unique Request ID. The callback message contains the same Request ID so that you can associate the callback contents with the original API request.

ThingSpace Platform callback services provide your application with real-time updates about service changes. They also create less load on your systems because your application does not have to poll for updates, which means better performance.

To receive callback messages, you must create and deploy one or more web services that can validate and process SOAP messages that conform to the M2M CallbackData schema. You must register the URLs of your web services with the ThingSpace Platform so that it knows where to send the callback messages.

Routing, Firewall, and Security Considerations

Your company must host your callback listening web services at an IP address or URL that can be reached by the ThingSpace Platform callback servers. (This means that you probably cannot use your own computer to test callbacks, unless you have an externally visible IP address that leads to your computer.)

For security reasons, your IT department will probably put the hosting servers behind your company firewall. The firewall must be configured to allow HTTP requests from the Verizon M2M servers to reach the servers hosting your listening service. Contact your Verizon Wireless business sales representative or sales engineer for the IP addresses used by the Verizon M2M callback services.

Requests from the M2M callback services will use TLS 1.1 or 1.2 protocols to secure the messages. Other protocols are not supported.

Create a Simple Callback Listener Service

This tutorial walks you through the steps to create a simple callback listening service that validates incoming requests and returns a callback response that contains the request ID. This simple service does not do anything with the callback data, but there are comments in the code showing where you could act on the data.

This tutorial uses Eclipse Kepler (4.3) or Luna (4.4), CXF 2.x, and Tomcat 8. (The instructions would be very similar for Axis2 instead of CXF, and Tomcat 7 instead of Tomcat 8.) Before starting the steps below, you should have the CXF runtime installed and have Eclipse preferences set to point to the runtime.

NOTE: This tutorial will not work with CXF release 3; you must use CXF release 2.

NOTE: The callback mechanism included in Axis2 cannot be used with ThingSpace Platform callbacks. Axis2 callbacks are for receiving a delayed response to a request. The ThingSpace Platform sends immediate responses to all requests, and then sends callback messages as new HTTP requests that originate on the ThingSpace Platform.

Create a Project

  1. From the File menu, select New and then Dynamic Web Project.
  2. Enter a name for the project. This tutorial uses "cbListener."

  3. Set the other project options based on the software versions that you have. For this tutorial we're using:

  4. Click Finish.

Enable CXF 2.x Web Services

  1. Right-click on the project and select Properties.
  2. On the Project Facets properties page, select the CXF 2.x Web Services facet in the list and click OK.

    NOTE: If you are using Axis2 instead of CXF, enable the Axis2 Web Services facet for your project.

Import the WSDL and XSD Files

  1. In the Project Explorer, expand the project and then right-click on WebContent folder and select Import.
  2. Under Select an Import Source, choose General > File System, then click Next.

  3. Browse to the “callback wsdls” folder in the SDK.
  4. Select both the CallbackService_WNS_Gen2wsdl file and the CallbackData_WNS_Gen2.xsd file, then click Finish.

Create a Web Service Skeleton from the WSDL

  1. Right-click on the WSDL file in the WebContent folder and select Web Services > Generate Java Bean Skeleton.
  2. The "Web Service" window should already be set to create a Top-down Java Bean Web Service, with the Service Definition pointing to the WSDL file.

  3. If the second line in the Configuration (circled in the picture above) does not say Apache CXF (or Axis2 if you're using that), click on that line and select the correct runtime in the Service Deployment Configuration window, and then click OK to close that window.

  4. Make sure that the Services slider is set to the Start Service position and then click Finish.
  5. Right-click on the CallbackData_WNS_Gen2.xsd file in the WebContent folder and select Generate > JAXB Classes.
  6. Select your project and then click Next.
  7. In the "Configure JAXB class generation" window, enter "com.nphase.unifiedwebservice.v2.data" as the Package name, and then click Finish.

Implement the CallbackService

The "Generate Java Bean Skeleton" wizard created the skeleton CallbackService in the JAX-WS Web Services > Web Services folder. (The actual file is named CallbackServiceImpl.java.) You need to edit the CallbackService to add code for processing callback requests.

The callback method in the CallbackService accepts a CallbackRequest object, which is a SOAP message that comes in as an HTTP request from the ThingSpace Platform and is converted to a Java object. It returns a CallbackResponse object, which gets converted to XML and is sent back to the ThingSpace Platform as an HTTP response.

  1. Open JAX-WS Web Services > Web Services > CallbackService for editing. The tab in the editor window should show "CallbackServiceImpl.java."
  2. Add import statements for CallbackRequest, CallbackResponse, and CallbackData classes. Note that the CallbackData class is in the com.nphase.unifiedwebservice.v2.data class.

    package com.yourcompany.m2mlistener  // use a package name that works for you
    
    import com.nphase.unifiedwebservice.v2.CallbackRequest;
    import com.nphase.unifiedwebservice.v2.CallbackResponse;
    import com.nphase.unifiedwebservice.v2.data.CallbackData;
    
  3. Add import statements for these other classes that you'll need as you implement the service:

    import java.io.StringReader;
    import java.io.StringWriter;
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Unmarshaller;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.TransformerException;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
    import org.w3c.dom.Node;
    
  4. Edit the callback method in the CallbackServiceImpl class to follow this logic, or copy and paste this code. The highlighted lines are placeholders for code that would do something with the callback data, but that is beyond the scope of this tutorial; you'll have to comment them out to be able to compile and run without errors.

    @javax.jws.WebService(
          serviceName = "CallbackService", 
          portName = "CallbackService", 
          targetNamespace = "http://nphase.com/unifiedwebservice/v2", 
          endpointInterface = "com.nphase.unifiedwebservice.v2.CallbackService", 
          wsdlLocation = "WEB-INF/wsdl/listener/CallbackService_WNS_Gen2.wsdl")
    public class CallbackServiceImpl implements CallbackService {
    
       private static final logger LOG = Logger.getLogger(CallbackServiceImpl.class.getName());
    
       public CallbackResponse callback(CallbackRequest callbackRequest) {
          LOG.info("Executing operation callback");
            
          // If the request is not null, record the request ID
          // then verify that the Data object is not null
          if (callbackRequest == null) {
             throw new NullPointerException("Null request");
          } else {
             LOG.info("Request ID: " + callbackRequest.getRequestId());
          }
          if (callbackRequest.getData() == null) {
             throw new NullPointerException("Null data");
          }
            
          // The Data object needs to be cast to a Node object
          // and then converted to a string of XML
          String cbDataXml = new String();
          Node node = (Node) callbackRequest.getData().getAny();
          try {
             cbDataXml = getOuterXml(node);
          } catch (TransformerException e1) {
             LOG.info("Unable to convert DOM node to XML string");
             e1.printStackTrace();
          }
            
          // Use JAXB to unmarshal the XML string into a CallbackData object
          try {
             JAXBContext jc = JAXBContext.newInstance(CallbackData.class);
             Unmarshaller u = jc.createUnmarshaller();
             StringReader reader = new StringReader(cbDataXml);
             CallbackData cbData = (CallbackData) ((JAXBElement) u.unmarshal(reader)).getValue();
                
             // If the request doesn't contain a SOAP fault, you can do whatever you need to with the contents.
             // This code shows how you could verify that the callback is for a ChangeDeviceState-Activate request, 
             // then compares the request ID with one that was stored from the response to an Activate request, 
             // and then adds the device identifiers to a database.
             if (cbData.getFault().isNil()) {
                if (!cbData.getCarrierService().isNil() &&
                       !cbData.getCarrierService().getValue().getChangeDeviceState().isNil() &&
                       !cbData.getCarrierService().getValue().getChangeDeviceState().getValue().getActivate().isNil()) {
                   // Check if the request ID matches the one for the device that you activated
                   if (callbackRequest.getRequestId() == activateRequestId) {
                      // Loop through the device identifiers
                      // and call a method to store them somewhere
                      for (com.nphase.unifiedwebservice.v2.data.DeviceIdentifier deviceId : cbData
                            .getCarrierService().getValue()
                            .getChangeDeviceState().getValue()
                            .getActivate().getValue()
                            .getDeviceIdentifierCollection()) {
                         AddToActivatedDevices(deviceId.getKind(), deviceId.getIdentifier());
                         // For this tutorial, log the device identifier info
                         LOG.info(deviceId.getKind() + " = " + deviceId.getIdentifier());
                      }
                   }
                }
             } else {
                 // callbackRequest contained a SOAP fault
                 LOG.info("Fault error: " + cbData.getFault().getValue().getFaultcode().toString()
                    + "\n" + cbData.getFault().getValue().getFaultstring());
             }
          } catch (JAXBException e) {
             LOG.info("Unmarshalled object is not of type CallbackData");
             LOG.info(e.getMessage());
          }
            
          // Return a CallbackResponse that contains the request ID to acknowledge 
          // having received the callback request
          CallbackResponse cr = new CallbackResponse();
          cr.setRequestId(callbackRequest.getRequestId());
          return cr;
       }
    
  5. Create the getOuterXml() method.

        // Get the markup containing this node and all its child nodes.
        public static String getOuterXml(Node node) throws TransformerException {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty("omit-xml-declaration", "yes");
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(node), new StreamResult(writer));
            return writer.toString();
        }
    
  6. The CallbackService also contains a skeleton for the smsCallback method. If you are registering to receive SMS callbacks, you can start with this simple code.

        public com.nphase.unifiedwebservice.v2.SmsCallbackResponse smsCallback(com.nphase.unifiedwebservice.v2.SmsCallbackRequest smsCallbackRequest) {
                System.out.println("Received SMS Message: " + smsCallbackRequest.getSmsMessage());
                if (!smsCallbackRequest.getDevice().isEmpty()) {
                    for (com.nphase.unifiedwebservice.v2.DeviceIdentifier deviceId : smsCallbackRequest.getDevice()) {
                        System.out.println("From device " + deviceId.getKind() + " = " + deviceId.getIdentifier());
                    }
                }
    
                return new com.nphase.unifiedwebservice.v2.SmsCallbackResponse();
        }
    
  7. You should be able to compile and run the callback listener on your server. If you register to receive CarrierService callback messages and then activate a device, your callback listener should process it.

Register to Receive Callback Messages

After you have built your callback listener web service and have it running on a server, you need to register the URL of the web service with the ThingSpace Platform so that it knows where to send the callback messages. You only need to do this once for each type of callback message (see the note below); the ThingSpace Platform will store the setting and always send callback messages for your account to that URL. The UWS Explorer tool (included in the Tools and Examples folder in the SDK) is an easy way to register your URL, or you can use a SOAP client like SoapUI, as described here. However, if you choose to do it programmatically, the steps are described below.

NOTE: The ThingSpace Platform can send several different types of callback messages, each relating to a different type of information. This tutorial only describes CarrierService callback messages for device activations. You do not need to subscribe to all callback messages -- only to those that are applicable to your needs.

  1. Add the import statements required to work with callback registration, and those required to add the session token to request headers.

    import org.apache.axiom.soap.impl.dom.soap11.SOAP11Factory;
    import org.apache.axiom.om.OMElement;
    
    import com.nphase.unifiedwebservice.v2.RegisterCallback;
    import com.nphase.unifiedwebservice.v2.CallbackRegistrationServiceStub;
    import com.nphase.unifiedwebservice.v2.RegisterCallbackResponse;
    import org.datacontract.schemas._2004._07.nphase_unifiedwebservice_apis_v2_contract_callbackregistrationservice.RegisterCallbackRequest;
    
  2. Add a method to register a URL as a callback listener for a specific service.

    NOTE: There are several services for which you can register a callback. See the Callback Registration Service page for a description of these services. This example use the CarrierService because it sends responses for device activations.

       private static void Registercallback(String service, String callbackUrl) {
    
          // Create a request for registering the callback and set the
          // account name, service name and URL for your listening service.
          RegisterCallbackRequest request = new RegisterCallbackRequest();
          request.setAccountName("MyAccount");
          request.setServiceName(service);
          request.setUrl(callbackUrl);
    
          RegisterCallback rcbObj = new RegisterCallback();
          rcbObj.setInput(request);
    
          try {
             // Create a new CallbackRegistrationService object
             CallbackRegistrationServiceStub callbackRegSvcStub = new CallbackRegistrationServiceStub();
    			
    			
             // Follow these steps to add the session token to a request header
             // Create an Axis2 soap factory to generate SOAP elements
             SOAP11Factory factory = new SOAP11Factory();
             // Then use the factory to create a "token" element
             OMElement e = factory.createOMElement("token", factory.createOMNamespace("http://nphase.com/unifiedwebservice/v2", "v2"));
             // Set the text of the token element to the current sessionToken 
             e.setText(sessionToken);
             // Add the element to the CallbackRegistrationService object
             callbackRegSvcStub._getServiceClient().addHeader(e);
    
             // Send the request
             RegisterCallbackResponse response = stub.registerCallback(rcbObj);
    
             //For the tutorial, display a success message
             System.out.println("Callback listener registered for " + response.getOutput().getServiceName());
    
          } catch (AxisFault e1) {
             // ...
          } catch (RemoteException e) {
             // ...
          }
       }
    
  3. Call the new method from main() and pass in a service name and URL.

       public static void main(String[] args) {
          Wns session = new Wns()
          session.Login("username", "password");
          session.Registercallback("CarrierService", "http://www.example.com/callbacklistener");
       }
    

NOTE: Allowed port numbers for new callback registrations are 80, 443, 9001-9006 and 50551-50559.