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

org.eclipse.microprofile.reactive.messaging.Message Maven / Gradle / Ivy

There is a newer version: 4.27.0
Show newest version
/**
 * Copyright (c) 2018-2019 Contributors to the Eclipse Foundation
 * 

* See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. *

* Licensed under the Apache License, Version 2.0 (the "License"); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eclipse.microprofile.reactive.messaging; import static java.util.Objects.requireNonNullElse; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.logging.Logger; import io.smallrye.common.annotation.Experimental; /** * A message envelope. *

* A message contains a non-{@code null} payload, an acknowledgement function and a set of metadata. * Metadata are indexed using the class name of the values. *

* * @param The type of the message payload. */ public interface Message { Logger LOGGER = Logger.getLogger(Message.class.getName()); Function> EMPTY_ACK = m -> CompletableFuture.completedFuture(null); BiFunction> EMPTY_NACK = (t, m) -> CompletableFuture.completedFuture(null); private static Function> validateAck(Function> ackM) { return (ackM != null) ? ackM : EMPTY_ACK; } private static Function> validateAck(Supplier> ack) { return (ack != null) ? m -> ack.get() : EMPTY_ACK; } private static BiFunction> validateNack( BiFunction> nackM) { return (nackM != null) ? nackM : EMPTY_NACK; } private static BiFunction> validateNack( Function> nack) { return (nack != null) ? (t, m) -> nack.apply(t) : EMPTY_NACK; } private static Function> wrapAck(Message message) { var ackM = message.getAckWithMetadata(); return ackM != null ? ackM : validateAck(message.getAck()); } private static BiFunction> wrapNack(Message message) { var nackM = message.getNackWithMetadata(); return nackM != null ? nackM : validateNack(message.getNack()); } private static Message newMessage(T payload, Metadata metadata) { return new Message<>() { @Override public T getPayload() { return payload; } @Override public Metadata getMetadata() { return metadata; } }; } private static Message newMessage(T payload, Function> actualAck) { return new Message<>() { @Override public T getPayload() { return payload; } @Override public Function> getAckWithMetadata() { return actualAck; } }; } private static Message newMessage(T payload, Metadata metadata, Function> actualAck) { return new Message<>() { @Override public T getPayload() { return payload; } @Override public Metadata getMetadata() { return metadata; } @Override public Function> getAckWithMetadata() { return actualAck; } }; } private static Message newMessage(T payload, Function> actualAck, BiFunction> actualNack) { return new Message<>() { @Override public T getPayload() { return payload; } @Override public Function> getAckWithMetadata() { return actualAck; } @Override public BiFunction> getNackWithMetadata() { return actualNack; } }; } private static Message newMessage(T payload, Metadata metadata, Function> actualAck, BiFunction> actualNack) { return new Message<>() { @Override public T getPayload() { return payload; } @Override public Metadata getMetadata() { return metadata; } @Override public Function> getAckWithMetadata() { return actualAck; } @Override public BiFunction> getNackWithMetadata() { return actualNack; } }; } /** * Create a message with the given payload. * No metadata are associated with the message, the acknowledgement and negative acknowledgement are immediate. * * @param payload The payload. * @param The type of payload * @return A message with the given payload, no metadata, and no-op ack and nack functions. */ static Message of(T payload) { return () -> payload; } /** * Create a message with the given payload and metadata. * The acknowledgement and negative-acknowledgement are immediate. * * @param payload The payload. * @param metadata The metadata, if {@code null} an empty set of metadata is used. * @param The type of payload * @return A message with the given payload, metadata and no-op ack and nack functions. */ static Message of(T payload, Metadata metadata) { return newMessage(payload, metadata == null ? Metadata.empty() : metadata); } /** * Create a message with the given payload and metadata. * The acknowledgement and negative-acknowledgement are immediate. * * @param payload The payload. * @param metadata The metadata, must not be {@code null}, must not contain {@code null} values, can be empty * @param The type of payload * @return A message with the given payload, metadata and no-op ack and nack functions. */ static Message of(T payload, Iterable metadata) { return newMessage(payload, Metadata.from(metadata)); } /** * Create a message with the given payload and ack function. * No metadata are associated with the message. * Negative-acknowledgement is immediate. * * @param payload The payload. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param the type of payload * @return A message with the given payload, no metadata and ack function. */ static Message of(T payload, Supplier> ack) { return newMessage(payload, validateAck(ack)); } /** * Create a message with the given payload and ack function. * No metadata are associated with the message. * Negative-acknowledgement is immediate. * * @param payload The payload. * @param ackM The ack function, this will be invoked when the returned messages {@link #ack(Metadata)} method is invoked. * @param the type of payload * @return A message with the given payload, no metadata and ack function. */ static Message of(T payload, Function> ackM) { return newMessage(payload, validateAck(ackM)); } /** * Create a message with the given payload, metadata and ack function. * Negative-acknowledgement is immediate. * * @param payload The payload. * @param metadata the metadata, if {@code null}, empty metadata are used. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param the type of payload * @return A message with the given payload and ack function. */ static Message of(T payload, Metadata metadata, Supplier> ack) { return newMessage(payload, metadata == null ? Metadata.empty() : metadata, validateAck(ack)); } /** * Create a message with the given payload, metadata and ack function. * Negative-acknowledgement is immediate. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param the type of payload * @return A message with the given payload and ack function. */ static Message of(T payload, Iterable metadata, Supplier> ack) { return newMessage(payload, Metadata.from(metadata), validateAck(ack)); } /** * Create a message with the given payload, metadata and ack function. * Negative-acknowledgement is immediate. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ackM The ack function, this will be invoked when the returned messages {@link #ack(Metadata)} method is invoked. * @param the type of payload * @return A message with the given payload and ack function. */ static Message of(T payload, Iterable metadata, Function> ackM) { return newMessage(payload, Metadata.from(metadata), validateAck(ackM)); } /** * Create a message with the given payload, ack and nack functions. * * @param payload The payload. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param nack The negative-ack function, this will be invoked when the returned messages {@link #nack(Throwable)} * method is invoked. * @param the type of payload * @return A message with the given payload, metadata, ack and nack functions. */ @Experimental("nack support is a SmallRye-only feature") static Message of(T payload, Supplier> ack, Function> nack) { return newMessage(payload, validateAck(ack), validateNack(nack)); } @Experimental("nack support is a SmallRye-only feature") static Message of(T payload, Function> ack, BiFunction> nack) { return newMessage(payload, validateAck(ack), validateNack(nack)); } /** * Create a message with the given payload, metadata and ack and nack functions. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param nack The negative-ack function, this will be invoked when the returned messages {@link #nack(Throwable)} * method is invoked. * @param the type of payload * @return A message with the given payload, metadata, ack and nack functions. */ @Experimental("nack support is a SmallRye-only feature") static Message of(T payload, Iterable metadata, Supplier> ack, Function> nack) { return newMessage(payload, Metadata.from(metadata), validateAck(ack), validateNack(nack)); } /** * Create a message with the given payload, metadata and ack and nack functions. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ackM The ack function, this will be invoked when the returned messages {@link #ack(Metadata)} method is invoked. * @param nackM The negative-ack function, this will be invoked when the returned messages * {@link #nack(Throwable, Metadata)} * method is invoked. * @param the type of payload * @return A message with the given payload, metadata, ack and nack functions. */ @Experimental("nack support is a SmallRye-only feature") static Message of(T payload, Iterable metadata, Function> ackM, BiFunction> nackM) { return newMessage(payload, Metadata.from(metadata), validateAck(ackM), validateNack(nackM)); } /** * Create a message with the given payload, metadata and ack and nack functions. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ack The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param nack The negative-ack function, this will be invoked when the returned messages {@link #nack(Throwable)} * method is invoked. * @param the type of payload * @return A message with the given payload, metadata, ack and nack functions. */ @Experimental("metadata propagation is a SmallRye-specific feature") static Message of(T payload, Metadata metadata, Supplier> ack, Function> nack) { return newMessage(payload, metadata == null ? Metadata.empty() : metadata, validateAck(ack), validateNack(nack)); } /** * Create a message with the given payload, metadata and ack and nack functions. * * @param payload The payload. * @param metadata the metadata, must not be {@code null}, must not contain {@code null} values. * @param ackM The ack function, this will be invoked when the returned messages {@link #ack()} method is invoked. * @param nackM The negative-ack function, this will be invoked when the returned messages {@link #nack(Throwable)} * method is invoked. * @param the type of payload * @return A message with the given payload, metadata, ack and nack functions. */ @Experimental("metadata propagation is a SmallRye-specific feature") static Message of(T payload, Metadata metadata, Function> ackM, BiFunction> nackM) { return newMessage(payload, metadata == null ? Metadata.empty() : metadata, validateAck(ackM), validateNack(nackM)); } /** * Creates a new instance of {@link Message} with the specified payload. * The metadata and ack/nack functions are taken from the current {@link Message}. * * @param payload the new payload. * @param

the type of the new payload * @return the new instance of {@link Message} */ default

Message

withPayload(P payload) { return Message.of(payload, Metadata.from(getMetadata()), wrapAck(this), wrapNack(this)); } /** * Creates a new instance of {@link Message} with the specified metadata. * The payload and ack/nack functions are taken from the current {@link Message}. * * @param metadata the metadata, must not be {@code null}, must not contain {@code null}. * @return the new instance of {@link Message} */ default Message withMetadata(Iterable metadata) { return Message.of(getPayload(), Metadata.from(metadata), wrapAck(this), wrapNack(this)); } /** * Creates a new instance of {@link Message} with the specified metadata. * The payload and ack/nack functions are taken from the current {@link Message}. * * @param metadata the metadata, must not be {@code null}. * @return the new instance of {@link Message} */ default Message withMetadata(Metadata metadata) { return Message.of(getPayload(), Metadata.from(metadata), wrapAck(this), wrapNack(this)); } /** * Creates a new instance of {@link Message} with the given acknowledgement supplier. * The payload, metadata and nack function are taken from the current {@link Message}. * * @param supplier the acknowledgement supplier * @return the new instance of {@link Message} */ default Message withAck(Supplier> supplier) { return Message.of(getPayload(), getMetadata(), validateAck(supplier), wrapNack(this)); } /** * Creates a new instance of {@link Message} with the given acknowledgement supplier. * The payload, metadata and nack function are taken from the current {@link Message}. * * @param supplier the acknowledgement supplier * @return the new instance of {@link Message} */ @Experimental("metadata propagation is a SmallRye-specific feature") default Message withAckWithMetadata(Function> supplier) { return Message.of(getPayload(), getMetadata(), supplier, wrapNack(this)); } /** * Creates a new instance of {@link Message} with the given negative-acknowledgement function. * The payload, metadata and acknowledgment are taken from the current {@link Message}. * * @param nack the negative-acknowledgement function * @return the new instance of {@link Message} */ @Experimental("metadata propagation is a SmallRye-specific feature") default Message withNack(Function> nack) { return Message.of(getPayload(), getMetadata(), wrapAck(this), validateNack(nack)); } /** * Creates a new instance of {@link Message} with the given negative-acknowledgement function. * The payload, metadata and acknowledgment are taken from the current {@link Message}. * * @param nack the negative-acknowledgement function * @return the new instance of {@link Message} */ @Experimental("metadata propagation is a SmallRye-specific feature") default Message withNackWithMetadata(BiFunction> nack) { return Message.of(getPayload(), getMetadata(), wrapAck(this), nack); } /** * @return The payload for this message. */ T getPayload(); /** * @return The set of metadata attached to this message, potentially empty. */ default Metadata getMetadata() { return Metadata.empty(); } /** * Retrieves the metadata associated with the given class. * * @param clazz the class of the metadata to retrieve, must not be {@code null} * @return an {@link Optional} containing the associated metadata, empty if none. */ @SuppressWarnings("unchecked") default Optional getMetadata(Class clazz) { if (clazz == null) { throw new IllegalArgumentException("`clazz` must not be `null`"); } // TODO we really should do `getMetadata().get(clazz)`, because it's more intuitive, corresponds // closer to the documentation, and performs a bit better, but it would be a breaking change, // as `Message.getMetadata(Class)` returns any metadata item that is a _subtype_ of given class, // while `Metadata.get(Class)` returns a metadata item that is _exactly_ of given class for (Object item : getMetadata()) { if (clazz.isInstance(item)) { return Optional.of((M) item); } } return Optional.empty(); } /** * @return the supplier used to retrieve the acknowledgement {@link CompletionStage}. */ default Supplier> getAck() { return () -> requireNonNullElse(getAckWithMetadata(), EMPTY_ACK).apply(this.getMetadata()); } /** * @return the supplier used to retrieve the acknowledgement {@link CompletionStage}. */ @Experimental("metadata propagation is a SmallRye-specific feature") default Function> getAckWithMetadata() { return null; } /** * @return the function used to retrieve the negative-acknowledgement asynchronous function. */ default Function> getNack() { return reason -> requireNonNullElse(getNackWithMetadata(), EMPTY_NACK).apply(reason, this.getMetadata()); } /** * @return the function used to retrieve the negative-acknowledgement asynchronous function. */ @Experimental("metadata propagation is a SmallRye-specific feature") default BiFunction> getNackWithMetadata() { return null; } /** * Acknowledge this message. * * @return a completion stage completed when the message is acknowledged. If the acknowledgement fails, the * completion stage propagates the failure. */ default CompletionStage ack() { return ack(this.getMetadata()); } /** * Acknowledge this message. * * @return a completion stage completed when the message is acknowledged. If the acknowledgement fails, the * completion stage propagates the failure. */ @Experimental("metadata propagation is a SmallRye-specific feature") default CompletionStage ack(Metadata metadata) { Function> ackM = getAckWithMetadata(); if (ackM != null) { return ackM.apply(metadata); } else { Supplier> ack = getAck(); if (ack != null) { return ack.get(); } else { return CompletableFuture.completedFuture(null); } } } /** * Acknowledge negatively this message. * nack is used to indicate that the processing of a message failed. The reason is passed as parameter. * * @param reason the reason of the nack, must not be {@code null} * @return a completion stage completed when the message is negative-acknowledgement has completed. If the * negative acknowledgement fails, the completion stage propagates the failure. */ default CompletionStage nack(Throwable reason) { return nack(reason, this.getMetadata()); } /** * Acknowledge negatively this message. * nack is used to indicate that the processing of a message failed. The reason is passed as parameter. * Additional metadata may be provided that the connector can use when nacking the message. The interpretation * of metadata is connector-specific. * * @param reason the reason of the nack, must not be {@code null} * @param metadata additional nack metadata the connector may use, may be {@code null} * @return a completion stage completed when the message is negative-acknowledgement has completed. If the * negative acknowledgement fails, the completion stage propagates the failure. */ @Experimental("nack support is a SmallRye-only feature; metadata propagation is a SmallRye-specific feature") default CompletionStage nack(Throwable reason, Metadata metadata) { if (reason == null) { throw new IllegalArgumentException("The reason must not be `null`"); } BiFunction> nackM = getNackWithMetadata(); if (nackM != null) { return nackM.apply(reason, metadata); } else { Function> nack = getNack(); if (nack == null) { LOGGER.warning( String.format("A message has been nacked, but no nack function has been provided. The reason was: %s", reason.getMessage())); LOGGER.finer(String.format("The full failure is: %s", reason)); return CompletableFuture.completedFuture(null); } else { return nack.apply(reason); } } } /** * Returns an object of the specified type to allow access to the connector-specific {@link Message} implementation, * and other classes. For example, a Kafka connector could implement this method to allow unwrapping to a specific * Kafka message implementation, or to {@code ConsumerRecord} and {@code ProducerRecord}. If the {@link Message} * implementation does not support the target class, an {@link IllegalArgumentException} should be raised. *

* The default implementation tries to cast the current {@link Message} instance to the target class. * When a connector provides its own {@link Message} implementation, it should override this method to support * specific types. * * @param unwrapType the class of the object to be returned, must not be {@code null} * @param the target type * @return an instance of the specified class * @throws IllegalArgumentException if the current {@link Message} instance does not support the call */ default C unwrap(Class unwrapType) { if (unwrapType == null) { throw new IllegalArgumentException("The target class must not be `null`"); } try { return unwrapType.cast(this); } catch (ClassCastException e) { throw new IllegalArgumentException("Cannot unwrap an instance of " + this.getClass().getName() + " to " + unwrapType.getName(), e); } } /** * Creates a new instance of {@link Message} with the current metadata, plus the given one. * The payload and ack/nack functions are taken from the current {@link Message}. * * @param metadata the metadata, must not be {@code null}. * @return the new instance of {@link Message} */ default Message addMetadata(Object metadata) { return Message.of(getPayload(), getMetadata().with(metadata), wrapAck(this), wrapNack(this)); } /** * Apply the given modifier function to the current message returning the result for further composition * * @param modifier the function to modify the current message * @return the modified message * @param the payload type of the modified message */ default Message thenApply(Function, Message> modifier) { return modifier.apply(this); } }