Skip to content

Commit

Permalink
Added Jackson Module for Spring Boot Admin
Browse files Browse the repository at this point in the history
  • Loading branch information
srempfer committed Apr 24, 2020
1 parent 31537a4 commit dbb075e
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;

import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.eventstore.InstanceEventStore;
import de.codecentric.boot.admin.server.services.ApplicationRegistry;
import de.codecentric.boot.admin.server.services.InstanceRegistry;
import de.codecentric.boot.admin.server.utils.jackson.RegistrationBeanSerializerModifier;
import de.codecentric.boot.admin.server.utils.jackson.RegistrationDeserializer;
import de.codecentric.boot.admin.server.utils.jackson.SanitizingMapSerializer;
import de.codecentric.boot.admin.server.utils.jackson.SpringBootAdminModule;
import de.codecentric.boot.admin.server.web.ApplicationsController;
import de.codecentric.boot.admin.server.web.InstancesController;
import de.codecentric.boot.admin.server.web.client.InstanceWebClient;
Expand All @@ -48,11 +45,7 @@ public AdminServerWebConfiguration(AdminServerProperties adminServerProperties)

@Bean
public SimpleModule adminJacksonModule() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Registration.class, new RegistrationDeserializer());
module.setSerializerModifier(new RegistrationBeanSerializerModifier(
new SanitizingMapSerializer(this.adminServerProperties.getMetadataKeysToSanitize())));
return module;
return new SpringBootAdminModule(this.adminServerProperties.getMetadataKeysToSanitize());
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.time.Instant;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.domain.values.Registration;

Expand All @@ -39,8 +41,10 @@ public InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Regis
this(instance, version, Instant.now(), registration);
}

public InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Instant timestamp,
Registration registration) {
@JsonCreator
public InstanceRegistrationUpdatedEvent(@JsonProperty("instance") InstanceId instance,
@JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp,
@JsonProperty("registration") Registration registration) {
super(instance, version, "REGISTRATION_UPDATED", timestamp);
this.registration = registration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import org.springframework.util.Assert;

@lombok.Data
Expand Down Expand Up @@ -52,7 +55,8 @@ private Endpoint(String id, String url) {
this.url = url;
}

public static Endpoint of(String id, String url) {
@JsonCreator
public static Endpoint of(@JsonProperty("id") String id, @JsonProperty("url") String url) {
return new Endpoint(id, url);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

import javax.annotation.Nullable;

import com.fasterxml.jackson.annotation.JsonCreator;

import static java.util.stream.Collectors.toMap;

@lombok.EqualsAndHashCode
Expand Down Expand Up @@ -68,6 +70,7 @@ public static Endpoints single(String id, String url) {
return new Endpoints(Collections.singletonList(Endpoint.of(id, url)));
}

@JsonCreator
public static Endpoints of(@Nullable Collection<Endpoint> endpoints) {
if (endpoints == null || endpoints.isEmpty()) {
return empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.codecentric.boot.admin.server.utils.jackson;

import java.time.Instant;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;
import de.codecentric.boot.admin.server.domain.values.Endpoints;
import de.codecentric.boot.admin.server.domain.values.InstanceId;

/**
* Jackson Mixin for {@link InstanceEndpointsDetectedEvent}.
*/
public class InstanceEndpointsDetectedEventMixin {

@JsonCreator
public InstanceEndpointsDetectedEventMixin(@JsonProperty("instance") InstanceId instance, @JsonProperty("version") long version,
@JsonProperty("timestamp") Instant timestamp, @JsonProperty("endpoints") Endpoints endpoints) {
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package de.codecentric.boot.admin.server.utils.jackson;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import de.codecentric.boot.admin.server.domain.events.InstanceDeregisteredEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceInfoChangedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;

/**
* Jackson Mixin class helps in serialize/deserialize {@link InstanceEvent}s.
*
* @author Stefan Rempfer
*/
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = InstanceEndpointsDetectedEvent.class, name = "ENDPOINTS_DETECTED"),
@JsonSubTypes.Type(value = InstanceRegistrationUpdatedEvent.class, name = "REGISTRATION_UPDATED"),
@JsonSubTypes.Type(value = InstanceInfoChangedEvent.class, name = "INFO_CHANGED"),
@JsonSubTypes.Type(value = InstanceDeregisteredEvent.class, name = "DEREGISTERED"),
@JsonSubTypes.Type(value = InstanceRegisteredEvent.class, name = "REGISTERED"),
@JsonSubTypes.Type(value = InstanceStatusChangedEvent.class, name = "STATUS_CHANGED")
})
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class InstanceEventMixin {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package de.codecentric.boot.admin.server.utils.jackson;

import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.values.Registration;

/**
* Jackson module for spring-boot-admin. This module register {@link InstanceEventMixin}.
* In order to use this module just add this modules into your ObjectMapper configuration.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new SpringBootAdminModule());
* mapper.registerModule(new JavaTimeModule());
* </pre>
*
* @author Stefan Rempfer
*/
public class SpringBootAdminModule extends SimpleModule {

private final String[] metadataKeyPatterns;

/**
* Constructs the module with a pattern for metadata keys.
* The values of the matched metadata keys will be sanitized before serializing to json.
*
* @param metadataKeyPatterns pattern for metadata keys which should be sanitized
*/
public SpringBootAdminModule(String[] metadataKeyPatterns) {
super(SpringBootAdminModule.class.getName(), new Version(1, 0, 0, null, null, null));
this.metadataKeyPatterns = metadataKeyPatterns;
}

@Override
public void setupModule(SetupContext context) {
SimpleDeserializers simpleDeserializers = new SimpleDeserializers();
simpleDeserializers.addDeserializer(Registration.class, new RegistrationDeserializer());
context.addDeserializers(simpleDeserializers);

context.addBeanSerializerModifier(
new RegistrationBeanSerializerModifier(new SanitizingMapSerializer(metadataKeyPatterns)));

context.setMixInAnnotations(InstanceEvent.class, InstanceEventMixin.class);
context.setMixInAnnotations(InstanceEndpointsDetectedEvent.class, InstanceEndpointsDetectedEventMixin.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package de.codecentric.boot.admin.server.utils.jackson;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import de.codecentric.boot.admin.server.domain.events.InstanceEndpointsDetectedEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;
import de.codecentric.boot.admin.server.domain.values.Endpoint;
import de.codecentric.boot.admin.server.domain.values.Endpoints;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.domain.values.Registration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import static org.assertj.core.api.Assertions.assertThat;

public class InstanceEventMixinTest {

private final ObjectMapper objectMapper;

public InstanceEventMixinTest() {
SpringBootAdminModule springBootAdminJacksonModule = new SpringBootAdminModule(new String[] { ".*password$" });
JavaTimeModule javaTimeModule = new JavaTimeModule();
objectMapper = Jackson2ObjectMapperBuilder.json().modules(springBootAdminJacksonModule, javaTimeModule).build();
}

@Test
public void verifySerializeOfInstanceId() throws JsonProcessingException {
InstanceId id = InstanceId.of("test123");
String json = objectMapper.writeValueAsString(id);
assertThat(json).isEqualTo("\"test123\"");
}

@Test
public void verifyDeserializeOfInstanceId() throws JsonProcessingException {
InstanceId id = objectMapper.readValue("\"test123\"", InstanceId.class);
assertThat(id).isEqualTo(InstanceId.of("test123"));
}

@Nested
class InstanceEndpointsDetectedEventTests {
static final String INSTANCE_ENDPOINTS_DETECTED_EVENT_JSON =
"{\"instance\":\"test123\",\"version\":12345678,\"timestamp\":1587751031.000000000,\"endpoints\":["
+ "{\"id\":\"health\",\"url\":\"http://localhost:8080/health\"},"
+ "{\"id\":\"info\",\"url\":\"http://localhost:8080/info\"}"
+ "],\"type\":\"ENDPOINTS_DETECTED\"}";

@Test
public void verifySerialize() throws JsonProcessingException {
InstanceId id = InstanceId.of("test123");
Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);
Endpoints endpoints = Endpoints.single("info", "http://localhost:8080/info")
.withEndpoint("health", "http://localhost:8080/health");

InstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 12345678L, timestamp, endpoints);
String json = objectMapper.writeValueAsString(event);
assertThat(json).isEqualTo(INSTANCE_ENDPOINTS_DETECTED_EVENT_JSON);
}

@Test
public void verifyDeserialize() throws JsonProcessingException {
InstanceEndpointsDetectedEvent event = objectMapper
.readValue(INSTANCE_ENDPOINTS_DETECTED_EVENT_JSON, InstanceEndpointsDetectedEvent.class);
assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123"));
assertThat(event.getVersion()).isEqualTo(12345678L);
assertThat(event.getTimestamp())
.isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));
Endpoints endpoints = event.getEndpoints();
assertThat(endpoints).contains(Endpoint.of("info", "http://localhost:8080/info"),
Endpoint.of("health", "http://localhost:8080/health"));
}
}

@Nested
class InstanceRegistrationUpdatedEventTest {

static final String INSTANCE_REGISTRATION_UPDATED_EVENT_JSON =
"{\"instance\":\"test123\",\"version\":12345678,\"timestamp\":1587751031.000000000,\"registration\":{"
+ "\"name\":\"test\","
+ "\"managementUrl\":\"http://localhost:8080/management\","
+ "\"healthUrl\":\"http://localhost:8080/health\","
+ "\"serviceUrl\":\"http://localhost:8080/servie\","
+ "\"source\":\"dummy-source\""
+ ",\"metadata\":{\"PASSWORD\":\"******\",\"user\":\"humptydumpty\"}},"
+ "\"type\":\"REGISTRATION_UPDATED\"}";

@Test
public void verifySerialize() throws JsonProcessingException {
InstanceId id = InstanceId.of("test123");
Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS);
Registration registration = Registration.create("test", "http://localhost:8080/health")
.managementUrl("http://localhost:8080/management")
.serviceUrl("http://localhost:8080/servie")
.source("dummy-source")
.metadata("PASSWORD", "qwertz123")
.metadata("user", "humptydumpty")
.build();

InstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 12345678L, timestamp, registration);
String json = objectMapper.writeValueAsString(event);
assertThat(json).isEqualTo(INSTANCE_REGISTRATION_UPDATED_EVENT_JSON);
}

@Test
public void verifyDeserialize() throws JsonProcessingException {
InstanceRegistrationUpdatedEvent event = objectMapper
.readValue(INSTANCE_REGISTRATION_UPDATED_EVENT_JSON, InstanceRegistrationUpdatedEvent.class);
assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123"));
assertThat(event.getVersion()).isEqualTo(12345678L);
assertThat(event.getTimestamp())
.isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS));
Registration registration = event.getRegistration();
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ public class RegistrationDeserializerTest {
private final ObjectMapper objectMapper;

public RegistrationDeserializerTest() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Registration.class, new RegistrationDeserializer());
module.setSerializerModifier(
new RegistrationBeanSerializerModifier(new SanitizingMapSerializer(new String[] { ".*password$" })));
SpringBootAdminModule module = new SpringBootAdminModule(new String[] { ".*password$" });
objectMapper = Jackson2ObjectMapperBuilder.json().modules(module).build();
}

Expand Down

0 comments on commit dbb075e

Please sign in to comment.