From 5d25c6bde6e2baf46518a41175f7d806b7ee8fe2 Mon Sep 17 00:00:00 2001 From: Stefan Rempfer Date: Sat, 25 Apr 2020 15:32:37 +0200 Subject: [PATCH] Add InstanceEvents to Jackson module solves #1348 --- .../events/InstanceDeregisteredEvent.java | 13 +- .../InstanceEndpointsDetectedEvent.java | 14 +- .../events/InstanceInfoChangedEvent.java | 14 +- .../events/InstanceRegisteredEvent.java | 13 +- .../InstanceRegistrationUpdatedEvent.java | 15 +- .../events/InstanceStatusChangedEvent.java | 14 +- .../admin/server/domain/values/Endpoint.java | 7 +- .../admin/server/domain/values/Endpoints.java | 5 +- .../boot/admin/server/domain/values/Info.java | 4 +- .../server/domain/values/StatusInfo.java | 8 +- .../utils/jackson/AdminServerModule.java | 10 +- .../utils/jackson/InstanceEventMixin.java | 46 + .../jackson/RegistrationDeserializer.java | 17 +- .../utils/jackson/InstanceEventMixinTest.java | 815 ++++++++++++++++++ 14 files changed, 963 insertions(+), 32 deletions(-) create mode 100644 spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixin.java create mode 100644 spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixinTest.java diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceDeregisteredEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceDeregisteredEvent.java index 0305cd01c73..417bc1ab391 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceDeregisteredEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceDeregisteredEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ 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; /** @@ -30,14 +33,18 @@ @lombok.ToString(callSuper = true) public class InstanceDeregisteredEvent extends InstanceEvent { + public static final String TYPE = "DEREGISTERED"; + private static final long serialVersionUID = 1L; public InstanceDeregisteredEvent(InstanceId instance, long version) { this(instance, version, Instant.now()); } - public InstanceDeregisteredEvent(InstanceId instance, long version, Instant timestamp) { - super(instance, version, "DEREGISTERED", timestamp); + @JsonCreator + public InstanceDeregisteredEvent(@JsonProperty("instance") InstanceId instance, + @JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp) { + super(instance, version, TYPE, timestamp); } } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEndpointsDetectedEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEndpointsDetectedEvent.java index 513761c3696..7d0558fcf9f 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEndpointsDetectedEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceEndpointsDetectedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ import java.time.Instant; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + import de.codecentric.boot.admin.server.domain.values.Endpoints; import de.codecentric.boot.admin.server.domain.values.InstanceId; @@ -31,6 +34,8 @@ @lombok.ToString(callSuper = true) public class InstanceEndpointsDetectedEvent extends InstanceEvent { + public static final String TYPE = "ENDPOINTS_DETECTED"; + private static final long serialVersionUID = 1L; private final Endpoints endpoints; @@ -39,8 +44,11 @@ public InstanceEndpointsDetectedEvent(InstanceId instance, long version, Endpoin this(instance, version, Instant.now(), endpoints); } - public InstanceEndpointsDetectedEvent(InstanceId instance, long version, Instant timestamp, Endpoints endpoints) { - super(instance, version, "ENDPOINTS_DETECTED", timestamp); + @JsonCreator + public InstanceEndpointsDetectedEvent(@JsonProperty("instance") InstanceId instance, + @JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp, + @JsonProperty("endpoints") Endpoints endpoints) { + super(instance, version, TYPE, timestamp); this.endpoints = endpoints; } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceInfoChangedEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceInfoChangedEvent.java index 0cf8783bbfe..973e161ef6e 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceInfoChangedEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceInfoChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ import java.time.Instant; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + import de.codecentric.boot.admin.server.domain.values.Info; import de.codecentric.boot.admin.server.domain.values.InstanceId; @@ -31,6 +34,8 @@ @lombok.ToString(callSuper = true) public class InstanceInfoChangedEvent extends InstanceEvent { + public static final String TYPE = "INFO_CHANGED"; + private static final long serialVersionUID = 1L; private final Info info; @@ -39,8 +44,11 @@ public InstanceInfoChangedEvent(InstanceId instance, long version, Info info) { this(instance, version, Instant.now(), info); } - public InstanceInfoChangedEvent(InstanceId instance, long version, Instant timestamp, Info info) { - super(instance, version, "INFO_CHANGED", timestamp); + @JsonCreator + public InstanceInfoChangedEvent(@JsonProperty("instance") InstanceId instance, + @JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp, + @JsonProperty("info") Info info) { + super(instance, version, TYPE, timestamp); this.info = info; } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegisteredEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegisteredEvent.java index 9cacd032385..30670b19716 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegisteredEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegisteredEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ 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; @@ -31,6 +34,8 @@ @lombok.ToString(callSuper = true) public class InstanceRegisteredEvent extends InstanceEvent { + public static final String TYPE = "REGISTERED"; + private static final long serialVersionUID = 1L; private final Registration registration; @@ -39,8 +44,10 @@ public InstanceRegisteredEvent(InstanceId instance, long version, Registration r this(instance, version, Instant.now(), registration); } - public InstanceRegisteredEvent(InstanceId instance, long version, Instant timestamp, Registration registration) { - super(instance, version, "REGISTERED", timestamp); + @JsonCreator + public InstanceRegisteredEvent(@JsonProperty("instance") InstanceId instance, @JsonProperty("version") long version, + @JsonProperty("timestamp") Instant timestamp, @JsonProperty("registration") Registration registration) { + super(instance, version, TYPE, timestamp); this.registration = registration; } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegistrationUpdatedEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegistrationUpdatedEvent.java index f063160b31a..06ba0d6a0ac 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegistrationUpdatedEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceRegistrationUpdatedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ 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; @@ -31,6 +34,8 @@ @lombok.ToString(callSuper = true) public class InstanceRegistrationUpdatedEvent extends InstanceEvent { + public static final String TYPE = "REGISTRATION_UPDATED"; + private static final long serialVersionUID = 1L; private final Registration registration; @@ -39,9 +44,11 @@ public InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Regis this(instance, version, Instant.now(), registration); } - public InstanceRegistrationUpdatedEvent(InstanceId instance, long version, Instant timestamp, - Registration registration) { - super(instance, version, "REGISTRATION_UPDATED", timestamp); + @JsonCreator + public InstanceRegistrationUpdatedEvent(@JsonProperty("instance") InstanceId instance, + @JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp, + @JsonProperty("registration") Registration registration) { + super(instance, version, TYPE, timestamp); this.registration = registration; } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceStatusChangedEvent.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceStatusChangedEvent.java index d6c8138808d..97d1ec47c80 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceStatusChangedEvent.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/events/InstanceStatusChangedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ 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.StatusInfo; @@ -31,6 +34,8 @@ @lombok.ToString(callSuper = true) public class InstanceStatusChangedEvent extends InstanceEvent { + public static final String TYPE = "STATUS_CHANGED"; + private static final long serialVersionUID = 1L; private final StatusInfo statusInfo; @@ -39,8 +44,11 @@ public InstanceStatusChangedEvent(InstanceId instance, long version, StatusInfo this(instance, version, Instant.now(), statusInfo); } - public InstanceStatusChangedEvent(InstanceId instance, long version, Instant timestamp, StatusInfo statusInfo) { - super(instance, version, "STATUS_CHANGED", timestamp); + @JsonCreator + public InstanceStatusChangedEvent(@JsonProperty("instance") InstanceId instance, + @JsonProperty("version") long version, @JsonProperty("timestamp") Instant timestamp, + @JsonProperty("statusInfo") StatusInfo statusInfo) { + super(instance, version, TYPE, timestamp); this.statusInfo = statusInfo; } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoint.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoint.java index 9e2b9e508c5..a3cdfaecfc5 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoint.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.util.Assert; @lombok.Data @@ -52,7 +54,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); } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoints.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoints.java index 8cdb12f6217..22c20a40ec7 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoints.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Endpoints.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import javax.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonCreator; + import static java.util.stream.Collectors.toMap; @lombok.EqualsAndHashCode @@ -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 endpoints) { if (endpoints == null || endpoints.isEmpty()) { return empty(); diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Info.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Info.java index 151bc9e0916..72cd327fb95 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Info.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/Info.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonCreator; /** * Represents the info fetched from the info actuator endpoint @@ -46,6 +47,7 @@ private Info(Map values) { } } + @JsonCreator public static Info from(@Nullable Map values) { if (values == null || values.isEmpty()) { return empty(); diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/StatusInfo.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/StatusInfo.java index af446d3052e..b454e924a83 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/StatusInfo.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/domain/values/StatusInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,9 @@ import javax.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.util.Assert; import static java.util.Arrays.asList; @@ -63,7 +65,9 @@ private StatusInfo(String status, @Nullable Map details) { this.details = (details != null) ? new HashMap<>(details) : Collections.emptyMap(); } - public static StatusInfo valueOf(String statusCode, @Nullable Map details) { + @JsonCreator + public static StatusInfo valueOf(@JsonProperty("status") String statusCode, + @JsonProperty("details") @Nullable Map details) { return new StatusInfo(statusCode, details); } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/AdminServerModule.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/AdminServerModule.java index 3e4c40aaabc..fe1a1b798ce 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/AdminServerModule.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/AdminServerModule.java @@ -18,10 +18,17 @@ import com.fasterxml.jackson.databind.module.SimpleModule; +import de.codecentric.boot.admin.server.domain.events.InstanceEvent; import de.codecentric.boot.admin.server.domain.values.Registration; /** - * Jackson module for Spring Boot Admin Server. + * Jackson module for Spring Boot Admin Server.
+ * In order to use this module just add this modules into your ObjectMapper configuration. + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new AdminServerModule());
+ *     mapper.registerModule(new JavaTimeModule());
+ * 
* * @author Stefan Rempfer */ @@ -37,6 +44,7 @@ public AdminServerModule(String[] metadataKeyPatterns) { addDeserializer(Registration.class, new RegistrationDeserializer()); setSerializerModifier(new RegistrationBeanSerializerModifier(new SanitizingMapSerializer(metadataKeyPatterns))); + setMixInAnnotation(InstanceEvent.class, InstanceEventMixin.class); } } diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixin.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixin.java new file mode 100644 index 00000000000..7bd42c825f7 --- /dev/null +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixin.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.server.utils.jackson; + +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 = InstanceEndpointsDetectedEvent.TYPE), + @JsonSubTypes.Type(value = InstanceRegistrationUpdatedEvent.class, + name = InstanceRegistrationUpdatedEvent.TYPE), + @JsonSubTypes.Type(value = InstanceInfoChangedEvent.class, name = InstanceInfoChangedEvent.TYPE), + @JsonSubTypes.Type(value = InstanceDeregisteredEvent.class, name = InstanceDeregisteredEvent.TYPE), + @JsonSubTypes.Type(value = InstanceRegisteredEvent.class, name = InstanceRegisteredEvent.TYPE), + @JsonSubTypes.Type(value = InstanceStatusChangedEvent.class, name = InstanceStatusChangedEvent.TYPE) }) +public abstract class InstanceEventMixin { + +} diff --git a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializer.java b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializer.java index d94d44a71c4..891658b029f 100644 --- a/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializer.java +++ b/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/utils/jackson/RegistrationDeserializer.java @@ -40,32 +40,37 @@ public Registration deserialize(JsonParser p, DeserializationContext ctxt) throw JsonNode node = p.readValueAsTree(); Registration.Builder builder = Registration.builder(); - if (node.has("name")) { + if (node.hasNonNull("name")) { builder.name(node.get("name").asText()); } - if (node.has("url")) { + if (node.hasNonNull("url")) { String url = node.get("url").asText(); builder.healthUrl(url.replaceFirst("/+$", "") + "/health").managementUrl(url); } else { - if (node.has("healthUrl")) { + if (node.hasNonNull("healthUrl")) { builder.healthUrl(node.get("healthUrl").asText()); } - if (node.has("managementUrl")) { + if (node.hasNonNull("managementUrl")) { builder.managementUrl(node.get("managementUrl").asText()); } - if (node.has("serviceUrl")) { + if (node.hasNonNull("serviceUrl")) { builder.serviceUrl(node.get("serviceUrl").asText()); } } - if (node.has("metadata")) { + if (node.hasNonNull("metadata")) { Iterator> it = node.get("metadata").fields(); while (it.hasNext()) { Map.Entry entry = it.next(); builder.metadata(entry.getKey(), entry.getValue().asText()); } } + + if (node.hasNonNull("source")) { + builder.source(node.get("source").asText()); + } + return builder.build(); } diff --git a/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixinTest.java b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixinTest.java new file mode 100644 index 00000000000..168328a0b5f --- /dev/null +++ b/spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/utils/jackson/InstanceEventMixinTest.java @@ -0,0 +1,815 @@ +/* + * Copyright 2014-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.codecentric.boot.admin.server.utils.jackson; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +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.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; +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.Info; +import de.codecentric.boot.admin.server.domain.values.InstanceId; +import de.codecentric.boot.admin.server.domain.values.Registration; +import de.codecentric.boot.admin.server.domain.values.StatusInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; + +public class InstanceEventMixinTest { + + private final ObjectMapper objectMapper; + + public InstanceEventMixinTest() { + AdminServerModule adminServerModule = new AdminServerModule(new String[] { ".*password$" }); + JavaTimeModule javaTimeModule = new JavaTimeModule(); + objectMapper = Jackson2ObjectMapperBuilder.json().modules(adminServerModule, javaTimeModule).build(); + } + + @Nested + public class InstanceDeregisteredEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "DEREGISTERED").toString(); + + InstanceDeregisteredEvent event = objectMapper.readValue(json, InstanceDeregisteredEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "DEREGISTERED").toString(); + + InstanceDeregisteredEvent event = objectMapper.readValue(json, InstanceDeregisteredEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + } + + @Test + public void verifySerialize() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceDeregisteredEvent event = new InstanceDeregisteredEvent(id, 12345678L, timestamp); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("DEREGISTERED"); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceDeregisteredEvent event = new InstanceDeregisteredEvent(id, 0L, timestamp); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("DEREGISTERED"); + } + + } + + @Nested + public class InstanceEndpointsDetectedEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "ENDPOINTS_DETECTED") + .put("endpoints", new JSONArray() + .put(new JSONObject().put("id", "info").put("url", "http://localhost:8080/info")) + .put(new JSONObject().put("id", "health").put("url", "http://localhost:8080/health"))) + .toString(); + + InstanceEndpointsDetectedEvent event = objectMapper.readValue(json, InstanceEndpointsDetectedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getEndpoints()).contains(Endpoint.of("info", "http://localhost:8080/info"), + Endpoint.of("health", "http://localhost:8080/health")); + } + + @Test + public void verifyDeserializeWithEmptyEndpoints() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "ENDPOINTS_DETECTED") + .put("endpoints", new JSONArray()).toString(); + + InstanceEndpointsDetectedEvent event = objectMapper.readValue(json, InstanceEndpointsDetectedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getEndpoints()).isEmpty(); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "ENDPOINTS_DETECTED").toString(); + + InstanceEndpointsDetectedEvent event = objectMapper.readValue(json, InstanceEndpointsDetectedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getEndpoints()).isNull(); + } + + @Test + public void verifySerialize() throws IOException { + 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); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("ENDPOINTS_DETECTED"); + assertThat(jsonContent).extractingJsonPathArrayValue("$.endpoints").hasSize(2); + + assertThat(jsonContent).extractingJsonPathStringValue("$.endpoints[0].id").isIn("info", "health"); + assertThat(jsonContent).extractingJsonPathStringValue("$.endpoints[0].url") + .isIn("http://localhost:8080/info", "http://localhost:8080/health"); + + assertThat(jsonContent).extractingJsonPathStringValue("$.endpoints[1].id").isIn("info", "health"); + assertThat(jsonContent).extractingJsonPathStringValue("$.endpoints[1].url") + .isIn("http://localhost:8080/info", "http://localhost:8080/health"); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 0L, timestamp, null); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("ENDPOINTS_DETECTED"); + assertThat(jsonContent).extractingJsonPathArrayValue("$.endpoints").isNull(); + } + + @Test + public void verifySerializeWithEmptyEndpoints() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceEndpointsDetectedEvent event = new InstanceEndpointsDetectedEvent(id, 0L, timestamp, + Endpoints.empty()); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("ENDPOINTS_DETECTED"); + assertThat(jsonContent).extractingJsonPathArrayValue("$.endpoints").isEmpty(); + } + + } + + @Nested + public class InstanceInfoChangedEventEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "INFO_CHANGED") + .put("info", + new JSONObject().put("build", new JSONObject().put("version", "1.0.0")).put("foo", "bar")) + .toString(); + + InstanceInfoChangedEvent event = objectMapper.readValue(json, InstanceInfoChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + Info info = event.getInfo(); + assertThat(info).isNotNull(); + assertThat(info.getValues()).containsExactly(entry("build", Collections.singletonMap("version", "1.0.0")), + entry("foo", "bar")); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "INFO_CHANGED").toString(); + + InstanceInfoChangedEvent event = objectMapper.readValue(json, InstanceInfoChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getInfo()).isNull(); + } + + @Test + public void verifyDeserializeWithEmptyInfo() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "INFO_CHANGED").put("info", new JSONObject()) + .toString(); + + InstanceInfoChangedEvent event = objectMapper.readValue(json, InstanceInfoChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + Info info = event.getInfo(); + assertThat(info).isNotNull(); + assertThat(info.getValues()).isEmpty(); + } + + @Test + public void verifySerialize() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + Map data = new HashMap<>(); + data.put("build", Collections.singletonMap("version", "1.0.0")); + data.put("foo", "bar"); + InstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 12345678L, timestamp, Info.from(data)); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("INFO_CHANGED"); + assertThat(jsonContent).extractingJsonPathMapValue("$.info").containsOnlyKeys("build", "foo"); + + assertThat(jsonContent).extractingJsonPathStringValue("$.info['build'].['version']").isEqualTo("1.0.0"); + assertThat(jsonContent).extractingJsonPathStringValue("$.info['foo']").isEqualTo("bar"); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 0L, timestamp, null); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("INFO_CHANGED"); + assertThat(jsonContent).extractingJsonPathMapValue("$.info").isNull(); + } + + @Test + public void verifySerializeWithEmptyInfo() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceInfoChangedEvent event = new InstanceInfoChangedEvent(id, 12345678L, timestamp, + Info.from(Collections.emptyMap())); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("INFO_CHANGED"); + assertThat(jsonContent).extractingJsonPathMapValue("$.info").isEmpty(); + } + + } + + @Nested + public class InstanceRegisteredEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTERED") + .put("registration", + new JSONObject().put("name", "test").put("managementUrl", "http://localhost:9080/") + .put("healthUrl", "http://localhost:9080/heath") + .put("serviceUrl", "http://localhost:8080/").put("source", "http-api") + .put("metadata", + new JSONObject().put("PASSWORD", "******").put("user", "humptydumpty"))) + .toString(); + + InstanceRegisteredEvent event = objectMapper.readValue(json, InstanceRegisteredEvent.class); + assertThat(event).isNotNull(); + 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(); + assertThat(registration).isNotNull(); + assertThat(registration.getName()).isEqualTo("test"); + assertThat(registration.getManagementUrl()).isEqualTo("http://localhost:9080/"); + assertThat(registration.getHealthUrl()).isEqualTo("http://localhost:9080/heath"); + assertThat(registration.getServiceUrl()).isEqualTo("http://localhost:8080/"); + assertThat(registration.getSource()).isEqualTo("http-api"); + assertThat(registration.getMetadata()).containsExactly(entry("PASSWORD", "******"), + entry("user", "humptydumpty")); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "REGISTERED") + .put("registration", + new JSONObject().put("name", "test").put("healthUrl", "http://localhost:9080/heath")) + .toString(); + + InstanceRegisteredEvent event = objectMapper.readValue(json, InstanceRegisteredEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + Registration registration = event.getRegistration(); + assertThat(registration).isNotNull(); + assertThat(registration.getName()).isEqualTo("test"); + assertThat(registration.getManagementUrl()).isNull(); + assertThat(registration.getHealthUrl()).isEqualTo("http://localhost:9080/heath"); + assertThat(registration.getServiceUrl()).isNull(); + assertThat(registration.getSource()).isNull(); + assertThat(registration.getMetadata()).isEmpty(); + } + + @Test + public void verifyDeserializeWithoutRegistration() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTERED").toString(); + + InstanceRegisteredEvent event = objectMapper.readValue(json, InstanceRegisteredEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getRegistration()).isNull(); + + } + + @Test + public void verifyDeserializeWithEmptyRegistration() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTERED") + .put("registration", new JSONObject()).toString(); + + assertThatThrownBy(() -> objectMapper.readValue(json, InstanceRegisteredEvent.class)) + .isInstanceOf(JsonMappingException.class).hasCauseInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("must not be empty"); + } + + @Test + public void verifySerialize() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + Registration registration = Registration.create("test", "http://localhost:9080/heath") + .managementUrl("http://localhost:9080/").serviceUrl("http://localhost:8080/").source("http-api") + .metadata("PASSWORD", "qwertz123").metadata("user", "humptydumpty").build(); + + InstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 12345678L, timestamp, registration); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTERED"); + assertThat(jsonContent).extractingJsonPathValue("$.registration").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.name").isEqualTo("test"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.managementUrl") + .isEqualTo("http://localhost:9080/"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.healthUrl") + .isEqualTo("http://localhost:9080/heath"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.serviceUrl") + .isEqualTo("http://localhost:8080/"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.source").isEqualTo("http-api"); + assertThat(jsonContent).extractingJsonPathMapValue("$.registration.metadata") + .containsExactly(entry("PASSWORD", "******"), entry("user", "humptydumpty")); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + Registration registration = Registration.create("test", "http://localhost:9080/heath").build(); + + InstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 0L, timestamp, registration); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTERED"); + assertThat(jsonContent).extractingJsonPathValue("$.registration").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.name").isEqualTo("test"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.managementUrl").isNull(); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.healthUrl") + .isEqualTo("http://localhost:9080/heath"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.serviceUrl").isNull(); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.source").isNull(); + assertThat(jsonContent).extractingJsonPathMapValue("$.registration.metadata").isEmpty(); + } + + @Test + public void verifySerializeWithoutRegistration() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceRegisteredEvent event = new InstanceRegisteredEvent(id, 12345678L, timestamp, null); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTERED"); + assertThat(jsonContent).extractingJsonPathValue("$.registration").isNull(); + } + + } + + @Nested + public class InstanceRegistrationUpdatedEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTRATION_UPDATED") + .put("registration", + new JSONObject().put("name", "test").put("managementUrl", "http://localhost:9080/") + .put("healthUrl", "http://localhost:9080/heath") + .put("serviceUrl", "http://localhost:8080/").put("source", "http-api") + .put("metadata", + new JSONObject().put("PASSWORD", "******").put("user", "humptydumpty"))) + .toString(); + + InstanceRegistrationUpdatedEvent event = objectMapper.readValue(json, + InstanceRegistrationUpdatedEvent.class); + assertThat(event).isNotNull(); + 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(); + assertThat(registration).isNotNull(); + assertThat(registration.getName()).isEqualTo("test"); + assertThat(registration.getManagementUrl()).isEqualTo("http://localhost:9080/"); + assertThat(registration.getHealthUrl()).isEqualTo("http://localhost:9080/heath"); + assertThat(registration.getServiceUrl()).isEqualTo("http://localhost:8080/"); + assertThat(registration.getSource()).isEqualTo("http-api"); + assertThat(registration.getMetadata()).containsExactly(entry("PASSWORD", "******"), + entry("user", "humptydumpty")); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "REGISTRATION_UPDATED") + .put("registration", + new JSONObject().put("name", "test").put("healthUrl", "http://localhost:9080/heath")) + .toString(); + + InstanceRegistrationUpdatedEvent event = objectMapper.readValue(json, + InstanceRegistrationUpdatedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + Registration registration = event.getRegistration(); + assertThat(registration).isNotNull(); + assertThat(registration.getName()).isEqualTo("test"); + assertThat(registration.getManagementUrl()).isNull(); + assertThat(registration.getHealthUrl()).isEqualTo("http://localhost:9080/heath"); + assertThat(registration.getServiceUrl()).isNull(); + assertThat(registration.getSource()).isNull(); + assertThat(registration.getMetadata()).isEmpty(); + } + + @Test + public void verifyDeserializeWithoutRegistration() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTRATION_UPDATED").toString(); + + InstanceRegistrationUpdatedEvent event = objectMapper.readValue(json, + InstanceRegistrationUpdatedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getRegistration()).isNull(); + } + + @Test + public void verifyDeserializeWithEmptyRegistration() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "REGISTRATION_UPDATED") + .put("registration", new JSONObject()).toString(); + + assertThatThrownBy(() -> objectMapper.readValue(json, InstanceRegistrationUpdatedEvent.class)) + .isInstanceOf(JsonMappingException.class).hasCauseInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("must not be empty"); + } + + @Test + public void verifySerialize() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + Registration registration = Registration.create("test", "http://localhost:9080/heath") + .managementUrl("http://localhost:9080/").serviceUrl("http://localhost:8080/").source("http-api") + .metadata("PASSWORD", "qwertz123").metadata("user", "humptydumpty").build(); + + InstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 12345678L, timestamp, + registration); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTRATION_UPDATED"); + assertThat(jsonContent).extractingJsonPathValue("$.registration").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.name").isEqualTo("test"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.managementUrl") + .isEqualTo("http://localhost:9080/"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.healthUrl") + .isEqualTo("http://localhost:9080/heath"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.serviceUrl") + .isEqualTo("http://localhost:8080/"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.source").isEqualTo("http-api"); + assertThat(jsonContent).extractingJsonPathMapValue("$.registration.metadata") + .containsExactly(entry("PASSWORD", "******"), entry("user", "humptydumpty")); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + Registration registration = Registration.create("test", "http://localhost:9080/heath").build(); + + InstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 0L, timestamp, + registration); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTRATION_UPDATED"); + assertThat(jsonContent).extractingJsonPathValue("$.registration").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.name").isEqualTo("test"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.managementUrl").isNull(); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.healthUrl") + .isEqualTo("http://localhost:9080/heath"); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.serviceUrl").isNull(); + assertThat(jsonContent).extractingJsonPathStringValue("$.registration.source").isNull(); + assertThat(jsonContent).extractingJsonPathMapValue("$.registration.metadata").isEmpty(); + } + + @Test + public void verifySerializeWithoutRegistration() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + InstanceRegistrationUpdatedEvent event = new InstanceRegistrationUpdatedEvent(id, 12345678L, timestamp, + null); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("REGISTRATION_UPDATED"); + assertThat(jsonContent).extractingJsonPathMapValue("$.registration").isNull(); + } + + } + + @Nested + public class InstanceStatusChangedEventTests { + + private JacksonTester jsonTester; + + @BeforeEach + public void setup() { + JacksonTester.initFields(this, objectMapper); + } + + @Test + public void verifyDeserialize() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "STATUS_CHANGED") + .put("statusInfo", new JSONObject().put("status", "OFFLINE").put("details", + new JSONObject().put("foo", "bar"))) + .toString(); + + InstanceStatusChangedEvent event = objectMapper.readValue(json, InstanceStatusChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + StatusInfo statusInfo = event.getStatusInfo(); + assertThat(statusInfo).isNotNull(); + assertThat(statusInfo.getStatus()).isEqualTo("OFFLINE"); + assertThat(statusInfo.getDetails()).containsExactly(entry("foo", "bar")); + } + + @Test + public void verifyDeserializeWithOnlyRequiredProperties() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("timestamp", 1587751031.000000000) + .put("type", "STATUS_CHANGED").put("statusInfo", new JSONObject().put("status", "OFFLINE")) + .toString(); + + InstanceStatusChangedEvent event = objectMapper.readValue(json, InstanceStatusChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(0L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + + StatusInfo statusInfo = event.getStatusInfo(); + assertThat(statusInfo).isNotNull(); + assertThat(statusInfo.getStatus()).isEqualTo("OFFLINE"); + assertThat(statusInfo.getDetails()).isEmpty(); + } + + @Test + public void verifyDeserializeWithoutStatusInfo() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "STATUS_CHANGED").toString(); + + InstanceStatusChangedEvent event = objectMapper.readValue(json, InstanceStatusChangedEvent.class); + assertThat(event).isNotNull(); + assertThat(event.getInstance()).isEqualTo(InstanceId.of("test123")); + assertThat(event.getVersion()).isEqualTo(12345678L); + assertThat(event.getTimestamp()) + .isEqualTo(Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS)); + assertThat(event.getStatusInfo()).isNull(); + } + + @Test + public void verifyDeserializeWithEmptyStatusInfo() throws JSONException, JsonProcessingException { + String json = new JSONObject().put("instance", "test123").put("version", 12345678L) + .put("timestamp", 1587751031.000000000).put("type", "STATUS_CHANGED") + .put("statusInfo", new JSONObject()).toString(); + + assertThatThrownBy(() -> objectMapper.readValue(json, InstanceStatusChangedEvent.class)) + .isInstanceOf(JsonMappingException.class).hasCauseInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("must not be empty"); + } + + @Test + public void verifySerialize() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + StatusInfo statusInfo = StatusInfo.valueOf("OFFLINE", Collections.singletonMap("foo", "bar")); + + InstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 12345678L, timestamp, statusInfo); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("STATUS_CHANGED"); + assertThat(jsonContent).extractingJsonPathValue("$.statusInfo").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.statusInfo.status").isEqualTo("OFFLINE"); + assertThat(jsonContent).extractingJsonPathMapValue("$.statusInfo.details") + .containsExactly(entry("foo", "bar")); + } + + @Test + public void verifySerializeWithOnlyRequiredProperties() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + StatusInfo statusInfo = StatusInfo.valueOf("OFFLINE"); + + InstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 0L, timestamp, statusInfo); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(0); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("STATUS_CHANGED"); + assertThat(jsonContent).extractingJsonPathValue("$.statusInfo").isNotNull(); + + assertThat(jsonContent).extractingJsonPathStringValue("$.statusInfo.status").isEqualTo("OFFLINE"); + assertThat(jsonContent).extractingJsonPathMapValue("$.statusInfo.details").isEmpty(); + } + + @Test + public void verifySerializeWithoutStatusInfo() throws IOException { + InstanceId id = InstanceId.of("test123"); + Instant timestamp = Instant.ofEpochSecond(1587751031).truncatedTo(ChronoUnit.SECONDS); + + InstanceStatusChangedEvent event = new InstanceStatusChangedEvent(id, 12345678L, timestamp, null); + + JsonContent jsonContent = jsonTester.write(event); + assertThat(jsonContent).extractingJsonPathStringValue("$.instance").isEqualTo("test123"); + assertThat(jsonContent).extractingJsonPathNumberValue("$.version").isEqualTo(12345678); + assertThat(jsonContent).extractingJsonPathNumberValue("$.timestamp").isEqualTo(1587751031.000000000); + assertThat(jsonContent).extractingJsonPathStringValue("$.type").isEqualTo("STATUS_CHANGED"); + assertThat(jsonContent).extractingJsonPathValue("$.statusInfo").isNull(); + } + + } + +}