Tag Archives: spring

Using Vaadin UI with Spring Boot for Spring Data Backend based on MongoDB

With the new Beta 1 of the  Vaadin Addon Vaadin Spring Boot you can use Spring Boot as base framework for your Vaadin UI. The following example application use Spring Data as service layer on top of a mongoDB as NoSQL database. Blackboard is used as generic event sub system inside the Vaadin UI. Complete Source Code is available on github.

Pre Requisites

  • Installed Java 8.x SDK
  • Installed Maven 3.x
  • Running MongoDB 2.x

Source Code

Maven

used maven pom.xml



	4.0.0

	de.schaeftlein.dev.vaadin
	demo-spring-vaadin
	0.0.1-SNAPSHOT
	jar

	demo
	Demo project for Spring Boot with Vaadin

	
		org.springframework.boot
		spring-boot-starter-parent
		1.2.2.RELEASE
		 
	

	
		UTF-8
		demo.DemoApplication
		1.8
		7.4.0
	

	
		
			org.vaadin.addons
			blackboard
			2.2.0
		
		
			com.vaadin
			vaadin-spring-boot
			1.0.0.beta1
		
		
			com.vaadin
			vaadin-themes
			${vaadin.version}
		
		
			com.vaadin
			vaadin-client-compiled
			${vaadin.version}
		
		
			org.springframework.boot
			spring-boot-starter-web
		
		
			org.springframework.boot
			spring-boot-starter-data-mongodb
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


Command Line Runner

Simple class for starting tomcat as embedded web container for our little application on port 8080

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Simple class for initiate mongodb with some test data

package demo;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import demo.data.Customer;
import demo.data.CustomerRepository;

@SpringBootApplication
public class MongoDBApplication implements CommandLineRunner {

	@Autowired
	private CustomerRepository repository;

	public static void main(String[] args) {
		SpringApplication.run(MongoDBApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {

		repository.deleteAll();

		// save a couple of customers
		repository.save(new Customer("Alice", "Smith"));
		repository.save(new Customer("Bob", "Smith"));

		// fetch all customers
		System.out.println("Customers found with findAll():");
		System.out.println("-------------------------------");
		for (Customer customer : repository.findAll()) {
			System.out.println(customer);
		}
		System.out.println();

		// fetch an individual customer
		System.out.println("Customer found with findByFirstName('Alice'):");
		System.out.println("--------------------------------");
		System.out.println(repository.findByFirstName("Alice"));

		System.out.println("Customers found with findByLastName('Smith'):");
		System.out.println("--------------------------------");
		for (Customer customer : repository.findByLastName("Smith")) {
			System.out.println(customer);
		}

	}

}

MongoDB Spring Data Repository

Entity class

package demo.data;

import org.springframework.data.annotation.Id;

public class Customer {

	@Id
	private String id;

	private String firstName;
	private String lastName;

	public Customer() {
	}

	public Customer(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return String.format("Customer[id=%s, firstName='%s', lastName='%s']",
				id, firstName, lastName);
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getId() {
		return id;
	}

}

Repository class as service layer

package demo.data;

import java.util.List;

import org.springframework.data.mongodb.repository.MongoRepository;

public interface CustomerRepository extends MongoRepository<Customer, String> {

	public Customer findByFirstName(String firstName);

	public List<Customer> findByLastName(String lastName);

}

Vaadin UI

main application class of the vaadin application

package demo.ui;

import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.annotations.Theme;
import com.vaadin.navigator.Navigator;
import com.vaadin.server.VaadinRequest;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.spring.navigator.SpringViewProvider;
import com.vaadin.ui.Button;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;

import demo.ui.event.EventSystem;

@Theme("valo")
@SpringUI
public class MyVaadinUI extends UI {

	@Autowired
	private SpringViewProvider viewProvider;
	
	@Autowired
	EventSystem eventSystem;

	@Override
	protected void init(VaadinRequest request) {
		initLayout();
                registerEvents();
	}

       private void registerEvents() {
		eventSystem.registerEvent(ReloadEntriesEvent.ReloadEntriesListener.class, ReloadEntriesEvent.class);
	}

	private void initLayout() {
		final VerticalLayout root = new VerticalLayout();
		root.setSizeFull();
		root.setMargin(true);
		root.setSpacing(true);
		setContent(root);

		final CssLayout navigationBar = new CssLayout();
		navigationBar.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
	
		navigationBar.addComponent(createNavigationButton("Default View",
                DefaultView.VIEW_NAME));		
        navigationBar.addComponent(createNavigationButton("MongoDB View",
                MongoDBUIView.VIEW_NAME));
		
		root.addComponent(navigationBar);

		final Panel viewContainer = new Panel();
		viewContainer.setSizeFull();
		root.addComponent(viewContainer);
		root.setExpandRatio(viewContainer, 1.0f);

		Navigator navigator = new Navigator(this, viewContainer);
		navigator.addProvider(viewProvider);
	}

	private Button createNavigationButton(String caption, final String viewName) {
        Button button = new Button(caption);
        button.addStyleName(ValoTheme.BUTTON_SMALL);
        button.addClickListener(event -> getUI().getNavigator().navigateTo(viewName));
        return button;
    }
}

default view as welcome page

package demo.ui;

import javax.annotation.PostConstruct;

import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;

@SpringView(name = DefaultView.VIEW_NAME)
public class DefaultView extends VerticalLayout implements View {
	/*
	 * This view is registered automatically based on the @SpringView annotation. 
	 * As it has an empty string as its view name, it will be shown when navigating to the Homepage
	 */
    public static final String VIEW_NAME = "";

    @PostConstruct
    void init() {
        addComponent(new Label("Welcome View"));
    }

    @Override
    public void enter(ViewChangeEvent event) {
        // the view is constructed in the init() method()
    }
}

mongo db view with a table and a edit form

package demo.ui;

import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.event.ItemClickEvent;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.AbstractLayout;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Table;
import com.vaadin.ui.VerticalLayout;

import demo.data.Customer;
import demo.data.CustomerRepository;
import demo.ui.event.EventSystem;
import demo.ui.event.ReloadEntriesEvent;

@SpringView(name = MongoDBUIView.VIEW_NAME)
public class MongoDBUIView extends VerticalLayout implements View,ReloadEntriesEvent.ReloadEntriesListener{
	public static final String VIEW_NAME = "mongodbui";
	private static final Log LOG = LogFactory.getLog(MongoDBUIView.class);
	
	private Table entityTable;
	private String selectedId;
	private Customer selectedCustomer;
	
	private Button deleteButton;
	private Button editButton;
	
    @Autowired
    private MongoDBContainer mongodbContainer;
    
    @Autowired
    private CustomerForm editForm;
    
    @Autowired
    private CustomerRepository service;
    
    @Autowired
    private EventSystem eventSystem;
	
	@PostConstruct
	void init() {
		registerEvents();
		initData();
		initLayout();
	}

	private void registerEvents() {
		eventSystem.addListener(this);
    }
	
	@SuppressWarnings("serial")
	private void initLayout(){
		setMargin(true);
		setSpacing(true);
		// vaadin table 
        entityTable = new Table();
        entityTable.setContainerDataSource(mongodbContainer);
        entityTable.setVisibleColumns(MongoDBContainer.PROPERTIES);
        entityTable.setColumnHeaders(MongoDBContainer.HEADERS);
        entityTable.setSelectable(true);
        entityTable.setWidth("100%");
        entityTable.setHeight("300px");

        // table select listener
        entityTable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
            @Override
            public void itemClick(ItemClickEvent event) {
                selectedId = (String) event.getItemId();
                selectedCustomer =  mongodbContainer.getItem(selectedId).getBean();

               LOG.info("Selected item id {"+ selectedId+"}");
            }
        });
        // button bar
        final AbstractLayout buttonBar = initButtonBar();
        buttonBar.setWidth("100%");
        
        // edit Form
        editForm.setVisible(false);
        
        addComponent(entityTable);
        addComponent(buttonBar);
        addComponent(editForm);
	}
	
    @SuppressWarnings("serial")
	private AbstractLayout initButtonBar() {
        final HorizontalLayout buttonBar = new HorizontalLayout();

        buttonBar.setSpacing(true);

        final Button addButton = new Button("Add entry");
        editButton = new Button("Edit Entry");
        deleteButton = new Button("Delete entry");

        buttonBar.addComponent(addButton);
        buttonBar.addComponent(editButton);
        buttonBar.addComponent(deleteButton);

        buttonBar.setComponentAlignment(addButton, Alignment.MIDDLE_LEFT);
        buttonBar.setComponentAlignment(editButton, Alignment.MIDDLE_CENTER);
        buttonBar.setComponentAlignment(deleteButton, Alignment.MIDDLE_RIGHT);

        addButton.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                editForm.setVisible(true);
            }
        });
        editButton.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                editSelectedEntry();
            }
        });
        deleteButton.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                removeSelectedEntry();
            }
        });

        return buttonBar;
    }
    
    private void editSelectedEntry() {
        if (selectedId != null) {
        	LOG.info("Edit Customer with id "+selectedId);
            editForm.setData(selectedCustomer);
            editForm.setVisible(true);
        }
    }
    
    private void removeSelectedEntry() {
        if (selectedId != null) {
        	LOG.info("Delete Customer with id "+selectedId);
            service.delete(selectedId);
            eventSystem.fire(new ReloadEntriesEvent());
        }
    }
	
	private void initData() {
		// load data
        List<:Customer> all = service.findAll();
        LOG.info(all);
        // clear table
        mongodbContainer.removeAllItems();
        // set table data
        mongodbContainer.addAll(all);
    }
	
	@Override
	public void enter(ViewChangeEvent event) {
		// the view is constructed in the init() method()
	}

	@Override
	public void reloadEntries(ReloadEntriesEvent event) {
        LOG.info("Received reload event. Refreshing entry table!");
        initData();
        entityTable.markAsDirty();
	}

}

table container definition

package demo.ui;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.vaadin.data.util.BeanContainer;

import demo.data.Customer;


@Component
@Scope("prototype")
public class MongoDBContainer extends BeanContainer<String, Customer> {

	private static final long serialVersionUID = 3090067422968423191L;

	public static final String BEAN_ID = "id";

    public static final Object[] PROPERTIES = {BEAN_ID, "firstName", "lastName"};

    public static final String[] HEADERS = {"ID", "First Name", "Last Name"};

    public MongoDBContainer() {
        super(Customer.class);
        setBeanIdProperty(BEAN_ID);
    }
}

edit form

package demo.ui;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.vaadin.ui.Button;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextField;

import demo.data.Customer;
import demo.data.CustomerRepository;
import demo.ui.event.EventSystem;
import demo.ui.event.ReloadEntriesEvent;


@Component
@Scope("prototype")
public class CustomerForm extends FormLayout {

    private Logger log = LoggerFactory.getLogger(CustomerForm.class);

    @Autowired
    private CustomerRepository customerService;

    @Autowired
    private EventSystem eventSystem;

    private String id = null;
    private TextField firstName = new TextField("First Name:");
    private TextField lastName = new TextField("Last Name:");

    public CustomerForm() {
        initForm();
    }
    
    public void setData(Customer customer){
    	id = customer.getId();
    	firstName.setValue(customer.getFirstName());
    	lastName.setValue(customer.getLastName());
    }

    private void initForm() {
        addComponent(firstName);
        addComponent(lastName);

        final Button commit = new Button("Commit");
        final Button cancel = new Button("Cancel");

        cancel.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                clearAndHide();
            }
        });
        commit.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                commitForm();
                fireCommitEvent();
                clearAndHide();
            }
        });

        final HorizontalLayout buttonBar = new HorizontalLayout();

        buttonBar.addComponent(commit);
        buttonBar.addComponent(cancel);

        addComponent(buttonBar);
    }

    private void commitForm() {
        if(id!=null){
        	log.info("Update user with id {}, name {} and address {}", id, firstName.getValue(), lastName.getValue());
        	Customer customer = customerService.findOne(id);
        	customer.setFirstName(firstName.getValue());
        	customer.setLastName(lastName.getValue());
        	customerService.save(customer);
        }
        else {        	
        	log.info("Creating user with name {} and address {}", firstName.getValue(), lastName.getValue());
        	customerService.save(new Customer(firstName.getValue(), lastName.getValue()));
        }
    }

    private void clearAndHide() {
        firstName.setValue("");
        lastName.setValue("");
        id = null;
        setVisible(false);
    }

    private void fireCommitEvent() {
        log.info("Fire commit event!");
        eventSystem.fire(new ReloadEntriesEvent());
    }
} 

