Skip to content

Commit

Permalink
fix: add support for java8 date time in signals
Browse files Browse the repository at this point in the history
Fixes #2891
  • Loading branch information
taefi committed Nov 8, 2024
1 parent c1245de commit 3ac14fa
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@

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;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
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;
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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;

Expand All @@ -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);
}
Expand Down Expand Up @@ -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<String, Object> serializationData)
throws JsonProcessingException {
return endpointMapper.writeValueAsString(serializationData);
return endpointObjectMapper.writeValueAsString(serializationData);
}

EndpointAccessChecker getAccessChecker() {
Expand All @@ -242,7 +223,7 @@ EndpointAccessChecker getAccessChecker() {

String writeValueAsString(Object returnValue)
throws JsonProcessingException {
return endpointMapper.writeValueAsString(returnValue);
return endpointObjectMapper.writeValueAsString(returnValue);
}

private List<ValidationErrorData> createBeanValidationErrors(
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static Optional<EventType> find(String type) {
}
}

static final ObjectMapper MAPPER = new ObjectMapper();
static ObjectMapper MAPPER;

private final String id;
private final EventType eventType;
Expand Down Expand Up @@ -101,6 +101,10 @@ public StateEvent(ObjectNode json, Class<T> valueType) {
this.expected = convertValue(expected, valueType);
}

public static void setMapper(ObjectMapper mapper) {
MAPPER = mapper;
}

public static <X> X convertValue(JsonNode rawValue, Class<X> valueType) {
if (rawValue == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -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));
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1296,10 +1297,12 @@ private <T> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.vaadin.hilla;

import com.vaadin.hilla.parser.jackson.JacksonObjectMapperFactory;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;

Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 3ac14fa

Please sign in to comment.