Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add support for java8 date time in signals #2899

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,49 @@ 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.
*/
@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 +154,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,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;
Expand Down Expand Up @@ -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.
* <p>
* <strong>Note:</strong> 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);
}
}
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.Signal;
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;
Signal.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,18 @@ public StateEvent(ObjectNode json, Class<T> 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;
}

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
Loading
Loading