ApacheCXFonNetBeans

Developing a loan processing application using Apache CXF and JPA in NetBeans 6.1

By Arulazi Dhesiaseelan

[Note: The tutorial has lost most of its content during the recent NetBeans wiki migration. The updated tutorial is made available here: http://sites.google.com/site/apachecxfnetbeans/loanproc]

[Thanks to Alyona Lompar for Ukrainian Translation: http://www.fatcow.com/edu/netbeans-beans-uk/]

Overview

This document is a step-by-step guide on developing a simple loan processing application in NetBeans 6.1 using Apache CXF 2.1 and JPA. My blog entry refers to some of the benefits of using NetBeans 6.1 for Java application development.

1. What's covered

The following list details all of the various parts of the loan processing application development that are covered over the course of the tutorial.

  • Domain model development using JPA
  • CXF Service Development
  • Unit and integration testing


2. Prerequisite software

The following prerequisite software and environment setup is assumed. You should also be reasonably comfortable using the following technologies.

  • Java SDK 1.5/1.6
  • NetBeans 6.1
  • MySQL 5.0.x

NetBeans IDE 6.1 Web & Java EE edition (http://download.netbeans.org/netbeans/6.1/final/) provides an excellent environment for rapid application development.


3. The application we are building

Our sample application is a very basic loan processing system. I will be addressing the basic use case where the borrower loan information is entered in the loan system and the loan is decisioned using credit and decisioning services. This use case is just a tip of ice berg in loan processing systems. The domain model is implemented using JPA. Credit and decisions services are implemented using CXF. This loan processing system is severely constrained in terms of scope. The whole purpose of this tutorial is how we leverage the powerful NetBeans infrastructure to rapidly develop, prototype and test Java applications in a productive way.

Apache CXF is gaining momentum due to its simple and powerful programming model for services development. It has decent tooling support for web services. It recently graduated from Apache incubation. CXF 2.1 release supports whole lot of features around web services security, addressing, reliability and RESTful web services.

We are not considering implementing UI aspects for our loan processing scenario as this aspect is covered in many other tutorials. We build secure web sevices using CXF for external systems such as credit and decisioning. The application can be easily extended to implement more robust and feature rich loan processing system. This application is definitely constrained by some limitations due to the varied implementation options and bandwidth. I will try to improve this tutorail over the course of time. But, this should be help in providing some ground work for building similar applications.

Chapter 1. Basic Application and Environment Setup

1.1. Create the NetBeans project and Create MySQL database

We are going to need a place to keep all the source and other files we will be creating, so let's create a project named 'LoanProcessing'. Create a new 'Java Application' project from File -> New Project and click Next. Enter 'LoanProcessing' as the project name and click Finish.

File:Project Creation Wizard


Next step is to register the 'MySQL Server'. This assumes that you already have MySQL server installed in your machine. Navigate to Services tab in NetBeans IDE and you should see a 'Databases' tree. Right click this node to register MySQL server.

File:MySQL Registration

Enter the MySQL server basic properties and then select the admin properties tab within the MySQL server properties window. Provide the location of your MySQL installation in this window. The screen shot below shows settings for my installation. You can also point to the MySQL Admin Tools which provides more management capabilities for the MySQL server. This requires a separate installation of MySQL GUI Tools package.

File:MySQL Basic Properties

File:MySQL Admin Properties


Once configuring the MySQL server, you should be able to start the server just by right clicking the MySQL entry in Databases tree. You can see the MySQL server output logs in the NetBeans console as shown below.

File:MySQL Server Startup


Next we will be creating a database for our sample application. Right click the MySQL server node in the databases tree to create a MySQL database 'loandb'.

File:MySQL Database Creation


After creating the database click ok, then we need to provide the database connection information.

File:MySQL Database Connection


Once these steps are successful, you should be able to see the new MySQL Database connection showing up in the Databases tree. If the icon is broken you can right click and connect again. This establishes a successful JDBC connectivity to MySQL database from within NetBeans.

File:MySQL Database Connection


Next we need to create the database tables for our sample application. You can create these tables manually or just run the below script within the SQL Command window. To do this, right click the Tables entry under the database connection and select the 'Execute Command...' option.


File:MySQL SQL Execute Command


The following script creates the necessary tables required for our loan processing application.

'setuploandb_ApacheCXFonNetBeans.sql':

create table address
(
        id INT not null,
        address_line1 VARCHAR(255),
        address_line2 VARCHAR(255),
        county VARCHAR(50),
        address_type VARCHAR(30),
        city VARCHAR(50),
        postal_code VARCHAR(10),
        state_code VARCHAR(10),
        primary key (id)
);
 
create table applicant
(
        id INT not null,
        ssn VARCHAR(15),
        date_of_birth VARCHAR(30),
        first_name VARCHAR(80),
        last_name VARCHAR(80),
        middle_name VARCHAR(50),
        applicant_role VARCHAR(15),
        p_address INT,
        p_application INT,
        primary key (id)
);
 
create table application
(
        id INT not null,
        loan_amount DOUBLE PRECISION,
        loan_type VARCHAR(15),
        p_address INT,
        primary key (id)
);
 
create table cbr_summary
(
        id INT not null,
        credit_score VARCHAR(10),
        credit_bureau VARCHAR(30),
        requested_date VARCHAR(30),
        p_applicant INT,
        primary key (id)
);


You should be able to see the tables in the loandb database. The data types for most of the columns are defined as VARCHAR for simplicity.


File:MySQL Table Creation

Add the TopLink, MySQL driver and CXF jars to the project library.

File:Project Libraries


Chapter 2. Loan Processing Domain Model

Our domain classes are Application, Applicant, and Address. The structure of these model is defined in the above database schema. In addition to these entities, we need to persist the CreditBureauResponse summary received from credit rating agencies. This is persisted in the CBRSummary table. The database schema is self explanatory.

2.1. Create JPA entities

NetBeans bundles TopLink persistence provider as part of the IDE. So, developing JPA applications is a pleasure with NetBeans. Right click the project and select 'New Entity Classes from Database'. Choose the MySQL database connection from the dropdown and add all the tables from left column to the right column as shown below.


File:Create Entities from Database Tables


Click Next. In this step we need to create a persistence unit for TopLink provider. Accept the defaults and click 'Create'.

File:Create PU for TopLink


Click Finish to complete the entity creation process.

File:Finish Entity Creation


Now, your project structure should look as shown below. This opens up the persistence.xml which defines the domain entities and database properties for TopLink provider. You need to add the TopLink logging level attribute so that you can see the SQL commands performed during each database transaction. On the left side of the project explorer, you see all the domain classes generated as a result of the earlier step.

File:Project Structure


You need to generate the constructors from NetBeans for all our domain classes. This will be used for creating and persisting entities in our tests.


    public Address(Integer id, String addressLine1, String addressLine2, String county, String addressType, String city, String postalCode, String stateCode) {
        this.id = id;
        this.addressLine1 = addressLine1;
        this.addressLine2 = addressLine2;
        this.county = county;
        this.addressType = addressType;
        this.city = city;
        this.postalCode = postalCode;
        this.stateCode = stateCode;
    }

    public Applicant(Integer id, String ssn, String dateOfBirth, String firstName, String lastName, String middleName, String applicantRole, Integer pAddress, Integer pApplication) {
        this.id = id;
        this.ssn = ssn;
        this.dateOfBirth = dateOfBirth;
        this.firstName = firstName;
        this.lastName = lastName;
        this.middleName = middleName;
        this.applicantRole = applicantRole;
        this.pAddress = pAddress;
        this.pApplication = pApplication;
    }


    public Application(Integer id, Double loanAmount, String loanType, Integer pAddress) {
        this.id = id;
        this.loanAmount = loanAmount;
        this.loanType = loanType;
        this.pAddress = pAddress;
    }

    public CBRSummary(Integer id, String creditScore, String creditBureau, String requestedDate, Integer pApplicant) {
        this.id = id;
        this.creditScore = creditScore;
        this.creditBureau = creditBureau;
        this.requestedDate = requestedDate;
        this.pApplicant = pApplicant;
    }


2.2. Create DAO for domain classes

We need to create DAO for operating on these domain objects. DAO provides CRUD operations on the model. The following classes use the JPA APIs to perform CRUD.

AddressDAO, ApplicantDAO, ApplicationDAO, and CBRSummaryDAO.

We will be defining one of these DAO in this tutorial and you can use same approach to implement other DAOs. For the impatience, code is attached below.

All these classes are defined in the package 'loanprocessing.dao'. The class shown below is self-explanatory.

'loanprocessing.dao.AddressDAO.java':


package loanprocessing.dao;

import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import loanprocessing.model.Address;

/**
 *
 * @author aruld
 */
public class AddressDAO {
    private EntityManager em;

    public AddressDAO(EntityManagerFactory emf) {
        em = emf.createEntityManager();
    }

    public void createAddress(Address address) {
        em.getTransaction().begin();
        em.persist(address);
        em.getTransaction().commit();
    }

    public Address findById(Integer id) {
        return em.find(Address.class, id);
    }
    
    public List findByAddressLine1(String addressLine1) {
        Query query = em.createNamedQuery("Address.findByAddressLine1");
        query.setParameter(addressLine1, em);
        List listOfAddresses = query.getResultList();
        return listOfAddresses;
    }    

    public void updateAddress(Address address)  {
        em.getTransaction().begin();
        em.merge(address);
        em.getTransaction().commit();
    }

    public void removeAddress(Address address)  {
        em.getTransaction().begin();
        em.remove(address);
        em.getTransaction().commit();
    }

    public List getAll() {
        Query query = em.createQuery("select a from Address a");
        List list = query.getResultList();
        return list;
    }

    public void removeAll() {
        em.getTransaction().begin();
        Query query = em.createQuery("select a from Address a");
        List list = query.getResultList();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Address address = (Address) it.next();
            em.remove(address);
        }
        em.getTransaction().commit();
    }

    public void close() {
        em.close();
    }
}


2.3. Unit testing

Let us unit test our domain classes using JUnit. The below test case creates some sample data and performs CRUD operations on these data using the DAO classes. We create three loan applications and each with a residential and property address. The applications


'loanprocessing.tests.LoanProcessingDAOTest.java':


package loanprocessing.tests;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
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.*;
import loanprocessing.model.*;
import loanprocessing.dao.*;
import loanprocessing.util.*;

/**
 *
 * @author aruld
 */
public class LoanProcessingDAOTest {

    private static ApplicationDAO am;
    private static AddressDAO adm;
    private static ApplicantDAO apm;
    private static EntityManager em;
    private static EntityManagerFactory emf;

    private static final Address address1 = new Address(1, "4209 Van Buren Dr", "Apt # 158", "POLK", AddressType.RESIDENTIAL.toString(), "WEST DES MOINES", "50266", "IA");   
    private static final Address address2 = new Address(2, "1265 11th ST", "Apt # 208", "POLK", AddressType.RESIDENTIAL.toString(), "WEST DES MOINES", "50265", "IA");   
    private static final Address address3 = new Address(3, "1660 Country Manor Blvd", "Apt # 226B", "YELLOWSTONE", AddressType.RESIDENTIAL.toString(), "BILLINGS", "59102", "MT");       

    private static final Address paddress1 = new Address(4, "1120 Jordan Creek PKWY", "Unit # 123", "POLK", AddressType.PROPERTY.toString(), "WEST DES MOINES", "50266", "IA");   
    private static final Address paddress2 = new Address(5, "1120 Jordan Creek PKWY", "Unit # 213", "POLK", AddressType.PROPERTY.toString(), "WEST DES MOINES", "50266", "IA");   
    private static final Address paddress3 = new Address(6, "1120 Jordan Creek PKWY", "Unit # 312", "POLK", AddressType.PROPERTY.toString(), "WEST DES MOINES", "50266", "IA");       
    
    private static final Application application1 = new Application(1, 120000.00, LoanType.FIXED.toString(), 1);
    private static final Application application2 = new Application(2, 110000.00, LoanType.ADJUSTABLE.toString(), 2);
    private static final Application application3 = new Application(3, 90000.00, LoanType.FIXED.toString(), 3);    
    
    private static final Applicant applicant1 = new Applicant(1, "000-00-0001", "07/11/1960", "KEN", "CUSTOMER", "", ApplicantRole.PRIMARY.toString(), 1, 1);
    private static final Applicant applicant2Primary = new Applicant(2, "000-00-0002", "08/11/1960", "ALAN", "APPLICANT", "", ApplicantRole.PRIMARY.toString(), 2, 2);
    private static final Applicant applicant2Secondary = new Applicant(3, "000-00-0003", "09/11/1965", "EMILY", "APPLICANT", "", ApplicantRole.SECONDARY.toString(), 2, 2);
    private static final Applicant applicant3 = new Applicant(4, "000-00-0004", "10/11/1970", "WILL", "SMITH", "", ApplicantRole.PRIMARY.toString(), 3, 3);    
            
    public LoanProcessingDAOTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {        
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
    }

    @Before
    public void setUp() {
        emf = Persistence.createEntityManagerFactory("LoanProcessingPU");
        em = emf.createEntityManager();
        am = new ApplicationDAO(emf);        
        apm = new ApplicantDAO(emf);        
        adm = new AddressDAO(emf);     
        adm.removeAll();
        apm.removeAll();
        am.removeAll();        
    }

    @After
    public void tearDown() {
        //this will remove all the entries in case if there are uncomplete/failed tests.
        //adm.removeAll();
        //apm.removeAll();
        //am.removeAll();
        am.close();
        adm.close();
        apm.close();
        em.close();
        emf.close();                
    }

    @Test
    public void testCRUD() {
        adm.createAddress(address1);
        Address address = adm.findById(1);
        assertEquals(address.getAddressLine1(), "4209 Van Buren Dr");
    
        address.setAddressLine1("4210 Van Buren Dr");
        adm.updateAddress(address);
    
        address = adm.findById(1);
        assertEquals(address.getAddressLine1(), "4210 Van Buren Dr");
    
        adm.createAddress(address2);
        adm.createAddress(address3);

        adm.createAddress(paddress1);
        Address paddress = adm.findById(4);
        assertEquals(paddress.getAddressLine1(), "1120 Jordan Creek PKWY");
    
        paddress.setAddressLine2("AshCreek Apartments Unit # 123");
        adm.updateAddress(paddress);
    
        paddress = adm.findById(4);
        assertEquals(paddress.getAddressLine2(), "AshCreek Apartments Unit # 123");

        paddress.setAddressLine2("Unit # 123");
        adm.updateAddress(paddress);
    
        adm.createAddress(paddress2);
        adm.createAddress(paddress3);

        am.createApplication(application1);
        Application application = am.findById(1);
        assertEquals(application.getLoanAmount(), 120000.00);
    
        application.setLoanAmount(117000.00);
        am.updateApplication(application);
    
        application = am.findById(1);
        assertEquals(application.getLoanAmount(), 117000.00);
    
        am.createApplication(application2);        
        am.createApplication(application3);
        
        apm.createApplicant(applicant1);
        Applicant applicant = apm.findById(1);
        assertEquals(applicant.getFirstName(), "KEN");
    
        applicant.setFirstName("KENNETH");
        apm.updateApplicant(applicant);
    
        applicant = apm.findById(1);
        assertEquals(applicant.getFirstName(), "KENNETH");
    
        apm.createApplicant(applicant2Primary);
        apm.createApplicant(applicant2Secondary);
        apm.createApplicant(applicant3);    
        
        List applicantList = apm.getAll();
        assertEquals(applicantList.size(), 4);
    
        //uncomment to clean up the database
        /*apm.removeAll();
        applicantList = apm.getAll();
        assertEquals(applicantList.size(), 0);*/

        List applicationList = am.getAll();
        assertEquals(applicationList.size(), 3);
    
        //uncomment to clean up the database
        /*am.removeAll();
        applicationList = am.getAll();
        assertEquals(applicationList.size(), 0);*/
        
        List addressList = adm.getAll();
        assertEquals(addressList.size(), 6);
    
        //uncomment to clean up the database
        /*adm.removeAll();
        addressList = adm.getAll();
        assertEquals(addressList.size(), 0);       */
        
        
    }
    
}



File:Executing JUnit tests


Chapter 3. CXF Services

3.1. Interface definition for credit and decision services

The interfaces for credit and decision services are defined in the package 'loanprocessing.services'.


'loanprocessing.services.CreditService.java':

package loanprocessing.services;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import loanprocessing.model.Address;
import loanprocessing.model.Applicant;
import loanprocessing.model.CBRSummary;

/**
 *
 * @author aruld
 */
@WebService
public interface CreditService {
    @WebMethod(operationName = "getCredit")
    @WebResult(name="creditBureauResponse")
    public CBRSummary getCredit(@WebParam(name = "applicant")
    Applicant applicant, @WebParam(name = "address")
    Address address);

}



The mock implementation for the credit service is provided below. In real scenario, credit rating systems will expose these web services and consumers should access these services remotely.


'loanprocessing.services.CreditServiceImpl.java':

package loanprocessing.services;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.jws.WebService;
import loanprocessing.model.Address;
import loanprocessing.model.Applicant;
import loanprocessing.model.CBRSummary;
import loanprocessing.util.CreditBureauType;

/**
 *
 * @author aruld
 */
@WebService(endpointInterface = "loanprocessing.services.CreditService", serviceName = "CreditService")
public class CreditServiceImpl implements CreditService {    
    
    public CBRSummary getCredit(Applicant applicant, Address address) {
        //TODO write your implementation code here:
        //Invoke External CreditBureau Web Service to get the actual credit report
        //Mocking up response.
        DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
        java.util.Date date = new java.util.Date();
        String datetime = dateFormat.format(date);        
        CBRSummary cbr = new CBRSummary();
        cbr.setCreditBureau(CreditBureauType.EQUIFAX.toString());
        cbr.setRequestedDate(datetime);
        cbr.setCreditScore("750");
        cbr.setPApplicant(applicant.getId());              
        
        return cbr;
    }

}


Let us also define the decision service interface and implementation.

'loanprocessing.services.DecisionService .java':

package loanprocessing.services;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import loanprocessing.model.Applicant;
import loanprocessing.model.CBRSummary;
import loanprocessing.util.DecisionType;

/**
 *
 * @author aruld
 */
@WebService
public interface DecisionService {
    @WebMethod(operationName = "getRiskDecision")
    @WebResult(name="decisionResponse")
    public DecisionType getRiskDecision(@WebParam(name = "applicant")
    Applicant applicant, @WebParam(name = "cbrSummary")
    CBRSummary cbrSummary);
}



The mock implementation for the decision service is provided below. This service always returns a decision type of 'APPROVE'. This needs to be plugged in with the real decision service.

'loanprocessing.services.DecisionServiceImpl .java':

package loanprocessing.services;

import javax.jws.WebService;
import loanprocessing.model.Applicant;
import loanprocessing.model.CBRSummary;
import loanprocessing.util.DecisionType;

/**
 *
 * @author aruld
 */
@WebService(endpointInterface = "loanprocessing.services.DecisionService", serviceName = "DecisionService")
public class DecisionServiceImpl implements DecisionService {

    public DecisionType getRiskDecision(Applicant applicant, CBRSummary cbrSummary) {
        //TODO write your implementation code here:
        //Invoke External Decision Web Service to get the actual credit report
        //Mocking up response.        
        return DecisionType.APPROVE;
    }
}


3.2. Securing the CXF services using WS-Security

In order to secure the above web services, we will use WSS4J APIs to implement security for these services. This requires callback handlers to be defined for server and the client. These handlers are invoked when ever a request is received from the client and processed at the server. For simplicity, we have hard-coded the credentials in the server handler. The credentials sent by the client should match these server credentials. Once the request is authenticated, the client can access the methods on the web service.

'loanprocessing.services.security.ServerPasswordCallback .java':

package loanprocessing.services.security;

/**
 *
 * @author aruld
 */
import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;
import org.apache.ws.security.WSSecurityException;

public class ServerPasswordCallback implements CallbackHandler {

    //this can be moved to a resource bundle
    private static final String username = "cxfuser";
    private static final String password = "secret";
    

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        String strClientPwd = pc.getPassword();
        int usage = pc.getUsage();

        if(pc.getIdentifer().equals(username)) {

            if(usage == WSPasswordCallback.USERNAME_TOKEN) { //PasswordDigest
                pc.setPassword(password);
            } else if(usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) { //PasswordText
                pc.setPassword(password);

                if(!strClientPwd.equalsIgnoreCase(password)){ //DIY compare
                    throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
                }
            }        
        }        
    }
}


'loanprocessing.services.security.ClientPasswordCallback .java':

package loanprocessing.services.security;

/**
 *
 * @author aruld
 */
import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class ClientPasswordCallback implements CallbackHandler {

    //this can be moved to a resource bundle
    private static final String password = "secret";

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        // set the password for our message.
        pc.setPassword(password);        
    }

}


Next, we need to deploy these services in a web container. CXF comes with an embedded Jetty server which we can use it to run our services and see CXF in action. The Server code looks as shown below. We create instances of credit and decision services and register them with the JaxWsServerFactoryBean. Your services is ready to be accessed with security.

'loanprocessing.services.Server .java':

package loanprocessing.services;

import java.util.HashMap;
import java.util.Map;
import loanprocessing.services.security.ServerPasswordCallback;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;

/**
 *
 * @author aruld
 */
public class Server {

    public static void main(String args[]) throws Exception {
        CreditServiceImpl creditServiceImpl = new CreditServiceImpl();
        DecisionServiceImpl decisionServiceImpl = new DecisionServiceImpl();        
        createCreditService(creditServiceImpl);
        createDecisionService(decisionServiceImpl);        
    }
    
    private static void createCreditService(Object serviceObj) {
        JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();
        svrFactory.setServiceClass(CreditService.class);
        svrFactory.setAddress("http://localhost:9000/services/CreditService");
        svrFactory.setServiceBean(serviceObj);        
        org.apache.cxf.endpoint.Server server = svrFactory.create();
        Endpoint cxfEndpoint = server.getEndpoint();
        
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        // Password type : plain text
        inProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
        // for hashed password use:
        //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        // Callback used to retrive password for given user.
        inProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ServerPasswordCallback.class.getName());        
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps);
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
    }
    
    private static void createDecisionService(Object serviceObj) {
        JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();
        svrFactory.setServiceClass(DecisionService.class);
        svrFactory.setAddress("http://localhost:9000/services/DecisionService");
        svrFactory.setServiceBean(serviceObj);        
        org.apache.cxf.endpoint.Server server = svrFactory.create();
        Endpoint cxfEndpoint = server.getEndpoint();
        
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        // Password type : plain text
        inProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
        // for hashed password use:
        //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        // Callback used to retrive password for given user.
        inProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ServerPasswordCallback.class.getName());        
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps);
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
        
    }
    
}


You can access the WSDL for both the services in the following URL.

http://localhost:9000/services/CreditService?wsdl http://localhost:9000/services/DecisionService?wsdl

File:Running the Server


The below is the Client program which can access the credit and decision services. CXF provides Java APIs as well as spring based XML configuration for deploying services as well as defining clients as Spring beans.


'loanprocessing.services.client.Client.java':

package loanprocessing.services.client;

/**
 *
 * @author aruld
 */
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import loanprocessing.services.CreditService;
import loanprocessing.services.DecisionService;
import loanprocessing.dao.AddressDAO;
import loanprocessing.dao.ApplicantDAO;
import loanprocessing.dao.CBRSummaryDAO;
import loanprocessing.model.Address;
import loanprocessing.model.Applicant;
import loanprocessing.model.CBRSummary;
import loanprocessing.services.security.ClientPasswordCallback;
import loanprocessing.util.DecisionType;

import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;

public final class Client {

    private Client() {
    } 

    public static void main(String args[]) throws Exception {
        
        CreditService creditClient = createCreditServiceClient();
        DecisionService decisionClient = createDecisionServiceClient();        
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("LoanProcessingPU");
        ApplicantDAO apm = new ApplicantDAO(emf);
        Applicant applicant = apm.findById(1);
        AddressDAO adm = new AddressDAO(emf);     
        Address address = adm.findById(4);
        
        CBRSummaryDAO cbrm = new CBRSummaryDAO(emf);
        //clear the existing entries for testing purpose only
        cbrm.removeAll();
    	CBRSummary cbr = creditClient.getCredit(applicant, address);
        cbr.setId(1);
        cbrm.createCreditBureauResponse(cbr);
    	System.out.println("Credit Score for : " + applicant.getFirstName() + " " + cbr.getCreditScore());
        
        DecisionType decision = decisionClient.getRiskDecision(applicant, cbr);
        System.out.println("Decision for : " + applicant.getFirstName() + " : " + decision);
        
        //close the resources
        apm.close();
        adm.close();        
        emf.close();
    	System.exit(0);

    }
    
