Learn More

· Oracle WebLogic Server 10g Release 3 (10.3)

· Web Services for Oracle® WebLogic Server 10g Release 3 (10.3)

· Getting Started With WebLogic Web Services Using JAX-WS

· Programming RESTful Web Services  With WebLogic Web Services JAX-WS

 
  Sample Code

· NearbyCityRest.java

· NearbyCityFinder.java

· NearbyCityData.java

· NearbyCityFinderImple.java

· Top100UsCities.java

· NearbyCityClientTest.java

· nearby_city.xsd

· build.xml

 
 Download

 Source Code

 

Oracle Take a REST on JAX-WS with WebLogic Server 10.3

This article uses a sample location search application to demonstrate how to build a Representational State Transfer (REST) implementation using WebLogic Server 10.3. It discusses how to use JAX-WS and JAXB technologies in WebLogic Server 10.3 to simplify the development and deployment of RESTful Web Services. The Java programming and configuration requirements are described in detail.

By Symon Chang

Published September 2008

Representational State Transfer (REST) describes any simple interface that transmits data over a standardized interface (such as HTTP) without an additional messaging layer, such as SOAP. Roy Fielding, the inventor of REST, said that the REST architectural style has been used to guide the design and development of the architecture for the modern Web.

In the RESTful programming model, the Web Services—referred to as RESTful Web Services—are viewed as resources and can be identified by their URLs. For example, a client sends an HTTP request to the RESTful endpoint on a server and the server responds with an XML message. In the RESTful scenario, neither the request nor the response employs a SOAP envelope.

With WebLogic Server 10.3, instead of using a Servlet to implement a RESTful Web Service, the RESTful endpoints can be created as a JAX-WS Web Service. All requests to the RESTful endpoint are processed by the same method regardless of the HTTP method used (for example, HTTP GET, POST, and so on)..

In this article, a simple location search application is used to demonstrate how to design and implement a RESTful Web Service using WebLogic Server 10.3. The Java application uses both JAX-WS and JAXB technologies to simplify the program development and deployment.

Now, let’s take a REST!

Overview of the Location Search Sample Application

RESTful Web Services are used widely in marsh-up and mobile applications where the resources are read-only and the SOAP envelope is not used. The simple location search application described in this article can be used for a marsh-up or a mobile application to locate a nearby city over the internet.

The input request of the application is a geocoding, which contains geographic coordinates expressed as latitude and longitude. The response message consists of the city name, state, and latitude and longitude of the nearby city. When using the REST API, there is no limitation on the number of cities that can be searched. However, to simplify the demonstration, the sample application only searches the top 100 cites within the US.

The following provides an example of the request URL: http://localhost:7001/NearbyCityRest/NearbyCityService?lat=40.1&long=-75.1

The previous request returns the city from the top 100 US cities that is nearby to the location whose geocoding equals latitude = 40.1 and longitude = -75.1. The expected response is in XML form, as shown:

      <NearbyCity>
          <City>Philadelphia</City>
          <State>PA</State>
          <Lat>39.952335</Lat>
          <Lng>-75.163789</Lng>
       </NearbyCity>

The following table summarizes the Java programs that are included in the sample application and that are described in this article. You can download the source code package from here.

Name

Description

NearbyCityRest.java

JAX-WS implementation of the RESTful Web Service. See “ Developing the JAX-WS Web Service.”

NearbyCityFinder.java

Interface to find the nearby city. See “ Developing the Nearby City Finder Interface.”

NearbyCityData.java

Wrapper class for the NearbyCity object that defines the methods to calculate the nearby city. See “ Developing the NearbyCityData Class.”

NearbyCityFinderImple.java

Sample implementation of the NearbyCityFinder interface. See “ Developing the Nearby City Finder Interface.”

Top100UsCities.java

Static NearbyCity implementation that stores geocoding information for the top 100 US cities. See “ Implementing the Nearby City Finder Interface.”

NearbyCityClientTest.java

junit test program. See “ Reviewing the NearbyCityRest Java Program.”

The following sections describe how to develop and deploy the sample application. Use of HTTP GET, Path Info, and HTTP Post are demonstrated.

Designing the XML Schema

Based on the expected XML output shown in the previous section, we can design the W3C XML schema (XSD) for the NearbyCity, with the following four required elements:

  • City for the name of the nearby city, in string format
  • State for the state name of the city, in string format and limited to two characters
  • Lat for the latitude of the city, in double format
  • Lng for the longitude of the city, in double format

 

