diff --git a/api/pom.xml b/api/pom.xml index fba28adb..baae08b8 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -56,8 +56,8 @@ org.glassfish - javax.el - ${javax.el.version} + jakarta.el + ${jakarta.el.version} @@ -79,7 +79,7 @@ 2.10.0.pr3 6.0.17.Final - 3.0.1-b11 + 3.0.3 diff --git a/api/src/main/java/io/cloudevents/Attributes.java b/api/src/main/java/io/cloudevents/Attributes.java index 96f65aaf..5a5f8d4b 100644 --- a/api/src/main/java/io/cloudevents/Attributes.java +++ b/api/src/main/java/io/cloudevents/Attributes.java @@ -15,8 +15,12 @@ */ package io.cloudevents; +import java.net.URI; import java.util.Optional; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + import com.fasterxml.jackson.annotation.JsonIgnore; /** @@ -27,6 +31,30 @@ import com.fasterxml.jackson.annotation.JsonIgnore; */ public interface Attributes { + /** + * @return Identifies the event. Producers MUST ensure that source + id is unique for each distinct event + */ + @NotBlank + String getId(); + + /** + * @return A value describing the type of event related to the originating occurrence. + */ + @NotBlank + String getType(); + + /** + * @return The context in which an event happened. + */ + @NotNull + URI getSource(); + + /** + * @return The version of the CloudEvents specification which the event uses + */ + @NotBlank + String getSpecversion(); + /** * A common way to get the media type of CloudEvents 'data'; * @return If has a value, it MUST follows the RFC2046 diff --git a/api/src/main/java/io/cloudevents/extensions/ExtensionFormat.java b/api/src/main/java/io/cloudevents/extensions/ExtensionFormat.java index d937e7c6..f2823ad6 100644 --- a/api/src/main/java/io/cloudevents/extensions/ExtensionFormat.java +++ b/api/src/main/java/io/cloudevents/extensions/ExtensionFormat.java @@ -42,7 +42,7 @@ public interface ExtensionFormat { */ Map transport(); - public static ExtensionFormat of(final InMemoryFormat inMemory, + static ExtensionFormat of(final InMemoryFormat inMemory, final String key, final String value) { final Map transport = new HashMap<>(); @@ -62,7 +62,7 @@ public interface ExtensionFormat { } @SafeVarargs - public static ExtensionFormat of(final InMemoryFormat inMemory, + static ExtensionFormat of(final InMemoryFormat inMemory, Entry ... transport){ Objects.requireNonNull(inMemory); Objects.requireNonNull(transport); @@ -89,7 +89,7 @@ public interface ExtensionFormat { * @param extensions * @return */ - public static Map marshal(Collection + static Map marshal(Collection extensions) { return extensions.stream() diff --git a/api/src/main/java/io/cloudevents/format/BinaryMarshaller.java b/api/src/main/java/io/cloudevents/format/BinaryMarshaller.java index 206cfa42..a1b0603b 100644 --- a/api/src/main/java/io/cloudevents/format/BinaryMarshaller.java +++ b/api/src/main/java/io/cloudevents/format/BinaryMarshaller.java @@ -53,7 +53,7 @@ public final class BinaryMarshaller { return new Builder(); } - public static interface AttributeMarshalStep { + public interface AttributeMarshalStep { /** * Marshals the {@link Attributes} instance into a * {@code Map} @@ -63,7 +63,7 @@ public final class BinaryMarshaller { ExtensionsAccessorStep map(AttributeMarshaller marshaller); } - public static interface ExtensionsAccessorStep { + public interface ExtensionsAccessorStep { /** * To get access of internal collection of {@link ExtensionFormat} @@ -74,7 +74,7 @@ public final class BinaryMarshaller { } - public static interface ExtensionsStep { + public interface ExtensionsStep { /** * Marshals the collection of {@link ExtensionFormat} into a * {@code Map} @@ -84,7 +84,7 @@ public final class BinaryMarshaller { HeaderMapStep map(ExtensionMarshaller marshaller); } - public static interface HeaderMapStep { + public interface HeaderMapStep { /** * Marshals the map of attributes and extensions into a map of headers * @param mapper @@ -93,7 +93,7 @@ public final class BinaryMarshaller { DataMarshallerStep map(FormatHeaderMapper mapper); } - public static interface DataMarshallerStep { + public interface DataMarshallerStep { /** * Marshals the 'data' into payload * @param marshaller @@ -102,7 +102,7 @@ public final class BinaryMarshaller { BuilderStep map(DataMarshaller marshaller); } - public static interface BuilderStep { + public interface BuilderStep { /** * Builds the {@link Wire} to use for wire transfer * @param builder diff --git a/api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java b/api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java index 22fa0db2..935e0d99 100644 --- a/api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java +++ b/api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -219,10 +220,8 @@ public final class BinaryUnmarshaller { A attributes = attributeUnmarshaller.unmarshal(attributesMap); T data = attributes.getMediaType() - .map((mime) -> { - return dataUnmarshallers.get(mime); - }) - .filter((un) -> null != un) + .map((mime) -> dataUnmarshallers.get(mime)) + .filter(Objects::nonNull) .map(unmarshaller -> unmarshaller.unmarshal(payload, attributes)) .orElse(null); diff --git a/api/src/main/java/io/cloudevents/format/StructuredMarshaller.java b/api/src/main/java/io/cloudevents/format/StructuredMarshaller.java index 749d58c6..009b8003 100644 --- a/api/src/main/java/io/cloudevents/format/StructuredMarshaller.java +++ b/api/src/main/java/io/cloudevents/format/StructuredMarshaller.java @@ -51,7 +51,7 @@ public class StructuredMarshaller { return new Builder<>(); } - public static interface MediaTypeStep { + public interface MediaTypeStep { /** * Sets the media type of CloudEvents envelope * @param headerName Example {@code Content-Type} for HTTP @@ -60,7 +60,7 @@ public class StructuredMarshaller { EnvelopeMarshallerStep mime(String headerName, H mediaType); } - public static interface EnvelopeMarshallerStep { + public interface EnvelopeMarshallerStep { /** * Sets the marshaller for the CloudEvent * @param marshaller @@ -68,7 +68,7 @@ public class StructuredMarshaller { ExtensionAccessorStep map(EnvelopeMarshaller marshaller); } - public static interface ExtensionAccessorStep { + public interface ExtensionAccessorStep { /** * To skip the extension special handling */ @@ -76,11 +76,11 @@ public class StructuredMarshaller { ExtensionMarshallerStep map(ExtensionFormatAccessor accessor); } - public static interface ExtensionMarshallerStep { + public interface ExtensionMarshallerStep { HeaderMapperStep map(ExtensionMarshaller marshaller); } - public static interface HeaderMapperStep { + public interface HeaderMapperStep { EventStep map(FormatHeaderMapper mapper); } diff --git a/api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java b/api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java index 65818426..e64a2301 100644 --- a/api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java +++ b/api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java @@ -144,18 +144,15 @@ public class StructuredUnmarshaller { Optional.ofNullable(extensionMapper) .map(mapper -> mapper.map(headers)) .orElse(new HashMap<>()); - - CloudEvent result = - unmarshaller.unmarshal(payload, - () -> + + return unmarshaller.unmarshal(payload, + () -> extensionUnmarshallers.stream() .map(unmarshaller -> unmarshaller.unmarshal(extensionsMap)) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList())); - - return result; } } diff --git a/api/src/main/java/io/cloudevents/v02/AttributesImpl.java b/api/src/main/java/io/cloudevents/v02/AttributesImpl.java index a88b612c..d4ecff74 100644 --- a/api/src/main/java/io/cloudevents/v02/AttributesImpl.java +++ b/api/src/main/java/io/cloudevents/v02/AttributesImpl.java @@ -170,7 +170,7 @@ public class AttributesImpl implements Attributes { URI schemaurl = Optional.ofNullable(attributes.get(ContextAttributes.schemaurl.name())) - .map(schema -> URI.create(schema)) + .map(URI::create) .orElse(null); String id = attributes.get(ContextAttributes.id.name()); @@ -201,19 +201,11 @@ public class AttributesImpl implements Attributes { result.put(ContextAttributes.id.name(), attributes.getId()); - attributes.getTime().ifPresent((value) -> { - result.put(ContextAttributes.time.name(), - value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - result.put(ContextAttributes.schemaurl.name(), - schema.toString()); - }); - - attributes.getContenttype().ifPresent((ct) -> { - result.put(ContextAttributes.contenttype.name(), ct); - }); + attributes.getTime().ifPresent((value) -> result.put(ContextAttributes.time.name(), + value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))); + attributes.getSchemaurl().ifPresent((schema) -> result.put(ContextAttributes.schemaurl.name(), + schema.toString())); + attributes.getContenttype().ifPresent((ct) -> result.put(ContextAttributes.contenttype.name(), ct)); return result; } diff --git a/api/src/main/java/io/cloudevents/v02/CloudEventBuilder.java b/api/src/main/java/io/cloudevents/v02/CloudEventBuilder.java index d50ec517..f6f651c9 100644 --- a/api/src/main/java/io/cloudevents/v02/CloudEventBuilder.java +++ b/api/src/main/java/io/cloudevents/v02/CloudEventBuilder.java @@ -1,12 +1,12 @@ /** * Copyright 2019 The CloudEvents Authors - * + *

* 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. @@ -15,8 +15,6 @@ */ package io.cloudevents.v02; -import static java.lang.String.format; - import java.net.URI; import java.time.ZonedDateTime; import java.util.Collection; @@ -35,242 +33,222 @@ import io.cloudevents.CloudEvent; import io.cloudevents.extensions.ExtensionFormat; import io.cloudevents.fun.EventBuilder; +import static java.lang.String.format; + /** * CloudEvent instances builder - * + * * @author fabiojose * @version 0.2 */ public class CloudEventBuilder implements EventBuilder, - Builder { - private CloudEventBuilder() {} - - private static Validator VALIDATOR; - - private static final String SPEC_VERSION = "0.2"; - private static final String MESSAGE_SEPARATOR = ", "; - private static final String MESSAGE = "'%s' %s"; - private static final String ERR_MESSAGE = "invalid payload: %s"; + Builder { + private CloudEventBuilder() { + } - private String type; - private String id; - private URI source; - - private ZonedDateTime time; - private URI schemaurl; - private String contenttype; - private T data; - - private final Set extensions = new HashSet<>(); - - private static Validator getValidator() { - if(null== VALIDATOR) { - VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); - } - return VALIDATOR; - } - - /** - * Gets a brand new builder instance - * @param The 'data' type - */ - public static CloudEventBuilder builder() { - return new CloudEventBuilder(); - } - - /** - * + private static Validator VALIDATOR; + + private static final String SPEC_VERSION = "0.2"; + private static final String MESSAGE_SEPARATOR = ", "; + private static final String MESSAGE = "'%s' %s"; + private static final String ERR_MESSAGE = "invalid payload: %s"; + + private String type; + private String id; + private URI source; + + private ZonedDateTime time; + private URI schemaurl; + private String contenttype; + private T data; + + private final Set extensions = new HashSet<>(); + private Validator validator; + + private static Validator getValidator() { + if (null == VALIDATOR) { + VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); + } + return VALIDATOR; + } + + /** + * Gets a brand new builder instance + * @param The 'data' type + */ + public static CloudEventBuilder builder() { + return new CloudEventBuilder<>(); + } + + /** + * * @param The 'data' type * @param base A base event to copy {@link CloudEvent#getAttributes()}, * {@link CloudEvent#getData()} and {@link CloudEvent#getExtensions()} * @return */ - public static CloudEventBuilder builder( - final CloudEvent base) { - Objects.requireNonNull(base); - - CloudEventBuilder result = new CloudEventBuilder<>(); - - AttributesImpl attributes = base.getAttributes(); - - result - .withId(attributes.getId()) - .withSource(attributes.getSource()) - .withType(attributes.getType()); - - attributes.getTime().ifPresent((time) -> { - result.withTime(time); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - result.withSchemaurl(schema); - }); - - attributes.getContenttype().ifPresent(contenttype -> { - result.withContenttype(contenttype); - }); - - Accessor.extensionsOf(base) - .forEach(extension -> { - result.withExtension(extension); - }); - - base.getData().ifPresent(data -> { - result.withData(data); - }); - - return result; - } - - /** - * - * @param the type of 'data' - * @param data the value of data - * @param attributes the context attributes - * @return An new {@link CloudEventImpl} immutable instance - * @throws IllegalStateException When there are specification constraints - * violations - */ - public static CloudEventImpl of(T data, AttributesImpl attributes, - Collection extensions) { - CloudEventBuilder builder = new CloudEventBuilder() - .withId(attributes.getId()) - .withSource(attributes.getSource()) - .withType(attributes.getType()); - - attributes.getTime().ifPresent((time) -> { - builder.withTime(time); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - builder.withSchemaurl(schema); - }); - - attributes.getContenttype().ifPresent(contenttype -> { - builder.withContenttype(contenttype); - }); - - extensions.stream() - .forEach(extension -> { - builder.withExtension(extension); - }); - - return builder.withData(data).build(); - } - - @Override - public CloudEvent build(T data, AttributesImpl attributes, - Collection extensions){ - return CloudEventBuilder.of(data, attributes, extensions); - } - - /** - * {@inheritDoc} - * @return An new {@link CloudEventImpl} immutable instance - * @throws IllegalStateException When there are specification constraints - * violations - */ - @Override - public CloudEventImpl build() { - AttributesImpl attributes = new AttributesImpl(type, SPEC_VERSION, - source, id, time, schemaurl, contenttype); - - CloudEventImpl event = new CloudEventImpl<>(attributes, data, extensions); - - Set> violations = - getValidator().validate(event); - - violations.addAll(getValidator().validate(event.getAttributes())); - - final String errs = - violations.stream() - .map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage())) - .collect(Collectors.joining(MESSAGE_SEPARATOR)); - - Optional.ofNullable( - "".equals(errs) ? null : errs - - ).ifPresent((e) -> { - throw new IllegalStateException(format(ERR_MESSAGE, e)); - }); - - return event; - } - - public CloudEventBuilder withType(String type) { - this.type = type; - return this; - } - - public CloudEventBuilder withId(String id) { - this.id = id; - return this; - } - - public CloudEventBuilder withSource(URI source) { - this.source = source; - return this; - } - - public CloudEventBuilder withTime(ZonedDateTime time) { - this.time = time; - return this; - } - - public CloudEventBuilder withSchemaurl(URI schemaurl) { - this.schemaurl = schemaurl; - return this; - } - - public CloudEventBuilder withContenttype(String contenttype) { - this.contenttype = contenttype; - return this; - } - - public CloudEventBuilder withData(T data) { - this.data = data; - return this; - } - - public CloudEventBuilder withExtension(ExtensionFormat extension) { - this.extensions.add(extension); - return this; - } + public static CloudEventBuilder builder( + CloudEvent base) { - /** - * {@inheritDoc} - */ - @Override - public CloudEvent - build(CloudEvent base, String id, TT newData) { - Objects.requireNonNull(base); - - AttributesImpl attributes = base.getAttributes(); - - CloudEventBuilder builder = new CloudEventBuilder() - .withId(id) - .withSource(attributes.getSource()) - .withType(attributes.getType()); - - attributes.getTime().ifPresent((time) -> { - builder.withTime(time); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - builder.withSchemaurl(schema); - }); - - attributes.getContenttype().ifPresent(contenttype -> { - builder.withContenttype(contenttype); - }); - - Collection extensions = Accessor.extensionsOf(base); - - extensions.stream() - .forEach(extension -> { - builder.withExtension(extension); - }); - - return builder.withData(newData).build(); - } + Objects.requireNonNull(base); + + CloudEventBuilder result = new CloudEventBuilder<>(); + + AttributesImpl attributes = base.getAttributes(); + + result + .withId(attributes.getId()) + .withSource(attributes.getSource()) + .withType(attributes.getType()); + + attributes.getTime().ifPresent(result::withTime); + attributes.getSchemaurl().ifPresent(result::withSchemaurl); + attributes.getContenttype().ifPresent(result::withContenttype); + Accessor.extensionsOf(base).forEach(result::withExtension); + base.getData().ifPresent(result::withData); + + return result; + } + + /** + * + * @param the type of 'data' + * @param data the value of data + * @param attributes the context attributes + * @return An new {@link CloudEventImpl} immutable instance + * @throws IllegalStateException When there are specification constraints + * violations + */ + public static CloudEventImpl of(T data, AttributesImpl attributes, + Collection extensions, Validator validator) { + CloudEventBuilder builder = new CloudEventBuilder() + .withId(attributes.getId()) + .withSource(attributes.getSource()) + .withType(attributes.getType()); + + attributes.getTime().ifPresent(builder::withTime); + attributes.getSchemaurl().ifPresent(builder::withSchemaurl); + attributes.getContenttype().ifPresent(builder::withContenttype); + extensions.forEach(builder::withExtension); + + return builder + .withData(data) + .withValidator(validator) + .build(); + } + + @Override + public CloudEvent build(T data, AttributesImpl attributes, + Collection extensions) { + return CloudEventBuilder.of(data, attributes, extensions, this.validator); + } + + /** + * {@inheritDoc} + * @return An new {@link CloudEventImpl} immutable instance + * @throws IllegalStateException When there are specification constraints + * violations + */ + @Override + public CloudEventImpl build() { + AttributesImpl attributes = new AttributesImpl(type, SPEC_VERSION, + source, id, time, schemaurl, contenttype); + + CloudEventImpl event = new CloudEventImpl<>(attributes, data, extensions); + + if (validator == null) { + validator = getValidator(); + } + Set> violations = + validator.validate(event); + + violations.addAll(validator.validate(event.getAttributes())); + + final String errs = + violations.stream() + .map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage())) + .collect(Collectors.joining(MESSAGE_SEPARATOR)); + + Optional.ofNullable( + "".equals(errs) ? null : errs + + ).ifPresent((e) -> { + throw new IllegalStateException(format(ERR_MESSAGE, e)); + }); + + return event; + } + + public CloudEventBuilder withType(String type) { + this.type = type; + return this; + } + + public CloudEventBuilder withId(String id) { + this.id = id; + return this; + } + + public CloudEventBuilder withSource(URI source) { + this.source = source; + return this; + } + + public CloudEventBuilder withTime(ZonedDateTime time) { + this.time = time; + return this; + } + + public CloudEventBuilder withSchemaurl(URI schemaurl) { + this.schemaurl = schemaurl; + return this; + } + + public CloudEventBuilder withContenttype(String contenttype) { + this.contenttype = contenttype; + return this; + } + + public CloudEventBuilder withData(T data) { + this.data = data; + return this; + } + + public CloudEventBuilder withExtension(ExtensionFormat extension) { + this.extensions.add(extension); + return this; + } + + public CloudEventBuilder withValidator(Validator validator) { + this.validator = validator; + return this; + } + + public CloudEvent + build(CloudEvent base, String id, TT newData) { + return build(base, id, newData, null); + } + + public CloudEvent + build(CloudEvent base, String id, TT newData, Validator validator) { + Objects.requireNonNull(base); + + AttributesImpl attributes = base.getAttributes(); + + CloudEventBuilder builder = new CloudEventBuilder() + .withId(id) + .withSource(attributes.getSource()) + .withType(attributes.getType()); + + attributes.getTime().ifPresent(builder::withTime); + attributes.getSchemaurl().ifPresent(builder::withSchemaurl); + attributes.getContenttype().ifPresent(builder::withContenttype); + Collection extensions = Accessor.extensionsOf(base); + extensions.forEach(builder::withExtension); + + return builder + .withData(newData) + .withValidator(validator) + .build(); + } } diff --git a/api/src/main/java/io/cloudevents/v02/CloudEventImpl.java b/api/src/main/java/io/cloudevents/v02/CloudEventImpl.java index 81e57f3a..72a48e69 100644 --- a/api/src/main/java/io/cloudevents/v02/CloudEventImpl.java +++ b/api/src/main/java/io/cloudevents/v02/CloudEventImpl.java @@ -117,7 +117,6 @@ public class CloudEventImpl implements CloudEvent { public static CloudEventImpl build( @JsonProperty("id") String id, @JsonProperty("source") URI source, - @JsonProperty("specversion") String specversion, @JsonProperty("type") String type, @JsonProperty("time") ZonedDateTime time, @JsonProperty("schemaurl") URI schemaurl, diff --git a/api/src/main/java/io/cloudevents/v02/ContextAttributes.java b/api/src/main/java/io/cloudevents/v02/ContextAttributes.java index e91c23b8..4517fc4f 100644 --- a/api/src/main/java/io/cloudevents/v02/ContextAttributes.java +++ b/api/src/main/java/io/cloudevents/v02/ContextAttributes.java @@ -36,8 +36,7 @@ public enum ContextAttributes { contenttype; public static final List VALUES = - Arrays.asList(ContextAttributes.values()) - .stream() + Arrays.stream(ContextAttributes.values()) .map(Enum::name) .collect(Collectors.toList()); } diff --git a/api/src/main/java/io/cloudevents/v02/http/ExtensionMapper.java b/api/src/main/java/io/cloudevents/v02/http/ExtensionMapper.java index 03399b83..1e703bba 100644 --- a/api/src/main/java/io/cloudevents/v02/http/ExtensionMapper.java +++ b/api/src/main/java/io/cloudevents/v02/http/ExtensionMapper.java @@ -58,9 +58,7 @@ public class ExtensionMapper { .filter(header -> null!= header.getValue()) .map(header -> new SimpleEntry<>(header.getKey() .toLowerCase(Locale.US), header.getValue().toString())) - .filter(header -> { - return !RESERVED_HEADERS.contains(header.getKey()); - }) + .filter(header -> !RESERVED_HEADERS.contains(header.getKey())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } diff --git a/api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java b/api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java index 63ee735d..30bc6721 100644 --- a/api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java +++ b/api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java @@ -1,5 +1,8 @@ package io.cloudevents.v02.http; +import javax.validation.Valid; +import javax.validation.Validator; + import io.cloudevents.extensions.DistributedTracingExtension; import io.cloudevents.format.BinaryUnmarshaller; import io.cloudevents.format.StructuredUnmarshaller; @@ -16,18 +19,33 @@ import io.cloudevents.v02.CloudEventImpl; */ public class Unmarshallers { private Unmarshallers() {} - + + /** + * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param type The type reference to use for 'data' unmarshal + * @return A step to supply the headers, payload and to unmarshal + * @see BinaryUnmarshaller + */ + public static HeadersStep + binary(Class type) { + return binary(type, null); + } + /** * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data * for HTTP Transport Binding * * @param The 'data' type * @param type The type reference to use for 'data' unmarshal + * @param validator Provide an existing instance of a {@link Validator} * @return A step to supply the headers, payload and to unmarshal * @see BinaryUnmarshaller */ public static HeadersStep - binary(Class type) { + binary(Class type, Validator validator) { return BinaryUnmarshaller.builder() .map(AttributeMapper::map) @@ -37,20 +55,35 @@ public class Unmarshallers { .map(ExtensionMapper::map) .map(DistributedTracingExtension::unmarshall) .next() - .builder(CloudEventBuilder.builder()::build); + .builder(CloudEventBuilder.builder().withValidator(validator)::build); } - + + /** + * Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param typeOfData The type reference to use for 'data' unmarshal + * @return A step to supply the headers, payload and to unmarshal + * @see StructuredUnmarshaller + */ + public static HeadersStep + structured(Class typeOfData) { + return structured(typeOfData, null); + } + /** * Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data * for HTTP Transport Binding * * @param The 'data' type * @param typeOfData The type reference to use for 'data' unmarshal + * @param validator Provided instance of a {@link Validator} * @return A step to supply the headers, payload and to unmarshal * @see StructuredUnmarshaller */ public static HeadersStep - structured(Class typeOfData) { + structured(Class typeOfData, Validator validator) { return StructuredUnmarshaller. @@ -69,8 +102,8 @@ public class Unmarshallers { extensions.get().forEach(extension -> { builder.withExtension(extension); }); - - return builder.build(); + + return builder.withValidator(validator).build(); }); } } diff --git a/api/src/main/java/io/cloudevents/v03/AttributesImpl.java b/api/src/main/java/io/cloudevents/v03/AttributesImpl.java index df40f0ce..f40f7682 100644 --- a/api/src/main/java/io/cloudevents/v03/AttributesImpl.java +++ b/api/src/main/java/io/cloudevents/v03/AttributesImpl.java @@ -168,27 +168,13 @@ public class AttributesImpl implements Attributes { result.put(ContextAttributes.id.name(), attributes.getId()); - attributes.getTime().ifPresent((value) -> { - result.put(ContextAttributes.time.name(), - value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - result.put(ContextAttributes.schemaurl.name(), - schema.toString()); - }); - - attributes.getDatacontenttype().ifPresent((ct) -> { - result.put(ContextAttributes.datacontenttype.name(), ct); - }); - - attributes.getDatacontentencoding().ifPresent(dce -> { - result.put(ContextAttributes.datacontentencoding.name(), dce); - }); - - attributes.getSubject().ifPresent(subject -> { - result.put(ContextAttributes.subject.name(), subject); - }); + attributes.getTime().ifPresent((value) -> result.put(ContextAttributes.time.name(), + value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))); + attributes.getSchemaurl().ifPresent((schema) -> result.put(ContextAttributes.schemaurl.name(), + schema.toString())); + attributes.getDatacontenttype().ifPresent((ct) -> result.put(ContextAttributes.datacontenttype.name(), ct)); + attributes.getDatacontentencoding().ifPresent(dce -> result.put(ContextAttributes.datacontentencoding.name(), dce)); + attributes.getSubject().ifPresent(subject -> result.put(ContextAttributes.subject.name(), subject)); return result; } @@ -210,7 +196,7 @@ public class AttributesImpl implements Attributes { URI schemaurl = Optional.ofNullable(attributes.get(ContextAttributes.schemaurl.name())) - .map(schema -> URI.create(schema)) + .map(URI::create) .orElse(null); String id = attributes.get(ContextAttributes.id.name()); diff --git a/api/src/main/java/io/cloudevents/v03/CloudEventBuilder.java b/api/src/main/java/io/cloudevents/v03/CloudEventBuilder.java index 2fa684bb..0431b6fd 100644 --- a/api/src/main/java/io/cloudevents/v03/CloudEventBuilder.java +++ b/api/src/main/java/io/cloudevents/v03/CloudEventBuilder.java @@ -36,72 +36,58 @@ import io.cloudevents.fun.EventBuilder; /** * The event builder. - * + * * @author fabiojose * */ -public final class CloudEventBuilder implements +public final class CloudEventBuilder implements EventBuilder { - private CloudEventBuilder(Validator validator) { - if(validator == null) { - this.validator = getValidator(); - } else { - this.validator = validator; - } - } + private CloudEventBuilder() {} private static Validator VALIDATOR; - + public static final String SPEC_VERSION = "0.3"; private static final String MESSAGE_SEPARATOR = ", "; private static final String MESSAGE = "'%s' %s"; private static final String ERR_MESSAGE = "invalid payload: %s"; - + private String id; private URI source; private String type; - + private ZonedDateTime time; private URI schemaurl; private String datacontentencoding; private String datacontenttype; private String subject; - + private T data; - + private final Set extensions = new HashSet<>(); - private final Validator validator; - + private Validator validator; + private static Validator getValidator() { if(null== VALIDATOR) { VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); } return VALIDATOR; } - + /** * Gets a brand new builder instance * @param The 'data' type */ public static CloudEventBuilder builder() { - return new CloudEventBuilder(null); + return new CloudEventBuilder<>(); } - public static CloudEventBuilder builder(Validator validator) { - return new CloudEventBuilder(validator); - } public static CloudEventBuilder builder( CloudEvent base) { - return builder(base, null); - } - - public static CloudEventBuilder builder( - CloudEvent base, Validator validator) { Objects.requireNonNull(base); - CloudEventBuilder result = new CloudEventBuilder<>(validator); + CloudEventBuilder result = new CloudEventBuilder<>(); AttributesImpl attributes = base.getAttributes(); @@ -110,34 +96,13 @@ public final class CloudEventBuilder implements .withSource(attributes.getSource()) .withType(attributes.getType()); - attributes.getTime().ifPresent(time -> { - result.withTime(time); - }); - - attributes.getSchemaurl().ifPresent((schema) -> { - result.withSchemaurl(schema); - }); - - attributes.getDatacontenttype().ifPresent(dc -> { - result.withDatacontenttype(dc); - }); - - attributes.getDatacontentencoding().ifPresent(dce -> { - result.withDatacontentencoding(dce); - }); - - attributes.getSubject().ifPresent(subject -> { - result.withSubject(subject); - }); - - Accessor.extensionsOf(base) - .forEach(extension -> { - result.withExtension(extension); - }); - - base.getData().ifPresent(data -> { - result.withData(data); - }); + attributes.getTime().ifPresent(result::withTime); + attributes.getSchemaurl().ifPresent(result::withSchemaurl); + attributes.getDatacontenttype().ifPresent(result::withDatacontenttype); + attributes.getDatacontentencoding().ifPresent(result::withDatacontentencoding); + attributes.getSubject().ifPresent(result::withSubject); + Accessor.extensionsOf(base).forEach(result::withExtension); + base.getData().ifPresent(result::withData); return result; } @@ -169,132 +134,124 @@ public final class CloudEventBuilder implements */ public static CloudEventImpl of(T data, AttributesImpl attributes, Collection extensions, Validator validator) { - CloudEventBuilder builder = CloudEventBuilder.builder(validator) + CloudEventBuilder builder = CloudEventBuilder.builder() .withId(attributes.getId()) .withSource(attributes.getSource()) .withType(attributes.getType()); - - attributes.getTime().ifPresent((time) -> { - builder.withTime(time); - }); - - attributes.getSchemaurl().ifPresent((schemaurl) -> { - builder.withSchemaurl(schemaurl); - }); - - attributes.getDatacontentencoding().ifPresent((dce) -> { - builder.withDatacontentencoding(dce); - }); - - attributes.getDatacontenttype().ifPresent((dct) -> { - builder.withDatacontenttype(dct); - }); - - attributes.getSubject().ifPresent((subject) -> { - builder.withSubject(subject); - }); - - extensions.stream() - .forEach(extension -> { - builder.withExtension(extension); - }); - - builder.withData(data); - - return builder.build(); + + attributes.getTime().ifPresent(builder::withTime); + attributes.getSchemaurl().ifPresent(builder::withSchemaurl); + attributes.getDatacontentencoding().ifPresent(builder::withDatacontentencoding); + attributes.getDatacontenttype().ifPresent(builder::withDatacontenttype); + attributes.getSubject().ifPresent(builder::withSubject); + extensions.forEach(builder::withExtension); + + return builder + .withData(data) + .withValidator(validator) + .build(); } - + @Override - public CloudEvent build(T data, AttributesImpl attributes, + public CloudEvent build(T data, AttributesImpl attributes, Collection extensions){ - return CloudEventBuilder.of(data, attributes, extensions, null); + return CloudEventBuilder.of(data, attributes, extensions, this.validator); } - + /** - * + * * @return An new {@link CloudEvent} immutable instance * @throws IllegalStateException When there are specification constraints * violations */ public CloudEventImpl build() { - + AttributesImpl attributes = new AttributesImpl(id, source, SPEC_VERSION, type, time, schemaurl, datacontentencoding, datacontenttype, subject); - - CloudEventImpl cloudEvent = - new CloudEventImpl(attributes, data, extensions); - + + CloudEventImpl cloudEvent = + new CloudEventImpl<>(attributes, data, extensions); + + if(validator == null) { + validator = getValidator(); + } + Set> violations = validator.validate(cloudEvent); - + violations.addAll(validator.validate(cloudEvent.getAttributes())); - - final String errs = + + final String errs = violations.stream() .map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage())) .collect(Collectors.joining(MESSAGE_SEPARATOR)); - + Optional.ofNullable( "".equals(errs) ? null : errs - + ).ifPresent((e) -> { throw new IllegalStateException(format(ERR_MESSAGE, e)); }); - + return cloudEvent; } - + public CloudEventBuilder withId(String id) { this.id = id; return this; } - + public CloudEventBuilder withSource(URI source) { this.source = source; return this; } - + public CloudEventBuilder withType(String type) { this.type = type; return this; } - + public CloudEventBuilder withTime(ZonedDateTime time) { this.time = time; return this; } - + public CloudEventBuilder withSchemaurl(URI schemaurl) { this.schemaurl = schemaurl; return this; } - + public CloudEventBuilder withDatacontentencoding( String datacontentencoding) { this.datacontentencoding = datacontentencoding; return this; } - + public CloudEventBuilder withDatacontenttype( String datacontenttype) { this.datacontenttype = datacontenttype; return this; } - + public CloudEventBuilder withSubject( String subject) { this.subject = subject; return this; } - + public CloudEventBuilder withData(T data) { this.data = data; return this; } - + public CloudEventBuilder withExtension(ExtensionFormat extension) { this.extensions.add(extension); return this; } + + public CloudEventBuilder withValidator(Validator validator) { + this.validator = validator; + return this; + } } diff --git a/api/src/main/java/io/cloudevents/v03/CloudEventImpl.java b/api/src/main/java/io/cloudevents/v03/CloudEventImpl.java index 767b9f19..05d4764d 100644 --- a/api/src/main/java/io/cloudevents/v03/CloudEventImpl.java +++ b/api/src/main/java/io/cloudevents/v03/CloudEventImpl.java @@ -114,7 +114,6 @@ public class CloudEventImpl implements CloudEvent { public static CloudEventImpl build( @JsonProperty("id") String id, @JsonProperty("source") URI source, - @JsonProperty("specversion") String specversion, @JsonProperty("type") String type, @JsonProperty("time") ZonedDateTime time, @JsonProperty("schemaurl") URI schemaurl, diff --git a/api/src/main/java/io/cloudevents/v03/ContextAttributes.java b/api/src/main/java/io/cloudevents/v03/ContextAttributes.java index da1c17e3..0fc38448 100644 --- a/api/src/main/java/io/cloudevents/v03/ContextAttributes.java +++ b/api/src/main/java/io/cloudevents/v03/ContextAttributes.java @@ -38,8 +38,7 @@ public enum ContextAttributes { subject; public static final List VALUES = - Arrays.asList(ContextAttributes.values()) - .stream() + Arrays.stream(ContextAttributes.values()) .map(Enum::name) .collect(Collectors.toList()); } diff --git a/api/src/main/java/io/cloudevents/v03/http/ExtensionMapper.java b/api/src/main/java/io/cloudevents/v03/http/ExtensionMapper.java index fd9d4adf..4a7a2215 100644 --- a/api/src/main/java/io/cloudevents/v03/http/ExtensionMapper.java +++ b/api/src/main/java/io/cloudevents/v03/http/ExtensionMapper.java @@ -58,9 +58,7 @@ public class ExtensionMapper { .filter(header -> null!= header.getValue()) .map(header -> new SimpleEntry<>(header.getKey() .toLowerCase(Locale.US), header.getValue().toString())) - .filter(header -> { - return !RESERVED_HEADERS.contains(header.getKey()); - }) + .filter(header -> !RESERVED_HEADERS.contains(header.getKey())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } diff --git a/api/src/main/java/io/cloudevents/v03/http/Unmarshallers.java b/api/src/main/java/io/cloudevents/v03/http/Unmarshallers.java index c8eea176..b40ca81e 100644 --- a/api/src/main/java/io/cloudevents/v03/http/Unmarshallers.java +++ b/api/src/main/java/io/cloudevents/v03/http/Unmarshallers.java @@ -15,6 +15,8 @@ */ package io.cloudevents.v03.http; +import javax.validation.Validator; + import io.cloudevents.extensions.DistributedTracingExtension; import io.cloudevents.format.BinaryUnmarshaller; import io.cloudevents.format.StructuredUnmarshaller; @@ -31,18 +33,33 @@ import io.cloudevents.v03.CloudEventImpl; */ public class Unmarshallers { private Unmarshallers() {} - + + /** + * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param type The type reference to use for 'data' unmarshal + * @return A step to supply the headers, payload and to unmarshal + * @see BinaryUnmarshaller + */ + public static HeadersStep + binary(Class type) { + return binary(type, null); + } + /** * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data * for HTTP Transport Binding * * @param The 'data' type * @param type The type reference to use for 'data' unmarshal + * @param validator Provided instance of a {@link Validator} * @return A step to supply the headers, payload and to unmarshal * @see BinaryUnmarshaller */ public static HeadersStep - binary(Class type) { + binary(Class type, Validator validator) { return BinaryUnmarshaller.builder() .map(AttributeMapper::map) @@ -52,20 +69,35 @@ public class Unmarshallers { .map(ExtensionMapper::map) .map(DistributedTracingExtension::unmarshall) .next() - .builder(CloudEventBuilder.builder()::build); + .builder(CloudEventBuilder.builder().withValidator(validator)::build); } - + + /** + * Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param typeOfData The type reference to use for 'data' unmarshal + * @return A step to supply the headers, payload and to unmarshal + * @see StructuredUnmarshaller + */ + public static HeadersStep + structured(Class typeOfData) { + return structured(typeOfData, null); + } + /** * Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data * for HTTP Transport Binding * * @param The 'data' type * @param typeOfData The type reference to use for 'data' unmarshal + * @param validator Provided instance of a {@link Validator} * @return A step to supply the headers, payload and to unmarshal * @see StructuredUnmarshaller */ public static HeadersStep - structured(Class typeOfData) { + structured(Class typeOfData, Validator validator) { return StructuredUnmarshaller. @@ -85,7 +117,7 @@ public class Unmarshallers { builder.withExtension(extension); }); - return builder.build(); + return builder.withValidator(validator).build(); }); } } diff --git a/api/src/main/java/io/cloudevents/v1/AttributesImpl.java b/api/src/main/java/io/cloudevents/v1/AttributesImpl.java index de25cc45..8c51cae4 100644 --- a/api/src/main/java/io/cloudevents/v1/AttributesImpl.java +++ b/api/src/main/java/io/cloudevents/v1/AttributesImpl.java @@ -165,23 +165,12 @@ public class AttributesImpl implements Attributes { result.put(ContextAttributes.type.name(), attributes.getType()); - attributes.getDatacontenttype().ifPresent(dct -> { - result.put(ContextAttributes.datacontenttype.name(), dct); - }); - - attributes.getDataschema().ifPresent(dataschema -> { - result.put(ContextAttributes.dataschema.name(), - dataschema.toString()); - }); - - attributes.getSubject().ifPresent(subject -> { - result.put(ContextAttributes.subject.name(), subject); - }); - - attributes.getTime().ifPresent(time -> { - result.put(ContextAttributes.time.name(), - time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - }); + attributes.getDatacontenttype().ifPresent(dct -> result.put(ContextAttributes.datacontenttype.name(), dct)); + attributes.getDataschema().ifPresent(dataschema -> result.put(ContextAttributes.dataschema.name(), + dataschema.toString())); + attributes.getSubject().ifPresent(subject -> result.put(ContextAttributes.subject.name(), subject)); + attributes.getTime().ifPresent(time -> result.put(ContextAttributes.time.name(), + time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))); return result; } @@ -203,7 +192,7 @@ public class AttributesImpl implements Attributes { URI dataschema = Optional.ofNullable(attributes.get(ContextAttributes.dataschema.name())) - .map(schema -> URI.create(schema)) + .map(URI::create) .orElse(null); String id = attributes.get(ContextAttributes.id.name()); diff --git a/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java b/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java index 4a7c4a53..c2d456f6 100644 --- a/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java +++ b/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java @@ -44,11 +44,13 @@ public class CloudEventBuilder implements private URI dataschema; private String subject; private ZonedDateTime time; - + private T data; - + private final Set extensions = new HashSet<>(); - + + private Validator validator; + private static Validator getValidator() { if(null== VALIDATOR) { VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); @@ -61,16 +63,16 @@ public class CloudEventBuilder implements * @param The 'data' type */ public static CloudEventBuilder builder() { - return new CloudEventBuilder(); + return new CloudEventBuilder<>(); } - + /** * Builder with base event to copy attributes * @param The 'data' type * @param base The base event to copy attributes */ public static CloudEventBuilder builder( - CloudEvent base) { + CloudEvent base) { Objects.requireNonNull(base); CloudEventBuilder result = new CloudEventBuilder<>(); @@ -82,31 +84,13 @@ public class CloudEventBuilder implements .withSource(attributes.getSource()) .withType(attributes.getType()); - attributes.getDataschema().ifPresent((schema) -> { - result.withDataschema(schema); - }); - - attributes.getDatacontenttype().ifPresent(dc -> { - result.withDataContentType(dc); - }); - - attributes.getSubject().ifPresent(subject -> { - result.withSubject(subject); - }); - - attributes.getTime().ifPresent(time -> { - result.withTime(time); - }); - - Accessor.extensionsOf(base) - .forEach(extension -> { - result.withExtension(extension); - }); - - base.getData().ifPresent(data -> { - result.withData(data); - }); - + attributes.getDataschema().ifPresent(result::withDataschema); + attributes.getDatacontenttype().ifPresent(result::withDataContentType); + attributes.getSubject().ifPresent(result::withSubject); + attributes.getTime().ifPresent(result::withTime); + Accessor.extensionsOf(base).forEach(result::withExtension); + base.getData().ifPresent(result::withData); + return result; } @@ -120,30 +104,16 @@ public class CloudEventBuilder implements .withSource(attributes.getSource()) .withType(attributes.getType()); - attributes.getTime().ifPresent((time) -> { - builder.withTime(time); - }); - - attributes.getDataschema().ifPresent((dataschema) -> { - builder.withDataschema(dataschema); - }); - - attributes.getDatacontenttype().ifPresent((dct) -> { - builder.withDataContentType(dct); - }); - - attributes.getSubject().ifPresent((subject) -> { - builder.withSubject(subject); - }); - - extensions.stream() - .forEach(extension -> { - builder.withExtension(extension); - }); - - builder.withData(data); - - return builder.build(); + attributes.getTime().ifPresent(builder::withTime); + attributes.getDataschema().ifPresent(builder::withDataschema); + attributes.getDatacontenttype().ifPresent(builder::withDataContentType); + attributes.getSubject().ifPresent(builder::withSubject); + extensions.forEach(builder::withExtension); + + return builder + .withData(data) + .withValidator(validator) + .build(); } /** @@ -157,17 +127,19 @@ public class CloudEventBuilder implements AttributesImpl attributes = new AttributesImpl(id, source, SPEC_VERSION, type, datacontenttype, dataschema, subject, time); - CloudEventImpl cloudEvent = - new CloudEventImpl(attributes, data, extensions); + CloudEventImpl cloudEvent = + new CloudEventImpl<>(attributes, data, extensions); if(data instanceof byte[]) { cloudEvent.setDataBase64((byte[])data); } - + if(validator == null) { + validator = getValidator(); + } Set> violations = - getValidator().validate(cloudEvent); + validator.validate(cloudEvent); - violations.addAll(getValidator().validate(cloudEvent.getAttributes())); + violations.addAll(validator.validate(cloudEvent.getAttributes())); final String errs = violations.stream() @@ -231,4 +203,9 @@ public class CloudEventBuilder implements this.extensions.add(extension); return this; } + + public CloudEventBuilder withValidator(Validator validator) { + this.validator = validator; + return this; + } } diff --git a/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java b/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java index c6dd53d2..5305582a 100644 --- a/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java +++ b/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java @@ -140,7 +140,6 @@ public class CloudEventImpl implements CloudEvent { public static CloudEventImpl build( @JsonProperty("id") String id, @JsonProperty("source") URI source, - @JsonProperty("specversion") String specversion, @JsonProperty("type") String type, @JsonProperty("datacontenttype") String datacontenttype, @JsonProperty("dataschema") URI dataschema, diff --git a/api/src/main/java/io/cloudevents/v1/ContextAttributes.java b/api/src/main/java/io/cloudevents/v1/ContextAttributes.java index 1f2a3d64..36407e83 100644 --- a/api/src/main/java/io/cloudevents/v1/ContextAttributes.java +++ b/api/src/main/java/io/cloudevents/v1/ContextAttributes.java @@ -36,8 +36,7 @@ public enum ContextAttributes { time; public static final List VALUES = - Arrays.asList(ContextAttributes.values()) - .stream() + Arrays.stream(ContextAttributes.values()) .map(Enum::name) .collect(Collectors.toList()); } diff --git a/api/src/main/java/io/cloudevents/v1/http/ExtensionMapper.java b/api/src/main/java/io/cloudevents/v1/http/ExtensionMapper.java index 6e5ebe02..ff4c7a28 100644 --- a/api/src/main/java/io/cloudevents/v1/http/ExtensionMapper.java +++ b/api/src/main/java/io/cloudevents/v1/http/ExtensionMapper.java @@ -44,9 +44,7 @@ public class ExtensionMapper { .filter(header -> null!= header.getValue()) .map(header -> new SimpleEntry<>(header.getKey() .toLowerCase(Locale.US), header.getValue().toString())) - .filter(header -> { - return !RESERVED_HEADERS.contains(header.getKey()); - }) + .filter(header -> !RESERVED_HEADERS.contains(header.getKey())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } } diff --git a/api/src/main/java/io/cloudevents/v1/http/HeaderMapper.java b/api/src/main/java/io/cloudevents/v1/http/HeaderMapper.java index f05397c3..faece4a5 100644 --- a/api/src/main/java/io/cloudevents/v1/http/HeaderMapper.java +++ b/api/src/main/java/io/cloudevents/v1/http/HeaderMapper.java @@ -58,14 +58,14 @@ public class HeaderMapper { .toLowerCase(Locale.US), header.getValue())) .filter(header -> !header.getKey() .equals(ContextAttributes.datacontenttype.name())) - .map(header -> new SimpleEntry<>(HEADER_PREFIX+header.getKey(), + .map(header -> new SimpleEntry<>(HEADER_PREFIX + header.getKey(), header.getValue())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); result.putAll( extensions.entrySet() .stream() - .filter(extension -> null!= extension.getValue()) + .filter(extension -> null != extension.getValue()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)) ); diff --git a/api/src/main/java/io/cloudevents/v1/http/Unmarshallers.java b/api/src/main/java/io/cloudevents/v1/http/Unmarshallers.java index 459f639c..90412c32 100644 --- a/api/src/main/java/io/cloudevents/v1/http/Unmarshallers.java +++ b/api/src/main/java/io/cloudevents/v1/http/Unmarshallers.java @@ -15,6 +15,8 @@ */ package io.cloudevents.v1.http; +import javax.validation.Validator; + import io.cloudevents.extensions.DistributedTracingExtension; import io.cloudevents.format.BinaryUnmarshaller; import io.cloudevents.format.StructuredUnmarshaller; @@ -23,8 +25,6 @@ import io.cloudevents.json.Json; import io.cloudevents.v1.AttributesImpl; import io.cloudevents.v1.CloudEventBuilder; import io.cloudevents.v1.CloudEventImpl; -import io.cloudevents.v1.http.AttributeMapper; -import io.cloudevents.v1.http.ExtensionMapper; /** * @@ -33,18 +33,33 @@ import io.cloudevents.v1.http.ExtensionMapper; */ public class Unmarshallers { private Unmarshallers() {} - + /** * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data * for HTTP Transport Binding - * + * * @param The 'data' type * @param type The type reference to use for 'data' unmarshal * @return A step to supply the headers, payload and to unmarshal * @see BinaryUnmarshaller */ - public static HeadersStep + public static HeadersStep binary(Class type) { + return binary(type, null); + } + + /** + * Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param type The type reference to use for 'data' unmarshal + * @param validator Provided instance of a {@link Validator} + * @return A step to supply the headers, payload and to unmarshal + * @see BinaryUnmarshaller + */ + public static HeadersStep + binary(Class type, Validator validator) { return BinaryUnmarshaller.builder() .map(AttributeMapper::map) @@ -54,7 +69,7 @@ public class Unmarshallers { .map(ExtensionMapper::map) .map(DistributedTracingExtension::unmarshall) .next() - .builder(CloudEventBuilder.builder()::build); + .builder(CloudEventBuilder.builder().withValidator(validator)::build); } /** @@ -68,7 +83,21 @@ public class Unmarshallers { */ public static HeadersStep structured(Class typeOfData) { - + return structured(typeOfData, null); + } + + /** + * Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data + * for HTTP Transport Binding + * + * @param The 'data' type + * @param typeOfData The type reference to use for 'data' unmarshal + * @param validator Provided instance of a {@link Validator} + * @return A step to supply the headers, payload and to unmarshal + * @see StructuredUnmarshaller + */ + public static HeadersStep + structured(Class typeOfData, Validator validator) { return StructuredUnmarshaller. builder() @@ -86,8 +115,8 @@ public class Unmarshallers { extensions.get().forEach(extension -> { builder.withExtension(extension); }); - - return builder.build(); + + return builder.withValidator(validator).build(); }); } } diff --git a/api/src/test/java/io/cloudevents/format/StructuredMarshallerTest.java b/api/src/test/java/io/cloudevents/format/StructuredMarshallerTest.java index 7d602907..45395d6e 100644 --- a/api/src/test/java/io/cloudevents/format/StructuredMarshallerTest.java +++ b/api/src/test/java/io/cloudevents/format/StructuredMarshallerTest.java @@ -78,10 +78,7 @@ public class StructuredMarshallerTest { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) + .map((ce) -> null) .skip() .withEvent(null); } @@ -91,10 +88,7 @@ public class StructuredMarshallerTest { // act StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) + .map((ce) -> null) .skip() .withEvent(() -> null); } @@ -106,10 +100,7 @@ public class StructuredMarshallerTest { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) + .map((ce) -> null) .map(null); } @@ -118,14 +109,8 @@ public class StructuredMarshallerTest { // act StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) - .map((event) -> { - - return null; - }); + .map((ce) -> null) + .map((event) -> null); } @Test @@ -135,14 +120,8 @@ public class StructuredMarshallerTest { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) - .map((event) -> { - - return null; - }) + .map((ce) -> null) + .map((event) -> null) .map(null); } @@ -150,18 +129,9 @@ public class StructuredMarshallerTest { public void should_ok_on_extension_marshaller() { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) - .map((event) -> { - - return null; - }) - .map((extensions) -> { - - return null; - }); + .map((ce) -> null) + .map((event) -> null) + .map((extensions) -> null); } @Test @@ -171,18 +141,9 @@ public class StructuredMarshallerTest { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) - .map((event) -> { - - return null; - }) - .map((extensions) -> { - - return null; - }) + .map((ce) -> null) + .map((event) -> null) + .map((extensions) -> null) .map(null); } @@ -190,18 +151,9 @@ public class StructuredMarshallerTest { public void should_ok_on_header_mapper() { StructuredMarshaller.builder() .mime("Content-Type", "application/cloudevents+json") - .map((ce) -> { - - return null; - }) - .map((event) -> { - - return null; - }) - .map((extensions) -> { - - return null; - }) + .map((ce) -> null) + .map((event) -> null) + .map((extensions) -> null) .map((attributes, extensions) -> null); } } diff --git a/api/src/test/java/io/cloudevents/v02/CloudEventBuilderTest.java b/api/src/test/java/io/cloudevents/v02/CloudEventBuilderTest.java index c7186e5d..8b39fedb 100644 --- a/api/src/test/java/io/cloudevents/v02/CloudEventBuilderTest.java +++ b/api/src/test/java/io/cloudevents/v02/CloudEventBuilderTest.java @@ -18,10 +18,15 @@ package io.cloudevents.v02; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.net.URI; import java.time.ZonedDateTime; +import java.util.Collections; +import javax.validation.Validator; + +import io.cloudevents.validation.MockValidator; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -318,4 +323,39 @@ public class CloudEventBuilderTest { assertEquals(expected, actual.getData().get()); assertEquals("0x010", actual.getAttributes().getId()); } + + @Test + public void should_build_event_using_custom_validator() { + Validator validator = new MockValidator(); + String expected = "test"; + + CloudEventImpl event = CloudEventBuilder + .builder() + .withData(expected) + .withValidator(validator) + .build(); + + assertNotNull(event); + assertEquals(expected, event.getData().get()); + } + + @Test + public void should_build_event_from_event_using_custom_validator() { + Validator validator = new MockValidator(); + String expected = "test"; + CloudEvent event = CloudEventBuilder.of( + expected, + new AttributesImpl(null, null, null, null, null, null, null), + Collections.emptyList(), + validator + ); + + CloudEvent result = CloudEventBuilder + .builder(event) + .withValidator(validator) + .build(); + + assertNotNull(result); + assertEquals(expected, result.getData().get()); + } } diff --git a/api/src/test/java/io/cloudevents/v03/CloudEventBuilderTest.java b/api/src/test/java/io/cloudevents/v03/CloudEventBuilderTest.java index b0db4cd3..07434be0 100644 --- a/api/src/test/java/io/cloudevents/v03/CloudEventBuilderTest.java +++ b/api/src/test/java/io/cloudevents/v03/CloudEventBuilderTest.java @@ -33,6 +33,7 @@ import javax.validation.ValidatorFactory; import javax.validation.executable.ExecutableValidator; import javax.validation.metadata.BeanDescriptor; +import io.cloudevents.validation.MockValidator; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -217,33 +218,15 @@ public class CloudEventBuilderTest { assertTrue(actual instanceof String); } - @Test - public void should_copy_event_using_custom_validator() { - Validator validator = new MockValidator(); - String expected = "test"; - CloudEvent event = CloudEventBuilder.of( - expected, - new AttributesImpl(null, null, null, null, null, null, null, null, null), - Collections.emptyList(), - validator - ); - - try { - CloudEventBuilder.builder().build(); - fail("Expected validation error"); - } catch (IllegalStateException e) { - assertNotNull(e); - } - } - @Test public void should_build_event_using_custom_validator() { Validator validator = new MockValidator(); String expected = "test"; CloudEventImpl event = CloudEventBuilder - .builder(validator) + .builder() .withData(expected) + .withValidator(validator) .build(); assertNotNull(event); @@ -262,43 +245,11 @@ public class CloudEventBuilderTest { ); CloudEvent result = CloudEventBuilder - .builder(event, validator) + .builder(event) + .withValidator(validator) .build(); assertNotNull(result); assertEquals(expected, result.getData().get()); } - - private static class MockValidator implements Validator { - - @Override - public Set> validate(T object, Class... groups) { - return new HashSet<>(); - } - - @Override - public Set> validateProperty(T object, String propertyName, Class... groups) { - return null; - } - - @Override - public Set> validateValue(Class beanType, String propertyName, Object value, Class... groups) { - return null; - } - - @Override - public BeanDescriptor getConstraintsForClass(Class clazz) { - return null; - } - - @Override - public T unwrap(Class type) { - return null; - } - - @Override - public ExecutableValidator forExecutables() { - return null; - } - } } diff --git a/api/src/test/java/io/cloudevents/v1/CloudEventBuilderTest.java b/api/src/test/java/io/cloudevents/v1/CloudEventBuilderTest.java index f7b832ef..683b88f9 100644 --- a/api/src/test/java/io/cloudevents/v1/CloudEventBuilderTest.java +++ b/api/src/test/java/io/cloudevents/v1/CloudEventBuilderTest.java @@ -18,9 +18,14 @@ package io.cloudevents.v1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.net.URI; +import java.util.Collections; +import javax.validation.Validator; + +import io.cloudevents.validation.MockValidator; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -191,4 +196,37 @@ public class CloudEventBuilderTest { assertNotNull(actual); assertTrue(actual instanceof String); } + + @Test + public void should_build_event_using_custom_validator() { + Validator validator = new MockValidator(); + String expected = "test"; + + CloudEventImpl event = CloudEventBuilder + .builder() + .withData(expected) + .withValidator(validator) + .build(); + + assertNotNull(event); + assertEquals(expected, event.getData().get()); + } + + @Test + public void should_build_event_from_event_using_custom_validator() { + Validator validator = new MockValidator(); + String expected = "test"; + CloudEvent event = CloudEventBuilder.builder() + .withData(expected) + .withValidator(validator) + .build(); + + CloudEvent result = CloudEventBuilder + .builder(event) + .withValidator(validator) + .build(); + + assertNotNull(result); + assertEquals(expected, result.getData().get()); + } } diff --git a/api/src/test/java/io/cloudevents/validation/MockValidator.java b/api/src/test/java/io/cloudevents/validation/MockValidator.java new file mode 100644 index 00000000..d50d9dd9 --- /dev/null +++ b/api/src/test/java/io/cloudevents/validation/MockValidator.java @@ -0,0 +1,42 @@ +package io.cloudevents.validation; + +import java.util.HashSet; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.executable.ExecutableValidator; +import javax.validation.metadata.BeanDescriptor; + +public class MockValidator implements Validator { + + @Override + public Set> validate(T object, Class... groups) { + return new HashSet<>(); + } + + @Override + public Set> validateProperty(T object, String propertyName, Class... groups) { + return null; + } + + @Override + public Set> validateValue(Class beanType, String propertyName, Object value, Class... groups) { + return null; + } + + @Override + public BeanDescriptor getConstraintsForClass(Class clazz) { + return null; + } + + @Override + public T unwrap(Class type) { + return null; + } + + @Override + public ExecutableValidator forExecutables() { + return null; + } +} \ No newline at end of file