From 3ac14faa3145e0b8aebad19d7fe24d7cc918e047 Mon Sep 17 00:00:00 2001 From: taefi Date: Fri, 8 Nov 2024 18:49:49 +0100 Subject: [PATCH 1/4] fix: add support for java8 date time in signals Fixes #2891 --- .../com/vaadin/hilla/EndpointController.java | 1 + .../EndpointControllerConfiguration.java | 47 +++++++++++++++-- .../com/vaadin/hilla/EndpointInvoker.java | 37 ++++--------- .../signals/config/SignalsConfiguration.java | 6 ++- .../hilla/signals/core/event/StateEvent.java | 6 ++- .../hilla/EndpointControllerDauTest.java | 2 +- .../hilla/EndpointControllerMockBuilder.java | 30 ++++++++++- .../vaadin/hilla/EndpointControllerTest.java | 13 +++-- .../com/vaadin/hilla/EndpointInvokerTest.java | 4 +- .../vaadin/hilla/signals/ListSignalTest.java | 20 +++++++ .../com/vaadin/hilla/signals/Message.java | 6 +++ .../hilla/signals/NumberSignalTest.java | 20 +++++++ .../vaadin/hilla/signals/ValueSignalTest.java | 52 +++++++++++++++++++ .../core/event/ListStateEventTest.java | 20 +++++++ .../signals/core/event/StateEventTest.java | 20 +++++++ .../signals/handler/SignalsHandlerTest.java | 20 +++++++ 16 files changed, 261 insertions(+), 43 deletions(-) create mode 100644 packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/Message.java diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java index 125e36c758..ebead999ed 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java @@ -82,6 +82,7 @@ public class EndpointController { * A qualifier to override the request and response default json mapper. */ public static final String ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER = "endpointMapperFactory"; + public static final String ENDPOINT_OBJECT_MAPPER_BEAN_QUALIFIER = "endpointObjectMapper"; private static final String SIGNALS_HANDLER_BEAN_NAME = "signalsHandler"; diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java index 287f93afd5..454a1a891a 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java @@ -18,6 +18,8 @@ import java.lang.reflect.Method; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vaadin.hilla.endpointransfermapper.EndpointTransferMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -25,6 +27,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.util.pattern.PathPatternParser; @@ -43,7 +46,9 @@ */ @Configuration public class EndpointControllerConfiguration { + private static final EndpointTransferMapper ENDPOINT_TRANSFER_MAPPER = new EndpointTransferMapper(); private final EndpointProperties endpointProperties; + private ObjectMapper endpointMapper; /** * Initializes the endpoint configuration. @@ -94,16 +99,50 @@ CsrfChecker csrfChecker(ServletContext servletContext) { } /** - * Registers the endpoint invoker. + * Creates ObjectMapper instance that is used for Hilla endpoints' + * serializing and deserializing request and response bodies. * * @param applicationContext * The Spring application context * @param endpointMapperFactory - * optional bean to override the default + * optional factory bean to override the default * {@link JacksonObjectMapperFactory} that is used for * serializing and deserializing request and response bodies Use * {@link EndpointController#ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER} * qualifier to override the mapper. + */ + @Qualifier(EndpointController.ENDPOINT_OBJECT_MAPPER_BEAN_QUALIFIER) + @Bean + ObjectMapper endpointObjectMapper(ApplicationContext applicationContext, + @Autowired(required = false) @Qualifier(EndpointController.ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER) JacksonObjectMapperFactory endpointMapperFactory) { + if (this.endpointMapper == null) { + this.endpointMapper = endpointMapperFactory != null + ? endpointMapperFactory.build() + : createDefaultEndpointMapper(applicationContext); + if (this.endpointMapper != null) { + this.endpointMapper.registerModule( + ENDPOINT_TRANSFER_MAPPER.getJacksonModule()); + } + } + return this.endpointMapper; + } + + private static ObjectMapper createDefaultEndpointMapper( + ApplicationContext applicationContext) { + var endpointMapper = new JacksonObjectMapperFactory.Json().build(); + applicationContext.getBean(Jackson2ObjectMapperBuilder.class) + .configure(endpointMapper); + return endpointMapper; + } + + /** + * Registers the endpoint invoker. + * + * @param applicationContext + * The Spring application context + * @param endpointObjectMapper + * ObjectMapper instance that is used for Hilla endpoints' + * serializing and deserializing request and response bodies. * @param explicitNullableTypeChecker * the method parameter and return value type checker to verify * that null values are explicit @@ -116,10 +155,10 @@ CsrfChecker csrfChecker(ServletContext servletContext) { */ @Bean EndpointInvoker endpointInvoker(ApplicationContext applicationContext, - @Autowired(required = false) @Qualifier(EndpointController.ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER) JacksonObjectMapperFactory endpointMapperFactory, + ObjectMapper endpointObjectMapper, ExplicitNullableTypeChecker explicitNullableTypeChecker, ServletContext servletContext, EndpointRegistry endpointRegistry) { - return new EndpointInvoker(applicationContext, endpointMapperFactory, + return new EndpointInvoker(applicationContext, endpointObjectMapper, explicitNullableTypeChecker, servletContext, endpointRegistry); } diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointInvoker.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointInvoker.java index 163b4762b6..6ee5c07f52 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointInvoker.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointInvoker.java @@ -27,7 +27,6 @@ import com.vaadin.hilla.EndpointInvocationException.EndpointNotFoundException; import com.vaadin.hilla.EndpointRegistry.VaadinEndpointData; import com.vaadin.hilla.auth.EndpointAccessChecker; -import com.vaadin.hilla.endpointransfermapper.EndpointTransferMapper; import com.vaadin.hilla.exception.EndpointException; import com.vaadin.hilla.exception.EndpointValidationException; import com.vaadin.hilla.exception.EndpointValidationException.ValidationErrorData; @@ -40,7 +39,6 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.lang.NonNullApi; import org.springframework.util.ClassUtils; @@ -72,10 +70,8 @@ * For internal use only. May be renamed or removed in a future release. */ public class EndpointInvoker { - - private static final EndpointTransferMapper endpointTransferMapper = new EndpointTransferMapper(); private final ApplicationContext applicationContext; - private final ObjectMapper endpointMapper; + private final ObjectMapper endpointObjectMapper; private final EndpointRegistry endpointRegistry; private final ExplicitNullableTypeChecker explicitNullableTypeChecker; private final ServletContext servletContext; @@ -86,7 +82,7 @@ public class EndpointInvoker { * * @param applicationContext * The Spring application context - * @param endpointMapperFactory + * @param endpointObjectMapper * optional factory bean to override the default * {@link JacksonObjectMapperFactory} that is used for * serializing and deserializing request and response bodies Use @@ -101,18 +97,12 @@ public class EndpointInvoker { * the registry used to store endpoint information */ public EndpointInvoker(ApplicationContext applicationContext, - JacksonObjectMapperFactory endpointMapperFactory, + ObjectMapper endpointObjectMapper, ExplicitNullableTypeChecker explicitNullableTypeChecker, ServletContext servletContext, EndpointRegistry endpointRegistry) { this.applicationContext = applicationContext; this.servletContext = servletContext; - this.endpointMapper = endpointMapperFactory != null - ? endpointMapperFactory.build() - : createDefaultEndpointMapper(applicationContext); - if (this.endpointMapper != null) { - this.endpointMapper - .registerModule(endpointTransferMapper.getJacksonModule()); - } + this.endpointObjectMapper = endpointObjectMapper; this.explicitNullableTypeChecker = explicitNullableTypeChecker; this.endpointRegistry = endpointRegistry; @@ -128,15 +118,6 @@ public EndpointInvoker(ApplicationContext applicationContext, : validator; } - private static ObjectMapper createDefaultEndpointMapper( - ApplicationContext applicationContext) { - var endpointMapper = new JacksonObjectMapperFactory.Json().build(); - applicationContext.getBean(Jackson2ObjectMapperBuilder.class) - .configure(endpointMapper); - - return endpointMapper; - } - private static Logger getLogger() { return LoggerFactory.getLogger(EndpointInvoker.class); } @@ -218,14 +199,14 @@ public VaadinEndpointData getVaadinEndpointData(String endpointName) } String createResponseErrorObject(String errorMessage) { - ObjectNode objectNode = endpointMapper.createObjectNode(); + ObjectNode objectNode = endpointObjectMapper.createObjectNode(); objectNode.put(EndpointException.ERROR_MESSAGE_FIELD, errorMessage); return objectNode.toString(); } String createResponseErrorObject(Map serializationData) throws JsonProcessingException { - return endpointMapper.writeValueAsString(serializationData); + return endpointObjectMapper.writeValueAsString(serializationData); } EndpointAccessChecker getAccessChecker() { @@ -242,7 +223,7 @@ EndpointAccessChecker getAccessChecker() { String writeValueAsString(Object returnValue) throws JsonProcessingException { - return endpointMapper.writeValueAsString(returnValue); + return endpointObjectMapper.writeValueAsString(returnValue); } private List createBeanValidationErrors( @@ -343,8 +324,8 @@ private Object[] getVaadinEndpointParameters( Type parameterType = javaParameters[i]; Type incomingType = parameterType; try { - Object parameter = endpointMapper - .readerFor(endpointMapper.getTypeFactory() + Object parameter = endpointObjectMapper + .readerFor(endpointObjectMapper.getTypeFactory() .constructType(incomingType)) .readValue(requestParameters.get(parameterNames[i])); endpointParameters[i] = parameter; diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java index 697bda7a5d..ce2096aee8 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java @@ -1,7 +1,9 @@ package com.vaadin.hilla.signals.config; +import com.fasterxml.jackson.databind.ObjectMapper; import com.vaadin.hilla.ConditionalOnFeatureFlag; import com.vaadin.hilla.EndpointInvoker; +import com.vaadin.hilla.signals.core.event.StateEvent; import com.vaadin.hilla.signals.core.registry.SecureSignalsRegistry; import com.vaadin.hilla.signals.handler.SignalsHandler; import org.springframework.context.annotation.Bean; @@ -17,8 +19,10 @@ public class SignalsConfiguration { private SignalsHandler signalsHandler; private final EndpointInvoker endpointInvoker; - public SignalsConfiguration(EndpointInvoker endpointInvoker) { + public SignalsConfiguration(EndpointInvoker endpointInvoker, + ObjectMapper endpointObjectMapper) { this.endpointInvoker = endpointInvoker; + StateEvent.setMapper(endpointObjectMapper); } /** diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java index 1eb1e248cd..0671d79b24 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java @@ -45,7 +45,7 @@ public static Optional find(String type) { } } - static final ObjectMapper MAPPER = new ObjectMapper(); + static ObjectMapper MAPPER; private final String id; private final EventType eventType; @@ -101,6 +101,10 @@ public StateEvent(ObjectNode json, Class valueType) { this.expected = convertValue(expected, valueType); } + public static void setMapper(ObjectMapper mapper) { + MAPPER = mapper; + } + public static X convertValue(JsonNode rawValue, Class valueType) { if (rawValue == null) { return null; diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerDauTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerDauTest.java index fbef55a2c5..df92783dd5 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerDauTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerDauTest.java @@ -58,7 +58,7 @@ public void setUp() { new EndpointNameChecker()); ApplicationContext appCtx = Mockito.mock(ApplicationContext.class); EndpointInvoker endpointInvoker = new EndpointInvoker(appCtx, - new JacksonObjectMapperFactory.Json(), + new JacksonObjectMapperFactory.Json().build(), new ExplicitNullableTypeChecker(), servletContext, endpointRegistry); controller = new EndpointController(appCtx, endpointRegistry, diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerMockBuilder.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerMockBuilder.java index f212937c6d..12e0040cb1 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerMockBuilder.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerMockBuilder.java @@ -2,6 +2,8 @@ import static org.mockito.Mockito.mock; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vaadin.hilla.endpointransfermapper.EndpointTransferMapper; import org.mockito.Mockito; import org.springframework.context.ApplicationContext; @@ -10,11 +12,13 @@ import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import jakarta.servlet.ServletContext; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /** * A helper class to build a mocked EndpointController. */ public class EndpointControllerMockBuilder { + private static final EndpointTransferMapper ENDPOINT_TRANSFER_MAPPER = new EndpointTransferMapper(); private ApplicationContext applicationContext; private EndpointNameChecker endpointNameChecker = mock( EndpointNameChecker.class); @@ -28,8 +32,10 @@ public EndpointController build() { ServletContext servletContext = Mockito.mock(ServletContext.class); Mockito.when(csrfChecker.validateCsrfTokenInRequest(Mockito.any())) .thenReturn(true); - EndpointInvoker invoker = Mockito - .spy(new EndpointInvoker(applicationContext, factory, + ObjectMapper endpointObjectMapper = createEndpointObjectMapper( + applicationContext, factory); + EndpointInvoker invoker = Mockito.spy( + new EndpointInvoker(applicationContext, endpointObjectMapper, explicitNullableTypeChecker, servletContext, registry)); EndpointController controller = Mockito.spy(new EndpointController( applicationContext, registry, invoker, csrfChecker)); @@ -38,6 +44,26 @@ public EndpointController build() { return controller; } + public static ObjectMapper createEndpointObjectMapper( + ApplicationContext applicationContext, + JacksonObjectMapperFactory factory) { + ObjectMapper endpointObjectMapper = factory != null ? factory.build() + : createDefaultEndpointMapper(applicationContext); + if (endpointObjectMapper != null) { + endpointObjectMapper.registerModule( + ENDPOINT_TRANSFER_MAPPER.getJacksonModule()); + } + return endpointObjectMapper; + } + + private static ObjectMapper createDefaultEndpointMapper( + ApplicationContext applicationContext) { + var endpointMapper = new JacksonObjectMapperFactory.Json().build(); + applicationContext.getBean(Jackson2ObjectMapperBuilder.class) + .configure(endpointMapper); + return endpointMapper; + } + public EndpointControllerMockBuilder withApplicationContext( ApplicationContext applicationContext) { this.applicationContext = applicationContext; diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerTest.java index 11c8ed3be4..126ebbce2d 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointControllerTest.java @@ -819,9 +819,10 @@ public void should_Never_UseSpringObjectMapper() { .thenReturn(Collections.emptyMap()); EndpointRegistry registry = new EndpointRegistry( mock(EndpointNameChecker.class)); - - EndpointInvoker invoker = new EndpointInvoker(contextMock, null, - mock(ExplicitNullableTypeChecker.class), + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(contextMock, null); + EndpointInvoker invoker = new EndpointInvoker(contextMock, + endpointObjectMapper, mock(ExplicitNullableTypeChecker.class), mock(ServletContext.class), registry); new EndpointController(contextMock, registry, invoker, null) @@ -1296,10 +1297,12 @@ private EndpointController createVaadinController(T endpoint, ApplicationContext mockApplicationContext = mockApplicationContext( endpoint); EndpointRegistry registry = new EndpointRegistry(endpointNameChecker); - + ObjectMapper endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(mockApplicationContext, + endpointMapperFactory); EndpointInvoker invoker = Mockito .spy(new EndpointInvoker(mockApplicationContext, - endpointMapperFactory, explicitNullableTypeChecker, + endpointObjectMapper, explicitNullableTypeChecker, mock(ServletContext.class), registry)); Mockito.doReturn(accessChecker).when(invoker).getAccessChecker(); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointInvokerTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointInvokerTest.java index 643e5bbb3c..9dbfd4c8a0 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointInvokerTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/EndpointInvokerTest.java @@ -1,5 +1,6 @@ package com.vaadin.hilla; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; @@ -66,7 +67,8 @@ public void setUp() { endpointRegistry = new EndpointRegistry(endpointNameChecker); - endpointInvoker = new EndpointInvoker(applicationContext, null, + endpointInvoker = new EndpointInvoker(applicationContext, + new JacksonObjectMapperFactory.Json().build(), explicitNullableTypeChecker, servletContext, endpointRegistry) { protected EndpointAccessChecker getAccessChecker() { return endpointAccessChecker; diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java index e445624139..fd45bf0142 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java @@ -3,12 +3,18 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import com.vaadin.hilla.signals.core.event.ListStateEvent; import com.vaadin.hilla.signals.core.event.StateEvent; import com.vaadin.hilla.signals.core.event.MissingFieldException; import jakarta.annotation.Nullable; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import reactor.core.publisher.Flux; import java.util.ArrayList; @@ -88,6 +94,20 @@ public int hashCode() { } } + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Test public void constructor_withNullArgs_doesNotAcceptNull() { assertThrows(NullPointerException.class, () -> new ListSignal<>(null)); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/Message.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/Message.java new file mode 100644 index 0000000000..7623cb10a2 --- /dev/null +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/Message.java @@ -0,0 +1,6 @@ +package com.vaadin.hilla.signals; + +import java.time.LocalDateTime; + +public record Message(String text, String author, LocalDateTime timestamp) { +} diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java index 5a675ae99b..6b730e6f7d 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java @@ -2,9 +2,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import com.vaadin.hilla.signals.core.event.StateEvent; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import reactor.core.publisher.Flux; import java.util.UUID; @@ -19,6 +25,20 @@ public class NumberSignalTest { private final ObjectMapper mapper = new ObjectMapper(); + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Test public void constructor_withValueArg_usesValueAsDefaultValue() { NumberSignal signal = new NumberSignal(42.0); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java index 33d2a6b1dd..cc67f14b23 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java @@ -1,12 +1,19 @@ package com.vaadin.hilla.signals; +import java.time.LocalDateTime; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import com.vaadin.hilla.signals.core.event.StateEvent; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import reactor.core.publisher.Flux; import org.junit.Assert; @@ -23,6 +30,20 @@ public class ValueSignalTest { private final ObjectMapper mapper = new ObjectMapper(); + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Test public void constructor_withValueArg_usesValueAsDefaultValue() { var numberValueSignal = new ValueSignal<>(42.0, Double.class); @@ -119,6 +140,37 @@ public void submit_notifies_subscribers() { assertEquals(2, counter.get()); } + @Test + public void serialization_for_java8_datetime_is_supported() { + var currentTimestamp = LocalDateTime.now(); + var signal = new ValueSignal<>( + new Message("Hi", "John Doe", currentTimestamp), Message.class); + var flux = signal.subscribe(); + AtomicInteger counter = new AtomicInteger(0); + var laterTimestamp = LocalDateTime.now(); + flux.subscribe(eventJson -> { + if (counter.get() == 0) { + assertNotNull(eventJson); + var stateEvent = new StateEvent<>(eventJson, Message.class); + assertEquals("Hi", stateEvent.getValue().text()); + assertEquals("John Doe", stateEvent.getValue().author()); + assertEquals(currentTimestamp, + stateEvent.getValue().timestamp()); + } else if (counter.get() == 1) { + var stateEvent = new StateEvent<>(eventJson, Message.class); + assertEquals("Hey", stateEvent.getValue().text()); + assertEquals("Jane Smith", stateEvent.getValue().author()); + assertEquals(laterTimestamp, stateEvent.getValue().timestamp()); + } + counter.incrementAndGet(); + }); + + signal.submit(createSetEvent( + new Message("Hey", "Jane Smith", laterTimestamp))); + + assertEquals(2, counter.get()); + } + @Test public void submit_conditionIsMet_notifies_subscribers_with_snapshot_event() { var signal = new ValueSignal<>(2.0, Double.class); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/ListStateEventTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/ListStateEventTest.java index 4b9f6c6bc1..7393da452c 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/ListStateEventTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/ListStateEventTest.java @@ -2,7 +2,11 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import com.vaadin.hilla.signals.ValueSignal; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import java.util.Arrays; @@ -11,6 +15,8 @@ import java.util.UUID; import com.vaadin.hilla.signals.Person; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import static org.junit.Assert.*; @@ -18,6 +24,20 @@ public class ListStateEventTest { + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Test public void constructor_withEntries_shouldCreateListStateEvent() { String id = UUID.randomUUID().toString(); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java index 83a08c8d87..f43b37e013 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/core/event/StateEventTest.java @@ -1,11 +1,17 @@ package com.vaadin.hilla.signals.core.event; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import java.util.UUID; import com.vaadin.hilla.signals.Person; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -17,6 +23,20 @@ public class StateEventTest { + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Test public void constructor_withoutExpected_shouldCreateStateEvent() { String id = UUID.randomUUID().toString(); diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java index b21872eabb..5db5602dd5 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java @@ -2,13 +2,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.EndpointControllerMockBuilder; +import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory; import com.vaadin.hilla.signals.NumberSignal; import com.vaadin.hilla.signals.Signal; import com.vaadin.hilla.signals.core.event.ListStateEvent; +import com.vaadin.hilla.signals.core.event.StateEvent; import com.vaadin.hilla.signals.core.registry.SecureSignalsRegistry; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; @@ -28,6 +34,20 @@ public class SignalsHandlerTest { private SignalsHandler signalsHandler; private SecureSignalsRegistry signalsRegistry; + @BeforeClass + public static void setup() { + var appCtx = Mockito.mock(ApplicationContext.class); + var endpointObjectMapper = EndpointControllerMockBuilder + .createEndpointObjectMapper(appCtx, + new JacksonObjectMapperFactory.Json()); + StateEvent.setMapper(endpointObjectMapper); + } + + @AfterClass + public static void tearDown() { + StateEvent.setMapper(null); + } + @Before public void setUp() { signalsRegistry = Mockito.mock(SecureSignalsRegistry.class); From 48ceec3056ae4dcb0622fcecc827e844d23fac0b Mon Sep 17 00:00:00 2001 From: taefi Date: Sat, 9 Nov 2024 00:47:32 +0100 Subject: [PATCH 2/4] remove unnecessary qualifier --- .../src/main/java/com/vaadin/hilla/EndpointController.java | 1 - .../java/com/vaadin/hilla/EndpointControllerConfiguration.java | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java index ebead999ed..125e36c758 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointController.java @@ -82,7 +82,6 @@ public class EndpointController { * A qualifier to override the request and response default json mapper. */ public static final String ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER = "endpointMapperFactory"; - public static final String ENDPOINT_OBJECT_MAPPER_BEAN_QUALIFIER = "endpointObjectMapper"; private static final String SIGNALS_HANDLER_BEAN_NAME = "signalsHandler"; diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java index 454a1a891a..bba4523d10 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/EndpointControllerConfiguration.java @@ -111,7 +111,6 @@ CsrfChecker csrfChecker(ServletContext servletContext) { * {@link EndpointController#ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER} * qualifier to override the mapper. */ - @Qualifier(EndpointController.ENDPOINT_OBJECT_MAPPER_BEAN_QUALIFIER) @Bean ObjectMapper endpointObjectMapper(ApplicationContext applicationContext, @Autowired(required = false) @Qualifier(EndpointController.ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER) JacksonObjectMapperFactory endpointMapperFactory) { From fd46d35452fba1e02d459ddbb3f5e7a4183c6ec3 Mon Sep 17 00:00:00 2001 From: taefi Date: Sat, 9 Nov 2024 01:33:11 +0100 Subject: [PATCH 3/4] introduce API to set ObjectMapper in Signal library --- .../java/com/vaadin/hilla/signals/Signal.java | 20 +++++++++++++++++++ .../signals/config/SignalsConfiguration.java | 4 ++-- .../vaadin/hilla/signals/ListSignalTest.java | 4 ++-- .../hilla/signals/NumberSignalTest.java | 4 ++-- .../vaadin/hilla/signals/ValueSignalTest.java | 4 ++-- .../signals/handler/SignalsHandlerTest.java | 5 ++--- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/Signal.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/Signal.java index dacbe425a6..283386fa02 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/Signal.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/Signal.java @@ -1,6 +1,8 @@ package com.vaadin.hilla.signals; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.vaadin.hilla.signals.core.event.StateEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -145,4 +147,22 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hashCode(getId()); } + + /** + * Sets the object mapper to be used for JSON serialization in Signals. This + * is helpful for testing purposes. If not set, the default Hilla endpoint + * object mapper is used. + *