We also assign the http://example.org namespace to the NearbyCity element.

The resulting XML schema, nearby_city.xsd,  is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://example.org" targetNamespace="http://example.org"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified" attributeFormDefault="unqualified">
  <xs:element name="NearbyCity">
    <xs:annotation>
      <xs:documentation>Nearby City for Take a REST example</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element name="City" type="xs:string">
          <xs:annotation>
            <xs:documentation>The City in US</xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element name="State">
          <xs:annotation>
            <xs:documentation>The state of this City</xs:documentation>
          </xs:annotation>
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:length value="2"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="Lat" type="xs:double">
          <xs:annotation>
            <xs:documentation>Latitude of the City</xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element name="Lng" type="xs:double">
          <xs:annotation>
            <xs:documentation>Longitude of the City</xs:documentation>
          </xs:annotation>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

The following figure shows the XML schema for the NearbyCity element in graphical form.

NearbyCity XSD
Figure 1. NearbyCity XSD

Generating Java Classes for the XML Schema Using the Binding Compiler (XJC)

Once the schema for the NearbyCity is created, we can use XJC on JAXB to generate Java classes for the NearbyCity element using the following Ant script:

<taskdef name="xjc" classname="com.sun.tools.xjc.XJC2Task" />
  <target name="jaxb" >
    <mkdir dir="./build" />
    <mkdir dir="./build/classes" />
    <xjc schema="nearby_city.xsd"
        package="restful.server.nbc" destdir="./build/classes" />
    <javac debug="true" srcdir="./build/classes" destdir="./build/classes" />
</target>

When we run the ant jaxb command, the XJC program uses the nearby_city.xsd schema to generate the following three Java programs:

  • NearbyCity.java – Defines the Java representation for XML content for NearbyCity, including the getter and setter methods for the NearbyCity element.
  • ObjectFactory.java – Constructs new instances of the NearbyCity object.
  • Package-info.java – Defines the mapping information of the package and namespace.

 

Developing the JAX-WS Web Service

The JAX-WS Web Service, NearbyCityRest.java, is the main entry point for the RESTful Web Service, NearbyCityService.

The following shows a simplified version of the NearbyCityRest.java program:

package restful.server;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.BindingType;
import javax.xml.ws.Provider;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.util.StringTokenizer;

@WebServiceProvider(
targetNamespace="http://example.org",
serviceName = "NearbyCityService")
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class NearbyCityRest implements Provider<Source> {
  @Resource(type=Object.class)
  protected WebServiceContext wsContext;
  NearbyCityFinder cityFinder;
  public NearbyCityRest() {
    this.cityFinder = new NearbyCityFinder();
  }
  public Source invoke(Source source)  {
    try {
        MessageContext messageContext = wsContext.getMessageContext();
        String query = (String)messageContext.get(MessageContext.QUERY_STRING);
        if (query != null && query.contains("lat=") &&
             query.contains("lng=")) {
            return this.createSource(query);
        }        
    } catch(Exception e) {
        e.printStackTrace();        
    }
    throw new HTTPException(500);
  }
  private Source createSource(String str) throws Exception {         
        StringTokenizer st = new StringTokenizer(str, "=&/");
        st.nextToken();
        double latitude = Double.parseDouble(st.nextToken());
        st.nextToken();
        double longitude = Double.parseDouble(st.nextToken());
        NearbyCityData nearby =
             this.cityFinder.findNearby(latitude, longitude);
        return new StreamSource(
             new ByteArrayInputStream(nearby.toXmlByteArray()));
    }
}

The following sections examine the Java code in more detail:

  • Implementing the Provider Interface
  • Using Annotations in the Provider Interface
  • Implementing the Provider.invoke() Method

Implementing the Provider Interface

The NearbyCityRest.java program is implemented as a RESTful Web Services. Specifically, the program:

  • Implements the javax.xml.ws.Provider interface.
  • Uses Provider endpoints that are based on the XML HTTP binding so that HTTP GET requests are passed to the endpoint.
  • Defines the invoke() method of the javax.xml.ws.Provider<T> interface, as described in “Implementing the Provider.invoke() Method.”

The Provider interface provides a dynamic alternative to building a service endpoint interface (SEI). By implementing the Provider interface in NearbyCityRest.java, requests and responses of NearbyCityService endpoint are processed by this class.

The following shows how to implement the Provider interface for this service:

@WebServiceProvider(
targetNamespace="http://example.org",
serviceName = "NearbyCityService")
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class NearbyCityRest implements Provider<Source> {
  @Resource(type=Object.class)
 . . .
 . . .
}

Using Annotations in the Provider Interface 

 

As shown in the previous sample excerpt, the sample application uses the following annotations:

  • @WebServiceProvider – In   JAX-WS, all Provider endpoints must include the @WebServiceProvider annotation. In the RESTful case, the annotation defines the service endpoint information and target namespace.
  • @BindingType – Provider endpoint also needs to be configured for different bindings using the binding IDs defined in the JAX-WS API. In the sample application, the @BindingType annotation is used to specify XML/HTTP binding.
  • @Resource –  This annotation is used by the JAX-WS runtime to inject a WebServiceContext into the NearbyCityRest instance.

Implementing the Provider.invoke() Method

To implement the T Provider.invoke(T request) method, we define the following method declaration which has a simple try-catch block to handle exceptions. Notice that the method takes a Source object as both the request and response. This corresponds to the type of Provider that we implemented. 

public Source invoke(Source source)  {
    try {
        MessageContext messageContext = wsContext.getMessageContext();
        String query = (String)messageContext.get(MessageContext.QUERY_STRING);
        if (query != null && query.contains("lat=") &&
             query.contains("lng=")) {
            return this.createSource(query);
        }        
    } catch(Exception e) {
        e.printStackTrace();        
    }
    throw new HTTPException(500);
  }
}

In the example above, the endpoint gets the necessary HTTP request query string using MessageContext.QUERY_STRING.

Developing the Nearby City Finder Interface

When the geocoding of the specified location is identified using the query string, the latitude and longitude can be used to find the nearby city. To accomplish this, we pass the whole string into the application for searching.

The sample application provides an interface to find the nearby city called NearbyCityFinder. It defines a single method. The input parameters for the method are latitude and longitude, and the return value is a NearbyCityData object. The following shows the NearbyCityFinder interface:

public interface NearbyCityFinder {
    /**
     * Find a nearby city object by given location with latitude and longitude
     * @param latitude  latitude
     * @param longitude longitude
     * @return NearbyCityData object
     */
    NearbyCityData findNearby(double latitude, double longitude);
}

Once we implement the NearbyCityFinder interface, we can use different implementations to find the nearby city. For example, the city and its geocoding can be stored in the database, and we can implement JDBC code to search for the nearby city.

Developing the NearbyCityData Class

The NearbyCityData class is a wrapper class that contains the NearbyCity object. It implements the required methods for finding the nearby city, including: 

  • distance() method – Calculates the distance between the geocoding of the specified location and this object.
  • toXmlByteArray() method – Gets the XML representation of the NearbyCity object.

 
The constructor of NearbyCityData needs to instantiate the NearbyCity object. This is accomplished using the ObjectFactory.class.

 

  public NearbyCityData() {
        this.nearbyCity =  (new ObjectFactory()).createNearbyCity();
}

Calculating the Distance

 

To find the nearby city from the specified location, we need to calculate the distance of the location to each of the top 100 US cities. We use the Haversine formula to calculate the distance from the  geocode of the specified location and the geocode of each city:

 
  distance = 2*asin(sqrt((sin((lat1-lat2)/2))^2 +
                cos(lat1)*cos(lat2)* (sin((lon1-lon2)/2))^2))

   Where: lat1,lon1 and lat2,lon2 are two points’ geocoding in radians.

 

The Haversine formula provides the best method of calculating the great circle distance (which deliberately ignores elevation differences) between two points. A detailed description of the Haversine formula is available at http://en.wikipedia.org/wiki/Haversine_formula.

The following shows the distance() method:

private static final double MILES_PER_RADIAN = 3956;

Public double distance( double lati, double longi) {
        // Convert from degrees to radians.
        double lat1 = Math.toRadians( lati );
        double lon1 = Math.toRadians( longi );
        double lat2 = Math.toRadians( this.getLatitude());
        double lon2 = Math.toRadians( this.getLongitude());
        double sinLat = Math.sin( (lat1 - lat2) / 2.0 );
        double sinLng = Math.sin( (lon1 - lon2) / 2.0 );
        double as2 = sinLat * sinLat +
                     Math.cos( lat1 ) * Math.cos( lat2 ) * sinLng * sinLng;
        double distanceInRadians =
                     2.0 * Math.asin( Math.min(1,Math.sqrt(as2) ));
        return  MILES_PER_RADIAN * distanceInRadians;    // for miles
}

Marshalling XML Using JAXB

 

To get the XML representation of the NearbyCity object, we use the Marshaller from the JAXBContext to marshal the NearbyCity JAXB object into a byte array.
The following code implements this logic and returns the XML of NearbyCity in this object. 

  public byte[] toXmlByteArray() throws Exception {
       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       try {
          JAXBContext jaxbContext= JAXBContext.newInstance(NearbyCity.class);
          Marshaller marshaller=jaxbContext.createMarshaller();
          marshaller.marshal( this.nearbyCity, bos);
          bos.close();
          return  bos.toByteArray();
       } catch (IOException ioe)  {
           throw ioe;
       } catch (JAXBException jaxbe) {
           throw jaxbe;
       }
}

Implementing the Nearby City Finder Interface

There is one method in the NearbyCityFinder interface, as described in “Developing the Nearby City Finder Interface.” By passing the location using the latitude and longitude, the method returns the NearbyCityData object.

For this example, the static in-memory implementation is provided. It searches a static table using a simple loop. The following shows the implementation of the NearbyCityFinder interface:

public NearbyCityData findNearby(double latitude, double longitude) {
   int n = 0;
   for (int i = 1; i < Top100UsCities.cities.length; i++)  {
      if (Top100UsCities.cities[i].distance(latitude, longitude) <
          Top100UsCities.cities[n].distance(latitude, longitude)) {
          n = i;
      }
   }
   return Top100UsCities.cities[n];
}

 

The method returns the city that is nearby to the specified location from the top 100 US cities. The top 100 cities in the US are stored as records in Top100UsCities.java. The geocoding information of the top 100 cities is obtained from http://www.geonames.org

Building and Deploying the Application

Next, we build and deploy the sample application to WebLogic Server. The following simple Ant script builds the .war file for deployment:

<project name="jaxws.restful" basedir="." default="help">
  <dirname property="jaxws.restful.dir" file="${ant.file.jaxws.restful}" />
  <target name="jaxb" >
       . . .
  </target>
  <taskdef name="jwsc" classname="weblogic.wsee.tools.anttasks.JwscTask" />
  <target name="server" depends="jaxb">
    <mkdir dir="./build/war" />
    <jwsc debug="true" srcdir="./src/restful/server"
      sourcepath="./build/classes;./src" destdir="./build/war">
      <jws file="NearbyCityRest.java" type="JAXWS">
        <zipfileset dir="./build/classes/restful/server/nbc"
          prefix="WEB-INF/classes/restful/server/nbc">
          <include name="*.class"/>
        </zipfileset>
      </jws>
    </jwsc>
  </target>
</project>

Steps to Build and Deploy the Application

 

To build and deploy the application, perform the following steps: 

  • Open an MS-DOS command prompt window (on Windows) or a command shell (on UNIX).
  • Go to the following directory:
    cd $DOMAIN_HOME\bin
  • Run the setDomainEnv.cmd or setDomainEnv.sh command to set your environment.
  • Go to the application folder.
  • Run the following Ant command:
    ant server
  • Copy build\war\NearbyCityRest.war to the WebLogic autodeploy folder. (It is assumed that you already started the server.)

 

The WebLogic autodeploy tool automatically deploys the NearbyCityRest application as a RESTful Web Service using the NearbyCityRest endpoint.

Testing the Deployment

 

To test the deployment of the NearbyCityRest endpoint, enter the following URL into a browser:  

http://localhost:7001/NearbyCityRest/NearbyCityService?lat=38.1&long=-123.1

The following information should be returned:

<ns2:NearbyCity xmlns:ns2="http://example.org">
  <City>San Francisco</City>
  <State>CA</State>
  <Lat>37.7749295</Lat>
  <Lng>-122.4194155</Lng>
</ns2:NearbyCity>

Using Path Info to Find the Nearby City

In the previous sections, we have demonstrated how to use a query string in HTTP GET to retrieve the nearby city information. The HTTP GET operation can be implemented as a Servlet as well, by getting the parameters from the query string. For example, you can update the invoke() method as follows:

  public Source invoke(Source source)
  {
    MessageContext messageContext = context.getMessageContext();
    HttpServletRequest servletRequest =
       (HttpServletRequest) messageContext.get(MessageContext.SERVLET_REQUEST);
    String lati = servletRequest.getParameter("lat");
    String longi = servletRequest.getParameter("lng");

    String body = this.getNearbyCityXML(lati,longi); 
    return new StreamSource(new ByteArrayInputStream(body.getBytes()));
  }

