Alexey Zvolinskiy

About Alexey Zvolinskiy

Alexey is a test developer with solid experience in automation of web-applications using Java, TestNG and Selenium. He is so much into QA that even after work he provides training courses for junior QA engineers.

Spring MVC: REST application with CNVR vol. 1

Not so long time ago I have read an article written by Paul Chapman about Content Negotiating View Resolver (CNVR). That post on a Spring Framework Blog inspired me to investigate this framework’s area. As a result I have developed a sample REST application based on Spring MVC with CNVR. The application demonstrates a basic flow of a REST service – creation, deletion, reading and edition of an entity.

Spring Framework supports REST services for a long time, earlier you could develop some service using Message Converters. In Spring 3.2 all this stuff become more easier in configuration and development. So lets stop talking because I’m going to show a basic setup and exploitation of Spring REST service with CNVR.

A basic idea of CNVR is to define which kind of representation form for a resource give back to a client depending on information which CNVR gets from the client’s request. You can ask me: what is that information in the request which can impact on CNVR decision? The answer is simple:

  • URL sufix (e.g. .xml, .json, .html etc
  • URL parameter (format by default)
  • HTTP Accept header property

Here is an illustration of high level CNVR workflow:

Spring-MVC-CNVR-schema

For the more information I recommend to read a full article of Paul Chapman.

Setup the Spring MVC REST project with CNVR

I will work with a maven project, as always I will provide a link to a project’s GitHub repository. Here is a screenshot of entire project:

CNVR-Project

I have explained numerous times how to setup Dynamic Web Project in Eclipse, so now I will provide just source files with some short notes. You can find required maven dependencies below:

<properties>
		<mysql.connector>5.1.25</mysql.connector>
		<hibernate.version>4.2.3.Final</hibernate.version>
		<spring.version>3.2.3.RELEASE</spring.version>
		<spring.data.version>1.3.2.RELEASE</spring.data.version>
		<jackson.version>1.9.12</jackson.version>
	</properties>

	<dependencies>
		<!-- DataBase libs -->
		<dependency>
			<groupid>mysql</groupid>
			<artifactid>mysql-connector-java</artifactid>
			<version>${mysql.connector}</version>
		</dependency>
		<dependency>
			<groupid>commons-dbcp</groupid>
			<artifactid>commons-dbcp</artifactid>
			<version>1.4</version>
		</dependency>
		<!-- Hibernate -->
		<dependency>
			<groupid>org.hibernate</groupid>
			<artifactid>hibernate-core</artifactid>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupid>org.hibernate</groupid>
			<artifactid>hibernate-entitymanager</artifactid>
			<version>${hibernate.version}</version>
		</dependency>
		<!-- Spring -->
		<dependency>
			<groupid>org.springframework</groupid>
			<artifactid>spring-webmvc</artifactid>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupid>org.springframework.data</groupid>
			<artifactid>spring-data-jpa</artifactid>
			<version>${spring.data.version}</version>
			<exclusions>
				<exclusion>
					<artifactid>spring-aop</artifactid>
					<groupid>org.springframework</groupid>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupid>org.springframework</groupid>
			<artifactid>spring-orm</artifactid>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupid>org.springframework</groupid>
			<artifactid>spring-tx</artifactid>
			<version>${spring.version}</version>
		</dependency>
		<!-- CGLIB is required to process @Configuration classes -->
		<dependency>
			<groupid>cglib</groupid>
			<artifactid>cglib</artifactid>
			<version>3.0</version>
		</dependency>
		<!-- Other -->
		<dependency>
			<groupid>javax.servlet</groupid>
			<artifactid>javax.servlet-api</artifactid>
			<version>3.0.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupid>jstl</groupid>
			<artifactid>jstl</artifactid>
			<version>1.2</version>
		</dependency>
		<!-- CNVR resources -->
		<dependency>
			<groupid>org.codehaus.jackson</groupid>
			<artifactid>jackson-mapper-asl</artifactid>
			<version>${jackson.version}</version>
		</dependency>
	</dependencies>

A full version of the pom.xml file you can find on GitHub. So let’s go ahead with the preparation. I will use MySQL as a database. And I need to create a following table in it:

CREATE TABLE `smartphones` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `producer` varchar(20) NOT NULL,
  `model` varchar(20) NOT NULL,
  `price` double NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

Now we need appropriate java object which will represent the smartphones table:

@Entity
@Table(name="smartphones")
public class Smartphone {

	@Id
	@GeneratedValue
	private Integer id;

	private String producer;

	private String model;

	private double price;

	/**
	 * Method updates already existed {@link Smartphone} object with values from the inputed argument.
	 * @param sPhone - Object which contains new Smartphone values.
	 * @return {@link Smartphone} object to which this method applied.
	 */
	public Smartphone update(Smartphone sPhone) {
		this.producer = sPhone.producer;
		this.model = sPhone.model;
		this.price = sPhone.price;
		return this;
	}

	@Override
	public String toString() {
		return producer+": "+model+" with price "+price;
	}

	public Integer getId() {
		return id;
	}

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

	public String getProducer() {
		return producer;
	}

	public void setProducer(String producer) {
		this.producer = producer;
	}

	public String getModel() {
		return model;
	}

	public void setModel(String model) {
		this.model = model;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

}

The most part of preparation is done. Service and DAO layer is the last thing which I need to do. I’m going to use Spring Data for the DAO layer, and in the one of my previous posts I have made a detail review of its setup.

public interface SmartphoneRepository extends JpaRepository< Smartphone, Integer >{ }

And here are corresponding service interface and its implementation:

public interface SmartphoneService {

	public Smartphone create(Smartphone sp);
	public Smartphone get(Integer id);
	public List< Smartphone > getAll();
	public Smartphone update(Smartphone sp) throws SmartphoneNotFoundException;
	public Smartphone delete(Integer id) throws SmartphoneNotFoundException;

}

Service implementation:

@Service
@Transactional(rollbackFor=SmartphoneNotFoundException.class)
public class SmartphoneServiceImpl implements SmartphoneService {

	@Autowired
	private SmartphoneRepository smartphoneRepository;

	@Override
	public Smartphone create(Smartphone sp) {
		return smartphoneRepository.save(sp);
	}

	@Override
	public Smartphone get(Integer id) {
		return smartphoneRepository.findOne(id);
	}

	@Override
	public List< Smartphone > getAll() {
		return smartphoneRepository.findAll();
	}

	@Override
	public Smartphone update(Smartphone sp) throws SmartphoneNotFoundException {
		Smartphone sPhoneToUpdate = get(sp.getId());
		if (sPhoneToUpdate == null)
			throw new SmartphoneNotFoundException(sp.getId().toString());
		sPhoneToUpdate.update(sp);
		return sPhoneToUpdate;
	}

	@Override
	public Smartphone delete(Integer id) throws SmartphoneNotFoundException {
		Smartphone sPhone = get(id);
		if (sPhone == null)
			throw new SmartphoneNotFoundException(id.toString());
		smartphoneRepository.delete(id);
		return sPhone;
	}
}

In the end of setting up of the project let’s consider the “heart” of configurations: Initializer and WebAppConfig files.

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.mobapp")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.mobapp.repository")
public class WebAppConfig extends WebMvcConfigurerAdapter {

    private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
    private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
    private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
    private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";

    private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";

	@Resource
	private Environment env;

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();

		dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
		dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
		dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
		dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));

		return dataSource;
	}

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
		entityManagerFactoryBean.setDataSource(dataSource());
		entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
		entityManagerFactoryBean.
setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));

		entityManagerFactoryBean.setJpaProperties(hibProperties());

		return entityManagerFactoryBean;
	}

	private Properties hibProperties() {
		Properties properties = new Properties();
		properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
		properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
		return properties;	
	}

	@Bean
	public JpaTransactionManager transactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
		return transactionManager;
	}

	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		configurer.favorPathExtension(true)
			.useJaf(false)
			.ignoreAcceptHeader(true)
			.mediaType("html", MediaType.TEXT_HTML)
			.mediaType("json", MediaType.APPLICATION_JSON)
			.defaultContentType(MediaType.TEXT_HTML);
	}

	@Bean
	public ViewResolver contentNegotiatingViewResolver(
			ContentNegotiationManager manager) {

		List< ViewResolver > resolvers = new ArrayList< ViewResolver >();

		InternalResourceViewResolver r1 = new InternalResourceViewResolver();
		r1.setPrefix("/WEB-INF/pages/");
		r1.setSuffix(".jsp");
		r1.setViewClass(JstlView.class);
		resolvers.add(r1);

		JsonViewResolver r2 = new JsonViewResolver();
		resolvers.add(r2);

		ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
		resolver.setViewResolvers(resolvers);
		resolver.setContentNegotiationManager(manager);
	    return resolver;

	}

	/**
	* View resolver for returning JSON in a view-based system. Always returns a
	* {@link MappingJacksonJsonView}.
	*/
	public class JsonViewResolver implements ViewResolver {
		public View resolveViewName(String viewName, Locale locale)
				throws Exception {
				MappingJacksonJsonView view = new MappingJacksonJsonView();
				view.setPrettyPrint(true);
				return view;
		}
	}

}

Despite that the file is big enough I want to focus your attention just on several things. The first one is the JsonViewResolver inner class. It’s required for handling of JSON requests. Of course, it can be declared separatly of the WebAppConfig class and just be imported in it. But I decide to put it directly in the WebAppConfig to avoid a dispersal of attention. The second one is the configureContentNegotiation method. There I set options for a content negotiation view resolver. And finally in the contentNegotiatingViewResolver bean I have determined which view resolvers will be available in the my application.

public class Initializer implements WebApplicationInitializer {

	private static final String DISPATCHER_SERVLET_NAME = "dispatcher";

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
		ctx.register(WebAppConfig.class);

		ctx.setServletContext(servletContext);		

		registerHiddenHttpMethodFilter(servletContext);	

		Dynamic servlet = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(ctx));
		servlet.addMapping("/");
		servlet.setLoadOnStartup(1);

	}

	private void registerHiddenHttpMethodFilter(ServletContext servletContext) {
		FilterRegistration.Dynamic fr = servletContext
				.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);
		fr.addMappingForServletNames(
				EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), 
				false, 
				DISPATCHER_SERVLET_NAME);
	}

}

In the Initializer class there is just one important thing in context of this tutorial. It is registerHiddenHttpMethodFilter method. This method will help to deal with such HTTP methods as PUT and DELETE.

I hope you didn’t tired, because the most interesting will wait for you in the following part of this tutorial.
 

Reference: Spring MVC: REST application with CNVR vol. 1 from our JCG partner Alexey Zvolinskiy at the Fruzenshtein’s notes blog.
Related Whitepaper:

Introduction to Web Applications Development

Kick start your web apps development with this introductory ebook!

This 376 page eBook 'Introduction to Web Applications Development', starts with an introduction to the internet, including a brief history of the TCT/IP protocol and World Wide Web. It defines the basic concepts for web servers and studies the case of Apache, the most used webserver, while other free software webservers are not forgotten. It continues with webpage design focusing on HTML and JavaScript. XML Schemas, their validation and transformation are covered as well as dynamic webpages built with CGI, PHP or JSP and database access.

Get it Now!  

Leave a Reply


× 8 = sixty four



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

15,153 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books
Get tutored by the Geeks! JCG Academy is a fact... Join Now
Hello. Add your message here.