    private static CreditService createCreditServiceClient() {
    	JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

    	factory.getInInterceptors().add(new LoggingInInterceptor());
    	factory.getOutInterceptors().add(new LoggingOutInterceptor());
    	factory.setServiceClass(CreditService.class);
    	factory.setAddress("http://localhost:9000/services/CreditService");
        CreditService service = (CreditService)factory.create();
        org.apache.cxf.endpoint.Client client = factory.getClientFactoryBean().getClient();
        
        Endpoint cxfEndpoint = client.getEndpoint();
        
        Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        // Specify our username
        outProps.put(WSHandlerConstants.USER, "cxfuser");
        // Password type : plain text
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);        
        // for hashed password use:
        //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        // Callback used to retrive password for given user.
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordCallback.class.getName());
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());        
        return service;
    }
    
    private static DecisionService createDecisionServiceClient() {
    	JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

    	factory.getInInterceptors().add(new LoggingInInterceptor());
    	factory.getOutInterceptors().add(new LoggingOutInterceptor());
    	factory.setServiceClass(DecisionService.class);
    	factory.setAddress("http://localhost:9000/services/DecisionService");
        DecisionService service = (DecisionService)factory.create();
        org.apache.cxf.endpoint.Client client = factory.getClientFactoryBean().getClient();
        
        Endpoint cxfEndpoint = client.getEndpoint();
        
        Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        // Specify our username
        outProps.put(WSHandlerConstants.USER, "cxfuser");
        // Password type : plain text
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);        
        // for hashed password use:
        //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        // Callback used to retrive password for given user.
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordCallback.class.getName());
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());        
        return service;
    }
    
}



