Last active
January 20, 2026 15:24
-
-
Save stefanofago73/dd03952e9a2c5f2f113ea064320785d0 to your computer and use it in GitHub Desktop.
This is an attempt to pattern the creation of extensions for the CloudEvents Java SDK. There are limitations, but it could be an accelerator for defining existing CloudEvents extensions, including compatibility with the OPC-UA protocol in the context of the IIOT!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.net.URI; | |
| import java.time.OffsetDateTime; | |
| import java.util.LinkedHashMap; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import io.cloudevents.CloudEventExtension; | |
| import io.cloudevents.CloudEventExtensions; | |
| import io.cloudevents.core.extensions.impl.ExtensionUtils; | |
| /** | |
| * Generic base class for simple CloudEvents extensions backed by a | |
| * fixed set of extension attributes. | |
| * | |
| * <p> | |
| * <strong>Mutability note:</strong> Instances of this class are mutable. | |
| * Methods such as {@code with(...)}, | |
| * {@link #readFrom(io.cloudevents.CloudEventExtensions)}, | |
| * {@link #overwriteFrom(io.cloudevents.CloudEventExtensions)} and | |
| * {@link #clear()} modify the internal state of the extension. | |
| * | |
| * <p> | |
| * {@code equals(Object)} and {@code hashCode()} are value-based and reflect the | |
| * current state of the extension. As a consequence, instances should not be | |
| * used as keys in hash-based collections (e.g. {@link java.util.HashMap}, | |
| * {@link java.util.HashSet}) if they are expected to be mutated after | |
| * insertion. | |
| * | |
| * <p> | |
| * This class is not thread-safe and is intended for single-threaded use during | |
| * CloudEvent construction or parsing. | |
| */ | |
| public abstract class GenericCloudEventExtension<T extends GenericCloudEventExtension<T>> | |
| implements CloudEventExtension { | |
| private final Set<String> keys; | |
| private final Map<String, Object> kv; | |
| /** | |
| * | |
| * Key sorting affects the sorting of extension features: this is to keep json rendering consistent | |
| * | |
| **/ | |
| protected GenericCloudEventExtension(String ... keys) { | |
| Objects.requireNonNull(keys, "keys"); | |
| this.keys = Set.of(keys); | |
| this.kv = preInitializeWithNull(keys, new LinkedHashMap<>(this.keys.size())); | |
| } | |
| @Override | |
| public final Set<String> getKeys() { | |
| return keys; | |
| } | |
| @Override | |
| public final Object getValue(String key) { | |
| validateKey(key); | |
| return kv.get(key); | |
| } | |
| /** | |
| * Default read behavior: "merge" semantics. | |
| * | |
| * For each declared key, the current value is overwritten only if the incoming | |
| * event provides a non-null value. If the incoming value is null, the current | |
| * value is unchanged. This avoids losing pre-populated values when the event | |
| * does not carry the attribute. | |
| * | |
| */ | |
| @Override | |
| public void readFrom(CloudEventExtensions extensions) { | |
| Objects.requireNonNull(extensions, "extensions"); | |
| for (String key : keys) { | |
| Object incoming = extensions.getExtension(key); | |
| if (incoming != null) { | |
| kv.put(key, incoming); | |
| } | |
| } | |
| } | |
| /** | |
| * <p> | |
| * This method is typically used when rebuilding an extension instance directly | |
| * from a CloudEvent, rather than merging with locally-set values. | |
| * | |
| */ | |
| public void overwriteFrom(CloudEventExtensions extensions) { | |
| Objects.requireNonNull(extensions, "extensions"); | |
| for (String key : keys) { | |
| kv.put(key, extensions.getExtension(key)); | |
| } | |
| } | |
| /** | |
| * Clears all stored values (sets all keys to null). | |
| */ | |
| public T clear() { | |
| for (String key : keys) { | |
| kv.put(key, null); | |
| } | |
| return self(); | |
| } | |
| @Override | |
| public final boolean equals(Object o) { | |
| if (this == o) | |
| return true; | |
| if (o == null || getClass() != o.getClass()) | |
| return false; | |
| GenericCloudEventExtension<?> that = (GenericCloudEventExtension<?>) o; | |
| return keys.equals(that.keys) && kv.equals(that.kv); | |
| } | |
| @Override | |
| public final int hashCode() { | |
| return Objects.hash(keys, kv); | |
| } | |
| @Override | |
| public final String toString() { | |
| StringBuilder sb = new StringBuilder(250).append(getClass().getSimpleName()).append('{'); | |
| boolean wroteAny = false; | |
| Object v = null; | |
| for (Map.Entry<String, Object> e : kv.entrySet()) { | |
| v = e.getValue(); | |
| if (v != null) { | |
| if (wroteAny) | |
| sb.append(','); | |
| sb.append(e.getKey()).append('=').append(v); | |
| wroteAny = true; | |
| } | |
| } | |
| return sb.append('}').toString(); | |
| } | |
| //---------------------------------------------------- | |
| // | |
| // DSL Wither Sections | |
| // | |
| //---------------------------------------------------- | |
| public T with(String key, String value) { | |
| return withInternal(key, value, kv); | |
| } | |
| public T with(String key, URI value) { | |
| return withInternal(key, value, kv); | |
| } | |
| public T with(String key, OffsetDateTime value) { | |
| return withInternal(key, value, kv); | |
| } | |
| public T with(String key, Number value) { | |
| return withInternal(key, value, kv); | |
| } | |
| public T with(String key, Boolean value) { | |
| return withInternal(key, value, kv); | |
| } | |
| // =========================================== | |
| // | |
| // Internals Section | |
| // | |
| // =========================================== | |
| private final Map<String, Object> preInitializeWithNull(String[] keys, Map<String, Object> kv) { | |
| for (String k : keys) { | |
| kv.put(k, null); | |
| } | |
| return kv; | |
| } | |
| private final void validateKey(String key) { | |
| if (!keys.contains(key)) { | |
| throw ExtensionUtils.generateInvalidKeyException(getClass(), key); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private final T self() { | |
| return (T) this; | |
| } | |
| protected final T withInternal(String key, Object value, Map<String, Object> kv) { | |
| validateKey(key); | |
| kv.put(key, value); | |
| return self(); | |
| } | |
| } | |
| // -------------------------------------------------- | |
| // | |
| // Here and example of usage of the base class. | |
| // The costructor is used to define the keys ordering and usage | |
| // Custom typed getter are possible. | |
| // Custom type wither are possible | |
| // | |
| // -------------------------------------------------- | |
| import java.util.Objects; | |
| import com.gewiss.cloudevents.extensions.GenericCloudEventExtension; | |
| public class AuthContextExtension extends GenericCloudEventExtension<AuthContextExtension>{ | |
| public enum AuthType{ | |
| app_user, | |
| user, | |
| service_account, | |
| api_key, | |
| system, | |
| unauthenticated, | |
| unknown; | |
| } | |
| public static final String AUTH_TYPE_KEY = "authtype"; | |
| public static final String AUTH_ID_KEY = "authid"; | |
| public static final String AUTH_CLAIMS_KEY = "authclaims"; | |
| public AuthContextExtension() { | |
| super(AUTH_TYPE_KEY,AUTH_ID_KEY,AUTH_CLAIMS_KEY); | |
| } | |
| public AuthContextExtension withAuthType(AuthType type) { | |
| return with(AUTH_TYPE_KEY, Objects.requireNonNull(type, "auth-type").name()); | |
| } | |
| public AuthContextExtension withAuthId(String authid) { | |
| return with(AUTH_ID_KEY, Objects.requireNonNull(authid, "auth-id")); | |
| } | |
| public AuthContextExtension withAuthClaims(String claims) { | |
| return with(AUTH_CLAIMS_KEY, Objects.requireNonNull(claims, "auth-claims")); | |
| } | |
| public AuthType getAuthType() { | |
| return AuthType.valueOf(getValue(AUTH_TYPE_KEY).toString()); | |
| } | |
| public String getAuthId() { | |
| return String.valueOf(getValue(AUTH_ID_KEY)); | |
| } | |
| public String getAuthClaims() { | |
| return String.valueOf(getValue(AUTH_CLAIMS_KEY)); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment