Merge branch 'eugenp:master' into master
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<log4j2.version>2.14.1</log4j2.version>
|
||||
<log4j2.version>2.17.1</log4j2.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -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>
|
||||
|
||||
+42
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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();
|
||||
}
|
||||
}
|
||||
+51
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -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);
|
||||
}
|
||||
}
|
||||
+112
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.baeldung.metrics.service;
|
||||
|
||||
public interface MetricService {
|
||||
|
||||
Object[][] getGraphData();
|
||||
|
||||
}
|
||||
+9
@@ -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>
|
||||
+43
@@ -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>
|
||||
+7
@@ -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>
|
||||
+64
@@ -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>
|
||||
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
+15
@@ -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)
|
||||
|
||||
+11
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
-5
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
@@ -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();
|
||||
}
|
||||
}
|
||||
-5
@@ -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>
|
||||
+19
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
+58
@@ -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;
|
||||
}
|
||||
}
|
||||
+115
@@ -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;
|
||||
}
|
||||
}
|
||||
+92
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
|
||||
}
|
||||
+36
@@ -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());
|
||||
}
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+18
@@ -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());
|
||||
}
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+57
@@ -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>
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -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()));
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.baeldung.spring.datasources.todos;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface TodoRepository extends JpaRepository<Todo, Long> {
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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()));
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.baeldung.spring.datasources.topics;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface TopicRepository extends JpaRepository<Topic, Long> {
|
||||
}
|
||||
+23
@@ -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
|
||||
+39
@@ -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
|
||||
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -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();
|
||||
}
|
||||
}
|
||||
+15
@@ -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");
|
||||
}
|
||||
}
|
||||
+21
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -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());
|
||||
}
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -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();
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -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>
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -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());
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.baeldung.swaggerenums.model;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
|
||||
@ApiModel
|
||||
public enum Role {
|
||||
Engineer, Clerk, Driver, Janitor;
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -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());
|
||||
}
|
||||
}
|
||||
+34
@@ -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"));
|
||||
}
|
||||
}
|
||||
+46
@@ -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
|
||||
+33
@@ -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"));
|
||||
}
|
||||
}
|
||||
+25
@@ -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>
|
||||
Reference in New Issue
Block a user