package restx.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import restx.AppSettings;
import restx.RestxContext;
import restx.entity.*;
import restx.factory.Module;
import restx.factory.Provides;
import javax.inject.Named;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Locale;
/**
* Date: 27/10/13
* Time: 14:55
*/
@Module(priority = 1000)
public class JsonContentTypeModule {
private static final Logger logger = LoggerFactory.getLogger(JsonContentTypeModule.class);
private static final String JACKSON_VIEW_PARAMETER = "view=";
@Provides
public EntityDefaultContentTypeProvider jsonEntityDefaultContentTypeProvider() {
return new EntityDefaultContentTypeProvider() {
@Override
public Optional<String> mayProvideDefaultContentType(Type type) {
return Optional.of("application/json");
}
};
}
@Provides
public EntityRequestBodyReaderFactory jsonEntityRequestBodyReaderFactory(
@Named(FrontObjectMapperFactory.READER_NAME) final ObjectReader reader) {
return new EntityRequestBodyReaderFactory() {
@Override
public <T> Optional<? extends EntityRequestBodyReader<T>> mayBuildFor(Type valueType, String contentType) {
if (!contentType.toLowerCase(Locale.ENGLISH).startsWith("application/json")) {
return Optional.absent();
}
Class<?> clazz = getCTJacksonViewClass(valueType, contentType, Views.Transient.class);
return Optional.of(
JsonEntityRequestBodyReader.<T>using(
valueType,
reader.withView(clazz)
.withType(TypeFactory.defaultInstance().constructType(valueType))))
;
}
};
}
@Provides
public EntityResponseWriterFactory jsonEntityResponseWriterFactory(
@Named(FrontObjectMapperFactory.WRITER_NAME) final ObjectWriter objectWriter) {
return new EntityResponseWriterFactory() {
@Override
public <T> Optional<? extends EntityResponseWriter<T>> mayBuildFor(Type valueType, String contentType) {
if (!contentType.toLowerCase(Locale.ENGLISH).startsWith("application/json")) {
return Optional.absent();
}
Class<?> clazz = getCTJacksonViewClass(valueType, contentType, Views.Transient.class);
ObjectWriter writer = objectWriter.withView(clazz);
if (valueType instanceof ParameterizedType) {
/* we set the type on writer only for parameterized types:
* if we set it for regular types, jackson will build the serializer based on this type, and not the
* instance type, making polymorphic returns broken.
* For parameterized types it helps Jackson which otherwise tries to guess the serializer based on
* value.getClass() which doesn't return type parameters information due to Java erasure.
*/
writer = writer.withType(TypeFactory.defaultInstance().constructType(valueType));
}
return Optional.of(JsonEntityResponseWriter.<T>using(valueType, writer));
}
};
}
private Class<?> getCTJacksonViewClass(Type valueType, String contentType, Class<?> defaultClazz) {
int parameterIndex = contentType.indexOf(JACKSON_VIEW_PARAMETER);
if (parameterIndex != -1) {
String className = contentType.substring(parameterIndex + JACKSON_VIEW_PARAMETER.length());
try {
return Class.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
logger.error("The Jackson view class '{}' was not found while marshalling type '{}' " +
"(content-type : '{}')", className, valueType, contentType);
throw new IllegalStateException(e);
}
}
return defaultClazz;
}
}