File:Running the Client


The SOAP request and response is shown in the CXF client logs.

'Credit service request/response':

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Header>
		<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
			<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-13798906" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
				<wsse:Username xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">cxfuser</wsse:Username>
				<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">secret</wsse:Password>
			</wsse:UsernameToken>
		</wsse:Security>
	</soap:Header>
	<soap:Body>
		<ns1:getCredit xmlns:ns1="http://services.loanprocessing/">
			<applicant>
				<applicantRole>PRIMARY</applicantRole>
				<dateOfBirth>07/11/1960</dateOfBirth>
				<firstName>KENNETH</firstName>
				<id>1</id>
				<lastName>CUSTOMER</lastName>
				<middleName/>
				<PAddress>1</PAddress>
				<PApplication>1</PApplication>
				<ssn>000-00-0001</ssn>
			</applicant>
			<address>
				<addressLine1>1120 Jordan Creek PKWY</addressLine1>
				<addressLine2>Unit # 123</addressLine2>
				<addressType>PROPERTY</addressType>
				<city>WEST DES MOINES</city>
				<county>POLK</county>
				<id>4</id>
				<postalCode>50266</postalCode>
				<stateCode>IA</stateCode>
			</address>
		</ns1:getCredit>
	</soap:Body>
</soap:Envelope>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns1:getCreditResponse xmlns:ns1="http://services.loanprocessing/">
			<creditBureauResponse>
				<creditBureau>EQUIFAX</creditBureau>
				<creditScore>750</creditScore>
				<PApplicant>1</PApplicant>
				<requestedDate>04/17/2008 23:10:12</requestedDate>
			</creditBureauResponse>
		</ns1:getCreditResponse>
	</soap:Body>
</soap:Envelope>




'Decision service request/response':


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Header>
		<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
			<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-21271291" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
				<wsse:Username xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">cxfuser</wsse:Username>
				<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">secret</wsse:Password>
			</wsse:UsernameToken>
		</wsse:Security>
	</soap:Header>
	<soap:Body>
		<ns1:getRiskDecision xmlns:ns1="http://services.loanprocessing/">
			<applicant>
				<applicantRole>PRIMARY</applicantRole>
				<dateOfBirth>07/11/1960</dateOfBirth>
				<firstName>KENNETH</firstName>
				<id>1</id>
				<lastName>CUSTOMER</lastName>
				<middleName/>
				<PAddress>1</PAddress>
				<PApplication>1</PApplication>
				<ssn>000-00-0001</ssn>
			</applicant>
			<cbrSummary>
				<creditBureau>EQUIFAX</creditBureau>
				<creditScore>750</creditScore>
				<id>1</id>
				<PApplicant>1</PApplicant>
				<requestedDate>04/17/2008 23:10:12</requestedDate>
			</cbrSummary>
		</ns1:getRiskDecision>
	</soap:Body>
</soap:Envelope>


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns1:getRiskDecisionResponse xmlns:ns1="http://services.loanprocessing/">
			<decisionResponse>APPROVE</decisionResponse>
		</ns1:getRiskDecisionResponse>
	</soap:Body>
