Completed implementation of message interfaces for events.

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
slinkydeveloper 2020-04-20 13:21:54 +02:00 committed by Francesco Guardiani
parent 223d786a3f
commit 8f1b8d2da9
24 changed files with 221 additions and 497 deletions

View File

@ -16,6 +16,9 @@
package io.cloudevents;
import com.fasterxml.jackson.databind.JsonNode;
import io.cloudevents.format.EventFormat;
import io.cloudevents.message.BinaryMessage;
import io.cloudevents.message.StructuredMessage;
import java.util.Map;
import java.util.Optional;
@ -58,6 +61,10 @@ public interface CloudEvent {
CloudEvent toV1();
BinaryMessage asBinaryMessage();
StructuredMessage asStructuredMessage(EventFormat format);
static io.cloudevents.v1.CloudEventBuilder buildV1() {
return new io.cloudevents.v1.CloudEventBuilder();
}

View File

@ -1,35 +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.fun;
import java.util.Map;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface AttributeMarshaller<A extends Attributes> {
/**
* Marshals the {@link Attributes} to map of attributes.
*/
Map<String, String> marshal(A attributes);
}

View File

@ -1,35 +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.fun;
import java.util.Map;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface AttributeUnmarshaller<A extends Attributes> {
/**
* Unmarshals the map of CloudEvent attributes into actual ones
*/
A unmarshal(Map<String, String> attributes);
}

View File

@ -1,35 +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.fun;
import java.util.Map;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface BinaryFormatAttributeMapper {
/**
* Maps the 'headers' of binary format into CloudEvent attributes
* @param headers
* @return
*/
Map<String, String> map(Map<String, Object> headers);
}

View File

@ -1,40 +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.fun;
import java.util.Map;
/**
*
* @author fabiojose
*
* @param <P> The payload type
* @param <T> The 'data' type
* @param <H> The type of headers value
*/
@FunctionalInterface
public interface DataMarshaller<P, T, H> {
/**
* Marshals the 'data' into payload
* @param data
* @param headers
* @return
* @throws RuntimeException When something bad happens during the marshal process
*/
P marshal(T data, Map<String, H> headers) throws RuntimeException;
}

View File

@ -1,40 +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.fun;
import io.cloudevents.Attributes;
/**
* For the 'data' unmarshalling.
*
* @author fabiojose
*
* @param <P> The payload type
* @param <T> The 'data' type
*/
@FunctionalInterface
public interface DataUnmarshaller<P, T, A extends Attributes> {
/**
* Unmarshals the payload into 'data'
* @param payload
* @param attributes
* @return
* @throws RuntimeException If something bad happens during the umarshal
*/
T unmarshal(P payload, A attributes);
}

View File

@ -1,35 +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.fun;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface EnvelopeMarshaller<A extends Attributes, T, P> {
/**
* Marshals a CloudEvent instance into the wire format
* @param event The CloudEvents instance
* @return the wire format
*/
P marshal(CloudEvent<A, T> event);
}

View File

@ -1,42 +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.fun;
import java.util.List;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
public interface EnvelopeUnmarshaller<A extends Attributes, T, P> {
/**
* Unmarshals the payload into {@link CloudEvent} instance implementation
*
* @param payload The envelope payload
* @param extensions Supplies a list of {@link ExtensionFormat}
* @return The unmarshalled impl of CloudEvent
*/
CloudEvent<A, T> unmarshal(P payload,
Supplier<List<ExtensionFormat>> extensions);
}

View File

@ -1,25 +0,0 @@
package io.cloudevents.fun;
import java.util.Collection;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionFormatAccessor<A extends Attributes, T> {
/**
* To get access to the internal collection of {@link ExtensionFormat} inside
* the {@link CloudEvent} implementation
*
* @param cloudEvent
* @return
*/
Collection<ExtensionFormat> extensionsOf(CloudEvent<A, T> cloudEvent);
}

View File

@ -1,38 +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.fun;
import java.util.Collection;
import java.util.Map;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionMarshaller {
/**
* Marshals a collections of {@link ExtensionFormat} into map
* @param extensions
* @return
*/
Map<String, String> marshal(Collection<ExtensionFormat> extensions);
}

View File

@ -1,38 +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.fun;
import java.util.Map;
import java.util.Optional;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionUmarshaller {
/**
* Unmarshals the map of extensions into {@link ExtensionFormat}
* @param extensions
* @return
*/
Optional<ExtensionFormat> unmarshal(Map<String, String> extensions);
}

View File

@ -1,34 +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.fun;
import java.util.Map;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface FormatExtensionMapper {
/**
* Maps the 'headers' of binary format into extensions
* @param headers
*/
Map<String, String> map(Map<String, Object> headers);
}

View File

@ -1,38 +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.fun;
import java.util.Map;
/**
*
* @author fabiojose
* @param <H> The header value type
*
*/
@FunctionalInterface
public interface FormatHeaderMapper<H> {
/**
* Maps the 'attributes' and 'extensions' of CloudEvent envelop to
* 'headers' of binary format
* @param attributes
* @return
*/
Map<String, H> map(Map<String, String> attributes,
Map<String, String> extensions);
}

View File

@ -1,38 +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.fun;
import java.util.Map;
import io.cloudevents.format.Wire;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface WireBuilder<P, K, V> {
/**
* Builds a wire format
* @param payload
* @param headers
* @return
*/
Wire<P, K, V> build(P payload, Map<K, V> headers);
}

View File

@ -0,0 +1,6 @@
package io.cloudevents.impl;
import io.cloudevents.Attributes;
import io.cloudevents.message.BinaryMessageAttributes;
public interface AttributesInternal extends Attributes, BinaryMessageAttributes { }

View File

@ -7,9 +7,11 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.DataConversionException;
import io.cloudevents.format.EventFormat;
import io.cloudevents.format.json.CloudEventDeserializer;
import io.cloudevents.format.json.CloudEventSerializer;
import io.cloudevents.json.Json;
import io.cloudevents.message.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -17,15 +19,15 @@ import java.util.*;
@JsonSerialize(using = CloudEventSerializer.class)
@JsonDeserialize(using = CloudEventDeserializer.class)
public final class CloudEventImpl implements CloudEvent {
public final class CloudEventImpl implements CloudEvent, BinaryMessage {
private final Attributes attributes;
private final AttributesInternal attributes;
private final Object data;
private final Map<String, Object> extensions;
public CloudEventImpl(Attributes attributes, Object data, Map<String, Object> extensions) {
protected CloudEventImpl(Attributes attributes, Object data, Map<String, Object> extensions) {
Objects.requireNonNull(attributes);
this.attributes = attributes;
this.attributes = (AttributesInternal) attributes;
this.data = data;
this.extensions = extensions != null ? extensions : new HashMap<>();
}
@ -130,4 +132,48 @@ public final class CloudEventImpl implements CloudEvent {
protected Object getRawData() {
return data;
}
// Message impl
public BinaryMessage asBinaryMessage() {
return this;
}
public StructuredMessage asStructuredMessage(EventFormat format) {
CloudEvent ev = this;
// TODO This sucks, will improve later
return new StructuredMessage() {
@Override
public <T> T visit(StructuredMessageVisitor<T> visitor) throws MessageVisitException, IllegalStateException {
return visitor.setEvent(format, format.serializeToBytes(ev));
}
};
}
@Override
public <T extends BinaryMessageVisitor<V>, V> V visit(BinaryMessageVisitorFactory<T, V> visitorFactory) throws MessageVisitException, IllegalStateException {
BinaryMessageVisitor<V> visitor = visitorFactory.apply(this.attributes.getSpecVersion());
this.attributes.visit(visitor);
// TODO to be improved
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
if (entry.getValue() instanceof String) {
visitor.setExtension(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Number) {
visitor.setExtension(entry.getKey(), (Number) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
visitor.setExtension(entry.getKey(), (Boolean) entry.getValue());
} else {
// This should never happen because we build that map only through our builders
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
}
}
// TODO to be improved to remove the allocation of useless optional
if (this.data != null) {
visitor.setBody(this.getDataAsBytes().get());
}
return visitor.end();
}
}

View File

@ -3,8 +3,11 @@ package io.cloudevents.message;
public class MessageVisitException extends RuntimeException {
public enum MessageVisitExceptionKind {
INVALID_ATTRIBUTE_TYPE,
INVALID_SPEC_VERSION,
INVALID_ATTRIBUTE_NAME,
INVALID_ATTRIBUTE_TYPE,
INVALID_ATTRIBUTE_VALUE,
INVALID_EXTENSION_TYPE,
}
private MessageVisitExceptionKind kind;
@ -23,13 +26,6 @@ public class MessageVisitException extends RuntimeException {
return kind;
}
public static MessageVisitException newInvalidAttributeType(String attributeName, Class<?> clazz) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_ATTRIBUTE_TYPE,
"Invalid attribute type for " + attributeName + ": " + clazz.getCanonicalName()
);
}
public static MessageVisitException newInvalidSpecVersion(String specVersion) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_ATTRIBUTE_TYPE,
@ -37,5 +33,32 @@ public class MessageVisitException extends RuntimeException {
);
}
public static MessageVisitException newInvalidAttributeName(String attributeName) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_ATTRIBUTE_NAME,
"Invalid attribute: " + attributeName
);
}
public static MessageVisitException newInvalidAttributeType(String attributeName, Class<?> clazz) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_ATTRIBUTE_TYPE,
"Invalid attribute type for " + attributeName + ": " + clazz.getCanonicalName()
);
}
public static MessageVisitException newInvalidAttributeValue(String attributeName, Object value, Throwable cause) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_ATTRIBUTE_VALUE,
"Invalid attribute value for " + attributeName + ": " + value,
cause
);
}
public static MessageVisitException newInvalidExtensionType(String extensionName, Class<?> clazz) {
return new MessageVisitException(
MessageVisitExceptionKind.INVALID_EXTENSION_TYPE,
"Invalid extension type for " + extensionName + ": " + clazz.getCanonicalName()
);
}
}

