Skip to content
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
1 change: 0 additions & 1 deletion clients/line-bot-client-base/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ dependencies {
implementation(libs.bundles.retrofit2)

compileOnly(libs.jackson.annotations)
implementation(libs.jackson.datatype.jsr310)

testImplementation(libs.wiremock)
testImplementation(libs.bundles.tests)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@

import java.io.IOException;

import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.linecorp.bot.jackson.ModelObjectMapper;

import okhttp3.Response;
import okhttp3.ResponseBody;
import tools.jackson.core.JacksonException;
import tools.jackson.core.exc.JacksonIOException;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.databind.ObjectMapper;

public abstract class AbstractExceptionBuilder<E> implements ExceptionBuilder {
private final ObjectMapper objectMapper = ModelObjectMapper.createNewObjectMapper();
Expand All @@ -41,18 +41,44 @@ public IOException build(Response response) throws IOException {
try {
E errorBody = this.parseErrorBody(response);
return this.buildException(response, errorBody);
} catch (StreamReadException | DatabindException e) {
} catch (ErrorBodyParseException e) {
return new LineClientJsonParseException(response, e);
} catch (JacksonException e) {
IOException failure = asIOException(e);
if (failure instanceof ErrorBodyParseException) {
return new LineClientJsonParseException(response, failure);
}
throw failure;
}
}

protected abstract IOException buildException(Response response, E errorBody);

protected E parseErrorBody(Response response) throws IOException, StreamReadException, DatabindException {
protected E parseErrorBody(Response response) throws IOException {
ResponseBody body = requireNonNull(response.body(), "response.body");
return objectMapper.readValue(
requireNonNull(body.byteStream()),
errorBodyClass
);
try {
return objectMapper.readValue(requireNonNull(body.byteStream()), errorBodyClass);
} catch (JacksonException e) {
throw asIOException(e);
}
}

private static IOException asIOException(JacksonException exception) {
if (exception instanceof JacksonIOException ioException) {
return ioException.getCause();
}
// Constraints outside property binding previously escaped without a databinding wrapper.
if (exception instanceof StreamConstraintsException && exception.getPath().isEmpty()) {
return new IOException(exception.getMessage(), exception);
}
return new ErrorBodyParseException(exception);
}

// Keep parse failures catchable as IOException without misclassifying transport failures.
@SuppressWarnings("serial")
private static final class ErrorBodyParseException extends IOException {
private ErrorBodyParseException(JacksonException cause) {
super(cause.getMessage(), cause);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.linecorp.bot.client.base.http.HttpAuthenticator;
import com.linecorp.bot.client.base.http.HttpChain;
import com.linecorp.bot.client.base.http.HttpInterceptor;
Expand All @@ -42,12 +40,15 @@
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;

public class ApiClientBuilder<T> {
private final ObjectMapper objectMapper = ModelObjectMapper.createNewObjectMapper();
private final AtomicReference<ObjectMapper> objectMapper =
new AtomicReference<>(ModelObjectMapper.createNewObjectMapper());
private final Class<T> clientClass;
private final ExceptionBuilder exceptionBuilder;

Expand Down Expand Up @@ -105,7 +106,7 @@ public ApiClientBuilder(URI apiEndPoint, Class<T> clientClass, ExceptionBuilder

private Integer maxRequestsPerHost = 5;

private boolean usingDefaultLogger = true;
private volatile boolean usingDefaultLogger = true;

/**
* API Endpoint.
Expand All @@ -117,11 +118,12 @@ public ApiClientBuilder<T> apiEndPoint(URI apiEndPoint) {

/**
* Fail on unknown properties while JSON deserialization.
* As a result,
*/
public ApiClientBuilder<T> failOnUnknownProperties(Boolean failOnUnknownProperties) {
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
failOnUnknownProperties);
objectMapper.updateAndGet(mapper -> mapper.rebuild()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
failOnUnknownProperties)
.build());
return this;
}

Expand Down Expand Up @@ -258,7 +260,7 @@ public T build() {
}

final Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.addConverterFactory(BotAwareJacksonConverter.create(objectMapper));
.addConverterFactory(BotAwareJacksonConverter.create(objectMapper::get));
retrofitBuilder.client(okHttpClientBuilder.build());
retrofitBuilder.addCallAdapterFactory(new ResultCallAdapterFactory());
retrofitBuilder.baseUrl(apiEndPoint.toString());
Expand All @@ -271,7 +273,7 @@ public T build() {
@Override
public String toString() {
return "ApiClientBuilder{"
+ "objectMapper=" + objectMapper
+ "objectMapper=" + objectMapper.get()
+ ", clientClass=" + clientClass
+ ", exceptionBuilder=" + exceptionBuilder
+ ", apiEndPoint=" + apiEndPoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,37 @@

package com.linecorp.bot.client.base;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Objects;
import java.util.function.Supplier;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import tools.jackson.core.JacksonException;
import tools.jackson.core.exc.JacksonIOException;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;

public class BotAwareJacksonConverter extends Converter.Factory {
private final JacksonConverterFactory jacksonConverterFactory;
private static final MediaType JSON = MediaType.parse("application/json; charset=UTF-8");
private final Supplier<ObjectMapper> objectMapperSupplier;

private BotAwareJacksonConverter(ObjectMapper mapper) {
this.jacksonConverterFactory = JacksonConverterFactory.create(mapper);
private BotAwareJacksonConverter(Supplier<ObjectMapper> objectMapperSupplier) {
this.objectMapperSupplier = objectMapperSupplier;
}

public static Converter.Factory create(ObjectMapper objectMapper) {
return new BotAwareJacksonConverter(objectMapper);
ObjectMapper mapper = Objects.requireNonNull(objectMapper, "mapper == null");
return new BotAwareJacksonConverter(() -> mapper);
}

static Converter.Factory create(Supplier<ObjectMapper> objectMapperSupplier) {
return new BotAwareJacksonConverter(objectMapperSupplier);
}

@Override
Expand All @@ -46,7 +56,18 @@ public static Converter.Factory create(ObjectMapper objectMapper) {
if (type == BlobContent.class) {
return BlobContent::new;
}
return jacksonConverterFactory.responseBodyConverter(type, annotations, retrofit);
ObjectMapper objectMapper = objectMapperSupplier.get();
JavaType javaType = objectMapper.constructType(type);
var reader = objectMapper.readerFor(javaType);
return body -> {
try (body) {
return reader.readValue(body.byteStream());
} catch (JacksonIOException e) {
throw e.getCause();
} catch (JacksonException e) {
throw new IOException(e.getMessage(), e);
}
};
}

@Override
Expand All @@ -56,11 +77,19 @@ public Converter<?, RequestBody> requestBodyConverter(Type type,
Retrofit retrofit) {
if (type == UploadFile.class) {
return this::buildRequestBodyFromUploadFile;
} else {
return jacksonConverterFactory.requestBodyConverter(
type, parameterAnnotations, methodAnnotations, retrofit
);
}
ObjectMapper objectMapper = objectMapperSupplier.get();
JavaType javaType = objectMapper.constructType(type);
var writer = objectMapper.writerFor(javaType);
return value -> {
try {
return RequestBody.create(writer.writeValueAsBytes(value), JSON);
} catch (JacksonIOException e) {
throw e.getCause();
} catch (JacksonException e) {
throw new IOException(e.getMessage(), e);
}
};
}

private RequestBody buildRequestBodyFromUploadFile(Object o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
import org.skyscreamer.jsonassert.JSONAssert;
import org.slf4j.Logger;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.linecorp.bot.jackson.ModelObjectMapper;

import tools.jackson.databind.ObjectMapper;

/**
* Get valid flex json and test deserialize and serialize it.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.linecorp.bot.jackson.ModelObjectMapper;

import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;

class FlexImageTest {
private ObjectMapper objectMapper = ModelObjectMapper.createNewObjectMapper();

@Test
void typeField() throws JsonProcessingException {
void typeField() throws JacksonException {
String json = objectMapper.writeValueAsString(
new FlexImage(
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.SerializationFeature;

import com.linecorp.bot.jackson.ModelObjectMapper;

import tools.jackson.databind.SerializationFeature;

class ModelObjectMapperTest {
@Test
void ignoresUnknownProperties() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.linecorp.bot.jackson.ModelObjectMapper;

import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;

public class RichMenuSwitchActionTest {
private static final ObjectMapper mapper =
ModelObjectMapper.createNewObjectMapper();

@Test
public void testSerialize() throws JsonProcessingException {
public void testSerialize() throws JacksonException {
String json = mapper.writeValueAsString(new RichMenuSwitchAction("aaa", "bbb", "ccc"));
assertThat(json).isEqualTo("""
{"type":"richmenuswitch","label":"aaa","data":"bbb","richMenuAliasId":"ccc"}""");
}

@Test
public void testDeserialize() throws JsonProcessingException {
public void testDeserialize() throws JacksonException {
Action action = mapper.readValue("""
{"type":"richmenuswitch","label":"aaa","data":"bbb","richMenuAliasId":"ccc"}""",
Action.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.EnumFeature;
import tools.jackson.databind.json.JsonMapper;

class UnknownFallbackTest {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
ObjectMapper objectMapper = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
.build();

@ParameterizedTest
@MethodSource("targets")
void testUnknownFallback(Class<?> klass, Class<?> target) throws JsonProcessingException {
void testUnknownFallback(Class<?> klass, Class<?> target) throws JacksonException {
Object object = objectMapper.readValue("""
{"type":"great-new-thing"}
""", klass);
Expand Down
Loading
Loading