ISY Developers:Java Web Services Tutorial

From Universal Devices, Inc. Wiki
Jump to: navigation, search

How to program/consume UDI web services in Java

This will show you how to use Java API for XML - WebServices (JAX-WS) to parse the UDI web services (and UDI Elk web services) wsdl files and turn them into Java object classes.

JAX-WS has an import tool (wsimport) that reads a W3C compliant wsdl file and turns its "services" into Java objects so all you have to do is call the appropriate Java classes.


For example; once the processing is complete, you can use a Java class like the following to access the ISY:

  package ca.bc.webarts.tools.isy;

  /*
  * These are all generated from the JAX-WS wsimport tool
  */
  import ca.bc.webarts.tools.isy.webservices.DateTime;
  import ca.bc.webarts.tools.isy.webservices.Empty;
  import ca.bc.webarts.tools.isy.webservices.UDIServices;
  import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;
  import ca.bc.webarts.tools.isy.webservices.Variable;

  import java.util.List;
  import java.util.Map;
  import javax.xml.ws.BindingProvider;

  public class IsyWSClient
  {
   private String isyUsername_ = "admin";
   private String isyPassword_ = "admin";
   private UDIServices service_ = new UDIServices();
   private UDIServicesPortType port_ = service_.getUDIServicesPort();

     public static void main(String[] args)
     {
       IsyWSClient instance = new IsyWSClient();

         Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();
         reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);
         reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);

         DateTime dt = instance.port_.getSystemDateTime(new Empty());
         System.out.println("NTP time=" + dt.getNTP());
     }
  }

Online Documentation

Before I start, here are some references to some good background information and tools, specific to processing Web Services in Java.

Requirements

or

I used NetBeans and will document the process using NetBeans.

Process

Basic setup

Download and install NetBeans. http://www.netbeans.org

Create a new 'Java Application' Project...

NBNewJavaApp.png


Name Your Project and NEW Class...

NBProjectName.png


Right-click on the your new project in the projects listed along the left (to bring up the context menu) and

click Properties then select Libraries to Add Library JAX-WS 1.1 ...

NBProjectAddLibrary.png

You should now have an template Class (without a main method) in your editing window...

NBNewEmptyClass.png

Now before you start writing your Java app, we need to get the UDI Web Services loaded from the wsdl file.

WSDL Import

Changes to the wsdl file