Vaadin Event System

Event sub system System

package demo.ui.event;

import java.io.Serializable;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.github.wolfie.blackboard.Blackboard;
import com.github.wolfie.blackboard.Event;
import com.github.wolfie.blackboard.Listener;

@Component
@Scope("session")
public class EventSystem implements Serializable {
	private static final long serialVersionUID = 7829012291289167478L;
	private Blackboard blackboard = new Blackboard();

    public EventSystem() {
        init();
    }

    private void init() {
        blackboard.enableLogging();
    }

    public void registerEvent(Class<? extends Listener> listenerClass, Class<? extends Event> eventClass) {
        blackboard.register(listenerClass, eventClass);
    }

    public <T extends Listener> void addListener(T listener) {
        blackboard.addListener(listener);
    }

    public <T extends Event> void fire(T event) {
        blackboard.fire(event);
    }
}

Custom Reload Event with Listener

package demo.ui.event;

import com.github.wolfie.blackboard.Event;
import com.github.wolfie.blackboard.Listener;
import com.github.wolfie.blackboard.annotation.ListenerMethod;

public class ReloadEntriesEvent implements Event {

	public interface ReloadEntriesListener extends Listener {
		@ListenerMethod
		public void reloadEntries(ReloadEntriesEvent event);
	}

	public ReloadEntriesEvent() {
	}
}

Testing

JUnit Test for Spring Data Repository

package demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import demo.data.Customer;
import demo.data.CustomerRepository;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MongoDBApplication.class)
public class MongoDBApplicationTests {
	
	@Autowired
	CustomerRepository repo;
	
	private final static Log LOG = LogFactory.getLog(MongoDBApplicationTests.class);

	@Test
	public void foundAlice(){
		Customer alice = repo.findByFirstName("Alice");
		Assert.assertNotNull(alice);
		LOG.info(alice);
	}
}

Running

Using Command Line Runner

Inside Eclipse run the “demo.DemoApplication” class and open the url http://localhost:8080/ to see the welcome page.

Welcome page

Switch to the mongodb view

mongodb view

Select a row and click on “Edit entry” to change data or “delete entry” to delete selected customer. With “Add entry” you can create a new customer in the mongoDB.

Replacing web.xml with Java based Configuration for Servlet 3.x Webapplications

With the Servlet 3.x API as part of JEE 6 come with the possibility to configure a java web application by writing a java class instead of having a web.xml file inside the WEB-INF folder. My sample apache cxf project has a web.xml with a spring configuration listener and a cxf servlet mapping. I use jetty for integration tests inside maven builds and tomcat as application server for deployment and running the web application. The web service sample use Spring 3.1 and Apache CXF 2.6.2.

I migrated my old web project with just a few steps:

Maven Settings

Step 1: Integrate latest Spring Web API

The Spring 3.1 web project contains the SpringServletContainerInitializer class which will be automatically invoked by a servlet 3.x container:

	
		2.6.2
		3.1.2.RELEASE
	
		
			javax
			javaee-api
			6.0
			provided
		
		
			org.apache.cxf
			cxf-rt-frontend-jaxws
			${cxf.version}
		
		
			org.apache.cxf
			cxf-rt-transports-http
			${cxf.version}
		

		
		
			org.apache.cxf
			cxf-rt-transports-http-jetty
			${cxf.version}
		
		
			org.eclipse.jetty
			jetty-webapp
			8.1.1.v20120215
		
		
			junit
			junit
			4.10
			jar
			compile
		
		
			org.springframework
			spring-test
			${spring.framework.version}
			jar
			compile
		
		
			org.springframework
			spring-core
			${spring.framework.version}
			jar
			compile
		
		
			org.springframework
			spring-beans
			${spring.framework.version}
		
		
			org.springframework
			spring-web
			${spring.framework.version}
		

Step 2: Update build plugin for jetty to use servlet 3.x api

The Jetty 8.x supports Servlet 3.x api to support bootstrapping and java based configurations and is used by the jetty plugin:

			
				org.mortbay.jetty
				jetty-maven-plugin
				8.1.5.v20120716
				
					10
					
						/${project.artifactId}
					
					true
					foo
					9999
					
						
							9090
							60000
						
					
				
				
					
						start-jetty
						pre-integration-test
						
							run
						
						
							0
							true
						
					
					
						stop-jetty
						post-integration-test
						
							stop
						
					
				
			

Step 3: Configure Maven War Plugin to ignore web.xml

The maven war plugin throws otherwise a exception if it does not found the web.xml file:

			
				org.apache.maven.plugins
				maven-war-plugin
				2.2
				
					src/main/webapp
					${project.artifactId}
					false
					
					
						
				
			

 

Java Configuration

Step 4: Write Configuration file

Write a class that implement the WebApplicationInitializer interface from Spring Web and override the onStartup Method. Inside that you have to setup spring application context and map the servlet classes to url patterns:

package demo.spring.cfg;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.XmlWebApplicationContext;

public class WebAppInitializer implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext container) throws ServletException {
		XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
		rootContext.setConfigLocations(new String[] { "classpath*:applicationContext.xml" });
		container.addListener(new ContextLoaderListener(rootContext));

		ServletRegistration.Dynamic dispatcher = container.addServlet("CXFServlet", CXFServlet.class);
		dispatcher.addMapping("/*");
	}
}

Step 5: Delete web.xml

Delete inside WEB-INF folder the web.xml file and use the mvn clean command to remove it from target folder.

Step 6: Testing

run mvn package to build the war file in your project. Download and extract tomcat 7.x for testing your war file. Put the war file in the webapps folder and run your server with the startup.bat in the bin folder of tomcat.

JUnit Tests for Spring 3 Rest Services

SpringSource has recently released 3.0.1 of their Spring Framework. Spring 3 extended the REST functionalities inside the Web MVC part of the Framework. Rest Service are defined as Controller and can be tested with the new Resttemplate. In this little sample project is Spring 3 combined with JUnit 4, Maven, Hibernate 3.2, Jetty, MySQL, JPA, JAXB and AspectJ. Additional tests was made with Poster as a Firefox Plugin to test REST bases Webservices. Jetty 6.1 is used as embedded container for the web application as backend for the seperated maven integration tests. Jetty can be started with mvn jetty:run for tests with Poster or will be started before the integration tests runs with mvn integration-test. The Project is configured via Maven pom.xml as seen later in this post. Via mvn eclipse:eclipse will the eclipse settings generated to use the project as WTP project. I used Maven 2.2.1 in combination with eclipse 3.5.2.. As backend infrastructure runs inside Ubuntu 9.1 Server a Bugzilla 3, Artifactory 2.2.1, Subversion 1.6 and a Hudson 1.348.

REST as representational state transferr has less protocol overhead as SOAP. It heavily depends on HTTP mechanism like PUT, GET, DELETE or POST method calls or HTTP accept header definition of the mime type like ‘text/xml’, ‘text/plain’ or ‘image/jpeg’ to define delivered and expected content. So you make a HTTP GET request with an accept header ‘image/jpeg’ with a url like http://myserver/restservice/1 in firefox to see their a picture of catalog item with id 1. A HTTP Post send data to the server as new data and a PUT override existing data. HTTP 1.1 defines a list of methods to use in your REST based Service.

The whole project is Java annotation driven and use JDK 1.6 but can easily switched to run with JDK 1.5 as well. Configuration is Spring based as IOC pattern and as annotations inside the Java classes. XML content is converted by JAXB Marshaller to use only domain objects inside your business logic. Persistence is defined as JPA annotations with Hibernate as implementation against a MySQL 5 database. The code was inspired by a blog from Solomon Duskis.

The Java Source files

The controller RestServiceController

package de.schaeftlein.dev.spring.rest.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import de.schaeftlein.dev.spring.rest.dao.PersonDao;
import de.schaeftlein.dev.spring.rest.domain.People;
import de.schaeftlein.dev.spring.rest.domain.Person;

@Controller
@RequestMapping("/people")
public class RestServiceController
{
  @Autowired
  private PersonDao personDao;
  

  @RequestMapping(method = RequestMethod.GET)
  @Transactional(readOnly = true)
  @ResponseBody
  public People getAll() {
    List persons = personDao.getPeople();
    People people = new People(persons);
    return people;
  }
  
  @RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
  @ResponseBody
  @Transactional(readOnly = true)
  public Person getPerson(@PathVariable("id") Long personId) {
    return personDao.getPerson(personId);
  }
  
  @RequestMapping(method = RequestMethod.POST)
  @Transactional(readOnly = false)
  @ResponseBody
  public Person savePerson(@RequestBody Person person) {
    personDao.savePerson(person);
    return person;
  }
}

The Interface for the DAO PersonDAO

package de.schaeftlein.dev.spring.rest.dao;

import java.util.List;

import de.schaeftlein.dev.spring.rest.domain.Person;

public interface PersonDao {

  public Person getPerson(Long personId);

  public void savePerson(Person person);

  public List getPeople();

  public Person getPersonByUsername(String username);

}

The DAO implementation PersonDAOHibernate

package de.schaeftlein.dev.spring.rest.dao.hibernate;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import de.schaeftlein.dev.spring.rest.dao.PersonDao;
import de.schaeftlein.dev.spring.rest.domain.Person;

@Repository
@Transactional
@SuppressWarnings("unchecked")
public class PersonDaoHibernate extends HibernateDaoSupport implements
    PersonDao {

  @Autowired
  public void setupSessionFactory(SessionFactory sessionFactory) {
    this.setSessionFactory(sessionFactory);
  }

  public Person getPerson(Long personId) throws DataAccessException {
    return this.getHibernateTemplate().get(Person.class, personId);
  }

  @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
  public void savePerson(Person person) throws DataAccessException {
    this.getHibernateTemplate().saveOrUpdate(person);
  }

  public List getPeople() throws DataAccessException {
    return this.getHibernateTemplate().find("select people from Person people");
  }

  public Person getPersonByUsername(String username) {
    List people = this.getHibernateTemplate().findByNamedParam(
        "select people from Person people "
            + "where people.username = :username", "username", username);
    Person person = getFirst(people);
    if (person != null)
      getHibernateTemplate().evict(person);
    return person;
  }


  private static  T getFirst(List list) {
    return CollectionUtils.isEmpty(list) ? null : list.get(0);
  }
}

The JAXB domain object People

package de.schaeftlein.dev.spring.rest.domain;

import java.io.Serializable;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class People implements Serializable {
  private static final long serialVersionUID = 1L;

  private List person;

  public People() {
    // empty constructor required for JAXB
  }

  public People(List person) {
    this.person = person;
  }

  public List getPerson() {
    return person;
  }

  public void setPerson(List person) {
    this.person = person;
  }

}

The JAXB and JPA entity domain object Person

package de.schaeftlein.dev.spring.rest.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.xml.bind.annotation.XmlRootElement;

@Entity
@XmlRootElement
public class Person implements Serializable
{
  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue
  private Long id;
  private String firstName;
  private String lastName;
  private String username;
  private String password;
  private int roleLevel;

  @Version
  private Integer version;

  public Person()
  {

  }

  public Person(String firstName, String lastName, String username, String password, int roleLevel)
  {
    this.firstName = firstName;
    this.lastName = lastName;
    this.username = username;
    this.password = password;
    this.roleLevel = roleLevel;
  }

  public Long getId()
  {
    return id;
  }

  public void setId(Long id)
  {
    this.id = id;
  }

  public String getFirstName()
  {
    return firstName;
  }

  public void setFirstName(String firstName)
  {
    this.firstName = firstName;
  }

  public String getLastName()
  {
    return lastName;
  }

  public void setLastName(String lastName)
  {
    this.lastName = lastName;
  }

  public String getUsername()
  {
    return username;
  }

  public void setUsername(String username)
  {
    this.username = username;
  }

  public String getPassword()
  {
    return password;
  }

  public void setPassword(String password)
  {
    this.password = password;
  }

  public int getRoleLevel()
  {
    return roleLevel;
  }

  public void setRoleLevel(int roleLevel)
  {
    this.roleLevel = roleLevel;
  }

  public Integer getVersion()
  {
    return version == null ? 1 : version;
  }

  public void setVersion(Integer version)
  {
    this.version = version;
  }

  public enum RoleLevel {
    ADMIN(1), GUEST(2), PUBLIC(3);
    private final int level;

    RoleLevel(int value)
    {
      this.level = value;
    }

    public static RoleLevel getLevel(String roleName)
    {
      return RoleLevel.valueOf(roleName);
    }

    public int getLevel()
    {
      return this.level;
    }
  }

  @Override
  public boolean equals(Object obj)
  {
    if (obj == null)
    {
      return false;
    }
    else if (!(obj instanceof Person))
    {
      return false;
    }
    else
    {
      Person p = (Person) obj;
      if (p.getId().equals(getId()) && p.getUsername().equals(getUsername()) && 
          p.getVersion().equals(getVersion()) && 
          p.getLastName().equals(getLastName()) && p.getPassword().equals(getPassword())
          && p.getFirstName().equals(getFirstName()) && p.getRoleLevel() == getRoleLevel())
      {
        return true;
      }
    }
    return false;
  }

}

The DAO test PersonDAOTest

package de.schaeftlein.dev.spring.rest.dao;

import java.util.List;

import static junit.framework.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

import de.schaeftlein.dev.spring.rest.domain.Person;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-context.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class PersonDaoTest {

  @Autowired
  PersonDao personDao;
  
  private Logger log = LoggerFactory.getLogger(PersonDaoTest.class);

  @Test
  public void testPerson() {
    String username = "jane.doe";
    Person person = new Person("Jane", "Doe", username, "password",
        Person.RoleLevel.ADMIN.getLevel());
    person.setVersion(1);
    personDao.savePerson(person);

    final List people = personDao.getPeople();
    assertEquals(1, people.size());
    assertEquals(person,people.get(0));
    
    Long personId = person.getId();
    Person savedPerson = personDao.getPerson(personId);
    assertEquals(username,savedPerson.getUsername());
  }

  @Test
  public void testVersion() {
    Person solomon = new Person("John", "Doe", "john.doe", "mypass",
        Person.RoleLevel.ADMIN.getLevel());
    // version 0
    personDao.savePerson(solomon);
    
    Integer version = solomon.getVersion();
    log.info("old version:"+solomon.getVersion());

    
    solomon = personDao.getPersonByUsername("john.doe");
    solomon.setPassword("password1");
    // version 1
    personDao.savePerson(solomon);
    
    log.info("new version:"+solomon.getVersion());
    
    assertTrue(!(version.equals(solomon.getVersion())));
  }
}

The Rest test RestClientTest

package de.schaeftlein.dev.spring.rest.itest.controller;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;

import java.util.Collections;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.web.client.RestTemplate;

import de.schaeftlein.dev.spring.rest.domain.People;
import de.schaeftlein.dev.spring.rest.domain.Person;

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations={"classpath:spring-context.xml"}) 
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class RestClientTest
{
  
  private static final String BASE_URL = "http://localhost:9090/spring3-rest-sample/people";
  
  private Logger log = LoggerFactory.getLogger(RestClientTest.class);
  
  @Autowired
  private RestTemplate restTemplate;

  @Test
  public void saveAndGet() throws Exception{
    // save
    Person input = new Person("jane","doe","jane.doe","pw",Person.RoleLevel.ADMIN.getLevel());
    assertNull(input.getId());
    Person output = restTemplate.postForObject(BASE_URL, input, Person.class, new Object[]{});
    assertNotNull("no person",output);
    assertNotNull(output.getId());
    assertEquals(input.getUsername(), output.getUsername());
    log.info("Saved jane.doe with id "+output.getId());
    // get all
    People people = restTemplate.getForObject(BASE_URL, People.class,new Object[]{});
    assertNotNull("no people",people);
    assertNotNull("no persons in people",people.getPerson());
    assertTrue("empty persons in people",!people.getPerson().isEmpty());
    assertEquals("no one person in people",input.getUsername(),people.getPerson().get(0).getUsername());
    log.info("Peple size "+people.getPerson().size());
    // get id
    Map vars = Collections.singletonMap("id", output.getId()+"");
    Person idPerson = restTemplate.getForObject(BASE_URL+"/person/{id}", Person.class,vars);
    assertNotNull("no person",idPerson);
    assertNotNull(idPerson.getId());
    assertEquals(input.getUsername(), idPerson.getUsername());
    log.info("Get person by id <"+output.getId()+"> : "+idPerson.getUsername());
  }
  
}

The configuration files

The Spring annotation.xml




	
	
	 
	
	
	
	

The JDBC settings jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=test
jdbc.password=test
jdbc.initialPoolSize=5
jdbc.maxPoolSize=100
jdbc.minPoolSize=5
jdbc.maxStatementsPerConnection=25
jdbc.maxStatements=50
jpa.databasePlatform=org.hibernate.dialect.MySQL5InnoDBDialect
jpa.showSql=true
jdbc.schema=test
jdbc.dataTypeFactory=org.dbunit.ext.mysql.MySqlDataTypeFactory
jdbc.autoCommitOnClose=true
jdbc.autoCommit=true
hibernate.release_mode=after_transaction
hibernate.hbm2ddl=create
hibernate.auto_close_session=true
hibernate.current_session_context_class=thread
hibernate.flush_before_completion=true

The JAXB marshalling.xml




  

  
    
      
        de.schaeftlein.dev.spring.rest.domain.Person
        de.schaeftlein.dev.spring.rest.domain.People
      
    
  




The AOP based transactionAdvices.xml




  
    
    
  

  
    
      
      
    
  




The spring-dispatcher.xml



  
  
  
   
  
    
  
    
      
        
      
    
 

  

The spring-context.xml



	
	
	

   

    
      
    

	
		
			${jdbc.driverClassName}
		
		
			${jdbc.url}
		
		
			${jdbc.username}
		
		
			${jdbc.password}
		
		
			${jdbc.autoCommitOnClose}
		
		
			${jdbc.initialPoolSize}
		
		
			${jdbc.maxPoolSize}
		
		
			${jdbc.minPoolSize}
		
		
			${jdbc.maxStatementsPerConnection}
		
		
			${jdbc.maxStatements}
		
	

	

	
		
			
				${jdbc.url} 
				${jdbc.driverClassName}
				${jdbc.username} 
				${jdbc.password}
				${jdbc.autoCommit}
				${hibernate.release_mode}
				${hibernate.hbm2ddl}
				${hibernate.auto_close_session}
				${hibernate.current_session_context_class}
				false
				false
				javassist
				${hibernate.flush_before_completion}
			
		
	

	
	
	   


