diff --git a/spring-rest-full/.gitignore b/spring-rest-full/.gitignore
new file mode 100644
index 0000000000..83c05e60c8
--- /dev/null
+++ b/spring-rest-full/.gitignore
@@ -0,0 +1,13 @@
+*.class
+
+#folders#
+/target
+/neoDb*
+/data
+/src/main/webapp/WEB-INF/classes
+*/META-INF/*
+
+# Packaged files #
+*.jar
+*.war
+*.ear
\ No newline at end of file
diff --git a/spring-rest-full/README.md b/spring-rest-full/README.md
new file mode 100644
index 0000000000..a0ba8df27d
--- /dev/null
+++ b/spring-rest-full/README.md
@@ -0,0 +1,37 @@
+## Spring REST Full
+
+This module contains articles about REST APIs with Spring
+
+### Courses
+
+The "REST With Spring" Classes: http://bit.ly/restwithspring
+
+The "Learn Spring Security" Classes: http://github.learnspringsecurity.com
+
+### Relevant Articles:
+- [Integration Testing with the Maven Cargo plugin](https://www.baeldung.com/integration-testing-with-the-maven-cargo-plugin)
+- [Project Configuration with Spring](https://www.baeldung.com/project-configuration-with-spring)
+- [Metrics for your Spring REST API](https://www.baeldung.com/spring-rest-api-metrics)
+- [Spring Security Expressions - hasRole Example](https://www.baeldung.com/spring-security-expressions-basic)
+
+
+### Build the Project
+```
+mvn clean install
+```
+
+
+### Set up MySQL
+```
+mysql -u root -p
+> CREATE USER 'tutorialuser'@'localhost' IDENTIFIED BY 'tutorialmy5ql';
+> GRANT ALL PRIVILEGES ON *.* TO 'tutorialuser'@'localhost';
+> FLUSH PRIVILEGES;
+```
+
+
+### Use the REST Service
+
+```
+curl http://localhost:8082/spring-rest-full/auth/foos
+```
diff --git a/spring-rest-full/pom.xml b/spring-rest-full/pom.xml
new file mode 100644
index 0000000000..5bc3a70ed5
--- /dev/null
+++ b/spring-rest-full/pom.xml
@@ -0,0 +1,297 @@
+
+ 4.0.0
+ com.baeldung
+ spring-rest-full
+ 0.1-SNAPSHOT
+ spring-rest-full
+ war
+
+
+ parent-boot-2
+ com.baeldung
+ 0.0.1-SNAPSHOT
+ ../parent-boot-2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.aspectj
+ aspectjweaver
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ provided
+
+
+
+
+
+ org.springframework
+ spring-core
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.springframework
+ spring-context
+
+
+ org.springframework
+ spring-jdbc
+
+
+ org.springframework
+ spring-beans
+
+
+ org.springframework
+ spring-aop
+
+
+ org.springframework
+ spring-tx
+
+
+ org.springframework
+ spring-expression
+
+
+ org.springframework
+ spring-web
+
+
+ org.springframework
+ spring-webmvc
+
+
+ org.springframework.data
+ spring-data-commons
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.apache.httpcomponents
+ httpcore
+
+
+
+
+
+ org.springframework
+ spring-orm
+
+
+ org.springframework.data
+ spring-data-jpa
+
+
+ org.hibernate
+ hibernate-entitymanager
+
+
+ xml-apis
+ xml-apis
+
+
+ org.javassist
+ javassist
+ ${javassist.version}
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ com.h2database
+ h2
+
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ javax.servlet
+ jstl
+ runtime
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+
+
+
+
+
+
+ org.hamcrest
+ hamcrest-library
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+ spring-rest-full
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+
+ org.codehaus.cargo
+ cargo-maven2-plugin
+ ${cargo-maven2-plugin.version}
+
+ true
+
+ jetty8x
+ embedded
+
+
+
+
+
+
+ 8082
+
+
+
+
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ ${apt-maven-plugin.version}
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
+
+
+
+
+
+
+ live
+
+
+
+ org.codehaus.cargo
+ cargo-maven2-plugin
+
+ false
+
+
+
+ start-server
+ pre-integration-test
+
+ start
+
+
+
+ stop-server
+ post-integration-test
+
+ stop
+
+
+
+
+
+
+
+
+
+
+
+ 1.4.9
+
+
+ 19.0
+ 3.25.0-GA
+
+
+ 1.6.1
+ 1.1.3
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/IOperations.java b/spring-rest-full/src/main/java/org/baeldung/persistence/IOperations.java
new file mode 100644
index 0000000000..0b617bf7ab
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/IOperations.java
@@ -0,0 +1,22 @@
+package org.baeldung.persistence;
+
+import java.io.Serializable;
+import java.util.List;
+
+public interface IOperations {
+
+ // read - one
+
+ T findOne(final long id);
+
+ // read - all
+
+ List findAll();
+
+ // write
+
+ T create(final T entity);
+
+ T update(final T entity);
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/dao/IFooDao.java b/spring-rest-full/src/main/java/org/baeldung/persistence/dao/IFooDao.java
new file mode 100644
index 0000000000..230abd0d5f
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/dao/IFooDao.java
@@ -0,0 +1,11 @@
+package org.baeldung.persistence.dao;
+
+import org.baeldung.persistence.model.Foo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+public interface IFooDao extends JpaRepository {
+
+ @Query("SELECT f FROM Foo f WHERE LOWER(f.name) = LOWER(:name)")
+ Foo retrieveByName(@Param("name") String name);
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/model/Foo.java b/spring-rest-full/src/main/java/org/baeldung/persistence/model/Foo.java
new file mode 100644
index 0000000000..8e1dee33e8
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/model/Foo.java
@@ -0,0 +1,83 @@
+package org.baeldung.persistence.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Foo implements Serializable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ public Foo() {
+ super();
+ }
+
+ public Foo(final String name) {
+ super();
+
+ this.name = name;
+ }
+
+ // API
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(final long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ //
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final Foo other = (Foo) obj;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Foo [name=").append(name).append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/model/User.java b/spring-rest-full/src/main/java/org/baeldung/persistence/model/User.java
new file mode 100644
index 0000000000..670d4a2e74
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/model/User.java
@@ -0,0 +1,94 @@
+package org.baeldung.persistence.model;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ private String firstName;
+
+ private String lastName;
+
+ private String email;
+
+ private int age;
+
+ public User() {
+ super();
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(final String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(final String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(final String username) {
+ email = username;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(final int age) {
+ this.age = age;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((email == null) ? 0 : email.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final User user = (User) obj;
+ return email.equals(user.email);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("User [firstName=").append(firstName).append("]").append("[lastName=").append(lastName).append("]").append("[username").append(email).append("]");
+ return builder.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/service/IFooService.java b/spring-rest-full/src/main/java/org/baeldung/persistence/service/IFooService.java
new file mode 100644
index 0000000000..60d607b9ef
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/service/IFooService.java
@@ -0,0 +1,10 @@
+package org.baeldung.persistence.service;
+
+import org.baeldung.persistence.IOperations;
+import org.baeldung.persistence.model.Foo;
+
+public interface IFooService extends IOperations {
+
+ Foo retrieveByName(String name);
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/service/common/AbstractService.java b/spring-rest-full/src/main/java/org/baeldung/persistence/service/common/AbstractService.java
new file mode 100644
index 0000000000..ceefbbe0e3
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/service/common/AbstractService.java
@@ -0,0 +1,45 @@
+package org.baeldung.persistence.service.common;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.baeldung.persistence.IOperations;
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.google.common.collect.Lists;
+
+@Transactional
+public abstract class AbstractService implements IOperations {
+
+ // read - one
+
+ @Override
+ @Transactional(readOnly = true)
+ public T findOne(final long id) {
+ return getDao().findById(id).orElse(null);
+ }
+
+ // read - all
+
+ @Override
+ @Transactional(readOnly = true)
+ public List findAll() {
+ return Lists.newArrayList(getDao().findAll());
+ }
+
+ // write
+
+ @Override
+ public T create(final T entity) {
+ return getDao().save(entity);
+ }
+
+ @Override
+ public T update(final T entity) {
+ return getDao().save(entity);
+ }
+
+ protected abstract PagingAndSortingRepository getDao();
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/persistence/service/impl/FooService.java b/spring-rest-full/src/main/java/org/baeldung/persistence/service/impl/FooService.java
new file mode 100644
index 0000000000..32fe1bd7e0
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/persistence/service/impl/FooService.java
@@ -0,0 +1,37 @@
+package org.baeldung.persistence.service.impl;
+
+import org.baeldung.persistence.dao.IFooDao;
+import org.baeldung.persistence.model.Foo;
+import org.baeldung.persistence.service.IFooService;
+import org.baeldung.persistence.service.common.AbstractService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional
+public class FooService extends AbstractService implements IFooService {
+
+ @Autowired
+ private IFooDao dao;
+
+ public FooService() {
+ super();
+ }
+
+ // API
+
+ @Override
+ protected PagingAndSortingRepository getDao() {
+ return dao;
+ }
+
+ // custom methods
+
+ @Override
+ public Foo retrieveByName(final String name) {
+ return dao.retrieveByName(name);
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/spring/Application.java b/spring-rest-full/src/main/java/org/baeldung/spring/Application.java
new file mode 100644
index 0000000000..ca72a4ef56
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/spring/Application.java
@@ -0,0 +1,41 @@
+package org.baeldung.spring;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.context.request.RequestContextListener;
+
+/**
+ * Main Application Class - uses Spring Boot. Just run this as a normal Java
+ * class to run up a Jetty Server (on http://localhost:8082/spring-rest-full)
+ *
+ */
+@EnableScheduling
+@EnableAutoConfiguration
+@ComponentScan("org.baeldung")
+@SpringBootApplication
+public class Application extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(Application.class);
+ }
+
+ @Override
+ public void onStartup(ServletContext sc) throws ServletException {
+ // Manages the lifecycle of the root application context
+ sc.addListener(new RequestContextListener());
+ }
+
+ public static void main(final String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/spring/PersistenceConfig.java b/spring-rest-full/src/main/java/org/baeldung/spring/PersistenceConfig.java
new file mode 100644
index 0000000000..f3a87b189e
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/spring/PersistenceConfig.java
@@ -0,0 +1,85 @@
+package org.baeldung.spring;
+
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import com.google.common.base.Preconditions;
+
+@Configuration
+@EnableTransactionManagement
+@PropertySource({ "classpath:persistence-${envTarget:h2}.properties" })
+@ComponentScan({ "org.baeldung.persistence" })
+// @ImportResource("classpath*:springDataPersistenceConfig.xml")
+@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao")
+public class PersistenceConfig {
+
+ @Autowired
+ private Environment env;
+
+ public PersistenceConfig() {
+ super();
+ }
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+ final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+ em.setDataSource(dataSource());
+ em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" });
+
+ final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ // vendorAdapter.set
+ em.setJpaVendorAdapter(vendorAdapter);
+ em.setJpaProperties(additionalProperties());
+
+ return em;
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ final DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
+ dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("jdbc.url")));
+ dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user")));
+ dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass")));
+
+ return dataSource;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ final JpaTransactionManager transactionManager = new JpaTransactionManager();
+ transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
+
+ return transactionManager;
+ }
+
+ @Bean
+ public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
+ return new PersistenceExceptionTranslationPostProcessor();
+ }
+
+ final Properties additionalProperties() {
+ final Properties hibernateProperties = new Properties();
+ hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
+ hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
+ // hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", "true");
+ return hibernateProperties;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/spring/WebConfig.java b/spring-rest-full/src/main/java/org/baeldung/spring/WebConfig.java
new file mode 100644
index 0000000000..a0db08d93d
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/spring/WebConfig.java
@@ -0,0 +1,36 @@
+package org.baeldung.spring;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
+
+@Configuration
+@ComponentScan("org.baeldung.web")
+@EnableWebMvc
+public class WebConfig implements WebMvcConfigurer {
+
+ public WebConfig() {
+ super();
+ }
+
+ @Bean
+ public ViewResolver viewResolver() {
+ final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
+ viewResolver.setPrefix("/WEB-INF/view/");
+ viewResolver.setSuffix(".jsp");
+ return viewResolver;
+ }
+
+ // API
+ @Override
+ public void addViewControllers(final ViewControllerRegistry registry) {
+ registry.addViewController("/graph.html");
+ registry.addViewController("/homepage.html");
+ }
+
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/controller/FooController.java b/spring-rest-full/src/main/java/org/baeldung/web/controller/FooController.java
new file mode 100644
index 0000000000..caaf422410
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/controller/FooController.java
@@ -0,0 +1,80 @@
+package org.baeldung.web.controller;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.baeldung.persistence.model.Foo;
+import org.baeldung.persistence.service.IFooService;
+import org.baeldung.web.util.RestPreconditions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+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 org.springframework.web.bind.annotation.ResponseStatus;
+
+import com.google.common.base.Preconditions;
+
+@Controller
+@RequestMapping(value = "/auth/foos")
+public class FooController {
+
+ @Autowired
+ private IFooService service;
+
+ public FooController() {
+ super();
+ }
+
+ // API
+
+ @RequestMapping(method = RequestMethod.GET, value = "/count")
+ @ResponseBody
+ @ResponseStatus(value = HttpStatus.OK)
+ public long count() {
+ return 2l;
+ }
+
+ // read - one
+
+ @RequestMapping(value = "/{id}", method = RequestMethod.GET)
+ @ResponseBody
+ public Foo findById(@PathVariable("id") final Long id, final HttpServletResponse response) {
+ final Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
+
+ return resourceById;
+ }
+
+ // read - all
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public List findAll() {
+ return service.findAll();
+ }
+
+ // write
+
+ @RequestMapping(method = RequestMethod.POST)
+ @ResponseStatus(HttpStatus.CREATED)
+ @ResponseBody
+ public Foo create(@RequestBody final Foo resource, final HttpServletResponse response) {
+ Preconditions.checkNotNull(resource);
+ final Foo foo = service.create(resource);
+
+ return foo;
+ }
+
+ @RequestMapping(method = RequestMethod.HEAD)
+ @ResponseStatus(HttpStatus.OK)
+ public void head(final HttpServletResponse resp) {
+ resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ resp.setHeader("bar", "baz");
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/controller/HomeController.java b/spring-rest-full/src/main/java/org/baeldung/web/controller/HomeController.java
new file mode 100644
index 0000000000..9c4d14cae3
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/controller/HomeController.java
@@ -0,0 +1,14 @@
+package org.baeldung.web.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping(value = "/")
+public class HomeController {
+
+ public String index() {
+ return "homepage";
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/controller/RootController.java b/spring-rest-full/src/main/java/org/baeldung/web/controller/RootController.java
new file mode 100644
index 0000000000..a66f3d1893
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/controller/RootController.java
@@ -0,0 +1,52 @@
+package org.baeldung.web.controller;
+
+import java.util.Map;
+
+import org.baeldung.web.metric.IActuatorMetricService;
+import org.baeldung.web.metric.IMetricService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping(value = "/auth/")
+public class RootController {
+
+ @Autowired
+ private IMetricService metricService;
+
+ @Autowired
+ private IActuatorMetricService actMetricService;
+
+ public RootController() {
+ super();
+ }
+
+ // API
+
+ @RequestMapping(value = "/metric", method = RequestMethod.GET)
+ @ResponseBody
+ public Map getMetric() {
+ return metricService.getFullMetric();
+ }
+
+ @RequestMapping(value = "/status-metric", method = RequestMethod.GET)
+ @ResponseBody
+ public Map getStatusMetric() {
+ return metricService.getStatusMetric();
+ }
+
+ @RequestMapping(value = "/metric-graph", method = RequestMethod.GET)
+ @ResponseBody
+ public Object[][] drawMetric() {
+ final Object[][] result = metricService.getGraphData();
+ for (int i = 1; i < result[0].length; i++) {
+ result[0][i] = result[0][i].toString();
+ }
+ return result;
+ }
+
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/exception/MyResourceNotFoundException.java b/spring-rest-full/src/main/java/org/baeldung/web/exception/MyResourceNotFoundException.java
new file mode 100644
index 0000000000..14b61f9832
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/exception/MyResourceNotFoundException.java
@@ -0,0 +1,21 @@
+package org.baeldung.web.exception;
+
+public final class MyResourceNotFoundException extends RuntimeException {
+
+ public MyResourceNotFoundException() {
+ super();
+ }
+
+ public MyResourceNotFoundException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public MyResourceNotFoundException(final String message) {
+ super(message);
+ }
+
+ public MyResourceNotFoundException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/ActuatorMetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/ActuatorMetricService.java
new file mode 100644
index 0000000000..4dcec17b9e
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/ActuatorMetricService.java
@@ -0,0 +1,110 @@
+package org.baeldung.web.metric;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+
+@Service
+public class ActuatorMetricService implements IActuatorMetricService {
+
+ @Autowired
+ private MeterRegistry publicMetrics;
+
+ private final List> statusMetricsByMinute;
+ private final List statusList;
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ public ActuatorMetricService() {
+ super();
+ statusMetricsByMinute = new ArrayList>();
+ statusList = new ArrayList();
+ }
+
+ @Override
+ public Object[][] getGraphData() {
+ final Date current = new Date();
+ final int colCount = statusList.size() + 1;
+ final int rowCount = statusMetricsByMinute.size() + 1;
+ final Object[][] result = new Object[rowCount][colCount];
+ result[0][0] = "Time";
+ int j = 1;
+
+ for (final String status : statusList) {
+ result[0][j] = status;
+ j++;
+ }
+
+ for (int i = 1; i < rowCount; i++) {
+ result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
+ }
+
+ List minuteOfStatuses;
+ List last = new ArrayList();
+
+ for (int i = 1; i < rowCount; i++) {
+ minuteOfStatuses = statusMetricsByMinute.get(i - 1);
+ for (j = 1; j <= minuteOfStatuses.size(); j++) {
+ result[i][j] = minuteOfStatuses.get(j - 1) - (last.size() >= j ? last.get(j - 1) : 0);
+ }
+ while (j < colCount) {
+ result[i][j] = 0;
+ j++;
+ }
+ last = minuteOfStatuses;
+ }
+ return result;
+ }
+
+ // Non - API
+
+ @Scheduled(fixedDelay = 60000)
+ private void exportMetrics() {
+ final ArrayList lastMinuteStatuses = initializeStatuses(statusList.size());
+
+ for (final Meter counterMetric : publicMetrics.getMeters()) {
+ updateMetrics(counterMetric, lastMinuteStatuses);
+ }
+
+ statusMetricsByMinute.add(lastMinuteStatuses);
+ }
+
+ private ArrayList initializeStatuses(final int size) {
+ final ArrayList counterList = new ArrayList();
+ for (int i = 0; i < size; i++) {
+ counterList.add(0);
+ }
+ return counterList;
+ }
+
+ private void updateMetrics(final Meter counterMetric, final ArrayList statusCount) {
+ String status = "";
+ int index = -1;
+ int oldCount = 0;
+
+ if (counterMetric.getId().getName().contains("counter.status.")) {
+ status = counterMetric.getId().getName().substring(15, 18); // example 404, 200
+ appendStatusIfNotExist(status, statusCount);
+ index = statusList.indexOf(status);
+ oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index);
+ statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount);
+ }
+ }
+
+ private void appendStatusIfNotExist(final String status, final ArrayList statusCount) {
+ if (!statusList.contains(status)) {
+ statusList.add(status);
+ statusCount.add(0);
+ }
+ }
+
+ //
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/CustomActuatorMetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/CustomActuatorMetricService.java
new file mode 100644
index 0000000000..cf30686e52
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/CustomActuatorMetricService.java
@@ -0,0 +1,92 @@
+package org.baeldung.web.metric;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.search.Search;
+
+@Service
+public class CustomActuatorMetricService implements ICustomActuatorMetricService {
+
+ @Autowired
+ private MeterRegistry registry;
+
+ private final List> statusMetricsByMinute;
+ private final List statusList;
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ public CustomActuatorMetricService() {
+ super();
+ statusMetricsByMinute = new ArrayList>();
+ statusList = new ArrayList();
+ }
+
+ // API
+
+ @Override
+ public void increaseCount(final int status) {
+ String counterName = "counter.status." + status;
+ registry.counter(counterName).increment(1);
+ if (!statusList.contains(counterName)) {
+ statusList.add(counterName);
+ }
+ }
+
+ @Override
+ public Object[][] getGraphData() {
+ final Date current = new Date();
+ final int colCount = statusList.size() + 1;
+ final int rowCount = statusMetricsByMinute.size() + 1;
+ final Object[][] result = new Object[rowCount][colCount];
+ result[0][0] = "Time";
+
+ int j = 1;
+ for (final String status : statusList) {
+ result[0][j] = status;
+ j++;
+ }
+
+ for (int i = 1; i < rowCount; i++) {
+ result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
+ }
+
+ List minuteOfStatuses;
+ for (int i = 1; i < rowCount; i++) {
+ minuteOfStatuses = statusMetricsByMinute.get(i - 1);
+ for (j = 1; j <= minuteOfStatuses.size(); j++) {
+ result[i][j] = minuteOfStatuses.get(j - 1);
+ }
+ while (j < colCount) {
+ result[i][j] = 0;
+ j++;
+ }
+ }
+ return result;
+ }
+
+ // Non - API
+
+ @Scheduled(fixedDelay = 60000)
+ private void exportMetrics() {
+ final ArrayList statusCount = new ArrayList();
+ for (final String status : statusList) {
+ Search search = registry.find(status);
+ if (search != null) {
+ Counter counter = search.counter();
+ statusCount.add(counter != null ? ((int) counter.count()) : 0);
+ registry.remove(counter);
+ } else {
+ statusCount.add(0);
+ }
+ }
+ statusMetricsByMinute.add(statusCount);
+ }
+}
\ No newline at end of file
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/IActuatorMetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/IActuatorMetricService.java
new file mode 100644
index 0000000000..191d347070
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/IActuatorMetricService.java
@@ -0,0 +1,5 @@
+package org.baeldung.web.metric;
+
+public interface IActuatorMetricService {
+ Object[][] getGraphData();
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/ICustomActuatorMetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/ICustomActuatorMetricService.java
new file mode 100644
index 0000000000..19ab7164ac
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/ICustomActuatorMetricService.java
@@ -0,0 +1,8 @@
+package org.baeldung.web.metric;
+
+public interface ICustomActuatorMetricService {
+
+ void increaseCount(final int status);
+
+ Object[][] getGraphData();
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/IMetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/IMetricService.java
new file mode 100644
index 0000000000..902d6ac811
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/IMetricService.java
@@ -0,0 +1,14 @@
+package org.baeldung.web.metric;
+
+import java.util.Map;
+
+public interface IMetricService {
+
+ void increaseCount(final String request, final int status);
+
+ Map getFullMetric();
+
+ Map getStatusMetric();
+
+ Object[][] getGraphData();
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricFilter.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricFilter.java
new file mode 100644
index 0000000000..6c2fb0cb39
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricFilter.java
@@ -0,0 +1,49 @@
+package org.baeldung.web.metric;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+@Component
+public class MetricFilter implements Filter {
+
+ @Autowired
+ private IMetricService metricService;
+
+ @Autowired
+ private ICustomActuatorMetricService actMetricService;
+
+ @Override
+ public void init(final FilterConfig config) throws ServletException {
+ if (metricService == null || actMetricService == null) {
+ metricService = (IMetricService) WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean("metricService");
+ actMetricService = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean(CustomActuatorMetricService.class);
+ }
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws java.io.IOException, ServletException {
+ final HttpServletRequest httpRequest = ((HttpServletRequest) request);
+ final String req = httpRequest.getMethod() + " " + httpRequest.getRequestURI();
+
+ chain.doFilter(request, response);
+
+ final int status = ((HttpServletResponse) response).getStatus();
+ metricService.increaseCount(req, status);
+ actMetricService.increaseCount(status);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricService.java b/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricService.java
new file mode 100644
index 0000000000..086068ad8f
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/metric/MetricService.java
@@ -0,0 +1,122 @@
+package org.baeldung.web.metric;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class MetricService implements IMetricService {
+
+ private ConcurrentMap> metricMap;
+ private ConcurrentMap statusMetric;
+ private ConcurrentMap> timeMap;
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ public MetricService() {
+ super();
+ metricMap = new ConcurrentHashMap>();
+ statusMetric = new ConcurrentHashMap();
+ timeMap = new ConcurrentHashMap>();
+ }
+
+ // API
+
+ @Override
+ public void increaseCount(final String request, final int status) {
+ increaseMainMetric(request, status);
+ increaseStatusMetric(status);
+ updateTimeMap(status);
+ }
+
+ @Override
+ public Map getFullMetric() {
+ return metricMap;
+ }
+
+ @Override
+ public Map getStatusMetric() {
+ return statusMetric;
+ }
+
+ @Override
+ public Object[][] getGraphData() {
+ final int colCount = statusMetric.keySet().size() + 1;
+ final Set allStatus = statusMetric.keySet();
+ final int rowCount = timeMap.keySet().size() + 1;
+
+ final Object[][] result = new Object[rowCount][colCount];
+ result[0][0] = "Time";
+
+ int j = 1;
+ for (final int status : allStatus) {
+ result[0][j] = status;
+ j++;
+ }
+ int i = 1;
+ ConcurrentMap tempMap;
+ for (final Entry> entry : timeMap.entrySet()) {
+ result[i][0] = entry.getKey();
+ tempMap = entry.getValue();
+ for (j = 1; j < colCount; j++) {
+ result[i][j] = tempMap.get(result[0][j]);
+ if (result[i][j] == null) {
+ result[i][j] = 0;
+ }
+ }
+ i++;
+ }
+
+ return result;
+ }
+
+ // NON-API
+
+ private void increaseMainMetric(final String request, final int status) {
+ ConcurrentHashMap statusMap = metricMap.get(request);
+ if (statusMap == null) {
+ statusMap = new ConcurrentHashMap();
+ }
+
+ Integer count = statusMap.get(status);
+ if (count == null) {
+ count = 1;
+ } else {
+ count++;
+ }
+ statusMap.put(status, count);
+ metricMap.put(request, statusMap);
+ }
+
+ private void increaseStatusMetric(final int status) {
+ final Integer statusCount = statusMetric.get(status);
+ if (statusCount == null) {
+ statusMetric.put(status, 1);
+ } else {
+ statusMetric.put(status, statusCount + 1);
+ }
+ }
+
+ private void updateTimeMap(final int status) {
+ final String time = dateFormat.format(new Date());
+ ConcurrentHashMap statusMap = timeMap.get(time);
+ if (statusMap == null) {
+ statusMap = new ConcurrentHashMap();
+ }
+
+ Integer count = statusMap.get(status);
+ if (count == null) {
+ count = 1;
+ } else {
+ count++;
+ }
+ statusMap.put(status, count);
+ timeMap.put(time, statusMap);
+ }
+
+}
diff --git a/spring-rest-full/src/main/java/org/baeldung/web/util/RestPreconditions.java b/spring-rest-full/src/main/java/org/baeldung/web/util/RestPreconditions.java
new file mode 100644
index 0000000000..4f2dedcfa0
--- /dev/null
+++ b/spring-rest-full/src/main/java/org/baeldung/web/util/RestPreconditions.java
@@ -0,0 +1,48 @@
+package org.baeldung.web.util;
+
+import org.springframework.http.HttpStatus;
+
+import org.baeldung.web.exception.MyResourceNotFoundException;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify correct arguments and state. If the Precondition fails, an {@link HttpStatus} code is thrown
+ */
+public final class RestPreconditions {
+
+ private RestPreconditions() {
+ throw new AssertionError();
+ }
+
+ // API
+
+ /**
+ * Check if some value was found, otherwise throw exception.
+ *
+ * @param expression
+ * has value true if found, otherwise false
+ * @throws MyResourceNotFoundException
+ * if expression is false, means value not found.
+ */
+ public static void checkFound(final boolean expression) {
+ if (!expression) {
+ throw new MyResourceNotFoundException();
+ }
+ }
+
+ /**
+ * Check if some value was found, otherwise throw exception.
+ *
+ * @param resource
+ * has value not null to be returned, otherwise throw exception
+ * @throws MyResourceNotFoundException
+ * if resource is null, means value not found.
+ */
+ public static T checkFound(final T resource) {
+ if (resource == null) {
+ throw new MyResourceNotFoundException();
+ }
+
+ return resource;
+ }
+
+}
diff --git a/spring-rest-full/src/main/resources/application.properties b/spring-rest-full/src/main/resources/application.properties
new file mode 100644
index 0000000000..52d93b4cff
--- /dev/null
+++ b/spring-rest-full/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+server.port=8082
+server.servlet.context-path=/spring-rest-full
+endpoints.metrics.enabled=true
\ No newline at end of file
diff --git a/spring-rest-full/src/main/resources/logback.xml b/spring-rest-full/src/main/resources/logback.xml
new file mode 100644
index 0000000000..56af2d397e
--- /dev/null
+++ b/spring-rest-full/src/main/resources/logback.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/resources/persistence-h2.properties b/spring-rest-full/src/main/resources/persistence-h2.properties
new file mode 100644
index 0000000000..839a466533
--- /dev/null
+++ b/spring-rest-full/src/main/resources/persistence-h2.properties
@@ -0,0 +1,22 @@
+## jdbc.X
+#jdbc.driverClassName=com.mysql.jdbc.Driver
+#jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true
+#jdbc.user=tutorialuser
+#jdbc.pass=tutorialmy5ql
+#
+## hibernate.X
+#hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
+#hibernate.show_sql=false
+#hibernate.hbm2ddl.auto=create-drop
+
+
+# jdbc.X
+jdbc.driverClassName=org.h2.Driver
+jdbc.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+jdbc.user=sa
+jdbc.pass=
+
+# hibernate.X
+hibernate.dialect=org.hibernate.dialect.H2Dialect
+hibernate.show_sql=false
+hibernate.hbm2ddl.auto=create-drop
diff --git a/spring-rest-full/src/main/resources/persistence-mysql.properties b/spring-rest-full/src/main/resources/persistence-mysql.properties
new file mode 100644
index 0000000000..8263b0d9ac
--- /dev/null
+++ b/spring-rest-full/src/main/resources/persistence-mysql.properties
@@ -0,0 +1,10 @@
+# jdbc.X
+jdbc.driverClassName=com.mysql.jdbc.Driver
+jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true
+jdbc.user=tutorialuser
+jdbc.pass=tutorialmy5ql
+
+# hibernate.X
+hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
+hibernate.show_sql=false
+hibernate.hbm2ddl.auto=create-drop
diff --git a/spring-rest-full/src/main/resources/springDataPersistenceConfig.xml b/spring-rest-full/src/main/resources/springDataPersistenceConfig.xml
new file mode 100644
index 0000000000..d6d0ec6e47
--- /dev/null
+++ b/spring-rest-full/src/main/resources/springDataPersistenceConfig.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/webapp/WEB-INF/api-servlet.xml b/spring-rest-full/src/main/webapp/WEB-INF/api-servlet.xml
new file mode 100644
index 0000000000..4ba9642448
--- /dev/null
+++ b/spring-rest-full/src/main/webapp/WEB-INF/api-servlet.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/webapp/WEB-INF/view/graph.jsp b/spring-rest-full/src/main/webapp/WEB-INF/view/graph.jsp
new file mode 100644
index 0000000000..e1d5fdc987
--- /dev/null
+++ b/spring-rest-full/src/main/webapp/WEB-INF/view/graph.jsp
@@ -0,0 +1,43 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+
+
+Metric Graph
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/webapp/WEB-INF/view/homepage.jsp b/spring-rest-full/src/main/webapp/WEB-INF/view/homepage.jsp
new file mode 100644
index 0000000000..7cc14b5dcd
--- /dev/null
+++ b/spring-rest-full/src/main/webapp/WEB-INF/view/homepage.jsp
@@ -0,0 +1,7 @@
+
+
+
+
+ This is the body of the sample view
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/main/webapp/WEB-INF/web.xml b/spring-rest-full/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..5f90c3519f
--- /dev/null
+++ b/spring-rest-full/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,52 @@
+
+
+
+ Spring REST Application
+
+
+
+ contextClass
+
+ org.springframework.web.context.support.AnnotationConfigWebApplicationContext
+
+
+
+ contextConfigLocation
+ org.baeldung.spring
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+
+ api
+ org.springframework.web.servlet.DispatcherServlet
+ 1
+
+
+ api
+ /
+
+
+
+
+ metricFilter
+ org.baeldung.web.metric.MetricFilter
+
+
+
+ metricFilter
+ /*
+
+
+
+ index.html
+
+
+
\ No newline at end of file
diff --git a/spring-rest-full/src/test/java/org/baeldung/Consts.java b/spring-rest-full/src/test/java/org/baeldung/Consts.java
new file mode 100644
index 0000000000..e5f0be160f
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/Consts.java
@@ -0,0 +1,5 @@
+package org.baeldung;
+
+public interface Consts {
+ int APPLICATION_PORT = 8082;
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/SpringContextIntegrationTest.java b/spring-rest-full/src/test/java/org/baeldung/SpringContextIntegrationTest.java
new file mode 100644
index 0000000000..35939c992f
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/SpringContextIntegrationTest.java
@@ -0,0 +1,16 @@
+package org.baeldung;
+
+import org.baeldung.spring.Application;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public class SpringContextIntegrationTest {
+
+ @Test
+ public void whenSpringContextIsBootstrapped_thenNoExceptions() {
+ }
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/SpringContextTest.java b/spring-rest-full/src/test/java/org/baeldung/SpringContextTest.java
new file mode 100644
index 0000000000..8debddc40a
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/SpringContextTest.java
@@ -0,0 +1,16 @@
+package org.baeldung;
+
+import org.baeldung.spring.Application;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public class SpringContextTest {
+
+ @Test
+ public void whenSpringContextIsBootstrapped_thenNoExceptions() {
+ }
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/persistence/PersistenceTestSuite.java b/spring-rest-full/src/test/java/org/baeldung/persistence/PersistenceTestSuite.java
new file mode 100644
index 0000000000..fb0fd00bb5
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/persistence/PersistenceTestSuite.java
@@ -0,0 +1,16 @@
+package org.baeldung.persistence;
+
+import org.baeldung.persistence.service.FooServicePersistenceIntegrationTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ // @formatter:off
+
+ FooServicePersistenceIntegrationTest.class
+
+}) //
+public class PersistenceTestSuite {
+
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/persistence/service/AbstractServicePersistenceIntegrationTest.java b/spring-rest-full/src/test/java/org/baeldung/persistence/service/AbstractServicePersistenceIntegrationTest.java
new file mode 100644
index 0000000000..79889e0f9e
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/persistence/service/AbstractServicePersistenceIntegrationTest.java
@@ -0,0 +1,255 @@
+package org.baeldung.persistence.service;
+
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.baeldung.persistence.IOperations;
+import org.baeldung.persistence.model.Foo;
+import org.baeldung.util.IDUtil;
+import org.hamcrest.Matchers;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.springframework.dao.DataAccessException;
+
+public abstract class AbstractServicePersistenceIntegrationTest {
+
+ // tests
+
+ // find - one
+
+ @Test
+ /**/public final void givenResourceDoesNotExist_whenResourceIsRetrieved_thenNoResourceIsReceived() {
+ // When
+ final Foo createdResource = getApi().findOne(IDUtil.randomPositiveLong());
+
+ // Then
+ assertNull(createdResource);
+ }
+
+ @Test
+ public void givenResourceExists_whenResourceIsRetrieved_thenNoExceptions() {
+ final Foo existingResource = persistNewEntity();
+ getApi().findOne(existingResource.getId());
+ }
+
+ @Test
+ public void givenResourceDoesNotExist_whenResourceIsRetrieved_thenNoExceptions() {
+ getApi().findOne(IDUtil.randomPositiveLong());
+ }
+
+ @Test
+ public void givenResourceExists_whenResourceIsRetrieved_thenTheResultIsNotNull() {
+ final Foo existingResource = persistNewEntity();
+ final Foo retrievedResource = getApi().findOne(existingResource.getId());
+ assertNotNull(retrievedResource);
+ }
+
+ @Test
+ public void givenResourceExists_whenResourceIsRetrieved_thenResourceIsRetrievedCorrectly() {
+ final Foo existingResource = persistNewEntity();
+ final Foo retrievedResource = getApi().findOne(existingResource.getId());
+ assertEquals(existingResource, retrievedResource);
+ }
+
+ // find - one - by name
+
+ // find - all
+
+ @Test
+ /**/public void whenAllResourcesAreRetrieved_thenNoExceptions() {
+ getApi().findAll();
+ }
+
+ @Test
+ /**/public void whenAllResourcesAreRetrieved_thenTheResultIsNotNull() {
+ final List resources = getApi().findAll();
+
+ assertNotNull(resources);
+ }
+
+ @Test
+ /**/public void givenAtLeastOneResourceExists_whenAllResourcesAreRetrieved_thenRetrievedResourcesAreNotEmpty() {
+ persistNewEntity();
+
+ // When
+ final List allResources = getApi().findAll();
+
+ // Then
+ assertThat(allResources, not(Matchers. empty()));
+ }
+
+ @Test
+ /**/public void givenAnResourceExists_whenAllResourcesAreRetrieved_thenTheExistingResourceIsIndeedAmongThem() {
+ final Foo existingResource = persistNewEntity();
+
+ final List resources = getApi().findAll();
+
+ assertThat(resources, hasItem(existingResource));
+ }
+
+ @Test
+ /**/public void whenAllResourcesAreRetrieved_thenResourcesHaveIds() {
+ persistNewEntity();
+
+ // When
+ final List allResources = getApi().findAll();
+
+ // Then
+ for (final Foo resource : allResources) {
+ assertNotNull(resource.getId());
+ }
+ }
+
+ // create
+
+ @Test(expected = RuntimeException.class)
+ /**/public void whenNullResourceIsCreated_thenException() {
+ getApi().create(null);
+ }
+
+ @Test
+ /**/public void whenResourceIsCreated_thenNoExceptions() {
+ persistNewEntity();
+ }
+
+ @Test
+ /**/public void whenResourceIsCreated_thenResourceIsRetrievable() {
+ final Foo existingResource = persistNewEntity();
+
+ assertNotNull(getApi().findOne(existingResource.getId()));
+ }
+
+ @Test
+ /**/public void whenResourceIsCreated_thenSavedResourceIsEqualToOriginalResource() {
+ final Foo originalResource = createNewEntity();
+ final Foo savedResource = getApi().create(originalResource);
+
+ assertEquals(originalResource, savedResource);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void whenResourceWithFailedConstraintsIsCreated_thenException() {
+ final Foo invalidResource = createNewEntity();
+ invalidate(invalidResource);
+
+ getApi().create(invalidResource);
+ }
+
+ /**
+ * -- specific to the persistence engine
+ */
+ @Test(expected = DataAccessException.class)
+ @Ignore("Hibernate simply ignores the id silently and still saved (tracking this)")
+ public void whenResourceWithIdIsCreated_thenDataAccessException() {
+ final Foo resourceWithId = createNewEntity();
+ resourceWithId.setId(IDUtil.randomPositiveLong());
+
+ getApi().create(resourceWithId);
+ }
+
+ // update
+
+ @Test(expected = RuntimeException.class)
+ /**/public void whenNullResourceIsUpdated_thenException() {
+ getApi().update(null);
+ }
+
+ @Test
+ /**/public void givenResourceExists_whenResourceIsUpdated_thenNoExceptions() {
+ // Given
+ final Foo existingResource = persistNewEntity();
+
+ // When
+ getApi().update(existingResource);
+ }
+
+ /**
+ * - can also be the ConstraintViolationException which now occurs on the update operation will not be translated; as a consequence, it will be a TransactionSystemException
+ */
+ @Test(expected = RuntimeException.class)
+ public void whenResourceIsUpdatedWithFailedConstraints_thenException() {
+ final Foo existingResource = persistNewEntity();
+ invalidate(existingResource);
+
+ getApi().update(existingResource);
+ }
+
+ @Test
+ /**/public void givenResourceExists_whenResourceIsUpdated_thenUpdatesArePersisted() {
+ // Given
+ final Foo existingResource = persistNewEntity();
+
+ // When
+ change(existingResource);
+ getApi().update(existingResource);
+
+ final Foo updatedResource = getApi().findOne(existingResource.getId());
+
+ // Then
+ assertEquals(existingResource, updatedResource);
+ }
+
+ // delete
+
+ // @Test(expected = RuntimeException.class)
+ // public void givenResourceDoesNotExists_whenResourceIsDeleted_thenException() {
+ // // When
+ // getApi().delete(IDUtil.randomPositiveLong());
+ // }
+ //
+ // @Test(expected = RuntimeException.class)
+ // public void whenResourceIsDeletedByNegativeId_thenException() {
+ // // When
+ // getApi().delete(IDUtil.randomNegativeLong());
+ // }
+ //
+ // @Test
+ // public void givenResourceExists_whenResourceIsDeleted_thenNoExceptions() {
+ // // Given
+ // final Foo existingResource = persistNewEntity();
+ //
+ // // When
+ // getApi().delete(existingResource.getId());
+ // }
+ //
+ // @Test
+ // /**/public final void givenResourceExists_whenResourceIsDeleted_thenResourceNoLongerExists() {
+ // // Given
+ // final Foo existingResource = persistNewEntity();
+ //
+ // // When
+ // getApi().delete(existingResource.getId());
+ //
+ // // Then
+ // assertNull(getApi().findOne(existingResource.getId()));
+ // }
+
+ // template method
+
+ protected Foo createNewEntity() {
+ return new Foo(randomAlphabetic(6));
+ }
+
+ protected abstract IOperations getApi();
+
+ private final void invalidate(final Foo entity) {
+ entity.setName(null);
+ }
+
+ private final void change(final Foo entity) {
+ entity.setName(randomAlphabetic(6));
+ }
+
+ protected Foo persistNewEntity() {
+ return getApi().create(createNewEntity());
+ }
+
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/persistence/service/FooServicePersistenceIntegrationTest.java b/spring-rest-full/src/test/java/org/baeldung/persistence/service/FooServicePersistenceIntegrationTest.java
new file mode 100644
index 0000000000..089d2d13a2
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/persistence/service/FooServicePersistenceIntegrationTest.java
@@ -0,0 +1,76 @@
+package org.baeldung.persistence.service;
+
+import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
+import static org.junit.Assert.assertNotNull;
+
+import org.baeldung.persistence.IOperations;
+import org.baeldung.persistence.model.Foo;
+import org.baeldung.spring.PersistenceConfig;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class)
+public class FooServicePersistenceIntegrationTest extends AbstractServicePersistenceIntegrationTest {
+
+ @Autowired
+ private IFooService service;
+
+ // tests
+
+ @Test
+ public final void whenContextIsBootstrapped_thenNoExceptions() {
+ //
+ }
+
+ @Test
+ public final void whenEntityIsCreated_thenNoExceptions() {
+ service.create(new Foo(randomAlphabetic(6)));
+ }
+
+ @Test(expected = DataIntegrityViolationException.class)
+ public final void whenInvalidEntityIsCreated_thenDataException() {
+ service.create(new Foo());
+ }
+
+ @Test(expected = DataIntegrityViolationException.class)
+ public final void whenEntityWithLongNameIsCreated_thenDataException() {
+ service.create(new Foo(randomAlphabetic(2048)));
+ }
+
+ // custom Query method
+
+ @Test
+ public final void givenUsingCustomQuery_whenRetrievingEntity_thenFound() {
+ final String name = randomAlphabetic(6);
+ service.create(new Foo(name));
+
+ final Foo retrievedByName = service.retrieveByName(name);
+ assertNotNull(retrievedByName);
+ }
+
+ // work in progress
+
+ @Test(expected = InvalidDataAccessApiUsageException.class)
+ @Ignore("Right now, persist has saveOrUpdate semantics, so this will no longer fail")
+ public final void whenSameEntityIsCreatedTwice_thenDataException() {
+ final Foo entity = new Foo(randomAlphabetic(8));
+ service.create(entity);
+ service.create(entity);
+ }
+
+ // API
+
+ @Override
+ protected final IOperations getApi() {
+ return service;
+ }
+
+}
diff --git a/spring-rest-full/src/test/java/org/baeldung/util/IDUtil.java b/spring-rest-full/src/test/java/org/baeldung/util/IDUtil.java
new file mode 100644
index 0000000000..85ab623e5f
--- /dev/null
+++ b/spring-rest-full/src/test/java/org/baeldung/util/IDUtil.java
@@ -0,0 +1,33 @@
+package org.baeldung.util;
+
+import java.util.Random;
+
+public final class IDUtil {
+
+ private IDUtil() {
+ throw new AssertionError();
+ }
+
+ // API
+
+ public static String randomPositiveLongAsString() {
+ return Long.toString(randomPositiveLong());
+ }
+
+ public static String randomNegativeLongAsString() {
+ return Long.toString(randomNegativeLong());
+ }
+
+ public static long randomPositiveLong() {
+ long id = new Random().nextLong() * 10000;
+ id = (id < 0) ? (-1 * id) : id;
+ return id;
+ }
+
+ private static long randomNegativeLong() {
+ long id = new Random().nextLong() * 10000;
+ id = (id > 0) ? (-1 * id) : id;
+ return id;
+ }
+
+}
diff --git a/spring-rest-full/src/test/resources/.gitignore b/spring-rest-full/src/test/resources/.gitignore
new file mode 100644
index 0000000000..83c05e60c8
--- /dev/null
+++ b/spring-rest-full/src/test/resources/.gitignore
@@ -0,0 +1,13 @@
+*.class
+
+#folders#
+/target
+/neoDb*
+/data
+/src/main/webapp/WEB-INF/classes
+*/META-INF/*
+
+# Packaged files #
+*.jar
+*.war
+*.ear
\ No newline at end of file
diff --git a/spring-rest-full/src/testFile b/spring-rest-full/src/testFile
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spring-rest-full/src/testFile
@@ -0,0 +1 @@
+