diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00ecbb54..14bd7863 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Set up Maven Central Repository - uses: actions/setup-java@v1 + uses: actions/setup-java@v5 with: - java-version: 20 + java-version: 21 + distribution: temurin server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bd876e2f..1f0883ed 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,13 +6,14 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Set up JDK 20 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - java-version: 20 + java-version: 21 + distribution: temurin - name: Build and analyze run: mvn -B verify -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Pcoverage env: diff --git a/.github/workflows/publish_on_release.yml b/.github/workflows/publish_on_release.yml index 01e13410..aa165f4b 100644 --- a/.github/workflows/publish_on_release.yml +++ b/.github/workflows/publish_on_release.yml @@ -8,11 +8,12 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Set up Maven Central Repository - uses: actions/setup-java@v1 + uses: actions/setup-java@v5 with: - java-version: 20 + java-version: 21 + distribution: temurin server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/pom.xml b/pom.xml index 45c40d16..c6fac3bf 100644 --- a/pom.xml +++ b/pom.xml @@ -74,9 +74,9 @@ 1.15 1.7.35 4.5.13 - 2.13.3 + 3.0.3 1.79 - 1.18.22 + 1.18.42 3.2.3-RELEASE @@ -161,18 +161,8 @@ provided - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 + tools.jackson.core + jackson-databind ${jackson.version} diff --git a/src/main/java/com/ibanity/apis/client/http/impl/IbanityHttpClientImpl.java b/src/main/java/com/ibanity/apis/client/http/impl/IbanityHttpClientImpl.java index c1d5184c..60808a3e 100644 --- a/src/main/java/com/ibanity/apis/client/http/impl/IbanityHttpClientImpl.java +++ b/src/main/java/com/ibanity/apis/client/http/impl/IbanityHttpClientImpl.java @@ -1,6 +1,5 @@ package com.ibanity.apis.client.http.impl; -import com.fasterxml.jackson.core.JsonProcessingException; import com.ibanity.apis.client.http.IbanityHttpClient; import com.ibanity.apis.client.http.handler.IbanityResponseHandler; import lombok.NonNull; @@ -11,6 +10,7 @@ import org.apache.http.client.methods.*; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; +import tools.jackson.core.JacksonException; import javax.net.ssl.SSLContext; import java.io.IOException; @@ -72,7 +72,7 @@ public HttpResponse post(@NonNull URI path, @NonNull Object requestApiModel, @No HttpPost httpPost = new HttpPost(path); httpPost.setEntity(createEntityRequest(objectMapper().writeValueAsString(requestApiModel))); return execute(additionalHeaders, customerAccessToken, httpPost); - } catch (JsonProcessingException exception) { + } catch (JacksonException exception) { throw new RuntimeException("An error occurred while converting object to json", exception); } } @@ -109,7 +109,7 @@ public HttpResponse patch(@NonNull URI path, @NonNull Object requestApiModel, @N HttpPatch httpPatch = new HttpPatch(path); httpPatch.setEntity(createEntityRequest(objectMapper().writeValueAsString(requestApiModel))); return execute(additionalHeaders, customerAccessToken, httpPatch); - } catch (JsonProcessingException exception) { + } catch (JacksonException exception) { throw new RuntimeException("An error occurred while converting object to json", exception); } } diff --git a/src/main/java/com/ibanity/apis/client/mappers/IbanityErrorMapper.java b/src/main/java/com/ibanity/apis/client/mappers/IbanityErrorMapper.java index 6adcd6dc..5fb60401 100644 --- a/src/main/java/com/ibanity/apis/client/mappers/IbanityErrorMapper.java +++ b/src/main/java/com/ibanity/apis/client/mappers/IbanityErrorMapper.java @@ -1,12 +1,12 @@ package com.ibanity.apis.client.mappers; -import com.fasterxml.jackson.core.JsonProcessingException; import com.ibanity.apis.client.jsonapi.FinancialInstitutionResponseApiModel; import com.ibanity.apis.client.jsonapi.IbanityErrorApiModel; import com.ibanity.apis.client.jsonapi.OAuth2ErrorResourceApiModel; import com.ibanity.apis.client.models.ErrorMeta; import com.ibanity.apis.client.models.FinancialInstitutionResponse; import com.ibanity.apis.client.models.IbanityError; +import tools.jackson.core.JacksonException; import static com.ibanity.apis.client.utils.IbanityUtils.objectMapper; @@ -47,7 +47,7 @@ private static String parseBody(FinancialInstitutionResponseApiModel financialIn } else { try { return objectMapper().writeValueAsString(body); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new RuntimeException("Invalid payload", e); } } diff --git a/src/main/java/com/ibanity/apis/client/mappers/IbanityWebhookEventMapper.java b/src/main/java/com/ibanity/apis/client/mappers/IbanityWebhookEventMapper.java index 465cdb56..1b450152 100644 --- a/src/main/java/com/ibanity/apis/client/mappers/IbanityWebhookEventMapper.java +++ b/src/main/java/com/ibanity/apis/client/mappers/IbanityWebhookEventMapper.java @@ -4,8 +4,8 @@ import com.ibanity.apis.client.jsonapi.ResourceApiModel; import com.ibanity.apis.client.models.IbanityWebhookEvent; import com.ibanity.apis.client.utils.IbanityUtils; +import tools.jackson.core.JacksonException; -import java.io.IOException; import java.util.function.Function; import static java.lang.String.format; @@ -17,7 +17,7 @@ public static T mapWebhookResource(String payloa try { DataApiModel dataApiModel = IbanityUtils.objectMapper().readValue(payload, ResourceApiModel.class).getData(); return customMapping.apply(dataApiModel); - } catch (IOException exception) { + } catch (JacksonException exception) { throw new IllegalArgumentException("Response cannot be parsed", exception); } } diff --git a/src/main/java/com/ibanity/apis/client/products/ponto_connect/services/impl/UsageServiceImpl.java b/src/main/java/com/ibanity/apis/client/products/ponto_connect/services/impl/UsageServiceImpl.java index 823280dd..2bc3b752 100644 --- a/src/main/java/com/ibanity/apis/client/products/ponto_connect/services/impl/UsageServiceImpl.java +++ b/src/main/java/com/ibanity/apis/client/products/ponto_connect/services/impl/UsageServiceImpl.java @@ -1,6 +1,6 @@ package com.ibanity.apis.client.products.ponto_connect.services.impl; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import com.ibanity.apis.client.http.IbanityHttpClient; import com.ibanity.apis.client.models.IbanityProduct; import com.ibanity.apis.client.products.ponto_connect.models.OrganizationUsage; @@ -44,7 +44,7 @@ public OrganizationUsage getOrganizationUsage(OrganizationUsageReadQuery readQue private OrganizationUsage map(JsonNode dataApiModel) { return OrganizationUsage.builder() - .id(dataApiModel.get("data").get("id").textValue()) + .id(dataApiModel.get("data").get("id").asString()) .paymentCount(new BigDecimal(dataApiModel.get("data").get("attributes").get("paymentCount").toString())) .accountCount(new BigDecimal(dataApiModel.get("data").get("attributes").get("accountCount").toString())) .paymentAccountCount(new BigDecimal(dataApiModel.get("data").get("attributes").get("paymentAccountCount").toString())) diff --git a/src/main/java/com/ibanity/apis/client/services/impl/ApiUrlProviderImpl.java b/src/main/java/com/ibanity/apis/client/services/impl/ApiUrlProviderImpl.java index 7e493f69..cf37360d 100644 --- a/src/main/java/com/ibanity/apis/client/services/impl/ApiUrlProviderImpl.java +++ b/src/main/java/com/ibanity/apis/client/services/impl/ApiUrlProviderImpl.java @@ -1,6 +1,6 @@ package com.ibanity.apis.client.services.impl; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import com.ibanity.apis.client.http.IbanityHttpClient; import com.ibanity.apis.client.models.IbanityProduct; import com.ibanity.apis.client.services.ApiUrlProvider; @@ -59,7 +59,7 @@ public String find(String rootPath, String[] subPaths) { return Stream.of(subPaths) .reduce(apiUrls, JsonNode::get, (jsonNode1, jsonNode2) -> jsonNode2) - .textValue(); + .asString(); } catch (Exception exception) { throw new IllegalArgumentException("Url cannot be found", exception); } diff --git a/src/main/java/com/ibanity/apis/client/utils/IbanityUtils.java b/src/main/java/com/ibanity/apis/client/utils/IbanityUtils.java index 908e3376..2c45084c 100644 --- a/src/main/java/com/ibanity/apis/client/utils/IbanityUtils.java +++ b/src/main/java/com/ibanity/apis/client/utils/IbanityUtils.java @@ -1,11 +1,6 @@ package com.ibanity.apis.client.utils; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.ibanity.apis.client.builders.IbanityConfiguration; import com.ibanity.apis.client.http.interceptor.IbanitySignatureInterceptor; import com.ibanity.apis.client.http.interceptor.IdempotencyInterceptor; @@ -17,6 +12,9 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.DefaultClientConnectionReuseStrategy; import org.apache.http.impl.client.HttpClientBuilder; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import javax.net.ssl.*; import java.io.IOException; @@ -35,6 +33,12 @@ public final class IbanityUtils { private static final String CA_TRUST_STORE_KEY = "ibanity-ca"; private static final String TLS_PROTOCOL = "TLS"; + public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL)) + .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL)) + .build(); + private IbanityUtils() { } @@ -75,12 +79,7 @@ public static HttpClient httpClient(IbanityConfiguration configuration) { } public static ObjectMapper objectMapper() { - return new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return OBJECT_MAPPER; } private static void configureHttpClient(SSLContext sslContext, diff --git a/src/main/java/com/ibanity/apis/client/webhooks/services/impl/WebhooksServiceImpl.java b/src/main/java/com/ibanity/apis/client/webhooks/services/impl/WebhooksServiceImpl.java index 5b56e42a..3618b2cf 100644 --- a/src/main/java/com/ibanity/apis/client/webhooks/services/impl/WebhooksServiceImpl.java +++ b/src/main/java/com/ibanity/apis/client/webhooks/services/impl/WebhooksServiceImpl.java @@ -1,7 +1,7 @@ package com.ibanity.apis.client.webhooks.services.impl; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; import com.ibanity.apis.client.exceptions.IbanityRuntimeException; import com.ibanity.apis.client.http.IbanityHttpClient; import com.ibanity.apis.client.models.IbanityWebhookEvent; @@ -32,9 +32,9 @@ public IbanityWebhookEvent verifyAndParseEvent(String payload, String jwt) { verify(payload, jwt); try { JsonNode jsonNode = IbanityUtils.objectMapper().readTree(payload); - String type = jsonNode.get("data").get("type").textValue(); + String type = jsonNode.get("data").get("type").asString(); return WebhooksUtils.webhookEventParser(payload, type); - } catch (JsonProcessingException exception) { + } catch (JacksonException exception) { throw new IllegalArgumentException("Response cannot be parsed", exception); } } diff --git a/src/test/java/com/ibanity/apis/client/utils/IbanityUtilsTest.java b/src/test/java/com/ibanity/apis/client/utils/IbanityUtilsTest.java new file mode 100644 index 00000000..c0693e82 --- /dev/null +++ b/src/test/java/com/ibanity/apis/client/utils/IbanityUtilsTest.java @@ -0,0 +1,88 @@ +package com.ibanity.apis.client.utils; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +class IbanityUtilsTest { + + @Test + void objectMapper_shouldDeserializeJsonWithUnknownProperties() { + ObjectMapper objectMapper = IbanityUtils.objectMapper(); + + String jsonWithUnknownProperty = "{\"name\":\"John\",\"unknownField\":\"value\",\"anotherUnknown\":123}"; + + TestPerson person = objectMapper.readValue(jsonWithUnknownProperty, TestPerson.class); + + assertThat(person.getName()).isEqualTo("John"); + } + + @Test + void objectMapper_shouldExcludeNullPropertiesFromSerialization() { + ObjectMapper objectMapper = IbanityUtils.objectMapper(); + + TestPerson person = TestPerson.builder() + .name("John") + .email(null) + .build(); + + String json = objectMapper.writeValueAsString(person); + + assertThat(json).isEqualTo("{\"name\":\"John\"}"); + } + + @Test + void objectMapper_shouldWriteDateAsTimestamp() { + ObjectMapper objectMapper = IbanityUtils.objectMapper(); + + Instant instant = Instant.parse("2023-01-15T10:30:00.000Z"); + Date date = Date.from(instant); + + TestWithDate testObject = TestWithDate.builder() + .id("123") + .createdAt(instant) + .updatedAt(date) + .build(); + + String json = objectMapper.writeValueAsString(testObject); + assertThat(json).isEqualTo("{\"createdAt\":\"2023-01-15T10:30:00Z\",\"id\":\"123\",\"updatedAt\":\"2023-01-15T10:30:00.000Z\"}"); + } + + @Test + void objectMapper_shouldHandleEmptyObjectSerialization() { + ObjectMapper objectMapper = IbanityUtils.objectMapper(); + + TestPerson person = TestPerson.builder().build(); + + String json = objectMapper.writeValueAsString(person); + + assertThat(json).isEqualTo("{}"); + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + static class TestPerson { + private String name; + private String email; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + static class TestWithDate { + private String id; + private Instant createdAt; + private Date updatedAt; + } +}