All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.occurrent.application.converter.jackson.JacksonCloudEventConverter Maven / Gradle / Ivy

There is a newer version: 0.19.7
Show newest version
/*
 *
 *  Copyright 2021 Johan Haleby
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.occurrent.application.converter.jackson;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventData;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.data.PojoCloudEventData;
import org.occurrent.application.converter.CloudEventConverter;
import org.occurrent.application.converter.typemapper.CloudEventTypeMapper;
import org.occurrent.application.converter.typemapper.ReflectionCloudEventTypeMapper;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;

import static java.time.ZoneOffset.UTC;
import static java.util.Objects.requireNonNull;

/**
 * An {@link CloudEventConverter} that uses a Jackson {@link ObjectMapper} to serialize a domain event to JSON (content type {@value #DEFAULT_CONTENT_TYPE}) that is used as data in a {@link CloudEvent}.
 *
 * @param  The type of your domain event(s) to convert
 */
public class JacksonCloudEventConverter implements CloudEventConverter {
    private static final String DEFAULT_CONTENT_TYPE = "application/json";

    private final ObjectMapper objectMapper;
    private final URI cloudEventSource;
    private final Function idMapper;
    private final CloudEventTypeMapper cloudEventTypeMapper;
    private final Function timeMapper;
    private final Function subjectMapper;
    private final String contentType;

    /**
     * Create a new instance of the {@link JacksonCloudEventConverter} that does the following:
     * 
    *
  1. Uses a random UUID as cloud event id
  2. *
  3. Uses the fully-qualified name of the domain event class as cloud event type. You should definitely change this in production!
  4. *
  5. Uses {@code OffsetDateTime.now(UTC)} as cloud event time
  6. *
  7. Uses charset UTF-8 when converting the domain event to/from JSON
  8. *
  9. No subject
  10. *
*

* See cloud event documentation for info on what the cloud event attributes mean.

* Use {@link Builder} for more advanced configuration. * * @param objectMapper The ObjectMapper instance to use * @param cloudEventSource The cloud event source. * @see Builder The Builder for more advanced configuration */ public JacksonCloudEventConverter(ObjectMapper objectMapper, URI cloudEventSource) { this(objectMapper, cloudEventSource, defaultIdMapperFunction(), defaultTypeMapper(), defaultTimeMapperFunction(), defaultSubjectMapperFunction(), DEFAULT_CONTENT_TYPE); } private JacksonCloudEventConverter(ObjectMapper objectMapper, URI cloudEventSource, Function idMapper, CloudEventTypeMapper cloudEventTypeMapper, Function timeMapper, Function subjectMapper, String contentType) { requireNonNull(objectMapper, ObjectMapper.class.getSimpleName() + " cannot be null"); requireNonNull(cloudEventSource, "cloudEventSource cannot be null"); requireNonNull(idMapper, "idMapper cannot be null"); requireNonNull(cloudEventTypeMapper, CloudEventTypeMapper.class.getSimpleName() + " cannot be null"); requireNonNull(timeMapper, "timeMapper cannot be null"); requireNonNull(subjectMapper, "subjectMapper cannot be null"); this.objectMapper = objectMapper; this.cloudEventSource = cloudEventSource; this.idMapper = idMapper; this.timeMapper = timeMapper; this.subjectMapper = subjectMapper; this.contentType = contentType; this.cloudEventTypeMapper = cloudEventTypeMapper; } /** * Converts the {@code domainEvent} into a {@link CloudEvent} using {@link ObjectMapper}. * * @param domainEvent The domain event to convert * @return A {@link CloudEvent} converted from the domainEvent. */ @Override public CloudEvent toCloudEvent(T domainEvent) { requireNonNull(domainEvent, "Domain event cannot be null"); // @formatter:off PojoCloudEventData> cloudEventData = PojoCloudEventData.wrap(objectMapper.convertValue(domainEvent, new TypeReference>() { }), objectMapper::writeValueAsBytes); // @formatter:on return CloudEventBuilder.v1() .withId(idMapper.apply(domainEvent)) .withSource(cloudEventSource) .withType(cloudEventTypeMapper.getCloudEventType(domainEvent)) .withTime(timeMapper.apply(domainEvent)) .withSubject(subjectMapper.apply(domainEvent)) .withDataContentType(contentType) .withData(cloudEventData) .build(); } /** * Converts the {@link CloudEvent} back into a {@code domainEvent} using {@link ObjectMapper}. * * @param cloudEvent The cloud event to convert * @return A domainEvent converted from a {@link CloudEvent}. */ @SuppressWarnings("unchecked") @Override public T toDomainEvent(CloudEvent cloudEvent) { CloudEventData data = cloudEvent.getData(); final Class domainEventType = cloudEventTypeMapper.getDomainEventType(cloudEvent.getType()); final T domainEvent; if (data instanceof PojoCloudEventData && ((PojoCloudEventData) data).getValue() instanceof Map) { Map value = (Map) ((PojoCloudEventData) data).getValue(); domainEvent = objectMapper.convertValue(value, domainEventType); } else { try { domainEvent = objectMapper.readValue(requireNonNull(data, "cloud event data cannot be null").toBytes(), domainEventType); } catch (IOException e) { throw new UncheckedIOException(e); } } return domainEvent; } @Override public String getCloudEventType(Class type) { return cloudEventTypeMapper.getCloudEventType(type); } public static final class Builder { private final ObjectMapper objectMapper; private final URI cloudEventSource; private String contentType = DEFAULT_CONTENT_TYPE; private Function idMapper = defaultIdMapperFunction(); private CloudEventTypeMapper cloudEventTypeMapper = defaultTypeMapper(); private Function timeMapper = defaultTimeMapperFunction(); private Function subjectMapper = defaultSubjectMapperFunction(); public Builder(ObjectMapper objectMapper, URI cloudEventSource) { this.objectMapper = objectMapper; this.cloudEventSource = cloudEventSource; } /** * @param contentType Specify the content type to use in the generated cloud event */ public Builder contentType(String contentType) { this.contentType = contentType; return this; } /** * @param idMapper A function that generates the cloud event id based on the domain event. By default, a random UUID is used. */ public Builder idMapper(Function idMapper) { this.idMapper = idMapper; return this; } /** * @param cloudEventTypeMapper A function that generates the cloud event type based on the domain event. By default, the "simple name" of the domain event is used. */ public Builder typeMapper(CloudEventTypeMapper cloudEventTypeMapper) { this.cloudEventTypeMapper = cloudEventTypeMapper; return this; } /** * @param timeMapper A function that generates the cloud event time based on the domain event. By default, {@code OffsetDateTime.now(UTC)} is always returned. */ public Builder timeMapper(Function timeMapper) { this.timeMapper = timeMapper; return this; } /** * @param subjectMapper A function that generates the cloud event subject based on the domain event. By default, {@code null} is always returned. */ public Builder subjectMapper(Function subjectMapper) { this.subjectMapper = subjectMapper; return this; } /** * @return A {@link JacksonCloudEventConverter} instance with the configured settings */ public JacksonCloudEventConverter build() { return new JacksonCloudEventConverter<>(objectMapper, cloudEventSource, idMapper, cloudEventTypeMapper, timeMapper, subjectMapper, contentType); } } private static Function defaultIdMapperFunction() { return __ -> UUID.randomUUID().toString(); } private static CloudEventTypeMapper defaultTypeMapper() { return ReflectionCloudEventTypeMapper.qualified(); } private static Function defaultTimeMapperFunction() { return __ -> OffsetDateTime.now(UTC); } private static Function defaultSubjectMapperFunction() { return __ -> null; } }