</soap:Envelope>



The project structure looks as shown below.


File:Project Structure


Once the client is run, then you should be seeing the CBRSummary table getting populated with the response from credit service.

File:Querying the CBRSummary table after running the Client


3.3. Testing Web Services using NetBeans IDE

NetBeans IDE provides built-in support for testing web services. Click the services tab and expand the Web Services node. Add a service group 'LoanProcessing'.

File:Adding a Web Service Group


Now, add the credit service WSDL and decision service WSDL to the web service group.


File:Adding the Credit Service


File:Adding the Decision Service


Now, you are ready to test the web services. Right click the operation getCredit to test the credit service. This fails because it does not provide a way to pass the credentials to the web service. If you remove the security, then it should work just fine. As we are mocking up the response, you should always receive the same response.


File:Testing Credit Service


Right click the operation getRiskDecision to test the decision service. This also fails for the same above mentioned reason. It will be useful to enhance the NetBeans IDE webservices tools to support testing secure web services.


File:Testing Decision Service


3.4. Testing Web Services using soapUI NetBeans plugin

soapUI is a web service testing tool which provides features such as functional/load/mock testing of web services. The soapUI NetBeans plugin is feature rich and can accomplish more testing including secured web services.

We need to install the soapUI plugin from the NetBeans plugin manager.