The 1st thing to note is that JAX-WS wsimport tool is very strict and will not process the default UDI wsdl file without a few tweaks. This is because the udi ( http://isy99/services.wsdl ) or the actual wsdl file udiws30.wsdl is not 100% compliant with the W3C specification for WS-I basic profile definition R2204 -A document-literal binding in a DESCRIPTION MUST refer, in each of its soapbind:body element(s), only to wsdl:part element(s) that have been defined using the element attribute. see http://www.ws-i.org/Profiles/BasicProfile-1.1.html#Bindings_and_Parts .

Many of the message parts refer to a 'type' instead of 'element' . For example...

 <wsdl:message name="GetNodesConfigResponse">
   <wsdl:part name="nodes" type="uo:nodes"/>
 </wsdl:message>

This is easily fixed with a small abstraction

 <xsd:element name="nodes" type="uo:nodes"/>

and then use that element in the original message part...

 <wsdl:message name="GetNodesConfigResponse">
   <wsdl:part name="nodes" element="u:nodes"/>
 </wsdl:message>


These are simple changes.

There are ~16 times in the file. I went through and updated the udiws30.wsdl file to add the following xsd:elements:

  <!--  RENAMED to add Type with the corresponding element that refers to it below -->
  <xsd:complexType name="UDIDefaultResponseType">
     <xsd:annotation>
         <xsd:documentation>
             Default status info response
         </xsd:documentation>
     </xsd:annotation>

     <xsd:sequence>
         <xsd:element name="status" minOccurs="1" maxOccurs="1" type="xsd:string"/>
         <xsd:element name="info" minOccurs="0" maxOccurs="1" type="xsd:string"/>
     </xsd:sequence>
  </xsd:complexType>

  <!--  RENAMED to add Type with the corresponding element that refers to it below -->
  <xsd:complexType name="UDITimeResponseType">
     <xsd:attribute name="val" type="xsd:dateTime" use="required">
         <xsd:annotation>
             <xsd:documentation>
                 Timestamp in the form of YYYYMMDD HH:MM:SS
             </xsd:documentation>
         </xsd:annotation>
     </xsd:attribute>
  </xsd:complexType>

  <!--  RENAMED to add Type with the corresponding element that refers to it below -->
  <xsd:complexType name="UDIIntResponseType">
     <xsd:attribute name="val" type="xsd:int" use="required">
         <xsd:annotation>
             <xsd:documentation>
                 Return value of type integer. Currently used for IsSubscribed
             </xsd:documentation>
         </xsd:annotation>
     </xsd:attribute>
  </xsd:complexType>

 <!-- PATCH - NEW Entries: Abstract types with the element attribute -->
     <xsd:element name="UDIDefaultResponse" type="u:UDIDefaultResponseType"/>
     <xsd:element name="UDITimeResponse" type="u:UDITimeResponseType"/>
     <xsd:element name="UDIIntResponse" type="u:UDIIntResponseType"/>
     <xsd:element name="nodes" type="uo:nodes"/>
     <xsd:element name="configuration" type="uo:configuration"/>
     <xsd:element name="SysStat" type="uo:SystemStatus"/>
     <xsd:element name="DT" type="uo:DateTime"/>
     <xsd:element name="SystemOptions" type="uo:SystemOptions"/>
     <xsd:element name="SMTPConfig" type="uo:SMTPConfiguration"/>
     <xsd:element name="DBG" type="uo:DBG"/>
     <xsd:element name="SceneProfiles" type="uo:SceneProfiles"/>
     <xsd:element name="SubscriptionResponse" type="uo:Subscription"/>
     <xsd:element name="LastError" type="uo:LastError"/>
     <xsd:element name="DDNSHost" type="uo:DDNSHost"/>
     <xsd:element name="Var" type="uo:Variable"/>
     <xsd:element name="Vars" type="uo:Variables"/>
 <!-- PATCH End: Abstract types with the element attribute -->


Then update the message parts to refer to these new elements= instead of type= .

The following message parts with type attributes were changed:

  • <wsdl:part name="response" element="u:UDIDefaultResponse"/>
  • <wsdl:part name="timeResponse" element="u:UDITimeResponse"/>
  • <wsdl:part name="intResponse" element="u:UDIIntResponse"/>
  • <wsdl:part name="nodes" element="u:nodes"/>
  • <wsdl:part name="configuration" element="u:configuration"/>
  • <wsdl:part name="SysStat" element="u:SysStat"/>
  • <wsdl:part name="DT" element="u:DT"/>
  • <wsdl:part name="SystemOptions" element="u:SystemOptions"/>
  • <wsdl:part name="SMTPConfig" element="u:SMTPConfig"/>
  • <wsdl:part name="DBG" element="u:DBG"/>
  • <wsdl:part name="SceneProfiles" element="u:SceneProfiles"/>
  • <wsdl:part name="SubscriptionResponse" element="u:SubscriptionResponse"/>
  • <wsdl:part name="LastError" element="u:LastError"/>
  • <wsdl:part name="DDNSHost" element="u:DDNSHost"/>
  • <wsdl:part name="Var" element="u:Var"/>
  • <wsdl:part name="Vars" element="u:Vars"/>

This process should work on any version of the wsdl http://isy99/services.wsdl . Note: the version of the wsdl that you get from your ISY is a small version that imports the main wsdl and defines the service and port. For ease, I suggest downloading the UDI WSDK and work on the local files contained in it.

  • A patched wsdl is attached udiws30_patched.wsdl
  • If you want, do a diff between them to be clear what changes were made

Import the updated udiws30_patched.wsdl file

Right click on the NetBeans project and click 'New Web Service Client'

  • This will ask you for the location of the wsdl file
  • It will also ask for a package to put the resulting code
    • I used ca.bc.webarts.tools.isy.webservices

NBNewPackage.png

NetBeans will call the wsimport tool and automatically generate a bunch of new classes - one for each UDIServices service. You can then use/call them in your Java application.

See them along the left under Web Services References. See the Grey menu item named Generated Sources (jax-ws) - this is where all the generated Java classes reside.

NBNewParsedServices.png

Import the ELK WSDL file

The Elk web services definition file imports without any of these type/element errors!

Right click on the NetBeans project and click New Web Service Client and point it to the
udielkws1.wsdl
file.

Write your Java Application

Now you can go back and write your code. Click on your Java app file in the Project/Source Packages . It should show an empty class. Create a main method.

Create the Service And Port

All of the services get called from the UDIServices and UDIServicesPortType so I created a class objects for these.

 import ca.bc.webarts.tools.isy.webservices.UDIServices;
 import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;
 ...
 UDIServices service_ = new UDIServices(); // this classname originally comes from the wsdl file
 UDIServicesPortType port_ = service_.getUDIServicesPort();

Note: your import package names will be different than my example.

HTTP Basic Authentication

The UDI ISY expects BASIC HTTP authentication that gets inserted into the HTTP header. This is easy in Java by getting the session context and adding the username/password to it.

 private String isyUsername_ = "admin";
 private String isyPassword_ = "yourPasswordHere";
 ...
 /* These calls deal with hashing them into Base64 and putting them into the http header */
 Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();
 reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);
 reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);

Call Web Services

Netbeans has an easy code create feature that automatically creates the code to call your new web services.

In the editor window, right click and then click on Insert Code... and select call web service operation. a list off all the services come up, you choose which one and it will create the code to call it.

NBInsertCodeWebServices.png

Now, the rest is the easy part because all the WebServices have been wrapped in Java methods. For example, to get the ISY Date and Time...

 import ca.bc.webarts.tools.isy.webservices.DateTime;
 import ca.bc.webarts.tools.isy.webservices.Empty;
   ...
 DateTime dt = port_.getSystemDateTime(new Empty());
 System.out.println("NTP time=" + dt.getNTP());

Sample Java App

Here is my full (very simple) UDI/ISY Web Services Client.

  package ca.bc.webarts.tools.isy;

  /* These are all generated from the JAX-WS wsimport tool */
  import ca.bc.webarts.tools.isy.webservices.DateTime;
  import ca.bc.webarts.tools.isy.webservices.Empty;
  import ca.bc.webarts.tools.isy.webservices.UDIServices;
  import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;
  import ca.bc.webarts.tools.isy.webservices.Variable;

  import java.util.List;
  import java.util.Map;
  import javax.xml.ws.BindingProvider;

  public class IsyWSClient
  {
    private String isyUsername_ = "admin";
    private String isyPassword_ = "admin";
    private UDIServices service_ = new UDIServices();
    private UDIServicesPortType port_ = service_.getUDIServicesPort();

     public static void main(String[] args)
     {
         IsyWSClient instance = new IsyWSClient();

         Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();
         reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);
         reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);

         DateTime dt = instance.getSystemDateTime();
         System.out.println("NTP time=" + dt.getNTP());

         //List<Variable> isyVars = instance.getVariables(1);// 1=Integer Variable 2=State Variable
         //for (Variable variable : isyVars)
         //{
        //  System.out.println(variable.getId() + "=" + variable.getVal());
         //}
     }

     /**
      * gets a list of isy variables by type spec'd.
      * @param type 1=IntegerVariable 2=StateVariable
      * @return a list of vars
      */
     private List<Variable> getVariables(int type)
     {
       return port_.getVariables(type);
     }

     /**
      * Gets the ISY DateTime object.
      * @return the ISY DateTime object
      */
     private DateTime getSystemDateTime()
     {
       return port_.getSystemDateTime(new Empty());
   }
  }

Debug SOAP Messages With TCPMon

If you want to watch the SOAP messages that are going-to --> and <--coming-back from your ISY, you can use a small (free) Java program called TCPMon.

It acts as a proxy. You point your Web Services app Endpoint Address at TCPMon and TCPMon echoes it to the screen and redirects it onto your ISY. To change your endpoint address, do either:

  • edit the WSDL file to point at the TCPMon localPort such as: http://localhost:8080 and re-import it the WSDL to have it regenerate the code

OR


You can even manually type or edit the SOAP message and send it direct to your ISY. TCPmonScreenshot.png

Download it at its Google Code hosted site (http://code.google.com/p/tcpmon).


WSDK Forum Page


ISY-99i Series INSTEON : Main Page