The web.xml





  spring3-rest-sample
  Spring 3 Rest sample
  

  
    contextConfigLocation
    classpath:spring-context.xml
  

  
    org.springframework.web.context.ContextLoaderListener
    
  

  
    springDispatcher
    org.springframework.web.servlet.DispatcherServlet
    
      contextConfigLocation
      classpath:spring-dispatcher.xml
    
    2
  

  
  
    springDispatcher
    /*
  


The Maven pom.xml


	4.0.0
	de.schaeftlein.dev.spring.rest
	spring3-rest-sample
	war
	1.0-SNAPSHOT
	spring3-rest-sample Maven Webapp
	A sample project for showing how to write JUnit Test against Spring 3 Rest Services
    
     
       Ralf Schäftlein
       http://ralf.schaeftlein.com
     
    
    2010
    
      bugzilla
      http://ubuntu-vm.localdomain/cgi-bin/bugzilla3/index.cgi
    
    http://ralf.schaeftlein.de/2010/03/05/junit-tests-for-spring-3-rest-spring-3-rest-services

	
		
			
				org.apache.maven.plugins
				maven-surefire-report-plugin
				2.5
			
			
				org.apache.maven.plugins
				maven-checkstyle-plugin
				2.5
			
			
				org.apache.maven.plugins
				maven-javadoc-plugin
				2.6.1
			
			
				org.codehaus.mojo
				cobertura-maven-plugin
				2.3
			
			
				org.apache.maven.plugins
				maven-pmd-plugin
				2.4
				
					utf-8
					1.6
				
			
			
				org.apache.maven.plugins
				maven-jxr-plugin
				2.1
				
					UTF-8
					UTF-8
				
			
			
				org.codehaus.mojo
				taglist-maven-plugin
				2.4
			
			
				org.apache.maven.plugins
				maven-changes-plugin
				2.3
				
					
						
							changes-report
						
					
				
			
		
	
	
		
			internal
			
				dav:http://ubuntu-vm.localdomain:8081/artifactory/internal
			
		
		
			snapshots
			
				dav:http://ubuntu-vm.localdomain:8081/artifactory/snapshots
			
		
	
	
		
           scm:svn:http://ubuntu-vm.localdomain/svn/
       
		
           scm:svn:http://ubuntu-vm.localdomain/svn/
       
		http://ubuntu-vm.localdomain/svn/
	
	
		Hudson
		http://ubuntu-vm.localdomain/hudson/job/spring3-rest-sample
	
	
		spring3-rest-sample
		
			
				org.apache.maven.plugins
				maven-surefire-report-plugin
				2.5
			
			
				org.apache.maven.plugins
				maven-deploy-plugin
				2.5
			
			
				org.apache.maven.plugins
				maven-surefire-plugin
				2.5
				
					true
				
				
					
						surefire-test
						test
						
							test
						
						
							false
							
								**/itest/**
							
						
					

					
						surefire-itest
						integration-test
						
							test
						
						
							false
							
								**/itest/**
							
						
					
				
			
			
				org.apache.maven.plugins
				maven-pmd-plugin
				2.4
				
					utf-8
					1.6
				
				
					
						
							check
							cpd-check
						
					
				
			
			
				org.codehaus.mojo
				cobertura-maven-plugin
				2.3
				
					
						html
						xml
					
				
			
			
				org.apache.maven.plugins
				maven-jxr-plugin
				2.1
			
			
				org.apache.maven.plugins
				maven-checkstyle-plugin
				2.5
			
			
				maven-compiler-plugin
				2.1
				
					1.6
					1.6
				
			
			
				org.mortbay.jetty
				maven-jetty-plugin
				6.1.22
				
					10
					foo
					9999
					/${artifactId}
					
						
							9090
							60000
						
					
				
				
					
						start-jetty
						pre-integration-test
						
							run
						
						
							0
							true
						
					
					
						stop-jetty
						post-integration-test
						
							stop
						
					
				
			

			
				org.apache.maven.plugins
				maven-eclipse-plugin
				2.8
				
					true
					true
					2.0
				
			
			
			
				org.apache.maven.plugins
				maven-jar-plugin
				2.3
				
					
						
							true
						
					
				
			
			
			
				org.apache.maven.plugins
				maven-source-plugin
				2.1.1
				
					
						attach-sources
						verify
						
							jar
						
					
				
			
			
				org.apache.maven.plugins
				maven-scm-plugin
				1.3
				
					${svn_username}
					${svn_password}
				
			
			
				org.apache.maven.plugins
				maven-war-plugin
				2.1-beta-1
				
					src/main/webapp
					src/main/webapp/WEB-INF/web.xml
					
					
						
					
						
							true
							lib/
						
					
				
			
		
		
		
			
				src/main/resources
				
					**/.svn
					**/.svn/**
					**/_svn
					_svn
					**/_svn/**
				
			
		
		
			
				src/test/resources
				
					**/.svn
					**/.svn/**
					**/_svn
					_svn
					**/_svn/**
				
			
		
	



	
		3.0.1.RELEASE
		1.5.2
		UTF-8
	

	
		
		
			org.aspectj
			aspectjweaver
			1.6.2
		
		
			org.aspectj
			aspectjrt
			1.6.2
			
		
		
			org.springframework
			spring-beans
			${spring.framework.version}
		
		
			org.springframework
			spring-context-support
			${spring.framework.version}
			
				
					quartz
					quartz
				
			
		
		
			org.springframework
			spring-core
			${spring.framework.version}
		
		
			org.springframework
			spring-aspects
			${spring.framework.version}
		
		
			org.springframework
			spring-jdbc
			${spring.framework.version}
		
		
			org.springframework
			spring-test
			${spring.framework.version}
			test
		
		
			org.springframework
			spring-orm
			${spring.framework.version}
		
		
			org.springframework
			spring-oxm
			${spring.framework.version}
		
		
			org.springframework
			spring-tx
			${spring.framework.version}
		
		
			org.springframework
			spring-web
			${spring.framework.version}
		
		
			org.springframework
			spring-webmvc
			${spring.framework.version}
		

		
		
			cglib
			cglib-nodep
			2.1_3
		
		
			org.hibernate
			hibernate-annotations
			3.2.0.ga
		
		
			org.hibernate
			hibernate
			3.2.6.ga
		
		
			javax.persistence
			persistence-api
			1.0
		
		
			javassist
			javassist
			3.11.0.GA
		

		
		
			mysql
			mysql-connector-java
			5.1.12
			jar
			compile
		
		
			c3p0
			c3p0
			0.9.1.2
			jar
			compile
		
		
			commons-dbcp
			commons-dbcp
			1.4
		
		
			commons-collections
			commons-collections
			3.2.1
		
		
			javax.transaction
			jta
			1.1
		

		
		
			javax.xml.bind
			jaxb-api
			2.0
		

		
		
			junit
			junit
			4.8.1
			test
		
		
		
			org.slf4j
			slf4j-api
			${org.slf4j.version}
		
		
			org.slf4j
			slf4j-simple
			${org.slf4j.version}
		
		
		
			org.mortbay.jetty
			maven-jetty-plugin
			6.1.15
			test
		

	


Sample Content for Poster

The person.xml



    Jane
    Doe
    jane.doe
    bar
    2

Spring 3.0 RC 2 and JSR-330 (Dependency Injection for Java)

SpringSource has published release candidate 2 of the upcoming 3.0 release of their Spring Framework. New Feature is the compliance with JSR-330 (“Dependency Injection for Java”). The JSR was developed together by Google (for their Guice Framework and SpringSource (for their Spring Framework) and is finally approved since 14.10.2009.

A little example shows how to develop services with interfaces and implementations without dependencies to Spring Framework or Google Guice:

The Maven pom.xml


	4.0.0
	de.schaeftlein.dev
	jsr330-sample
	jar
	jsr330-sample
	0.0.1-SNAPSHOT
	Sample App with Spring 3.0 RC 2 and JSR330
	
		3.0.0.RC2
	

	
		
			org.springframework
			spring-core
			${springVersion}
		
		
			org.springframework
			spring-beans
			${springVersion}
		
		
			org.springframework
			spring-context
			${springVersion}
		
		
			org.springframework
			spring-asm
			${springVersion}
		
		
			org.springframework
			spring-expression
			${springVersion}
		
		
			junit
			junit
			4.7
			test
		
		
			javax.inject
			javax.inject
			1
		
	
	
		jsr330-sample
		src/main/java
		${basedir}/src/test/java

		
		
			
				src/main/resources
				
					**/.svn
					**/.svn/**
					**/_svn
					_svn
					**/_svn/**
				
			
		
		
			
				src/test/resources
				
					**/.svn
					**/.svn/**
					**/_svn
					_svn
					**/_svn/**
				
			
		
		
			
				org.apache.maven.plugins
				maven-compiler-plugin
				
					1.6
					1.6
				
			
			
				org.apache.maven.plugins
				maven-eclipse-plugin
				
					true
					true
					
						_svn
						.svn
					
				
			
			
				org.apache.maven.plugins
				maven-jar-plugin
				
					
						
							true
						
					
				
			
			
			
				org.apache.maven.plugins
				maven-source-plugin
				
					
						attach-sources
						verify
						
							jar
						
					
				
			
		
	

a simple encode/decode interface

package de.schaeftlein.dev.spring;

public interface Encryption
{
  String encode(String value);
  String decode(String value);
}

a simple implementation for Encryption

package de.schaeftlein.dev.spring;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

import javax.inject.Named;

@Named // service name is the name of the class
public class URLEncoderEncyrption implements Encryption
{

  @Override
  public String decode(String value)
  {
    try
    {
      return URLDecoder.decode(value, "UTF-8");
    }
    catch (UnsupportedEncodingException e)
    {
      return null; // never happen
    }
  }

  @Override
  public String encode(String value)
  {
    try
    {
      return URLEncoder.encode(value, "UTF-8");
    }
    catch (UnsupportedEncodingException e)
    {
      return null; // never happen
    }
  }

}

a more secure implementation for Encryption

package de.schaeftlein.dev.spring;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.inject.Named;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import com.sun.org.apache.xml.internal.security.utils.Base64;

@Named("secure") // named service bean
public class Base64Encyption implements Encryption
{
  private sun.misc.BASE64Encoder base64encoder;
  private SecretKey key;

  public Base64Encyption()
  {
    try
    {
      DESKeySpec keySpec = new DESKeySpec("Your secret Key phrase".getBytes("UTF8"));
      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
      key = keyFactory.generateSecret(keySpec);
      base64encoder = new BASE64Encoder();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  @Override
  public String decode(String input)
  {
    try
    {
      Cipher cipher = Cipher.getInstance("DES"); // cipher is not thread safe
      cipher.init(Cipher.DECRYPT_MODE, key);
      byte[] bOut = cipher.doFinal(Base64.decode(input.getBytes("UTF-8")));
      return new String(bOut, "UTF-8");
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public String encode(String plainTextPassword)
  {
    try
    {
      byte[] cleartext = plainTextPassword.getBytes("UTF8");

      Cipher cipher = Cipher.getInstance("DES"); // cipher is not thread safe
      cipher.init(Cipher.ENCRYPT_MODE, key);
      return base64encoder.encode(cipher.doFinal(cleartext));
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    return null;
  }

}

a small interface for a service

package de.schaeftlein.dev.spring;

public interface SecureUtil
{
  void compareEncryption(String input);
}

a implementation for our service

package de.schaeftlein.dev.spring;

import javax.inject.Inject;
import javax.inject.Named;

@Named("SecureUtil")
public class SecureUtilImpl implements SecureUtil
{
  @Inject // automatically set by DI framework
  @Named("secure") // get the namend bean
  private Encryption secureEncryption;
  
  @Inject // automatically set by DI framework
  @Named("URLEncoderEncyrption") // get the bean by its classname
  private Encryption unsecureEncryption;
  
  public void compareEncryption(String input){
    
    String encodedSecure = secureEncryption.encode(input);
    System.out.println("Secure encoded: "+encodedSecure);
    String encodeUnsecure = unsecureEncryption.encode(input);
    System.out.println("Unsecure encoded: "+encodeUnsecure);
    System.out.println("Secure decoded: "+secureEncryption.decode(encodedSecure));
    System.out.println("Unsecure decoded: "+unsecureEncryption.decode(encodeUnsecure));
  }
}

finally a main class for testing

package de.schaeftlein.dev.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main
{

  /**
   * main method
   */
  public static void main(String[] args)
  {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(Main.class.getPackage().getName()); // new way to get Application context without applicationContext.xml available
    SecureUtil util = ctx.getBean("SecureUtil", SecureUtil.class); // get bean with new generics method
    util.compareEncryption("an sample input string 1234567890");
  }

}

output of our Main class

19.11.2009 18:56:03 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@c1b531: startup date [Thu Nov 19 18:56:03 CET 2009]; root of context hierarchy
19.11.2009 18:56:03 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor 
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
19.11.2009 18:56:03 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a83b8a: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,secure,SecureUtil,URLEncoderEncyrption]; root of factory hierarchy
Secure encoded: QDQvd4uK14YNUQ4uoqhqsZEMDDxUqJAMyisvZr2wsA2GyMC5tSEIiw==
Unsecure encoded: an+sample+input+string+1234567890
Secure decoded: an sample input string 1234567890
Unsecure decoded: an sample input string 1234567890

Dbunit with JUnit 4.x and Spring for testing Oracle DB Application

DBUnit is very nice for testing database content changes made by an application. You define in XML the data including the structure of your tables (dataset.xml).



	

Simple_Data is the name of the table and each column is a attribute in the xml doc with the content value e.g. id with value 1.

The Getting Started of DBUnit work with JUnit 3.8 and self handling of the JDBC Connection.

JUnit 4.x are more comfortable with annotations based test methods and Spring comes with dependency injection for separating
configuration from implementation code.

The following approach combines DBUnit with JUnit 4.4 and Spring 2.5.6 to test comfortable a Oracle 10g database.