File:Install soapUI NetBeans plugin


Once the plugin installation is successful, create a soapUI web service testing project 'LoanProcessingWebServicesTest'.

File:Create soapUI project

Let us provide the WSDL location for our Credit web service. We need to make sure our Server program is running for this step to work.

File:Specify the WSDL location during project creation

File:WSDL Imported into the Project

Expand the getCredit operation in the project tree. Open 'Request 1' and copy the SOAP request for our credit service shown in previous section and submit the request to specified endpoint URL. You should see the SOAP response in the right side of the same window. This is basic testing of our secured web service. More testing can be done with soapUI. For instance, you can simulate the requests and do a load testing and measure the response times. This tool is very powerful and rock solid.

File:Test getCredit operation


We can create test cases and run the tests as part of the test suite. Right click 'Request 1' and select 'Add to TestCase' option.

File:Add request to TestCase

File:Add TestSuite name

File:Add TestCase name

File:TestCase Options

File:Project Structure

File:Running the TestCase

File:MessageExchange Results

Decision service can be tested in the same manner and this is left as an exercise to the readers. You can find the SOAP request for the decision service in the previous section. There are many different ways to test your web services from soapUI plugin. Let us try testing decision service by configuring security headers in soapUI for the getRiskDecision request. The first step is to add the WSDL to our existing soapUI project. Right click the soapUI project node and select 'Add WSDL from URL' option.

File:Specify the Decision service WSDL URL

Click yes to create default requests for all the operations in the decision web service. We do not care for changing the input in the request as we hard code the response in the web service. So, we leave the input request generated by soapUI with empty optional elements.

File:Create default requests for all operations

Open the 'Request 1' from the 'DecisionServiceServiceSoapBinding' tree. Select the 'Aut' tab below the request and provide the security credentials.

File:Add WSS credentials

Right click the request and select 'Add WSS Username Token' option. Select password type as 'PasswordText'. This will generate the WSS header in the the SOAP request.

File:Add WSS Username Token to the SOAP request

File:PasswordText type

File:SOAP request with WSS headers

Click 'Submit request to specified endpoint URL'. You will find the response on the right side of the window.

File:SOAP request with WSS headers

These are just two ways of testing our web services from soapUI plugin. You can generate the client-side stubs and then invoke the web service programatically. Our Client discussed earlier uses CXF APIs to invoke the service. CXF provides server and client side APIs to develop and test web services. It also comes with embedded Jetty server to see the CXF services in action. In real application scenario, we could deploy these services to any servlet container.

Logging can be configured both on the client and server side and it can be configured programmatically or through XML files. In our application, we have programatically configured the logging as shown below.

In Server.java, add the following code after the SAAJInInterceptor to both createCreditService and createDecisionService methods. This will log all the incoming SOAP requests and SOAP responses on the server side.

'Logging configuration in Server.java':

        cxfEndpoint.getInInterceptors().add(new LoggingInInterceptor());
        cxfEndpoint.getOutInterceptors().add(new LoggingOutInterceptor());


4. References

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo