Merge branch 'eugenp:master' into master

This commit is contained in:
JoannaaKL
2022-03-14 21:02:32 +01:00
committed by GitHub
719 changed files with 19190 additions and 2266 deletions
+2 -1
View File
@@ -36,7 +36,6 @@
<module>spring-boot-deployment</module>
<module>spring-boot-di</module>
<module>spring-boot-disable-logging</module>
<module>spring-boot-camel</module>
<module>spring-boot-ci-cd</module>
<!-- <module>spring-boot-cli</module> --> <!-- Not a maven project -->
<module>spring-boot-custom-starter</module>
@@ -51,7 +50,9 @@
<!-- <module>spring-boot-keycloak</module> --> <!-- Fixing under JAVA-8271 -->
<module>spring-boot-libraries</module>
<module>spring-boot-libraries-2</module>
<module>spring-boot-libraries-comparison</module>
<module>spring-boot-logging-log4j2</module>
<module>spring-boot-multiple-datasources</module>
<module>spring-boot-mvc</module>
<module>spring-boot-mvc-2</module>
<module>spring-boot-mvc-3</module>
+1 -1
View File
@@ -68,7 +68,7 @@
</build>
<properties>
<log4j2.version>2.14.1</log4j2.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>
+2 -2
View File
@@ -62,8 +62,8 @@
</build>
<properties>
<log4j2.version>2.14.1</log4j2.version>
<spring-core.version>5.3.13</spring-core.version>
<log4j2.version>2.17.1</log4j2.version>
<spring-core.version>5.3.15</spring-core.version>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
@@ -12,3 +12,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring
- [Health Indicators in Spring Boot](https://www.baeldung.com/spring-boot-health-indicators)
- [How to Enable All Endpoints in Spring Boot Actuator](https://www.baeldung.com/spring-boot-actuator-enable-endpoints)
- [Spring Boot Startup Actuator Endpoint](https://www.baeldung.com/spring-boot-actuator-startup)
- [Metrics for your Spring REST API](https://www.baeldung.com/spring-rest-api-metrics)
@@ -23,6 +23,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -35,6 +39,16 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -51,6 +65,10 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
</dependencies>
<build>
@@ -0,0 +1,42 @@
package com.baeldung.metrics;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
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;
import javax.servlet.ServletContext;
@EnableScheduling
@ComponentScan("com.baeldung.metrics")
@SpringBootApplication
public class MetricsApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MetricsApplication.class);
}
@Override
public void onStartup(ServletContext sc) {
// Manages the lifecycle of the root application context
sc.addListener(new RequestContextListener());
}
public static void main(final String[] args) {
// only load properties for this application
System.setProperty("spring.config.location", "classpath:application-metrics.properties");
SpringApplication.run(MetricsApplication.class, args);
}
}
@@ -0,0 +1,31 @@
package com.baeldung.metrics;
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("com.baeldung.metrics")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
registry.addViewController("/metrics/graph.html");
registry.addViewController("/metrics/homepage.html");
}
}
@@ -0,0 +1,41 @@
package com.baeldung.metrics.controller;
import com.baeldung.metrics.service.InMemoryMetricService;
import com.baeldung.metrics.service.MetricService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@Controller
@RequestMapping(value = "/metrics")
@ResponseBody
public class MetricsController {
@Autowired
private InMemoryMetricService metricService;
// change the qualifier to use the in-memory implementation
@Autowired
@Qualifier("customActuatorMetricService")
private MetricService graphMetricService;
@GetMapping(value = "/metric")
public Map<String, Map<Integer, Integer>> getMetric() {
return metricService.getFullMetric();
}
@GetMapping(value = "/status-metric")
public Map<Integer, Integer> getStatusMetric() {
return metricService.getStatusMetric();
}
@GetMapping(value = "/metric-graph-data")
public Object[][] getMetricData() {
return graphMetricService.getGraphData();
}
}
@@ -0,0 +1,51 @@
package com.baeldung.metrics.filter;
import com.baeldung.metrics.service.CustomActuatorMetricService;
import com.baeldung.metrics.service.InMemoryMetricService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
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;
@Component
public class MetricFilter implements Filter {
@Autowired
private InMemoryMetricService metricService;
@Autowired
private CustomActuatorMetricService actMetricService;
@Override
public void init(final FilterConfig config) {
if (metricService == null || actMetricService == null) {
WebApplicationContext appContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(config.getServletContext());
metricService = appContext.getBean(InMemoryMetricService.class);
actMetricService = appContext.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);
}
}
@@ -0,0 +1,105 @@
package com.baeldung.metrics.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Service
public class ActuatorMetricService implements MetricService {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Autowired
private MeterRegistry publicMetrics;
private final List<List<Integer>> statusMetricsByMinute;
private final List<String> statusList;
public ActuatorMetricService() {
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] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i))));
}
List<Integer> minuteOfStatuses;
List<Integer> 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;
}
@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}")
private void exportMetrics() {
final List<Integer> lastMinuteStatuses = initializeStatuses(statusList.size());
for (final Meter counterMetric : publicMetrics.getMeters()) {
updateMetrics(counterMetric, lastMinuteStatuses);
}
statusMetricsByMinute.add(lastMinuteStatuses);
}
private List<Integer> initializeStatuses(int size) {
List<Integer> counterList = new ArrayList<>();
for (int i = 0; i < size; i++) {
counterList.add(0);
}
return counterList;
}
private void updateMetrics(Meter counterMetric, List<Integer> statusCount) {
String metricName = counterMetric.getId().getName();
if (metricName.contains("counter.status.")) {
// example 404, 200
String status = metricName.substring(15, 18);
appendStatusIfNotExist(status, statusCount);
int index = statusList.indexOf(status);
int oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index);
statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount);
}
}
private void appendStatusIfNotExist(String status, List<Integer> statusCount) {
if (!statusList.contains(status)) {
statusList.add(status);
statusCount.add(0);
}
}
}
@@ -0,0 +1,86 @@
package com.baeldung.metrics.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.search.Search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Service
public class CustomActuatorMetricService implements MetricService {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Autowired
private MeterRegistry registry;
private final List<List<Integer>> statusMetricsByMinute;
private final List<String> statusList;
public CustomActuatorMetricService() {
statusMetricsByMinute = new ArrayList<>();
statusList = new ArrayList<>();
}
public void increaseCount(int status) {
String counterName = "counter.status." + status;
registry.counter(counterName).increment();
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] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i))));
}
List<Integer> 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;
}
@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}")
private void exportMetrics() {
List<Integer> statusCount = new ArrayList<>();
for (final String status : statusList) {
Search search = registry.find(status);
Counter counter = search.counter();
if (counter == null) {
statusCount.add(0);
} else {
statusCount.add((int) counter.count());
registry.remove(counter);
}
}
statusMetricsByMinute.add(statusCount);
}
}
@@ -0,0 +1,112 @@
package com.baeldung.metrics.service;
import org.springframework.stereotype.Service;
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;
@Service
public class InMemoryMetricService implements MetricService {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private final Map<String, Map<Integer, Integer>> metricMap;
private final Map<Integer, Integer> statusMetric;
private final Map<String, Map<Integer, Integer>> timeMap;
public InMemoryMetricService() {
metricMap = new ConcurrentHashMap<>();
statusMetric = new ConcurrentHashMap<>();
timeMap = new ConcurrentHashMap<>();
}
public void increaseCount(String request, int status) {
increaseMainMetric(request, status);
increaseStatusMetric(status);
updateTimeMap(status);
}
public Map<String, Map<Integer, Integer>> getFullMetric() {
return metricMap;
}
public Map<Integer, Integer> getStatusMetric() {
return statusMetric;
}
public Object[][] getGraphData() {
final int colCount = statusMetric.keySet().size() + 1;
final Set<Integer> 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;
Map<Integer, Integer> tempMap;
for (final Entry<String, Map<Integer, Integer>> entry : timeMap.entrySet()) {
result[i][0] = entry.getKey();
tempMap = entry.getValue();
for (j = 1; j < colCount; j++) {
result[i][j] = tempMap.get((Integer) result[0][j]);
if (result[i][j] == null) {
result[i][j] = 0;
}
}
i++;
}
for (int k = 1; k < result[0].length; k++) {
result[0][k] = result[0][k].toString();
}
return result;
}
private void increaseMainMetric(String request, int status) {
Map<Integer, Integer> 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(int status) {
statusMetric.merge(status, 1, Integer::sum);
}
private void updateTimeMap(int status) {
final String time = DATE_FORMAT.format(new Date());
Map<Integer, Integer> 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);
}
}
@@ -0,0 +1,7 @@
package com.baeldung.metrics.service;
public interface MetricService {
Object[][] getGraphData();
}
@@ -0,0 +1,9 @@
management.endpoints.web.exposure.include=info,health,metrics
# JPA and Security is not required for Metrics application
spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, \
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, \
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, \
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, \
org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, \
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd" >
</beans>
@@ -0,0 +1,43 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Metric Graph</title>
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {
packages : [ "corechart" ]
});
function drawChart() {
$.get("<c:url value="/metrics/metric-graph-data"/>",
function(mydata) {
var data = google.visualization.arrayToDataTable(mydata);
var options = {
title : 'Website Metric',
hAxis : {
title : 'Time',
titleTextStyle : {
color : '#333'
}
},
vAxis : {
minValue : 0
}
};
var chart = new google.visualization.AreaChart(document
.getElementById('chart_div'));
chart.draw(data, options);
});
}
</script>
</head>
<body onload="drawChart()">
<div id="chart_div" style="width: 900px; height: 500px;"></div>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<head></head>
<body>
<h1>This is the body of the sample view</h1>
</body>
</html>
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"
>
<display-name>Spring REST Application</display-name>
<!-- Spring root -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.baeldung.metrics</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring child -->
<servlet>
<servlet-name>api</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>api</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Metric filter -->
<filter>
<filter-name>metricFilter</filter-name>
<filter-class>com.baeldung.metrics.filter.MetricFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>metricFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
@@ -0,0 +1,64 @@
package com.baeldung.metrics;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.ActiveProfiles;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(
classes = MetricsApplication.class,
webEnvironment = RANDOM_PORT,
properties = {"fixedDelay.in.milliseconds=2000"}
)
@ActiveProfiles("metrics")
class MetricsApplicationIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void givenStatuses_WhenScheduledMethodExecuted_ExpectCountsAreAggregated() {
restTemplate.getForObject("/metrics/metric/notFound", String.class);
restTemplate.getForObject("/metrics/metric", String.class);
await().untilAsserted(() -> {
Object[][] statusCounts = restTemplate.getForObject("/metrics/metric-graph-data", Object[][].class);
assertThat(statusCounts[0]).contains("counter.status.200", "counter.status.404");
List<Integer> requestCounts = getRequestCounts(statusCounts);
verify404RequestFrom(requestCounts);
verify200RequestsFrom(requestCounts);
});
}
private static void verify200RequestsFrom(List<Integer> requestCounts) {
assertThat(requestCounts.size()).isGreaterThan(1);
}
private static void verify404RequestFrom(List<Integer> requestCounts) {
assertThat(requestCounts).contains(1);
}
private static List<Integer> getRequestCounts(Object[][] statusCounts) {
List<Integer> requestCounts = new ArrayList<>();
for (int i = 1; i < statusCounts.length; i++) {
for (int j = 1; j < statusCounts[i].length; j++) {
Integer count = (Integer) statusCounts[i][j];
if (count >= 1) {
requestCounts.add(count);
}
}
}
return requestCounts;
}
}
@@ -101,7 +101,7 @@
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.18</version>
<executions>
<!-- Invokes both the integration-test and the verify goals of the Failsafe Maven
<!-- Invokes both the integration-test and the verify goals of the Failsafe Maven
plugin -->
<execution>
<id>integration-tests</id>
@@ -110,7 +110,7 @@
<goal>verify</goal>
</goals>
<configuration>
<!-- Skips integration tests if the value of skip.integration.tests property
<!-- Skips integration tests if the value of skip.integration.tests property
is true -->
<includes>
<include>**/ExternalPropertyFileLoaderIntegrationTest.java</include>
@@ -186,7 +186,7 @@
<properties>
<!-- The main class to start by executing java -jar -->
<start-class>com.baeldung.boot.Application</start-class>
<start-class>com.baeldung.wrapper.DemoApplication</start-class>
<jquery.version>3.1.1</jquery.version>
<bootstrap.version>3.3.7-1</bootstrap.version>
<jpa.version>2.2</jpa.version>
@@ -195,4 +195,4 @@
<httpclient.version>4.5.8</httpclient.version>
</properties>
</project>
</project>
@@ -0,0 +1,12 @@
package com.baeldung.wrapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = { "com.baeldung" })
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@@ -0,0 +1,15 @@
package com.baeldung.wrapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DemoController {
@GetMapping(value = "/demo")
public String demo(Model model) {
return "index";
}
}
@@ -16,22 +16,22 @@
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-servlet-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-swagger-java-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>${camel.version}</version>
</dependency>
@@ -64,7 +64,8 @@
</build>
<properties>
<camel.version>3.0.0-M4</camel.version>
<java.version>11</java.version>
<camel.version>3.15.0</camel.version>
</properties>
</project>
@@ -1,5 +1,6 @@
### Relevant Articles:
- [HttpMessageNotWritableException: No Converter for [class …] With Preset Content-Type](https://www.baeldung.com/spring-no-converter-with-preset)
- [Spring Boot: Customize the Jackson ObjectMapper](https://www.baeldung.com/spring-boot-customize-jackson-objectmapper)
- [“HttpMessageNotWritableException: No converter found for return value of type”](https://www.baeldung.com/spring-no-converter-found)
- [Creating a Read-Only Repository with Spring Data](https://www.baeldung.com/spring-data-read-only-repository)
@@ -1,5 +1,6 @@
package com.baeldung.boot.noconverterfound.controller;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -18,4 +19,14 @@ public class StudentRestController {
return ResponseEntity.ok(new Student(id, "John", "Wiliams", "AA"));
}
@GetMapping(value = "/student/v2/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Student> getV2(@PathVariable("id") int id) {
return ResponseEntity.ok(new Student(id, "Kevin", "Cruyff", "AA"));
}
@GetMapping(value = "/student/v3/{id}", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<Student> getV3(@PathVariable("id") int id) {
return ResponseEntity.ok(new Student(id, "Robert", "Miller", "BB"));
}
}
@@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,16 +26,16 @@ public class NoConverterFoundIntegrationTest {
/* Remove Getters from Student class to successfully run this test case
* @Test
public void whenGettersNotDefined_thenThrowException() throws Exception {
String url = "/api/student/1";
this.mockMvc.perform(get(url))
.andExpect(status().isInternalServerError())
.andExpect(result -> assertThat(result.getResolvedException())
.isInstanceOf(HttpMessageNotWritableException.class))
.andExpect(result -> assertThat(result.getResolvedException().getMessage())
.contains("No converter found for return value of type"));
}
*/
@@ -44,9 +45,28 @@ public class NoConverterFoundIntegrationTest {
String url = "/api/student/2";
this.mockMvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(jsonPath("$.firstName").value("John"));
.andExpect(status().isOk())
.andExpect(jsonPath("$.firstName").value("John"));
}
@Test
public void whenJsonConverterIsFound_thenReturnResponse() throws Exception {
String url = "/api/student/v2/1";
this.mockMvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().json("{'id':1,'firstName':'Kevin','lastName':'Cruyff', 'grade':'AA'}"));
}
@Test
public void whenConverterNotFound_thenThrowException() throws Exception {
String url = "/api/student/v3/1";
this.mockMvc.perform(get(url))
.andExpect(status().isInternalServerError())
.andExpect(result -> assertThat(result.getResolvedException()).isInstanceOf(HttpMessageNotWritableException.class))
.andExpect(result -> assertThat(result.getResolvedException()
.getMessage()).contains("No converter for [class com.baeldung.boot.noconverterfound.model.Student] with preset Content-Type"));
}
}
@@ -0,0 +1,14 @@
package com.baeldung.keycloak;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KeycloakConfig {
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
@@ -23,11 +23,6 @@ class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// Specifies the session authentication strategy
@Bean
@Override
@@ -0,0 +1,7 @@
## Spring Boot Libraries
This module contains articles about various Spring Boot libraries Comparison
### Relevant Articles:
- [GraphQL vs REST](https://www.baeldung.com/graphql-vs-rest)
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-libraries-comparison</artifactId>
<parent>
<groupId>com.baeldung.spring-boot-modules</groupId>
<artifactId>spring-boot-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>${graphql-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>${graphql-java-tools.version}</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>${graphql-spring-boot-starter.version}</version>
</dependency>
</dependencies>
<properties>
<graphql-spring-boot-starter.version>5.0.2</graphql-spring-boot-starter.version>
<graphql-java-tools.version>5.2.4</graphql-java-tools.version>
</properties>
</project>
@@ -0,0 +1,19 @@
package com.baeldung.graphqlvsrest;
import com.baeldung.graphqlvsrest.configuration.GraphqlConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(GraphqlConfiguration.class)
@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
public class GraphqlVsRestApplication {
public static void main(String[] args) {
SpringApplication.run(GraphqlVsRestApplication.class, args);
}
}
@@ -0,0 +1,35 @@
package com.baeldung.graphqlvsrest.configuration;
import com.baeldung.graphqlvsrest.repository.OrderRepository;
import com.baeldung.graphqlvsrest.resolver.Mutation;
import com.baeldung.graphqlvsrest.resolver.ProductResolver;
import com.baeldung.graphqlvsrest.resolver.Query;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GraphqlConfiguration {
@Autowired
ProductRepository productRepository;
@Autowired
OrderRepository orderRepository;
@Bean
public Query query() {
return new Query(productRepository);
}
@Bean
public ProductResolver productResolver(){
return new ProductResolver(orderRepository);
}
@Bean
public Mutation mutation() {
return new Mutation(productRepository);
}
}
@@ -0,0 +1,25 @@
package com.baeldung.graphqlvsrest.controller;
import com.baeldung.graphqlvsrest.entity.Order;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import com.baeldung.graphqlvsrest.repository.OrderRepository;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
OrderRepository orderRepository;
@GetMapping()
public List<Order> getOrders(@RequestParam("product-id") Integer productId){
return orderRepository.getOrdersByProduct(productId);
}
}
@@ -0,0 +1,38 @@
package com.baeldung.graphqlvsrest.controller;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("product")
public class ProductController {
@Autowired
ProductRepository productRepository;
@GetMapping
public List<Product> getProducts(Pageable pageable){
return productRepository.getProducts(pageable.getPageSize(), pageable.getPageNumber());
}
@GetMapping("/{product-id}")
public Product getProducts(@PathVariable("product-id") Integer productId){
return productRepository.getProduct(productId);
}
@PostMapping
public Product save(@RequestBody ProductModel productModel){
return productRepository.save(productModel);
}
@PutMapping("/{product-id}")
public Product update(@PathVariable("product-id") Integer productId, @RequestBody ProductModel productModel){
return productRepository.update(productId, productModel);
}
}
@@ -0,0 +1,58 @@
package com.baeldung.graphqlvsrest.entity;
public class Order {
private Integer id;
private Integer product_id;
private String customer_uuid;
private String status;
private String address;
private String creation_date;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getProduct_id() {
return product_id;
}
public void setProduct_id(Integer product_id) {
this.product_id = product_id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCustomer_uuid() {
return customer_uuid;
}
public void setCustomer_uuid(String customer_uuid) {
this.customer_uuid = customer_uuid;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCreation_date() {
return creation_date;
}
public void setCreation_date(String creation_date) {
this.creation_date = creation_date;
}
}
@@ -0,0 +1,115 @@
package com.baeldung.graphqlvsrest.entity;
import com.baeldung.graphqlvsrest.model.ProductModel;
import java.util.List;
public class Product {
private Integer id;
private String name;
private String description;
private String status;
private String currency;
private Double price;
private List<String> image_url;
private List<String> video_url;
private Integer stock;
private Float average_rating;
public Product(Integer id, ProductModel productModel) {
this.id = id;
this.name = productModel.getName();
this.description = productModel.getDescription();
this.currency = productModel.getCurrency();
this.price = productModel.getPrice();
this.stock = productModel.getStock();
this.image_url = productModel.getImage_url();
this.video_url = productModel.getVideo_url();
this.average_rating = 0F;
this.status = productModel.getStatus();
}
public Product(){
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public List<String> getImage_url() {
return image_url;
}
public void setImage_url(List<String> image_url) {
this.image_url = image_url;
}
public List<String> getVideo_url() {
return video_url;
}
public void setVideo_url(List<String> video_url) {
this.video_url = video_url;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public Float getAverage_rating() {
return average_rating;
}
public void setAverage_rating(Float average_rating) {
this.average_rating = average_rating;
}
}
@@ -0,0 +1,92 @@
package com.baeldung.graphqlvsrest.model;
import java.util.List;
public class ProductModel {
private String name;
private String description;
private String status;
private String currency;
private Double price;
private List<String> image_url;
private List<String> video_url;
private Integer stock;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public List<String> getImage_url() {
return image_url;
}
public void setImage_url(List<String> image_url) {
this.image_url = image_url;
}
public List<String> getVideo_url() {
return video_url;
}
public void setVideo_url(List<String> video_url) {
this.video_url = video_url;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
@Override
public String toString() {
return "ProductModel{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", status='" + status + '\'' +
", currency='" + currency + '\'' +
", price=" + price +
", image_url=" + image_url +
", video_url=" + video_url +
", stock=" + stock +
'}';
}
}
@@ -0,0 +1,11 @@
package com.baeldung.graphqlvsrest.repository;
import com.baeldung.graphqlvsrest.entity.Order;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import java.util.List;
public interface OrderRepository {
List<Order> getOrdersByProduct(Integer productId);
}
@@ -0,0 +1,14 @@
package com.baeldung.graphqlvsrest.repository;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import java.util.List;
public interface ProductRepository {
List<Product> getProducts(Integer pageSize, Integer pageNumber);
Product getProduct(Integer id);
Product save(ProductModel productModel);
Product update(Integer productId, ProductModel productModel);
}
@@ -0,0 +1,36 @@
package com.baeldung.graphqlvsrest.repository.impl;
import com.baeldung.graphqlvsrest.entity.Order;
import com.baeldung.graphqlvsrest.repository.OrderRepository;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Repository
public class OrderRepositoryImpl implements OrderRepository {
private static List<Order> orderList = new ArrayList<>();
public OrderRepositoryImpl() {
for (int i = 1; i <= 100; i++){
Order order = new Order();
order.setId(i);
order.setProduct_id(i%10);
order.setAddress(UUID.randomUUID().toString());
order.setCustomer_uuid(UUID.randomUUID().toString());
order.setCreation_date(new Date(System.currentTimeMillis()).toString());
order.setStatus("Delivered");
orderList.add(order);
}
}
@Override
public List<Order> getOrdersByProduct(Integer productId) {
return orderList.stream().filter(order -> order.getProduct_id().equals(productId)).collect(Collectors.toList());
}
}
@@ -0,0 +1,74 @@
package com.baeldung.graphqlvsrest.repository.impl;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Repository
public class ProductRepositoryImpl implements ProductRepository {
private static List<Product> productList = new ArrayList<>();
public ProductRepositoryImpl() {
for (int i = 1; i <= 10; i++){
Product product = new Product();
product.setId(i);
product.setName(String.format("Product %d", i));
product.setDescription(String.format("Product %d description", i));
product.setCurrency(String.format("Product %d currency", i));
product.setPrice(Double.valueOf(i^2));
product.setStock(10);
product.setAverage_rating(0F);
product.setImage_url(Arrays.asList(String.format("www.baeldung.com/imageurl/%d", i)));
product.setVideo_url(Arrays.asList(String.format("www.baeldung.com/videourl/%d", i)));
productList.add(product);
}
}
@Override
public List<Product> getProducts(Integer pageSize, Integer pageNumber) {
return productList.stream().skip(pageSize*pageNumber).limit(pageSize).collect(Collectors.toList());
}
@Override
public Product getProduct(Integer id) {
return productList.stream().filter(product -> product.getId().equals(id)).findFirst().orElse(null);
}
@Override
public Product save(ProductModel productModel) {
Product product = new Product(productList.size()+1, productModel);
productList.add(product);
return product;
}
@Override
public Product update(Integer productId, ProductModel productModel) {
Product product = getProduct(productId);
if (product != null){
update(product, productModel);
}
return product;
}
private void update(Product product, ProductModel productModel){
if (productModel != null) {
System.out.println(productModel.toString());
Optional.ofNullable(productModel.getName()).ifPresent(product::setName);
Optional.ofNullable(productModel.getDescription()).ifPresent(product::setDescription);
Optional.ofNullable(productModel.getCurrency()).ifPresent(product::setCurrency);
Optional.ofNullable(productModel.getImage_url()).ifPresent(product::setImage_url);
Optional.ofNullable(productModel.getStock()).ifPresent(product::setStock);
Optional.ofNullable(productModel.getStatus()).ifPresent(product::setStatus);
Optional.ofNullable(productModel.getVideo_url()).ifPresent(product::setVideo_url);
Optional.ofNullable(productModel.getPrice()).ifPresent(product::setPrice);
}
}
}
@@ -0,0 +1,22 @@
package com.baeldung.graphqlvsrest.resolver;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.model.ProductModel;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
public class Mutation implements GraphQLMutationResolver {
private ProductRepository productRepository;
public Mutation(ProductRepository productRepository){
this.productRepository = productRepository;
}
public Product saveProduct(ProductModel productModel) {
return productRepository.save(productModel);
}
public Product updateProduct(Integer productId, ProductModel productModel) {
return productRepository.update(productId, productModel);
}
}
@@ -0,0 +1,18 @@
package com.baeldung.graphqlvsrest.resolver;
import com.baeldung.graphqlvsrest.entity.Order;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.repository.OrderRepository;
import com.coxautodev.graphql.tools.GraphQLResolver;
import java.util.List;
public class ProductResolver implements GraphQLResolver<Product> {
private OrderRepository orderRepository;
public ProductResolver(OrderRepository orderRepository){
this.orderRepository = orderRepository;
}
public List<Order> getOrders(Product product){
return orderRepository.getOrdersByProduct(product.getId());
}
}
@@ -0,0 +1,27 @@
package com.baeldung.graphqlvsrest.resolver;
import com.baeldung.graphqlvsrest.entity.Order;
import com.baeldung.graphqlvsrest.entity.Product;
import com.baeldung.graphqlvsrest.repository.OrderRepository;
import com.baeldung.graphqlvsrest.repository.ProductRepository;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import java.util.List;
public class Query implements GraphQLQueryResolver {
private ProductRepository productRepository;
public Query(ProductRepository productRepository){
this.productRepository = productRepository;
}
public List<Product> getProducts(int pageSize, int pageNumber) {
return productRepository.getProducts(pageSize, pageNumber);
}
public Product getProduct(int id) {
return productRepository.getProduct(id);
}
}
@@ -0,0 +1,57 @@
type Product {
id: ID
name: String!
description: String
status: String
currency: String!
price: Float
image_url: [String]
video_url: [String]
stock: Int
average_rating: Float
orders:[Order]
}
type Order{
id:ID
product_id:Int
customer_uuid:String
address:String
status:String
creation_date:String
}
input ProductModel {
name: String!
description: String
status: String
currency: String!
price: Float
image_url: [String]
video_url: [String]
stock: Int
}
input ProductUpdateModel {
name: String
description: String
status: String
currency: String
price: Float
image_url: [String]
video_url: [String]
stock: Int
}
# The Root Query for the application
type Query {
products(size: Int, page: Int): [Product]!
product(id: Int): Product!
}
# The Root Mutation for the application
type Mutation {
saveProduct(product: ProductModel) : Product!
updateProduct(id: Int, product: ProductUpdateModel) : Product!
}
@@ -102,6 +102,7 @@
<!-- used only in dependency management to force this version, not included as a direct dependency -->
<junit.version>4.13.2</junit.version>
<junit-jupiter.version>5.8.1</junit-jupiter.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>
@@ -0,0 +1 @@
.local-db
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-multiple-datasources</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>spring-boot-multiple-datasources</name>
<packaging>jar</packaging>
<description>Module For Spring Boot With Multiple Datasources</description>
<parent>
<groupId>com.baeldung.spring-boot-modules</groupId>
<artifactId>spring-boot-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<spring-boot.version>2.6.3</spring-boot.version>
</properties>
</project>
@@ -0,0 +1,13 @@
package com.baeldung.spring.datasources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MultipleDatasourcesApplication {
public static void main(String[] args) {
SpringApplication.run(MultipleDatasourcesApplication.class, args);
}
}
@@ -0,0 +1,48 @@
package com.baeldung.spring.datasources.todos;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private boolean completed;
public Todo() {
}
public Todo(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
}
@@ -0,0 +1,28 @@
package com.baeldung.spring.datasources.todos;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class TodoDatasourceConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.todos")
public DataSourceProperties todosDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource todosDataSource() {
return todosDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
}
@@ -0,0 +1,40 @@
package com.baeldung.spring.datasources.todos;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Objects;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackageClasses = Todo.class,
entityManagerFactoryRef = "todosEntityManagerFactory",
transactionManagerRef = "todosTransactionManager"
)
public class TodoJpaConfiguration {
@Bean
public LocalContainerEntityManagerFactoryBean todosEntityManagerFactory(
@Qualifier("todosDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource)
.packages(Todo.class)
.build();
}
@Bean
public PlatformTransactionManager todosTransactionManager(
@Qualifier("todosEntityManagerFactory") LocalContainerEntityManagerFactoryBean todosEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(todosEntityManagerFactory.getObject()));
}
}
@@ -0,0 +1,6 @@
package com.baeldung.spring.datasources.todos;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
@@ -0,0 +1,39 @@
package com.baeldung.spring.datasources.topics;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Topic {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
public Topic() {
}
public Topic(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
@@ -0,0 +1,26 @@
package com.baeldung.spring.datasources.topics;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class TopicDatasourceConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.topics")
public DataSourceProperties topicsDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource topicsDataSource() {
return topicsDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
}
@@ -0,0 +1,41 @@
package com.baeldung.spring.datasources.topics;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Objects;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackageClasses = Topic.class,
entityManagerFactoryRef = "topicsEntityManagerFactory",
transactionManagerRef = "topicsTransactionManager"
)
public class TopicJpaConfiguration {
@Bean
public LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory(
@Qualifier("topicsDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder
) {
return builder
.dataSource(dataSource)
.packages(Topic.class)
.build();
}
@Bean
public PlatformTransactionManager topicsTransactionManager(
@Qualifier("topicsEntityManagerFactory") LocalContainerEntityManagerFactoryBean topicsEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(topicsEntityManagerFactory.getObject()));
}
}
@@ -0,0 +1,6 @@
package com.baeldung.spring.datasources.topics;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TopicRepository extends JpaRepository<Topic, Long> {
}
@@ -0,0 +1,23 @@
spring:
datasource:
todos:
url: jdbc:h2:./.local-db/todos;DB_CLOSE_DELAY=-1;MODE=DB2;AUTO_SERVER=TRUE
username: sa
password: null
driverClassName: org.h2.Driver
topics:
url: jdbc:h2:./.local-db/topics;DB_CLOSE_DELAY=-1;MODE=DB2;AUTO_SERVER=TRUE
username: sa
password: null
driverClassName: org.h2.Driver
h2:
console:
enabled: true
path: /h2-console
jpa:
generate-ddl: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
@@ -0,0 +1,39 @@
package com.baeldung.spring.datasources;
import com.baeldung.spring.datasources.todos.Todo;
import com.baeldung.spring.datasources.todos.TodoRepository;
import com.baeldung.spring.datasources.topics.Topic;
import com.baeldung.spring.datasources.topics.TopicRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest // no test database!
class MultipleDatasourcesIntegrationTest {
@Autowired
TodoRepository todoRepo;
@Autowired
TopicRepository topicRepo;
@Test
void shouldSaveTodoToTodoDB() {
Todo todo = new Todo("test");
Todo saved =todoRepo.save(todo);
Optional<Todo> result= todoRepo.findById(saved.getId());
assertThat(result).isPresent();
}
@Test
void shouldSaveTopicToTopicDB() {
Topic todo = new Topic("test");
Topic saved =topicRepo.save(todo);
Optional<Topic> result= topicRepo.findById(saved.getId());
assertThat(result).isPresent();
}
}
@@ -70,6 +70,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<eclipse.birt.runtime.version>4.8.0</eclipse.birt.runtime.version>
<log4j.version>1.2.17</log4j.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>
@@ -11,6 +11,18 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -128,6 +140,7 @@
<yarn.version>v1.12.1</yarn.version>
<spring-boot.version>2.4.4</spring-boot.version>
<javafaker.version>1.0.2</javafaker.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>
@@ -10,6 +10,7 @@ This module contains articles about Spring Boot Security
- [Guide to @CurrentSecurityContext in Spring Security](https://www.baeldung.com/spring-currentsecuritycontext)
- [Disable Security for a Profile in Spring Boot](https://www.baeldung.com/spring-security-disable-profile)
- [Spring @EnableWebSecurity vs. @EnableGlobalMethodSecurity](https://www.baeldung.com/spring-enablewebsecurity-vs-enableglobalmethodsecurity)
- [Spring Security Configuring Different URLs](https://www.baeldung.com/spring-security-configuring-urls)
### Spring Boot Security Auto-Configuration
@@ -0,0 +1,13 @@
package com.baeldung.antmatchers;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AntMatchersExampleApplication {
public static void main(String[] args) {
SpringApplication.run(AntMatchersExampleApplication.class, args);
}
}
@@ -0,0 +1,39 @@
package com.baeldung.antmatchers.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("password")).roles("USER", "ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("password")).roles("USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/products/**").permitAll()
.and()
.authorizeRequests()
.antMatchers("/customers/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@@ -0,0 +1,15 @@
package com.baeldung.antmatchers.controllers;
import com.baeldung.antmatchers.dtos.Customer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CustomerController {
@GetMapping("/customers/{id}")
public Customer getCustomerById(@PathVariable("id") String id) {
return new Customer("Customer 1", "Address 1", "Phone 1");
}
}
@@ -0,0 +1,21 @@
package com.baeldung.antmatchers.controllers;
import com.baeldung.antmatchers.dtos.Product;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RestController
public class ProductController {
@GetMapping("/products")
public List<Product> getProducts() {
return new ArrayList<>(Arrays.asList(
new Product("Product 1", "Description 1", 1.0),
new Product("Product 2", "Description 2", 2.0)
));
}
}
@@ -0,0 +1,39 @@
package com.baeldung.antmatchers.dtos;
import java.io.Serializable;
public class Customer implements Serializable {
private String name;
private String address;
private String phone;
public Customer(String name, String address, String phone) {
this.name = name;
this.address = address;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
@@ -0,0 +1,39 @@
package com.baeldung.antmatchers.dtos;
import java.io.Serializable;
public class Product implements Serializable {
private String name;
private String description;
private double price;
public Product(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
@@ -0,0 +1,38 @@
package com.baeldung.antmatchers.controllers;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(value = CustomerController.class)
class CustomerControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void getCustomerByIdUnauthorized() throws Exception {
mockMvc.perform(get("/customers/1")).andExpect(status().isUnauthorized());
}
@Test
void getCustomerByIdForbidden() throws Exception {
mockMvc.perform(get("/customers/1").with(user("user").roles("USER")))
.andExpect(status().isForbidden());
}
@Test
void getCustomerByIdOk() throws Exception {
mockMvc.perform(get("/customers/1").with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());
}
}
@@ -0,0 +1,25 @@
package com.baeldung.antmatchers.controllers;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(value = ProductController.class)
class ProductControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void getProducts() throws Exception {
mockMvc.perform(get("/products"))
.andExpect(status().isOk());
}
}
@@ -0,0 +1,13 @@
package com.baeldung.swaggerresponseapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SwaggerResponseApiApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerResponseApiApplication.class, args);
}
}
@@ -0,0 +1,38 @@
package com.baeldung.swaggerresponseapi.controller;
import com.baeldung.swaggerresponseapi.model.Product;
import com.baeldung.swaggerresponseapi.service.ProductService;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping("/create")
public Product addProduct(@RequestBody Product product) {
return productService.addProducts(product);
}
@ApiResponses(value = { @ApiResponse(content = { @Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = Product.class))) }) })
@GetMapping("/products")
public List<Product> getProductsList() {
return productService.getProductsList();
}
}
@@ -0,0 +1,28 @@
package com.baeldung.swaggerresponseapi.model;
public class Product {
String code;
String name;
public Product(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,21 @@
package com.baeldung.swaggerresponseapi.service;
import com.baeldung.swaggerresponseapi.model.Product;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ProductService {
List<Product> productsList = new ArrayList<>();
public Product addProducts(Product product) {
productsList.add(product);
return product;
}
public List<Product> getProductsList() {
return productsList;
}
}
@@ -24,6 +24,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
@@ -62,6 +69,7 @@
<spring-boot.version>2.4.5</spring-boot.version>
<springfox.version>3.0.0</springfox.version>
<keycloak.version>15.0.2</keycloak.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>
</project>
@@ -3,3 +3,6 @@
- [Hiding Endpoints From Swagger Documentation in Spring Boot](https://www.baeldung.com/spring-swagger-hiding-endpoints)
- [Swagger @Api Description Is Deprecated](https://www.baeldung.com/java-swagger-api-description-deprecated)
- [Generate PDF from Swagger API Documentation](https://www.baeldung.com/swagger-generate-pdf)
- [Remove Basic Error Controller In SpringFox Swagger-UI](https://www.baeldung.com/spring-swagger-remove-error-controller)
- [Setting Example and Description with Swagger](https://www.baeldung.com/swagger-set-example-description)
- [Document Enum in Swagger](https://www.baeldung.com/swagger-enum)
@@ -25,6 +25,11 @@
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>${swagger-maven-plugin.version}</version>
</dependency>
</dependencies>
<build>
@@ -33,11 +38,50 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>${swagger-maven-plugin.version}</version>
<configuration>
<apiSources>
<apiSource>
<springmvc>false</springmvc>
<locations>com.baeldung.swaggerenums.controller</locations>
<schemes>http,https</schemes>
<host>baeldung.com</host>
<basePath>/api</basePath>
<info>
<title>Baeldung - Document Enum</title>
<version>v1</version>
<description>This is a Baeldung Document Enum Sample Code</description>
<contact>
<email>pmurria@baeldung.com</email>
<name>Parikshit Murria</name>
</contact>
<license>
<url>https://www.apache.org/licenses/LICENSE-2.0.html</url>
<name>Apache 2.0</name>
</license>
</info>
<swaggerDirectory>${basedir}/target/swagger-ui</swaggerDirectory>
</apiSource>
</apiSources>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<springfox.version>3.0.0</springfox.version>
<swagger-maven-plugin.version>3.1.1</swagger-maven-plugin.version>
</properties>
</project>
</project>
@@ -0,0 +1,13 @@
package com.baeldung.swaggerconf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootSwaggerConfApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSwaggerConfApplication.class, args);
}
}
@@ -0,0 +1,37 @@
package com.baeldung.swaggerconf.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
import static springfox.documentation.builders.PathSelectors.regex;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
private ApiInfo apiInfo() {
return new ApiInfo("My REST API", "Some custom description of API.", "API TOS", "Terms of service",
new Contact("General UserName", "www.baeldung.com", "user-name@gmail.com"),
"License of API", "API license URL", Collections.emptyList());
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.baeldung.swaggerconf.controller"))
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(regex("/good-path/.*"))
.build();
}
}
@@ -0,0 +1,17 @@
package com.baeldung.swaggerconf.controller;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@RequestMapping("good-path/error-excluded-annotation")
public class ErrorControllerExcludedForAnnotation extends BasicErrorController {
public ErrorControllerExcludedForAnnotation(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
}
@@ -0,0 +1,21 @@
package com.baeldung.swaggerconf.controller;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
@Component
@RequestMapping("good-path/error-excluded-apiignore")
@RestController
@ApiIgnore
public class ErrorControllerExcludedForApiIgnore extends BasicErrorController {
public ErrorControllerExcludedForApiIgnore(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
}
@@ -0,0 +1,19 @@
package com.baeldung.swaggerconf.controller;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Component
@RequestMapping("wrong-path/error-excluded-path")
@RestController
public class ErrorControllerExcludedForPath extends BasicErrorController {
public ErrorControllerExcludedForPath(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
}
@@ -0,0 +1,33 @@
package com.baeldung.swaggerconf.controller;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDate;
import java.time.LocalTime;
@RestController
@RequestMapping("good-path")
public class RegularRestController {
@ApiOperation(value = "This method is used to get the author name.")
@GetMapping("/getAuthor")
public String getAuthor() {
return "Name Surname";
}
@ApiOperation(value = "This method is used to get the current date.")
@GetMapping("/getDate")
public LocalDate getDate() {
return LocalDate.now();
}
@ApiOperation(value = "This method is used to get the current time.")
@GetMapping("/getTime")
public LocalTime getTime() {
return LocalTime.now();
}
}
@@ -0,0 +1,17 @@
package com.baeldung.swaggerconf.excluded;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@RequestMapping("good-path/error-excluded-package")
public class ErrorControllerExcludedForPackage extends BasicErrorController {
public ErrorControllerExcludedForPackage(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
}
}
@@ -0,0 +1,13 @@
package com.baeldung.swaggerenums;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SwaggerEnumsApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerEnumsApplication.class, args);
}
}
@@ -0,0 +1,22 @@
package com.baeldung.swaggerenums.controller;
import com.baeldung.swaggerenums.model.Employee;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Api
@Path(value="/hire")
@Produces({"application/json"})
public class HireController {
@POST
@ApiOperation(value = "This method is used to hire employee with a specific role")
public String hireEmployee(@ApiParam(value = "role", required = true) Employee employee) {
return String.format("Hired for role: %s", employee.role.name());
}
}
@@ -0,0 +1,17 @@
package com.baeldung.swaggerenums.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel
public class Employee {
@ApiModelProperty
public Role role;
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
@@ -0,0 +1,8 @@
package com.baeldung.swaggerenums.model;
import io.swagger.annotations.ApiModel;
@ApiModel
public enum Role {
Engineer, Clerk, Driver, Janitor;
}
@@ -0,0 +1,13 @@
package com.baeldung.swaggerexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SwaggerExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerExampleApplication.class, args);
}
}
@@ -0,0 +1,38 @@
package com.baeldung.swaggerexample.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Collections;
@Configuration
@EnableWebMvc
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.baeldung.swaggerexample"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfo(
"Products API",
"API to let you add and view product",
"0.0.1",
"Terms of service",
new Contact("John Doe", "www.example.com", "myemail@company.com"),
"License of API", "API license URL", Collections.emptyList());
}
}
@@ -0,0 +1,34 @@
package com.baeldung.swaggerexample.controller;
import com.baeldung.swaggerexample.entity.Product;
import io.swagger.annotations.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@ApiOperation("Products API")
public class ProductController {
@ApiOperation(value = "Create a new product", notes = "Creates a new product as per the request body")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Successfully created"),
@ApiResponse(code = 400, message = "Bad request - The product is not valid"),
@ApiResponse(code = 500, message = "Internal server error - Something went wrong")
})
@PostMapping(value = "/products")
public ResponseEntity<Void> createProduct(@RequestBody Product product) {
return new ResponseEntity<>(HttpStatus.CREATED);
}
@ApiOperation(value = "Get a product by id", notes = "Returns a product as per the id")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved"),
@ApiResponse(code = 404, message = "Not found - The product was not found")
})
@GetMapping("/products/{id}")
public ResponseEntity<Product> getProduct(@PathVariable("id") @ApiParam(name = "id", value = "Product id", example = "1") Long id) {
//retrieval logic
return ResponseEntity.ok(new Product(1, "Product 1", "$21.99"));
}
}
@@ -0,0 +1,46 @@
package com.baeldung.swaggerexample.entity;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
public class Product implements Serializable {
@ApiModelProperty(notes = "Product ID", example = "1", required = true)
private Long id;
@ApiModelProperty(notes = "Product name", example = "Product 1", required = false)
private String name;
@ApiModelProperty(notes = "Product price", example = "$100.00", required = true)
private String price;
// constructor and getter/setters
public Product(long id, String name, String price) {
this.id = id;
this.name = name;
this.price = price;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
@@ -0,0 +1 @@
spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER
@@ -0,0 +1,33 @@
package com.baeldung.swaggerconf;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SwaggerConfExcludeErrorControllerIntegrationTest {
@Autowired
private MockMvc mvc;
@Test
public void whenCallingSwaggerJSON_stringObjectDoesNotContainAnyErrorControllers() throws Exception {
ResultActions resultActions = mvc.perform(get("/v2/api-docs")).andExpect(status().isOk());
MvcResult result = resultActions.andReturn();
String content = result.getResponse().getContentAsString();
Assert.assertNotNull(content);
Assert.assertFalse(content.contains("error-controller"));
}
}
@@ -0,0 +1,25 @@
package com.baeldung.swaggerenums.controller;
import com.baeldung.swaggerenums.model.Employee;
import com.baeldung.swaggerenums.model.Role;
import org.junit.Assert;
import org.junit.Test;
public class HireControllerUnitTest {
@Test
public void givenRoleEngineer_whenHireEmployee_thenReturnsRoleInString() {
//Arrange
Role testRole = Role.Engineer;
Employee employee = new Employee();
employee.setRole(testRole);
//Act
HireController hireController = new HireController();
String response = hireController.hireEmployee(employee);
//Assert
Assert.assertEquals(String.format("Hired for role: %s", testRole),
response);
}
}
@@ -14,6 +14,18 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -131,6 +143,7 @@
<gmavenplus-plugin.version>1.6</gmavenplus-plugin.version>
<redis.version>0.7.2</redis.version>
<spring-boot.version>2.5.0</spring-boot.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</project>