Saturday, December 17, 2011

Testing and Using the REST Client/Service

For web services, we can test methods that are available via GET with a simple web browser.  In our service, we have 2 such methods.  The first is the find by name.  If we goto: http://localhost:8080/JSFDemoApp/resources/Part/findByName/Tower, we get:


Next, we can go to http://localhost:8080/JSFDemoApp/resources/Part/createByGet/Tower/newSubPart and we get:

Now the DELETE and POST methods are a little more complicated, so we will create a unit test.  Starting with an example:

PartEntityRESTClientTest.java

package com.sample.service;

import com.sample.service.other.Part;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class PartEntityRESTClientTest {
    public PartEntityRESTClientTest() {
    }
    @BeforeClass
    public static void setUpClass() throws Exception {
    }
    @AfterClass
    public static void tearDownClass() throws Exception {
    }
    @Before
    public void setUp() {
    }
    @After
    public void tearDown() {
    }

    /**
     * Test the CRUD methods in the REST client against the REST web service
     */
    @Test
    public void testCRUD() {
        // Test we can find a part that exists.  Note: this part only exists from data created earlier
        PartEntityRESTClient client = new PartEntityRESTClient();
        Part response = client.findByName("Tower");
        assertNotNull(response);
        assertFalse(response.getId() == 0l);

        // Test we can create a new part with the post method.  Note: no error checking is done when we pass a parent that doesn't exist
        Part newPart = new Part("NewPart", "Toxwer");
        client.create_XML(newPart);
        response = client.findByName("NewPart");
        assertNotNull(response);
        assertFalse(response.getId() == 0l);

        // Clean up, and verify that clean up worked
        client.remove(response.getId().toString());
        response = client.findByName("NewPart");
        assertNotNull(response);
        assertTrue(response.getId() == 0l);

        // Test we can create the same part, via the Get method
        // and that the returned part is the same as the part in the database
        newPart = client.createByGet("Tower", "NewPart");
        assertNotNull(newPart);
        assertFalse(newPart.getId() == 0l);
        response = client.findByName("NewPart");
        assertNotNull(response);
        assertFalse(response.getId() == 0l);
        assertEquals("Tower", response.getParent());
        assertEquals(newPart.getId(), response.getId());

        // Clean up and verify
        client.remove(response.getId().toString());
        response = client.findByName("NewPart");
        assertNotNull(response);
        assertTrue(response.getId() == 0l);
       
        newPart = client.createByGet("NewPart", "Tower");
        assertNotNull(newPart);
        assertTrue(newPart.getId() == 0l);
        client.close();
    }
}


Discussion:

We note several things here.  We don't have any entity managers, factories, or even entities.  We have no setup or configuration apart from the URI.  This requires no class except Part, and the test environment only because this was a JUnit test.  So, to use the client we can run it from almost anything.  And when we review our Part class from the REST Service, we note that it also is a simple Java class with nothing more then an annotation that lets it be translated from and to XML notation.  Conclusion: using a RESTful web service is quite trivial.

One thing to mention:  from a client side, we have no idea how the other side is going to behave to bad input, or even good input if their systems are having trouble.  The service may very well return codes or exceptions that we need to be prepared to deal with.   Be sure to include try/catches as you access the service.  From a service side, we should be well behaved, with documented ways we are going to let the client know something went wrong.

Next, we decided to add a UI to our application to use the web service.  Although it is using the service running on the same machine, there is no difference if it was elsewhere.  Only the url would change.

startPage.xhtml

...
        <p:dialog id="restClient" header="PartEntity REST Client : Create, Find, Delete" widgetVar="restDlg">
            <h:form id="restForm" prependId="false">
                <h:panelGrid id="restPanelGrid" columns="2" cellpadding="5" >
                    <h:outputLabel for="restName" value="Name for Create/Find or ID for Delete: " />
                    <h:inputText value="#{restBean.name}" id="restName" />
                    <h:outputLabel for="restParentName" value="Parent Part Name: " />
                    <h:inputText value="#{restBean.parentName}" id="restParentName" />
                    <h:outputText value="Results: "/>
                    <h:outputText value="#{restBean.msg}" id="restMsg"/>
                    <f:facet name="footer">
                        <p:commandButton actionListener="#{restBean.createByGet}" update="restMsg" value="Create By Get" /> 
                        <p:commandButton actionListener="#{restBean.createByPost}" update="restMsg" value="Create By Post" /> 
                        <p:commandButton value="Delete" actionListener="#{restBean.delete}" update="restMsg"/>
                        <p:commandButton value="Find" actionListener="#{restBean.find}" update="restMsg"/>    
                        <p:commandButton type="button" value="Close" immediate="true" onclick="restDlg.hide();" />  
                    </f:facet>
                </h:panelGrid>
            </h:form>
        </p:dialog>
...

And the backing bean:
RestBean.java

package com.sample;

import com.sample.service.PartEntityRESTClient;
import com.sample.service.other.Part;
import java.awt.event.ActionEvent;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

/**
 * JSF Named bean for interacting with a RESTful web service via REST client
 * Request Scope
 * @author Thomas Dias
 */
@Named(value = "restBean")
@RequestScoped
public class RestBean implements Serializable {
    private String name = "", parentName = "", msg = "";

    /** Default constructor */
    public RestBean() {
    }

    /**
     * Listener method to create a part via an http GET - gets part with name of name
     * @param event
     */
    public void createByGet(ActionEvent event) {
        createByGet();
    }

    /**
     * Action method to create a part via an http GET - gets part with name of name
     * @return empty string
     */
    public String createByGet() {
        Part part = new PartEntityRESTClient().createByGet(parentName, name);
        msg = "Created " + part.getId() + " " + part.getName();
        return "";
    }

    /**
     * Listener method to create a part via an http POST - creates part whose name is name, and parent is parentname
     * @param event
     */
    public void createByPost(ActionEvent event) {
        createByPost();
    }

    /**
     * Action method to create a part via an http POST - creates part whose name is name, and parent is parentname
     * @return empty string
     */
    public String createByPost() {
        new PartEntityRESTClient().create_XML(new Part(name, parentName));
        msg = "Created by post";
        return "";
    }

    /**
     * Listener method for deleting a part - deletes part whose id = name
     * @param event
     */
    public void delete(ActionEvent event) {
        delete();
    }

    /**
     * Action method for deleting a part - deletes part whose id = name
     * @return empty string
     */
    public String delete() {
        PartEntityRESTClient client = new PartEntityRESTClient();
        Part part = client.findByName(name);
        if (part.getId() == 0l) {
            msg = "No Part Found";
        } else {
            client.remove(part.getId().toString());
            msg = "Deleted " + name;
        }
        return "";
    }

    /**
     * Listener method for retrieving a part whose name is name
     * @param event
     */
    public void find(ActionEvent event) {
        find();
    }

    /**
     * Action method for retrieving a part whose name is name
     * @return
     */
    public String find() {
        Part part = new PartEntityRESTClient().findByName(name);
        msg = part.toString();
        return "";
    }

    // *** Standard getters and setters below this line
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getParentName() {
        return parentName;
    }
    public void setParentName(String parentName) {
        this.parentName = parentName;
    }
}


Discussion

Viewing the dialog box we see:

The buttons call the bean methods, the bean methods call the client, the client accesses the service, the service interacts with the database and/or the other systems.  This is all done without injection of any beans, entity managers, entities, etc. on the client side.  The only thing we needed to know is what the service expected to see and or the response.  Now if you consider the ramifications of this, you see how we can create entire systems with web services.  That is the beginning of SOA (Service Oriented Architecture) development.

If you've made it through the tutorial this far, I shouldn't have to explain what is going on with these classes.  Although we haven't implemented an update method, it is simply done with a PUT and is handled just like the create with POST. 

Now that we have seen a round trip with REST, we'll do the same thing with SOAP.

No comments:

Post a Comment