In addition to HTTP GET, you may choose to specify the URL with Path Info to represent a NearbyCity resource, rather than using a query string.

The following shows an example of the URL with Path Info: 

http://localhost:7001/NearbyCityRest/NearbyCityService/lat/40.1/long/-74.1

The following shows the same example using the query string:

http://localhost:7001/NearbyCityRest/NearbyCityService?lat=40.1&long=-74.1

In the REST model, a resource is a conceptual entity, for example any one of the NearbyCity objects is a resource. A representation is a concrete manifestation of the resource. Regardless of which URL we use to access the representation of a resource, they both should go to the same class for processing.

Updating the Java Code to Use Path Info

 

In JAX-WS, from the same message context we can retrieve information for both the MessageContext.QUERY_STRING and MessageContext.PATH_INFO.

To implement the logic for retrieving and processing Path Info on the HTTP GET request, we modify the invoke() method of the NearbyCityRest class as follows: 

package restful.server;
. . .
public class NearbyCityRest implements Provider<Source> {
 . . .
  public Source invoke(Source source)  {
    try {
        MessageContext messageContext = wsContext.getMessageContext();
        String query = (String)messageContext.get(MessageContext.QUERY_STRING);
        if (query != null && query.contains("lat=") &&
             query.contains("lng=")) {
            return this.createSource(query);
        }   
        String path = (String)messageContext.get(MessageContext.PATH_INFO); 
        if (path != null) {
            return this.createSource(path);
        }    
    } catch(Exception e) {
        e.printStackTrace();        
    }
    throw new HTTPException(500);
  }
  private Source createSource(String str) throws Exception {         
     // same code
     . . .
  }
}

Updating the Ant Scripts to Use Path Info

 

After we have updated  the Java code to retrieve Path Info from the message context, to enable the resource retrieval using Path Info, we need to ensure the GET request with Path Info routes to the NearbyCityRest class from the Servlet. 

The Servlet maintains the service and URL mapping in the web.xml file. When we use jwsc to generate the web.xml file, it generates the following element:

  <servlet-mapping>
    <servlet-name>NearbyCityServiceServlethttp</servlet-name>
    <url-pattern>/NearbyCityService</url-pattern>
  </servlet-mapping>

This url-pattern works fine if the GET requests use query string only. If the endpoint expects GET requests to contain extra path information after the endpoint address, then the url-pattern should have /* appended to it in the web.xml file, as shown below:

<?xml version='1.0' encoding='UTF-8'?>
<web-app xmlns="http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5">
  <servlet-mapping>
    <servlet-name>NearbyCityServiceServlethttp</servlet-name>
    <url-pattern>/NearbyCityService/*</url-pattern>
  </servlet-mapping>
</web-app>

In a single war deployment scenario, weblogic.xml is used to specify the context-root. If not specified in weblogic.xml, the context-root is set to the filename of the WAR file by default. Although not required, it is recommended that you define the context-root in the weblogic.xml file. For example:

<?xml version='1.0' encoding='UTF-8'?>
<weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">
  <description>jaxws_restful</description>
  <context-root>/NearbyCityRest</context-root>
</weblogic-web-app>

 
The Ant script target (server) that builds the JAX-WS .war file using jwsc should be modified to place the web.xml and weblogic.xml files in the ./etc subdirectory, as follows:

  <target name="server" depends="jaxb">
    <mkdir dir="./build/war" />
    <jwsc debug="true" srcdir="./src/restful/server"
      sourcepath="./build/classes;./src" destdir="./build/war">
      <jws file="NearbyCityRest.java" type="JAXWS">
        <zipfileset dir="./build/classes/restful/server/nbc"
          prefix="WEB-INF/classes/restful/server/nbc">
          <include name="*.class"/>
        </zipfileset>
        <descriptor file="./etc/web.xml" />
        <descriptor file="./etc/weblogic.xml" />
      </jws>
    </jwsc>
  </target>

Run the ant server command to generate the NearbyCityRest.war. The web.xml in the NearbyCityRest.war is modified by the Ant script, as shown:

<?xml version='1.0' encoding='UTF-8'?>
<web-app xmlns="http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5">
  <display-name>NearbyCityServiceWebApp</display-name>
  <servlet>
    <servlet-name>NearbyCityServiceServlethttp</servlet-name>
    <servlet-class>restful.server.NearbyCityRest</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>NearbyCityServiceServlethttp</servlet-name>
    <url-pattern>/NearbyCityService/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>NearbyCityServiceServlethttp</servlet-name>
    <url-pattern>/NearbyCityService</url-pattern>
  </servlet-mapping>
</web-app>

Testing the Path Info

 

After the NearbyCityRest.war is built, we can copy it to the WebLogic autodeploy folder to deploy the application automatically.

Once deployed, we can test the application by entering the following URL into a browser:  

http://localhost:7001/NearbyCityRest/NearbyCityService/lat/40.1/long/-74.1

The following information should be returned:

<ns2:NearbyCity xmlns:ns2="http://example.org">
  <City>New York City</City>
  <State>NY</State>
  <Lat>40.7142691</Lat>
  <Lng>-74.0059729</Lng>
</ns2:NearbyCity>

Using HTTP POST

In addition to supporting the HTTP GET method, the RESTful Web Service supports other main HTTP methods, such as POST, PUT, and DELETE.

In JAX-WS, all HTTP methods go to the same implementation for a given endpoint. In the case of the sample application, other HTTP methods, such as HTTP POST, use the same invoke() method in the NearbyCityRest class as the HTTP GET request.

We can use the MessageContext HTTP_REQUEST_METHOD in the invoke() method to get the HTTP method type, as shown:

String httpMethod =
        (String)messageContext.get(MessageContext.HTTP_REQUEST_METHOD);

To implement the logic for processing the HTTP POST method, we modify the invoke() method in the NearbyCityRest class as follows: 

package restful.server;
. . .
public class NearbyCityRest implements Provider<Source> {
 . . .
  public Source invoke(Source source)  {
    try {
        MessageContext messageContext = wsContext.getMessageContext();
        String httpMethod =
               (String)messageContext.get(MessageContext.HTTP_REQUEST_METHOD);
        if ("POST".equalsIgnoreCase(httpMethod)) {
           return this.createSource();
        }
        if ("GET".equalsIgnoreCase(httpMethod)) {
          String query = (String)
                           messageContext.get(MessageContext.QUERY_STRING);
          if (query != null && query.contains("lat=") &&
             query.contains("lng=")) {
              return this.createSource(query);
          }   
          String path = (String)messageContext.get(MessageContext.PATH_INFO); 
          if (path != null) {
              return this.createSource(path);
          }  
        } 
    } catch(Exception e) {
        e.printStackTrace();        
    }
    throw new HTTPException(500);
  }
  private Source createSource() throws Exception {         
     // return default object 
     NearbyCityData nearby = this.cityFinder.findNearby(40.1,-74.1);
     return new StreamSource(
                  new ByteArrayInputStream(nearby.toXmlByteArray()));
  }
. . .
. . .
}

The sample application supports only the HTTP GET and POST methods; an exception will be thrown if other HTTP method types are used.

NOTE: Retrieving a posted DOM object from the Provider<DOMSource> implementation is not supported in WebLogic Server 10.3. If you wish to use this feature, you must create your own implementation of  Provider<DOMSource>. 

Testing HTTP POST

Run the ant server command to generate the NearbyCityRest.war and copy it to the WebLogic autodeploy folder to deploy the application automatically.

Once deployed, we can write a simple Java client to test the sample application. In this example, we use a junit test client to demonstrate how to POST a request and get the response from the NearbyCityService. For example:

public class NearbyCityClientTest extends TestCase {

  public static final String NEARBY_CITY_REST =
               "http://localhost:7001/NearbyCityRest/NearbyCityService";

  public void testPostRequest() throws Exception {       
        URL url = new URL(NEARBY_CITY_REST);
        URLConnection connection = url.openConnection();
        connection.setDoOutput(true);
        OutputStreamWriter out = new OutputStreamWriter(
                                  connection.getOutputStream());
        out.write("Get default city");
        out.close();
        BufferedReader in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
        String decodedString;
        while ((decodedString = in.readLine()) != null) {
            System.out.println(decodedString);
        }
        in.close();
   }
}

Putting It All Together

The following sections show the final versions of the NearbyCityRest Web Service, Ant script, and test client.

Reviewing the NearbyCityRest Java Program

The following shows the complete source code for NearbyCityRest.java with the HTTP GET Path Info and HTTP POST updates as well as some modifications to the parameter positioning and error checking:

package restful.server;

import java.io.ByteArrayInputStream;
import java.util.StringTokenizer;

import javax.annotation.Resource;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.BindingType;
import javax.xml.ws.Provider;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;

@WebServiceProvider(
    targetNamespace = "http://example.org",
    serviceName = "NearbyCityService")
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class NearbyCityRest implements Provider<Source> {
  @Resource(type = Object.class)
  protected WebServiceContext wsContext;

  NearbyCityFinder cityFinder;

  public NearbyCityRest() {
    this.cityFinder = new NearbyCityFinderImpl();
  }

  /**
   * the main entry point of this Jax-WS REST program
   * @param source  the Source object of the request
   * @return  the response Source object
   */
  public Source invoke(Source source) {
    try {
      MessageContext messageContext = wsContext.getMessageContext();
      String query = (String) messageContext.get(MessageContext.QUERY_STRING);
      String path = (String) messageContext.get(MessageContext.PATH_INFO);
      String httpMethod =
           (String) messageContext.get(MessageContext.HTTP_REQUEST_METHOD);

      System.out.println("Query String = " +
           query + "\n Path = " + path + "\n httpMethod = " + httpMethod);
      if ("POST".equalsIgnoreCase(httpMethod)) {
        return this.createSource();
      }
      if ("GET".equalsIgnoreCase(httpMethod)) {
        if (query != null && query.contains("lat=")
            && (query.contains("long=") || query.contains("lng="))) {
          return this.createSource(query);
        }
        if (path != null) {
          return this.createSource(path);
        }
      }
      System.err.println("ERROR: Http Method = " +
           httpMethod + " Query String = " + query + " Path = " + path);
    } catch (Exception e) {
      e.printStackTrace();
    }
    throw new HTTPException(500);
  }

  /**
   * This method parses the query string, calls the nearby city finder and
   * return with the response Source object of the NearbyCity XML object
   * @param str the input query string
   * @return  the response Source object of the NearbyCity XML object
   * @throws Exception binding or IO errors
   */
  private Source createSource(String str) throws Exception {
    double latitude;
    double longitude;
    StringTokenizer st = new StringTokenizer(str, "=&/");
    String latLong = st.nextToken();
    double d1 = Double.parseDouble(st.nextToken());
    st.nextToken();
    double d2 = Double.parseDouble(st.nextToken());
    if ((latLong.startsWith("lat")) || (latLong.startsWith("Lat"))) {
      latitude = d1;
      longitude = d2;
    } else {
      latitude = d2;
      longitude = d1;
    }
    NearbyCityData nearby = this.cityFinder.findNearby(latitude, longitude);
    return new StreamSource(new ByteArrayInputStream(nearby.toXmlByteArray()));
  }

    /**
     * Get the default NearbyCity object.
     * @return  a XML for NearbyCity in the Source object
     * @throws Exception  on binding or IO errors
     */
  private Source createSource() throws Exception {
    NearbyCityData nearby = this.cityFinder.findNearby(40.1, -74.1);
    return new StreamSource(new ByteArrayInputStream(nearby.toXmlByteArray()));
  }
}

