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 @@ +