All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.net.causal.shoelaces.jersey.common.AbstractJacksonDateTimeParamConverterProvider Maven / Gradle / Ivy

The newest version!
package au.net.causal.shoelaces.jersey.common;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Set;

/**
 * Param converter that uses Jackson for parsing date and time types.
 * 

* * This class is abstract - subclasses supply a Jackson object mapper that is used for value conversion. * * TODO describe how format can be customized with @JsonFormat */ public abstract class AbstractJacksonDateTimeParamConverterProvider implements ParamConverterProvider { private static final Set> DEFAULT_HANDLED_TYPES = Set.of( LocalDate.class, LocalTime.class, LocalDateTime.class, ZonedDateTime.class, OffsetDateTime.class, OffsetTime.class, Instant.class, YearMonth.class, MonthDay.class, Year.class, Period.class, Duration.class, ZoneId.class, ZoneOffset.class ); private final Set> handledTypes; protected AbstractJacksonDateTimeParamConverterProvider(Set> handledTypes) { this.handledTypes = Set.copyOf(handledTypes); } protected AbstractJacksonDateTimeParamConverterProvider() { this(DEFAULT_HANDLED_TYPES); } /** * Obtains a Jackson object mapper suitable for converting a type. *

* * Typically, subclasses will just use a single object mapper for all types, however sometimes it might be desirable to override the object mapper for * a particular type of parameter configured with certain annotations. * * @param rawType the raw type of the object to be converted. * @param genericType the type of object to be converted. E.g. if an String value * representing the injected request parameter * is to be converted into a method parameter, this will be the * formal type of the method parameter as returned by {@code Class.getGenericParameterTypes}. * @param annotations an array of the annotations associated with the convertible * parameter instance. E.g. if a string value is to be converted into a method parameter, * this would be the annotations on that parameter as returned by * {@link java.lang.reflect.Method#getParameterAnnotations}. * * @return a Jackson object mapper that is used for value conversion. */ protected abstract ObjectMapper objectMapper(Class rawType, Type genericType, Annotation[] annotations); @Override public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { if (handledTypes.contains(rawType)) { ObjectMapper objectMapper = objectMapper(rawType, genericType, annotations); //Attempt to find JsonFormat annotation JsonFormat jsonFormatAnnotation = findFirstInstance(annotations, JsonFormat.class); if (jsonFormatAnnotation != null) { //Don't modify original object mapper if it is being reconfigured objectMapper = objectMapper.copy(); objectMapper.configOverride(rawType).setFormat(new JsonFormat.Value(jsonFormatAnnotation)); } final ObjectMapper fObjectMapper = objectMapper; return new ParamConverter<>() { @Override public T fromString(String value) { //First attempt to parse value as string try { return fObjectMapper.convertValue(value, rawType); } catch (IllegalArgumentException e) { //Could not parse as string, try to parse as JSON (maybe a number / timestamp?) try { return fObjectMapper.readValue(value, rawType); } catch (JsonProcessingException ex) { //Parse as raw JSON failed as well, throw original error but add this one as suppressed e.addSuppressed(ex); throw createExceptionFromJsonError(value, e); } } } @Override public String toString(T value) { try { return fObjectMapper.writeValueAsString(value); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } }; } //Param converter can't handle this type return null; } /** * Finds the first object in an array of supertypes that implements/extends a target type. * * @param values an array of values. * @param targetType search for an object of this type in the array of values. * @param element type of the array to search. * @param target type to search for. * * @return the first element in the values array that is of the target type, or null if none was found. */ static T findFirstInstance(A[] values, Class targetType) { for (A value : values) { if (targetType.isInstance(value)) return targetType.cast(value); } return null; } /** * Converts a JSON processing/parsing error into a JAX-RS or other runtime error. *

* * By default, this method generates a JAX-RS web application exception subclass with an HTTP bad request error code. Subclasses may override this method to customize the * error code or other aspects of the exception when JSON parsing fails. * * @param e the JSON error to convert. * * @return a JAX-RS exception. * * @see WebApplicationParamParsingException */ protected RuntimeException createExceptionFromJsonError(String rawParameterValue, Exception e) { throw new WebApplicationParamParsingException(e, Response.Status.BAD_REQUEST, rawParameterValue); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy