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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user