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

DEVEXP-648: Form parameters serializer #169

Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,11 @@
package com.sinch.sdk.domains.mailgun.api.v1.adapters;

import com.sinch.sdk.core.databind.FormSerializer;

public class DeliveryTimeFormSerializer extends FormSerializer<Integer> {

@Override
public String serialize(Integer in) {
return String.format("%dh", in);
}
}
6 changes: 6 additions & 0 deletions core/src/main/com/sinch/sdk/core/databind/FormSerializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.sinch.sdk.core.databind;

public abstract class FormSerializer<T> {

public abstract Object serialize(T in);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.sinch.sdk.core.databind.annotation;

import com.sinch.sdk.core.databind.FormSerializer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@SinchAnnotation
public @interface FormSerialize {

@SuppressWarnings("rawtypes") // to work around JDK8 bug wrt Class-valued annotation properties
Class<? extends FormSerializer> using();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sinch.sdk.core.databind.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@SinchAnnotation
public @interface PropertiesOrder {

String[] value() default {};
}
14 changes: 14 additions & 0 deletions core/src/main/com/sinch/sdk/core/databind/annotation/Property.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sinch.sdk.core.databind.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@SinchAnnotation
public @interface Property {

String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sinch.sdk.core.databind.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SinchAnnotation {}
127 changes: 127 additions & 0 deletions core/src/main/com/sinch/sdk/core/databind/multipart/ObjectMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.sinch.sdk.core.databind.multipart;

import com.sinch.sdk.core.databind.FormSerializer;
import com.sinch.sdk.core.databind.annotation.FormSerialize;
import com.sinch.sdk.core.databind.annotation.PropertiesOrder;
import com.sinch.sdk.core.databind.annotation.Property;
import com.sinch.sdk.core.exceptions.SerializationException;
import com.sinch.sdk.core.models.OptionalValue;
import com.sinch.sdk.core.utils.EnumDynamic;
import com.sinch.sdk.core.utils.Pair;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class ObjectMapper {

public Map<String, Object> serialize(Object value)
throws IntrospectionException,
InvocationTargetException,
IllegalAccessException,
ClassNotFoundException,
NoSuchMethodException,
InstantiationException {

BeanInfo beanInfo = Introspector.getBeanInfo(value.getClass(), Object.class);
List<Pair<String, Method>> serializableProperties = collectSerializableProperties(beanInfo);
return serializeProperties(serializableProperties, value);
}

private List<Pair<String, Method>> collectSerializableProperties(BeanInfo beanInfo) {

ArrayList<Pair<String, Method>> result = new ArrayList<>();
final MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();

for (MethodDescriptor methodDescriptor : methodDescriptors) {
getPropertyGetter(methodDescriptor.getMethod()).ifPresent(result::add);
}
return sortProperties(beanInfo, result);
}

private List<Pair<String, Method>> sortProperties(
BeanInfo beanInfo, List<Pair<String, Method>> properties) {

PropertiesOrder propertyOrder =
beanInfo.getBeanDescriptor().getBeanClass().getAnnotation(PropertiesOrder.class);

if (null == propertyOrder) {
return properties;
}

ArrayList<Pair<String, Method>> sorted = new ArrayList<>(properties.size());

for (String property : propertyOrder.value()) {
properties.stream()
.filter(p -> p.getLeft().equals(property))
.findFirst()
.ifPresent(sorted::add);
asein-sinch marked this conversation as resolved.
Show resolved Hide resolved
}
return sorted;
}

private Optional<Pair<String, Method>> getPropertyGetter(Method method) {

Property property = method.getDeclaredAnnotation(Property.class);
if (null == property) {
return Optional.empty();
}
return Optional.of(new Pair<>(property.value(), method));
}

private Map<String, Object> serializeProperties(
List<Pair<String, Method>> serializableProperties, Object object)
throws InvocationTargetException, IllegalAccessException {
Map<String, Object> properties = new LinkedHashMap<>();
for (Pair<String, Method> property : serializableProperties) {

serializeProperty(object, property.getRight())
.ifPresent(v -> properties.put(property.getLeft(), v));
}
return properties;
}

private OptionalValue<?> serializeProperty(Object object, Method method)
throws InvocationTargetException, IllegalAccessException {

OptionalValue<?> propertyValue = (OptionalValue<?>) method.invoke(object);

if (!propertyValue.isPresent() || null == propertyValue.get()) {
return propertyValue;
}

Object value = propertyValue.get();

FormSerialize formSerialize = method.getDeclaredAnnotation(FormSerialize.class);
if (null != formSerialize) {
value = handleOverriddenSerialization(formSerialize, value);
}

if (value instanceof EnumDynamic) {
EnumDynamic<?, ?> enumDynamic = (EnumDynamic<?, ?>) value;
return OptionalValue.of((enumDynamic.value().toString()));
}
return OptionalValue.of(value);
}

private Object handleOverriddenSerialization(FormSerialize formSerialize, Object value) {
try {
Class<?> clazz = Class.forName(formSerialize.using().getName());
@SuppressWarnings("unchecked")
Constructor<FormSerializer<Object>> ctor =
(Constructor<FormSerializer<Object>>) clazz.getConstructor();
FormSerializer<Object> serializer = ctor.newInstance();
return serializer.serialize(value);
} catch (Exception e) {
throw new SerializationException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.sinch.sdk.core.exceptions;

/**
* Exception related to serialization
*
* @since __TO_BE_DEFINED__
*/
public class SerializationException extends RuntimeException {

private static final long serialVersionUID = -1L;

/**
* Constructs a new exception with the specified cause and a detail message of (cause==null ? null
* : cause.toString()) (which typically contains the class and detail message of cause). This
* constructor is useful for exceptions that are little more than wrappers for other throwables
*
* @param throwable Cause
* @since __TO_BE_DEFINED__
*/
public SerializationException(Throwable throwable) {
super(null, throwable);
}

/**
* Constructs an SerializationException with the specified detail message.
*
* @param message the detail message.
* @since __TO_BE_DEFINED__
*/
public SerializationException(String message) {
super(message, null);
}
}
6 changes: 5 additions & 1 deletion core/src/main/com/sinch/sdk/core/http/HttpContentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class HttpContentType {
public static final String CONTENT_TYPE_HEADER = "content-type";
public static final String APPLICATION_JSON = "application/json";
public static final String TEXT_PLAIN = "text/plain";

public static final String MULTIPART_FORM_DATA = "multipart/form-data";
static Pattern charsetPattern = Pattern.compile("(.*;$)?\\s*?charset=\\s*([^;\\s]+)");

public static boolean isMimeJson(Collection<String> mimes) {
Expand All @@ -24,6 +24,10 @@ public static boolean isMimeTextPlain(Collection<String> mimes) {
return mimes.stream().anyMatch(TEXT_PLAIN::equalsIgnoreCase);
}

public static boolean isMimeMultiPartFormData(Collection<String> mimes) {
return null != mimes && mimes.stream().anyMatch(MULTIPART_FORM_DATA::equalsIgnoreCase);
}

public static Optional<String> getCharsetValue(String contentTypeHeader) {
if (StringUtil.isEmpty(contentTypeHeader)) {
return Optional.empty();
Expand Down
24 changes: 22 additions & 2 deletions core/src/main/com/sinch/sdk/core/http/HttpMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.core.utils.databind.MultiPartMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;

public class HttpMapper {

Expand Down Expand Up @@ -52,14 +54,32 @@ public String serialize(Collection<String> contentTypes, Object body) {
if (null == body) {
return null;
}

if (null == contentTypes || contentTypes.isEmpty() || isMimeJson(contentTypes)) {
try {
return Mapper.getInstance().writeValueAsString(body);
} catch (JsonProcessingException e) {
throw new ApiException(e);
}
}
throw new ApiException(
"Deserialization for content type '" + contentTypes + "' not supported ");

throw new ApiException("Serialization for content type '" + contentTypes + "' not supported ");
}

public Map<String, Object> serializeFormParameters(
Collection<String> contentTypes, Object parameters) {
if (null == parameters) {
return null;
}

if (isMimeMultiPartFormData(contentTypes)) {
try {
return MultiPartMapper.getInstance().serialize(parameters);
} catch (Exception e) {
throw new ApiException(e);
}
}

throw new ApiException("Serialization for content type '" + contentTypes + "' not supported ");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sinch.sdk.core.utils.databind;

import com.sinch.sdk.core.databind.multipart.ObjectMapper;

public class MultiPartMapper {

public static ObjectMapper getInstance() {
return LazyHolder.INSTANCE;
}

private MultiPartMapper() {}

private static class LazyHolder {

public static final ObjectMapper INSTANCE = new ObjectMapper();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sinch.sdk.core.utils.databind;

import com.sinch.sdk.core.databind.FormSerializer;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class RFC822FormSerializer extends FormSerializer<Instant> {

@Override
public String serialize(Instant in) {
return DateTimeFormatter.RFC_1123_DATE_TIME.format(in.atZone(ZoneId.of("UTC")));
}
}
Loading
Loading