View File

@ -1,7 +1,13 @@
package io.cloudevents.types;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public final class Time {
public static final DateTimeFormatter RFC3339_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
public static ZonedDateTime parseTime(String time) throws DateTimeParseException {
return ZonedDateTime.parse(time, RFC3339_DATE_FORMAT);
}
}

View File

@ -17,7 +17,7 @@ package io.cloudevents.v03;
import io.cloudevents.Attributes;
import io.cloudevents.SpecVersion;
import io.cloudevents.message.BinaryMessageAttributes;
import io.cloudevents.impl.AttributesInternal;
import io.cloudevents.message.BinaryMessageAttributesVisitor;
import io.cloudevents.message.MessageVisitException;
@ -32,7 +32,7 @@ import java.util.Optional;
* @author slinkydeveloper
*
*/
public class AttributesImpl implements Attributes, BinaryMessageAttributes {
public final class AttributesImpl implements AttributesInternal {
private final String id;
private final URI source;

View File

@ -19,9 +19,12 @@ import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.BaseCloudEventBuilder;
import io.cloudevents.message.MessageVisitException;
import io.cloudevents.types.Time;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
/**
@ -112,16 +115,63 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
@Override
public void setAttribute(String name, String value) throws MessageVisitException {
switch (name) {
case "id":
withId(value);
return;
case "source":
try {
withSource(new URI(value));
} catch (URISyntaxException e) {
throw MessageVisitException.newInvalidAttributeValue("source", value, e);
}
return;
case "type":
withType(value);
return;
case "datacontenttype":
withDataContentType(value);
return;
case "schemaurl":
try {
withSchemaUrl(new URI(value));
} catch (URISyntaxException e) {
throw MessageVisitException.newInvalidAttributeValue("schemaurl", value, e);
}
return;
case "subject":
withSubject(value);
return;
case "time":
try {
withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw MessageVisitException.newInvalidAttributeValue("time", value, e);
}
return;
}
throw MessageVisitException.newInvalidAttributeName(name);
}
@Override
public void setAttribute(String name, URI value) throws MessageVisitException {
switch (name) {
case "source":
withSource(value);
return;
case "schemaurl":
withDataSchema(value);
return;
}
throw MessageVisitException.newInvalidAttributeType(name, URI.class);
}
@Override
public void setAttribute(String name, ZonedDateTime value) throws MessageVisitException {
if ("time".equals(name)) {
withTime(value);
return;
}
throw MessageVisitException.newInvalidAttributeType(name, ZonedDateTime.class);
}
}

View File

@ -44,4 +44,9 @@ public enum ContextAttributes {
public static ContextAttributes parse(String value) {
return ContextAttributes.valueOf(value.toUpperCase());
}
@Override
public String toString() {
return name().toLowerCase();
}
}

View File

@ -17,7 +17,7 @@ package io.cloudevents.v1;
import io.cloudevents.Attributes;
import io.cloudevents.SpecVersion;
import io.cloudevents.message.BinaryMessageAttributes;
import io.cloudevents.impl.AttributesInternal;
import io.cloudevents.message.BinaryMessageAttributesVisitor;
import io.cloudevents.message.MessageVisitException;
@ -31,7 +31,7 @@ import java.util.Optional;
* @author slinkydeveloper
* @version 1.0
*/
public class AttributesImpl implements Attributes, BinaryMessageAttributes {
public final class AttributesImpl implements AttributesInternal {
private final String id;
private final URI source;

View File

@ -4,9 +4,12 @@ import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.BaseCloudEventBuilder;
import io.cloudevents.message.MessageVisitException;
import io.cloudevents.types.Time;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
/**
*
@ -90,16 +93,63 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
@Override
public void setAttribute(String name, String value) throws MessageVisitException {
switch (name) {
case "id":
withId(value);
return;
case "source":
try {
withSource(new URI(value));
} catch (URISyntaxException e) {
throw MessageVisitException.newInvalidAttributeValue("source", value, e);
}
return;
case "type":
withType(value);
return;
case "datacontenttype":
withDataContentType(value);
return;
case "dataschema":
try {
withDataSchema(new URI(value));
} catch (URISyntaxException e) {
throw MessageVisitException.newInvalidAttributeValue("dataschema", value, e);
}
return;
case "subject":
withSubject(value);
return;
case "time":
try {
withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw MessageVisitException.newInvalidAttributeValue("time", value, e);
}
return;
}
throw MessageVisitException.newInvalidAttributeName(name);
}
@Override
public void setAttribute(String name, URI value) throws MessageVisitException {
switch (name) {
case "source":
withSource(value);
return;
case "dataschema":
withDataSchema(value);
return;
}
throw MessageVisitException.newInvalidAttributeType(name, URI.class);
}
@Override
public void setAttribute(String name, ZonedDateTime value) throws MessageVisitException {
if ("time".equals(name)) {
withTime(value);
return;
}
throw MessageVisitException.newInvalidAttributeType(name, ZonedDateTime.class);
}
}

View File

@ -35,11 +35,15 @@ public enum ContextAttributes {
TIME;
public static final List<String> VALUES =
Arrays.stream(ContextAttributes.values())
.map(Enum::name)
.map(String::toLowerCase)
.map(ContextAttributes::toString)
.collect(Collectors.toList());
public static ContextAttributes parse(String value) {
return ContextAttributes.valueOf(value.toUpperCase());
}
@Override
public String toString() {
return name().toLowerCase();
}
}