Reviewing the Ant Script

 

The final version of the Ant script, build.xml, contains the following targets:


Ant Target

Description

ant jaxb

Generates and compiles the JAXB files.

ant server

Builds the service endpoint WAR.

ant client

Builds the client test code.

ant test

Tests the Web Service and client

The following shows the complete Ant script:

<project name="jaxws.restful" basedir="." default="help">
  <dirname property="jaxws.restful.dir" file="${ant.file.jaxws.restful}" />
  <property name="output.dir" value="${jaxws.restful.dir}/build" />
  <property name="output.classes.dir"
       value="${jaxws.restful.dir}/build/classes" />
  <property name="output.war.dir" value="${jaxws.restful.dir}/build/war" />
  <property name="etc" value="${jaxws.restful.dir}/etc" />
  <property name="src" value="${jaxws.restful.dir}/src" />

  <taskdef name="xjc" classname="com.sun.tools.xjc.XJC2Task" />
  <taskdef name="jwsc" classname="weblogic.wsee.tools.anttasks.JwscTask" />

  <target name="setup">
    <mkdir dir="${output.dir}" />
    <mkdir dir="${output.classes.dir}" />
    <mkdir dir="${output.war.dir}" />
  </target>

  <target name="clean">
    <delete dir="${output.dir}" includeEmptyDirs="true" />
  </target>
  <target name="jaxb" depends="setup">
    <xjc schema="${jaxws.restful.dir}/etc/nearby_city.xsd"
       package="restful.server.nbc" destdir="${output.classes.dir}" />
     <javac debug="true" srcdir="${output.classes.dir}"
          destdir="${output.classes.dir}" />
  </target>

  <target name="server" depends="jaxb">
    <jwsc debug="true" srcdir="${src}/restful/server"
        sourcepath="${output.classes.dir};${src}" destdir="${output.war.dir}">
      <jws file="NearbyCityRest.java" type="JAXWS">
        <zipfileset dir="${output.classes.dir}/restful/server/nbc"
           prefix="WEB-INF/classes/restful/server/nbc">
          <include name="*.class" />
        </zipfileset>
        <descriptor file="${etc}/web.xml" />
        <descriptor file="${etc}/weblogic.xml" />
      </jws>
    </jwsc>
  </target>

  <target name="client" depends="setup">
    <javac debug="true" srcdir="${src}/restful/client"
       destdir="${output.classes.dir}" />
  </target>

  <target name="test">
    <java fork="true" classname="restful.client.NearbyCityClientTest">
      <classpath>
        <pathelement location="${output.classes.dir}"/>
      </classpath>
    </java>
  </target>

  <target name="help">
        <echo message="jaxb:    Generates and compiles the JAXB files" />
        <echo message="server:  Builds the service endpoint WAR" />
    <echo message="client:      Builds the client test code" />
    <echo message="test:        Test it from the client" />   
  </target>
</project>

Reviewing the Test Client

 

The junit test program, NearbyCityClientTest.java, is provided to show the client side code. In addition to the junit code used to test the HTTP POST method, as described in “ Testing HTTP POST,” we provide two additional methods to test the HTTP GET with query string and Path Info. For example:

public class NearbyCityClientTest extends TestCase {

  public static final String NEARBY_CITY_REST =
               "http://localhost:7001/NearbyCityRest/NearbyCityService";

  public void testPostRequest() throws Exception {
    // same code
   . . .
  }

  public void testGetNearbyCityWithQueryString() throws Exception {
     try {
        URL url = new URL(NEARBY_CITY_REST  + "?lat=34.1&long=-118.1");
        InputStream inputStream = url.openStream();
        DocumentBuilderFactory documentBuilderFactory =
                                  DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        Document doc =
             documentBuilderFactory.newDocumentBuilder().parse(inputStream );
        NodeList nodeList =
             doc.getElementsByTagNameNS("http://example.org", "NearbyCity");
        assertEquals(1, nodeList.getLength());
        Element nearbyCity = (Element) nodeList.item(0);
        Element city = (Element) nearbyCity.getFirstChild();
        assertNotNull(city);
        assertEquals(city.getLocalName(),"City");
        assertEquals("Los Angeles",city.getFirstChild().getNodeValue());
        Element state = (Element) city.getNextSibling();
        assertNotNull(state);
        assertEquals(state.getLocalName(),"State");
        assertEquals("CA", state.getFirstChild().getNodeValue());
      } catch (Exception e) {
          e.printStackTrace();
          throw e;
     }
  }

   public void testGetNearbyCityWithPathInfo() throws Exception {
         try {
            URL url = new URL(NEARBY_CITY_REST + "/lat/37.1/long/-123.1");
            InputStream inputStream = url.openStream();

            StreamSource source = new StreamSource(inputStream);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            StreamResult sr = new StreamResult(bos );
            Transformer trans =
                     TransformerFactory.newInstance().newTransformer();
            Properties oprops = new Properties();
            oprops.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
            trans.setOutputProperties(oprops);
            trans.transform(source, sr);
            assertNotNull(bos);
            String out = bos.toString();
            bos.close();
            assertNotNull(out);
            System.out.println("Rest Response XML:\n"+out);
            assertFalse(out.indexOf("San Francisco")==-1);
        } catch(Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}

Conclusion

The RESTful Web Service features described in this article are available with WebLogic Server 10.3. It uses JAX-WS and JAXB technologies in WebLogic Server 10.3 to simplify the development and deployment of RESTful Web Services. This approach will let program to take a rest when developing RESTful Web Services.


Symon Chang is a Principle Member of Technical Staff at Oracle Corporation, developing Web Services solutions for WebLogic Server.