Added conversion between spec versions

Added copy builder

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
slinkydeveloper 2020-04-20 10:45:04 +02:00 committed by Francesco Guardiani
parent ad5ab5d491
commit d54dcf7f83
22 changed files with 156 additions and 894 deletions

View File

@ -60,4 +60,8 @@ public interface Attributes {
Optional<ZonedDateTime> getTime();
Attributes toV03();
Attributes toV1();
}

View File

@ -54,23 +54,23 @@ public interface CloudEvent {
*/
Map<String, Object> getExtensions();
/**
* Write an extension into this cloud event
* @param e
*/
default void writeExtension(Extension e) {
e.writeToEvent(this);
}
CloudEvent toV03();
static io.cloudevents.v1.CloudEventBuilder build() {
return buildV1();
}
CloudEvent toV1();
static io.cloudevents.v1.CloudEventBuilder buildV1() {
return new io.cloudevents.v1.CloudEventBuilder();
}
static io.cloudevents.v1.CloudEventBuilder buildV1(CloudEvent event) {
return new io.cloudevents.v1.CloudEventBuilder(event);
}
static io.cloudevents.v03.CloudEventBuilder buildV03() {
return new io.cloudevents.v03.CloudEventBuilder();
}
static io.cloudevents.v03.CloudEventBuilder buildV03(CloudEvent event) {
return new io.cloudevents.v03.CloudEventBuilder(event);
}
}

View File

@ -1,9 +1,11 @@
package io.cloudevents;
import java.util.Map;
public interface Extension {
void readFromEvent(CloudEvent event);
void writeToEvent(CloudEvent event);
Map<String, Object> asMap();
}

View File

@ -3,6 +3,10 @@ package io.cloudevents.extensions;
import io.cloudevents.CloudEvent;
import io.cloudevents.Extension;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class DistributedTracingExtension implements Extension {
public static final String TRACEPARENT = "traceparent";
@ -42,13 +46,11 @@ public final class DistributedTracingExtension implements Extension {
}
@Override
public void writeToEvent(CloudEvent event) {
if (traceparent != null) {
event.getExtensions().put(TRACEPARENT, this.traceparent);
}
if (tracestate != null) {
event.getExtensions().put(TRACESTATE, this.tracestate);
}
public Map<String, Object> asMap() {
HashMap<String, Object> map = new HashMap<>();
map.put(TRACEPARENT, this.traceparent);
map.put(TRACESTATE, this.tracestate);
return Collections.unmodifiableMap(map);
}
@Override

View File

@ -18,6 +18,8 @@ public final class ExtensionsParser {
private HashMap<Class<?>, Supplier<Extension>> extensionFactories;
// TODO SPI in future?
private ExtensionsParser() {
this.extensionFactories = new HashMap<>();
registerExtension(DistributedTracingExtension.class, DistributedTracingExtension::new);

View File

@ -6,9 +6,7 @@ import io.cloudevents.CloudEvent;
import io.cloudevents.Extension;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class BaseCloudEventBuilder<B extends BaseCloudEventBuilder<B, T>, T extends Attributes> {
@ -18,15 +16,25 @@ public abstract class BaseCloudEventBuilder<B extends BaseCloudEventBuilder<B, T
private Object data;
private Map<String, Object> extensions;
private List<Extension> materializedExtensions;
@SuppressWarnings("unchecked")
public BaseCloudEventBuilder() {
this.self = (B)this;
this.extensions = new HashMap<>();
this.materializedExtensions = new ArrayList<>();
}
@SuppressWarnings("unchecked")
public BaseCloudEventBuilder(CloudEvent event) {
this.self = (B)this;
CloudEventImpl ev = (CloudEventImpl) event;
this.setAttributes(ev.getAttributes());
this.data = ev.getRawData();
this.extensions = new HashMap<>(ev.getExtensions());
}
protected abstract void setAttributes(Attributes attributes);
protected abstract B withDataContentType(String contentType);
protected abstract B withDataSchema(URI dataSchema);
@ -49,15 +57,15 @@ public abstract class BaseCloudEventBuilder<B extends BaseCloudEventBuilder<B, T
}
public B withData(String contentType, URI dataSchema, String data) {
return withEncodedata(contentType, dataSchema, (Object) data);
return withEncodeData(contentType, dataSchema, (Object) data);
}
public B withData(String contentType, URI dataSchema, byte[] data) {
return withEncodedata(contentType, dataSchema, (Object) data);
return withEncodeData(contentType, dataSchema, (Object) data);
}
public B withData(String contentType, URI dataSchema, JsonNode data) {
return withEncodedata(contentType, dataSchema, (Object) data);
return withEncodeData(contentType, dataSchema, (Object) data);
}
public B withExtension(String key, String value) {
@ -76,19 +84,12 @@ public abstract class BaseCloudEventBuilder<B extends BaseCloudEventBuilder<B, T
}
public B withExtension(Extension extension) {
this.materializedExtensions.add(extension);
this.extensions.putAll(extension.asMap());
return self;
}
public CloudEvent build() {
CloudEvent event = new CloudEventImpl(this.buildAttributes(), data, extensions);
// Write materialized extensions into the event
for (Extension ext : this.materializedExtensions) {
ext.writeToEvent(event);
}
return event;
return new CloudEventImpl(this.buildAttributes(), data, extensions);
}
private B withEncodedData(String contentType, Object data) {
@ -97,7 +98,7 @@ public abstract class BaseCloudEventBuilder<B extends BaseCloudEventBuilder<B, T
return self;
}
private B withEncodedata(String contentType, URI dataSchema, Object data) {
private B withEncodeData(String contentType, URI dataSchema, Object data) {
withDataContentType(contentType);
withDataSchema(dataSchema);
this.data = data;

View File

@ -13,14 +13,11 @@ import io.cloudevents.json.Json;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
@JsonSerialize(using = CloudEventSerializer.class)
@JsonDeserialize(using = CloudEventDeserializer.class)
public class CloudEventImpl implements CloudEvent {
public final class CloudEventImpl implements CloudEvent {
private final Attributes attributes;
private final Object data;
@ -109,6 +106,28 @@ public class CloudEventImpl implements CloudEvent {
@Override
public Map<String, Object> getExtensions() {
return extensions;
return Collections.unmodifiableMap(extensions);
}
@Override
public CloudEvent toV03() {
return new CloudEventImpl(
attributes.toV03(),
data,
extensions
);
}
@Override
public CloudEvent toV1() {
return new CloudEventImpl(
attributes.toV1(),
data,
extensions
);
}
protected Object getRawData() {
return data;
}
}

View File

@ -32,21 +32,16 @@ import java.util.Optional;
public class AttributesImpl implements Attributes {
private final String id;
private final URI source;
private final String type;
private final ZonedDateTime time;
private final URI schemaurl;
private final String datacontenttype;
private final String subject;
AttributesImpl(String id, URI source, String type,
ZonedDateTime time, URI schemaurl,
String datacontenttype, String subject) {
public AttributesImpl(String id, URI source, String type,
ZonedDateTime time, URI schemaurl,
String datacontenttype, String subject) {
this.id = id;
this.source = source;
this.type = type;
@ -72,6 +67,25 @@ public class AttributesImpl implements Attributes {
public Optional<ZonedDateTime> getTime() {
return Optional.ofNullable(time);
}
@Override
public Attributes toV03() {
return this;
}
@Override
public Attributes toV1() {
return new io.cloudevents.v1.AttributesImpl(
this.id,
this.source,
this.type,
this.datacontenttype,
this.schemaurl,
this.subject,
this.time
);
}
public Optional<URI> getDataSchema() {
return getSchemaUrl();
}

View File

@ -15,6 +15,8 @@
*/
package io.cloudevents.v03;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.BaseCloudEventBuilder;
import java.net.URI;
@ -29,20 +31,35 @@ import java.time.ZonedDateTime;
*/
public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBuilder, AttributesImpl> {
public CloudEventBuilder() {
super();
}
private String id;
private URI source;
private String type;
private ZonedDateTime time;
private URI schemaurl;
private String datacontenttype;
private String subject;
public CloudEventBuilder() {
super();
}
public CloudEventBuilder(CloudEvent event) {
super(event);
}
@Override
protected void setAttributes(Attributes attributes) {
AttributesImpl attr = (AttributesImpl) attributes.toV03();
this
.withId(attr.getId())
.withSource(attr.getSource())
.withType(attr.getType());
attr.getDataContentType().ifPresent(this::withDataContentType);
attr.getSchemaUrl().ifPresent(this::withSchemaUrl);
attr.getSubject().ifPresent(this::withSubject);
attr.getTime().ifPresent(this::withTime);
}
public CloudEventBuilder withId(String id) {
this.id = id;
return this;

View File

@ -1,80 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v03.http;
import static java.util.stream.Collectors.toMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import io.cloudevents.fun.BinaryFormatAttributeMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.3
*/
public class AttributeMapper {
private AttributeMapper() {}
static final String HEADER_PREFIX = "ce-";
/**
* Following the signature of {@link BinaryFormatAttributeMapper#map(Map)}
* @param headers Map of HTTP request
* @return Map with spec attributes and values without parsing
* @see ContextAttributes
*/
public static Map<String, String> map(final Map<String, Object> headers) {
Objects.requireNonNull(headers);
final AtomicReference<Optional<Entry<String, Object>>> ct =
new AtomicReference<>();
ct.set(Optional.empty());
Map<String, String> result = headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.peek(header -> {
if("content-type".equals(header.getKey())) {
ct.set(Optional.ofNullable(header));
}
})
.filter(header -> header.getKey().startsWith(HEADER_PREFIX))
.map(header -> new SimpleEntry<>(header.getKey()
.substring(HEADER_PREFIX.length()), header.getValue()))
.map(header -> new SimpleEntry<>(header.getKey(),
header.getValue().toString()))
.collect(toMap(Entry::getKey, Entry::getValue));
ct.get().ifPresent(contentType -> {
result.put(ContextAttributes.DATACONTENTTYPE.name(),
contentType.getValue().toString());
});
return result;
}
}

View File

@ -1,65 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v03.http;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatExtensionMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.3
*/
public class ExtensionMapper {
private ExtensionMapper() {}
private static final List<String> RESERVED_HEADERS =
ContextAttributes.VALUES.stream()
.map(attribute -> AttributeMapper
.HEADER_PREFIX + attribute)
.collect(Collectors.toList());
static {
RESERVED_HEADERS.add("content-type");
};
/**
* Following the signature of {@link FormatExtensionMapper}
* @param headers The HTTP headers
* @return The potential extensions without parsing
*/
public static Map<String, String> map(Map<String, Object> headers) {
Objects.requireNonNull(headers);
// remove all reserved words and the remaining may be extensions
return
headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue().toString()))
.filter(header -> !RESERVED_HEADERS.contains(header.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

View File

@ -1,79 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v03.http;
import static io.cloudevents.v03.http.AttributeMapper.HEADER_PREFIX;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatHeaderMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
*
*/
public class HeaderMapper {
private HeaderMapper() {}
private static final String HTTP_CONTENT_TYPE = "Content-Type";
/**
* Following the signature of {@link FormatHeaderMapper}
* @param attributes The map of attributes created by {@link AttributeMapper}
* @param extensions The map of extensions created by {@link ExtensionMapper}
* @return The map of HTTP Headers
*/
public static Map<String, String> map(Map<String, String> attributes,
Map<String, String> extensions) {
Objects.requireNonNull(attributes);
Objects.requireNonNull(extensions);
Map<String, String> result = attributes.entrySet()
.stream()
.filter(attribute -> null!= attribute.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.filter(header -> !header.getKey()
.equals(ContextAttributes.DATACONTENTTYPE.name()))
.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())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
);
Optional.ofNullable(attributes
.get(ContextAttributes.DATACONTENTTYPE.name()))
.ifPresent((dct) -> {
result.put(HTTP_CONTENT_TYPE, dct);
});
return result;
}
}

View File

@ -1,83 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v03.http;
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.BinaryMarshaller;
import io.cloudevents.format.StructuredMarshaller;
import io.cloudevents.format.Wire;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.v03.Accessor;
import io.cloudevents.v03.AttributesImpl;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.3
*/
public class Marshallers {
private Marshallers() {}
private static final Map<String, String> NO_HEADERS =
new HashMap<String, String>();
/**
* Builds a Binary Content Mode marshaller to marshal cloud events as JSON for
* HTTP Transport Binding
*
* @param <T> The 'data' type
* @return A step to provide the {@link CloudEventImpl} and marshal as JSON
* @see BinaryMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> binary() {
return
BinaryMarshaller.<AttributesImpl, T, String, String>
builder()
.map(AttributesImpl::marshal)
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map)
.map(Json.<T, String>marshaller()::marshal)
.builder(Wire<String, String, String>::new);
}
/**
* Builds a Structured Content Mode marshaller to marshal cloud event as JSON for
* HTTP Transport Binding
* @param <T> The 'data' type
* @return A step to provider the {@link CloudEventImpl} and marshal as JSON
* @see StructuredMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> structured() {
return
StructuredMarshaller.
<AttributesImpl, T, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((event) -> {
return Json.<CloudEvent<AttributesImpl, T>, String>
marshaller().marshal(event, NO_HEADERS);
})
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map);
}
}

View File

@ -1,123 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v03.http;
import javax.validation.Validator;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.format.BinaryUnmarshaller;
import io.cloudevents.format.StructuredUnmarshaller;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.json.Json;
import io.cloudevents.v03.AttributesImpl;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.3
*/
public class Unmarshallers {
private Unmarshallers() {}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type) {
return binary(type, null);
}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type, Validator validator) {
return
BinaryUnmarshaller.<AttributesImpl, T, String>builder()
.map(AttributeMapper::map)
.map(AttributesImpl::unmarshal)
.map("application/json", Json.umarshaller(type)::unmarshal)
.next()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.builder(CloudEventBuilder.<T>builder().withValidator(validator)::build);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData) {
return structured(typeOfData, null);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData, Validator validator) {
return
StructuredUnmarshaller.<AttributesImpl, T, String>
builder()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.map((payload, extensions) -> {
CloudEventImpl<T> event =
Json.<CloudEventImpl<T>>
decodeValue(payload, CloudEventImpl.class, typeOfData);
CloudEventBuilder<T> builder =
CloudEventBuilder.<T>builder(event);
extensions.get().forEach(extension -> {
builder.withExtension(extension);
});
return builder.withValidator(validator).build();
});
}
}

View File

@ -31,17 +31,11 @@ import java.util.Optional;
public class AttributesImpl implements Attributes {
private final String id;
private final URI source;
private final String type;
private final String datacontenttype;
private final URI dataschema;
private final String subject;
private final ZonedDateTime time;
public AttributesImpl(String id, URI source,
@ -91,7 +85,25 @@ public class AttributesImpl implements Attributes {
return Optional.ofNullable(time);
}
@Override
@Override
public Attributes toV03() {
return new io.cloudevents.v03.AttributesImpl(
this.id,
this.source,
this.type,
this.time,
this.dataschema,
this.datacontenttype,
this.subject
);
}
@Override
public Attributes toV1() {
return this;
}
@Override
public String toString() {
return "Attibutes V1.0 [id=" + id + ", source=" + source
+ ", type=" + type

View File

@ -1,5 +1,7 @@
package io.cloudevents.v1;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.BaseCloudEventBuilder;
import java.net.URI;
@ -15,7 +17,6 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
private String id;
private URI source;
private String type;
private String datacontenttype;
private URI dataschema;
@ -26,6 +27,23 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
super();
}
public CloudEventBuilder(CloudEvent event) {
super(event);
}
@Override
protected void setAttributes(Attributes attributes) {
AttributesImpl attr = (AttributesImpl) attributes.toV1();
this
.withId(attr.getId())
.withSource(attr.getSource())
.withType(attr.getType());
attr.getDataContentType().ifPresent(this::withDataContentType);
attr.getDataSchema().ifPresent(this::withDataSchema);
attr.getSubject().ifPresent(this::withSubject);
attr.getTime().ifPresent(this::withTime);
}
public CloudEventBuilder withId(String id) {
this.id = id;
return this;

View File

@ -1,80 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v1.http;
import static java.util.stream.Collectors.toMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import io.cloudevents.fun.BinaryFormatAttributeMapper;
import io.cloudevents.v1.ContextAttributes;
/**
*
* @author fabiojose
* @version 1.0
*/
public class AttributeMapper {
private AttributeMapper() {}
static final String HEADER_PREFIX = "ce-";
/**
* Following the signature of {@link BinaryFormatAttributeMapper#map(Map)}
* @param headers Map of HTTP request
* @return Map with spec attributes and values without parsing
* @see ContextAttributes
*/
public static Map<String, String> map(final Map<String, Object> headers) {
Objects.requireNonNull(headers);
final AtomicReference<Optional<Entry<String, Object>>> ct =
new AtomicReference<>();
ct.set(Optional.empty());
Map<String, String> result = headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.peek(header -> {
if("content-type".equals(header.getKey())) {
ct.set(Optional.ofNullable(header));
}
})
.filter(header -> header.getKey().startsWith(HEADER_PREFIX))
.map(header -> new SimpleEntry<>(header.getKey()
.substring(HEADER_PREFIX.length()), header.getValue()))
.map(header -> new SimpleEntry<>(header.getKey(),
header.getValue().toString()))
.collect(toMap(Entry::getKey, Entry::getValue));
ct.get().ifPresent(contentType -> {
result.put(ContextAttributes.DATACONTENTTYPE.name(),
contentType.getValue().toString());
});
return result;
}
}

View File

@ -1,50 +0,0 @@
package io.cloudevents.v1.http;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatExtensionMapper;
import io.cloudevents.v1.ContextAttributes;
import io.cloudevents.v1.http.AttributeMapper;
/**
*
* @author fabiojose
* @version 1.0
*/
public class ExtensionMapper {
private ExtensionMapper() {}
private static final List<String> RESERVED_HEADERS =
ContextAttributes.VALUES.stream()
.map(attribute -> AttributeMapper
.HEADER_PREFIX + attribute)
.collect(Collectors.toList());
static {
RESERVED_HEADERS.add("content-type");
};
/**
* Following the signature of {@link FormatExtensionMapper}
* @param headers The HTTP headers
* @return The potential extensions without parsing
*/
public static Map<String, String> map(Map<String, Object> headers) {
Objects.requireNonNull(headers);
// remove all reserved words and the remaining may be extensions
return
headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue().toString()))
.filter(header -> !RESERVED_HEADERS.contains(header.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

View File

@ -1,79 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v1.http;
import static io.cloudevents.v1.http.AttributeMapper.HEADER_PREFIX;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatHeaderMapper;
import io.cloudevents.v1.ContextAttributes;
/**
*
* @author fabiojose
*
*/
public class HeaderMapper {
private HeaderMapper() {}
private static final String HTTP_CONTENT_TYPE = "Content-Type";
/**
* Following the signature of {@link FormatHeaderMapper}
* @param attributes The map of attributes created by {@link AttributeMapper}
* @param extensions The map of extensions created by {@link ExtensionMapper}
* @return The map of HTTP Headers
*/
public static Map<String, String> map(Map<String, String> attributes,
Map<String, String> extensions) {
Objects.requireNonNull(attributes);
Objects.requireNonNull(extensions);
Map<String, String> result = attributes.entrySet()
.stream()
.filter(attribute -> null!= attribute.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.filter(header -> !header.getKey()
.equals(ContextAttributes.DATACONTENTTYPE.name()))
.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())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
);
Optional.ofNullable(attributes
.get(ContextAttributes.DATACONTENTTYPE.name()))
.ifPresent((dct) -> {
result.put(HTTP_CONTENT_TYPE, dct);
});
return result;
}
}

View File

@ -1,67 +0,0 @@
package io.cloudevents.v1.http;
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.BinaryMarshaller;
import io.cloudevents.format.StructuredMarshaller;
import io.cloudevents.format.Wire;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.v1.Accessor;
import io.cloudevents.v1.AttributesImpl;
import io.cloudevents.v1.CloudEventImpl;
import io.cloudevents.v1.http.HeaderMapper;
/**
*
* @author fabiojose
* @version 1.0
*/
public class Marshallers {
private Marshallers() {}
private static final Map<String, String> NO_HEADERS =
new HashMap<String, String>();
/**
* Builds a Binary Content Mode marshaller to marshal cloud events as JSON for
* HTTP Transport Binding
*
* @param <T> The 'data' type
* @return A step to provide the {@link CloudEventImpl} and marshal as JSON
* @see BinaryMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> binary() {
return
BinaryMarshaller.<AttributesImpl, T, String, String>
builder()
.map(AttributesImpl::marshal)
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map)
.map(Json.<T, String>marshaller()::marshal)
.builder(Wire::new);
}
/**
* Builds a Structured Content Mode marshaller to marshal cloud event as JSON for
* HTTP Transport Binding
* @param <T> The 'data' type
* @return A step to provider the {@link CloudEventImpl} and marshal as JSON
* @see StructuredMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> structured() {
return
StructuredMarshaller.
<AttributesImpl, T, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((event) -> Json.<CloudEvent<AttributesImpl, T>, String>
marshaller().marshal(event, NO_HEADERS))
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map);
}
}

View File

@ -1,122 +0,0 @@
/**
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.v1.http;
import javax.validation.Validator;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.format.BinaryUnmarshaller;
import io.cloudevents.format.StructuredUnmarshaller;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.json.Json;
import io.cloudevents.v1.AttributesImpl;
import io.cloudevents.v1.CloudEventBuilder;
import io.cloudevents.v1.CloudEventImpl;
/**
*
* @author fabiojose
* @version 1.0
*/
public class Unmarshallers {
private Unmarshallers() {}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type) {
return binary(type, null);
}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type, Validator validator) {
return
BinaryUnmarshaller.<AttributesImpl, T, String>builder()
.map(AttributeMapper::map)
.map(AttributesImpl::unmarshal)
.map("application/json", Json.umarshaller(type)::unmarshal)
.next()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.builder(CloudEventBuilder.<T>builder().withValidator(validator)::build);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData) {
return structured(typeOfData, null);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> 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 <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData, Validator validator) {
return
StructuredUnmarshaller.<AttributesImpl, T, String>
builder()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.map((payload, extensions) -> {
CloudEventImpl<T> event =
Json.<CloudEventImpl<T>>
decodeValue(payload, CloudEventImpl.class, typeOfData);
CloudEventBuilder<T> builder =
CloudEventBuilder.<T>builder(event);
extensions.get().forEach(extension -> {
builder.withExtension(extension);
});
return builder.withValidator(validator).build();
});
}
}

View File

@ -33,8 +33,7 @@ public class DistributedTracingExtensionTest {
tracing.setTraceparent("parent");
tracing.setTracestate("state");
CloudEvent event = CloudEvent.build().build();
tracing.writeToEvent(event);
CloudEvent event = CloudEvent.buildV1().withExtension(tracing).build();
assertThat(event.getExtensions())
.containsEntry(DistributedTracingExtension.TRACEPARENT, "parent")
@ -43,7 +42,7 @@ public class DistributedTracingExtensionTest {
@Test
public void parseExtension() {
CloudEvent event = CloudEvent.build()
CloudEvent event = CloudEvent.buildV1()
.withExtension(DistributedTracingExtension.TRACEPARENT, "parent")
.withExtension(DistributedTracingExtension.TRACESTATE, "state")
.build();