+ * Note: If a custom endpointMapperFactory bean defined + * using the + * {@code EndpointController.ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER} + * qualifier, the mapper from that factory is used also in Signals, and + * there is no need to set it manually here. + * + * @param mapper + * the object mapper to be used in Signals + */ + public static void setMapper(ObjectMapper mapper) { + StateEvent.setMapper(mapper); + } } diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java index ce2096aee8..862bec0575 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/config/SignalsConfiguration.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.vaadin.hilla.ConditionalOnFeatureFlag; import com.vaadin.hilla.EndpointInvoker; -import com.vaadin.hilla.signals.core.event.StateEvent; +import com.vaadin.hilla.signals.Signal; import com.vaadin.hilla.signals.core.registry.SecureSignalsRegistry; import com.vaadin.hilla.signals.handler.SignalsHandler; import org.springframework.context.annotation.Bean; @@ -22,7 +22,7 @@ public class SignalsConfiguration { public SignalsConfiguration(EndpointInvoker endpointInvoker, ObjectMapper endpointObjectMapper) { this.endpointInvoker = endpointInvoker; - StateEvent.setMapper(endpointObjectMapper); + Signal.setMapper(endpointObjectMapper); } /** diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java index fd45bf0142..a92d14e6f5 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ListSignalTest.java @@ -100,12 +100,12 @@ public static void setup() { var endpointObjectMapper = EndpointControllerMockBuilder .createEndpointObjectMapper(appCtx, new JacksonObjectMapperFactory.Json()); - StateEvent.setMapper(endpointObjectMapper); + Signal.setMapper(endpointObjectMapper); } @AfterClass public static void tearDown() { - StateEvent.setMapper(null); + Signal.setMapper(null); } @Test diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java index 6b730e6f7d..9a2aa454af 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/NumberSignalTest.java @@ -31,12 +31,12 @@ public static void setup() { var endpointObjectMapper = EndpointControllerMockBuilder .createEndpointObjectMapper(appCtx, new JacksonObjectMapperFactory.Json()); - StateEvent.setMapper(endpointObjectMapper); + Signal.setMapper(endpointObjectMapper); } @AfterClass public static void tearDown() { - StateEvent.setMapper(null); + Signal.setMapper(null); } @Test diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java index cc67f14b23..eb7d1d9ffe 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/ValueSignalTest.java @@ -36,12 +36,12 @@ public static void setup() { var endpointObjectMapper = EndpointControllerMockBuilder .createEndpointObjectMapper(appCtx, new JacksonObjectMapperFactory.Json()); - StateEvent.setMapper(endpointObjectMapper); + Signal.setMapper(endpointObjectMapper); } @AfterClass public static void tearDown() { - StateEvent.setMapper(null); + Signal.setMapper(null); } @Test diff --git a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java index 5db5602dd5..f117e056a7 100644 --- a/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java +++ b/packages/java/endpoint/src/test/java/com/vaadin/hilla/signals/handler/SignalsHandlerTest.java @@ -7,7 +7,6 @@ import com.vaadin.hilla.signals.NumberSignal; import com.vaadin.hilla.signals.Signal; import com.vaadin.hilla.signals.core.event.ListStateEvent; -import com.vaadin.hilla.signals.core.event.StateEvent; import com.vaadin.hilla.signals.core.registry.SecureSignalsRegistry; import org.junit.AfterClass; import org.junit.Before; @@ -40,12 +39,12 @@ public static void setup() { var endpointObjectMapper = EndpointControllerMockBuilder .createEndpointObjectMapper(appCtx, new JacksonObjectMapperFactory.Json()); - StateEvent.setMapper(endpointObjectMapper); + Signal.setMapper(endpointObjectMapper); } @AfterClass public static void tearDown() { - StateEvent.setMapper(null); + Signal.setMapper(null); } @Before From 34d0abf2faa1df62ede8df0806b826964cb57cac Mon Sep 17 00:00:00 2001 From: taefi Date: Sat, 9 Nov 2024 01:34:58 +0100 Subject: [PATCH 4/4] add/improve javadoc --- .../com/vaadin/hilla/signals/core/event/StateEvent.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java index 0671d79b24..d6a1e6b945 100644 --- a/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java +++ b/packages/java/endpoint/src/main/java/com/vaadin/hilla/signals/core/event/StateEvent.java @@ -101,6 +101,14 @@ public StateEvent(ObjectNode json, Class valueType) { this.expected = convertValue(expected, valueType); } + /** + * Sets the object mapper to be used for serialization and deserialization + * of state events in Signal library. + * + * @param mapper + * The object mapper to be used for serialization and + * deserialization of state events. + */ public static void setMapper(ObjectMapper mapper) { MAPPER = mapper; }