I use Maven 2.x to define the depending libraries used by the example (pom.xml):



	4.0.0
	de.schaeftlein.dev.dbunit
	test-dbunit
	test-dbunit
	0.0.1-SNAPSHOT
	
	
		
			org.dbunit
			dbunit
			2.4.2
		
		
			org.springframework
			spring
			2.5.6
			jar
			compile
		
		
			junit
			junit
			4.4
		
		
			commons-dbcp
			commons-dbcp
			1.2.2
		
		
			org.springframework
			spring-test
			2.5.6
		
		
			org.slf4j
			slf4j-api
			1.5.6
		
		
			org.slf4j
			log4j-over-slf4j
			1.5.6
		
		
			log4j
			log4j
			1.2.14
		
		
			org.slf4j
			slf4j-log4j12
			1.5.6
		
		
			com.oracle
			ojdbc14
			10.2.0.2.0
		
	

Keep in mind that the Oracle JDBC Driver has to be downloaded manually.
The public maven repos include only the Pom definition for the oracle driver. Generate with maven command line tool the eclipse project files:

mvn clean eclipse:clean eclipse:eclipse

The JDBC datasource is defined via Spring (applicationContext.xml):

 


  
    
    
    
    
  
 

Additionally we define the expected data as well in XML for DBUnit (expectedDataSet.xml):



	

Now we can code our JUnit 4.x Test to

  1. load data before the test method
  2. change the data via JDBC to emulate a application
  3. compare the changed data with expected data
  4. clean up the database

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"}) 
public class TestDBUnitWithSpring {

	@Autowired
	private DataSource dataSource;
	
	@Before
	public void init() throws Exception{
		// insert data into db
		DatabaseOperation.CLEAN_INSERT.execute(getConnection(), getDataSet());
	}
	
	@After
	public void after() throws Exception{
		// insert data into db
		DatabaseOperation.DELETE_ALL.execute(getConnection(), getDataSet());
	}
	
	private IDatabaseConnection getConnection() throws Exception{
	// get connection
		Connection con = dataSource.getConnection();
		DatabaseMetaData  databaseMetaData = con.getMetaData();
		// oracle schema name is the user name
		IDatabaseConnection connection = new DatabaseConnection(con,databaseMetaData.getUserName().toUpperCase());
		DatabaseConfig config = connection.getConfig();
		// oracle 10g 
		config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new Oracle10DataTypeFactory());
		// receycle bin
		config.setFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, Boolean.TRUE);
		return connection;
	}
	
	private IDataSet getDataSet() throws Exception{
		// get insert data
		File file = new File("src/test/resources/dataset.xml");
		return new FlatXmlDataSet(file);
	}
	
	@Test
	public void testSQLUpdate() throws Exception{
		Connection con = dataSource.getConnection();
		Statement stmt = con.createStatement();
		// get current data
		ResultSet rst = stmt.executeQuery("select * from simple_data where id = 1");
		if(rst.next()){
			// from dataset.xml
			assertEquals("value_before", rst.getString("content"));
			rst.close();
			
			// update via sql
			int count = stmt.executeUpdate("update simple_data set content='value_after' where id=1");

			stmt.close();
			con.close();

			// expect only one row to be updated
			assertEquals("one row should be updated", 1, count);

			// Fetch database data after executing the code
			QueryDataSet databaseSet = new QueryDataSet(getConnection());
			// filter data
			databaseSet.addTable("simple_data", "select * from simple_data where id = 1");
			ITable actualTable = databaseSet.getTables()[0];

			// Load expected data from an XML dataset
			IDataSet expectedDataSet = new FlatXmlDataSet(new File("src/test/resources/expectedDataSet.xml"));
			ITable expectedTable = expectedDataSet.getTable("simple_data");

			// filter unnecessary columns of current data by xml definition
			actualTable = DefaultColumnFilter.includedColumnsTable(actualTable, expectedTable.getTableMetaData().getColumns());

			// Assert actual database table match expected table
			assertEquals(1,expectedTable.getRowCount());
			assertEquals(expectedTable.getRowCount(), actualTable.getRowCount());
			assertEquals(expectedTable.getValue(0, "content"), actualTable.getValue(0, "content"));
			
		} else {
			fail("no rows");
			rst.close();
			stmt.close();
			con.close();
		}

	}
}