Customizing the Java SDK

Serialization

The SDK uses Jackson for serializing and deserializing JSON. The default configured ObjectMapper uses some modules to correctly work with commercetools APIs. The details can be found in JsonUtils.createObjectMapper(ModuleOptions).

Customization

To allow customization of the ObjectMapper, the SDK uses ServiceLoader for ModuleSupplier. Adding the file resources/META-INF/services/io.vrap.rmf.base.client.utils.json.ModuleSupplier to your project with the fully qualified class name of the module supplier to be used will register the supplied modules.

io.vrap.rmf.base.client.utils.json.ModuleSupplier:
com.commercetools.api.json.ApiModuleSupplier
package com.commercetools.api.json;
import java.util.Optional;
import com.commercetools.api.models.cart.ReplicaCartDraft;
import com.commercetools.api.models.product.AttributeImpl;
import com.commercetools.api.models.review.Review;
import com.commercetools.api.models.type.FieldContainerImpl;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.vrap.rmf.base.client.utils.json.modules.ModuleOptions;
/**
* Module to configure the default jackson {@link com.fasterxml.jackson.databind.ObjectMapper} e.g. to deserialize attributes and custom fields
*/
public class ApiModule extends SimpleModule {
private static final long serialVersionUID = 0L;
public ApiModule(ModuleOptions options) {
boolean attributeAsDateString = Boolean.parseBoolean(
Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_DATE_ATTRIBUTE_AS_STRING))
.orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_DATE_ATTRIBUTE_AS_STRING)));
boolean customFieldAsDateString = Boolean
.parseBoolean(Optional.ofNullable(options.getOption(ApiModuleOptions.DESERIALIZE_DATE_FIELD_AS_STRING))
.orElse(System.getProperty(ApiModuleOptions.DESERIALIZE_DATE_FIELD_AS_STRING)));
addDeserializer(AttributeImpl.class, new AtrributeDeserializer(attributeAsDateString));
addDeserializer(FieldContainerImpl.class, new CustomFieldDeserializer(customFieldAsDateString));
setMixInAnnotation(Review.class, ReviewMixin.class);
setMixInAnnotation(ReplicaCartDraft.class, ReplicaCartDraftMixin.class);
}
}

DateTime attributes

When using Date, Time and DateTime types for Product attributes or Custom Fields the SDK deserializes them as LocalDate, LocalTime and ZonedDateTime.

To deserialize them as a String, the ObjectMapper can be configured with ModuleOptions. The ApiModule also loads the configuration options using System.getProperty(String) (for example: commercetools.deserializeDateAttributeAsString).

ApiModuleOptions options = ApiModuleOptions
.of()
.withDateAttributeAsString(true)
.withDateCustomFieldAsString(true);
ObjectMapper mapper = JsonUtils.createObjectMapper(options);
ProjectApiRoot apiRoot = ApiRootBuilder
.of()
.withApiBaseUrl(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withSerializer(ResponseSerializer.of(mapper))
.build("test");
ProductVariant variant = mapper.readValue(
stringFromResource("attributes.json"),
ProductVariant.class
);
assertThat(variant.getAttributes()).isNotEmpty();
Map<String, Attribute> attributes = variant.withProductVariant(
AttributeAccessor::asMap
);
assertThat(attributes.get("date").getValue())
.isInstanceOfSatisfying(
String.class,
localDate -> assertThat(localDate).isEqualTo("2020-01-01")
);
assertThat(attributes.get("time").getValue())
.isInstanceOfSatisfying(
String.class,
localTime -> assertThat(localTime).isEqualTo("13:15:00.123")
);
assertThat(attributes.get("datetime").getValue())
.isInstanceOfSatisfying(
String.class,
dateTime -> assertThat(dateTime).isEqualTo("2020-01-01T13:15:00.123Z")
);
assertThat(attributes.get("date").withAttribute(AttributeAccessor::asDate))
.isInstanceOfSatisfying(
LocalDate.class,
localDate -> assertThat(localDate).isEqualTo("2020-01-01")
);
assertThat(attributes.get("time").withAttribute(AttributeAccessor::asTime))
.isInstanceOfSatisfying(
LocalTime.class,
localTime -> assertThat(localTime).isEqualTo("13:15:00.123")
);
assertThat(
attributes.get("datetime").withAttribute(AttributeAccessor::asDateTime)
)
.isInstanceOfSatisfying(
ZonedDateTime.class,
dateTime -> assertThat(dateTime).isEqualTo("2020-01-01T13:15:00.123Z")
);
assertThat(attributes.get("set-date").getValue())
.asList()
.first()
.isInstanceOf(String.class);
assertThat(attributes.get("set-time").getValue())
.asList()
.first()
.isInstanceOf(String.class);
assertThat(attributes.get("set-datetime").getValue())
.asList()
.first()
.isInstanceOf(String.class);
assertThat(
attributes.get("set-date").withAttribute(AttributeAccessor::asSetDate)
)
.asList()
.first()
.isInstanceOf(LocalDate.class);
assertThat(
attributes.get("set-time").withAttribute(AttributeAccessor::asSetTime)
)
.asList()
.first()
.isInstanceOf(LocalTime.class);
assertThat(
attributes.get("set-datetime").withAttribute(AttributeAccessor::asSetDateTime)
)
.asList()
.first()
.isInstanceOf(ZonedDateTime.class);

Tuning the client

Blocking execution

In some frameworks there is no support for asynchronous execution and so it is necessary to wait for the responses.

The client can wait for responses with the method .executeBlocking().

This method enforces a timeout for resilience and throws an ApiHttpException.

ProjectApiRoot apiRoot = createProjectClient();
Project project = apiRoot.get().executeBlocking().getBody();

Configure the underlying HTTP client

The ApiRootBuilder has create methods which allow passing a preconfigured HTTP client.

Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy", 8080));
VrapHttpClient httpClient = new CtOkHttp4Client(builder -> builder.proxy(proxy)
);
ProjectApiRoot apiRoot = ApiRootBuilder
.of(httpClient)
.defaultClient(
ClientCredentials
.of()
.withClientId("your-client-id")
.withClientSecret("your-client-secret")
.withScopes("your-scopes")
.build(),
ServiceRegion.GCP_EUROPE_WEST1
)
.build("my-project");