*/
package org.opendaylight.controller.cluster.access.concepts;
-import com.google.common.annotations.Beta;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Verify;
+import java.io.DataInput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.io.Serializable;
-import javax.annotation.Nonnull;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.controller.cluster.access.ABIVersion;
import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.concepts.WritableIdentifier;
+import org.opendaylight.yangtools.concepts.WritableObjects;
/**
* An abstract concept of a Message. This class cannot be instantiated directly, use its specializations {@link Request}
* and {@link Response}.
*
+ * <p>
* Messages have a target and a sequence number. Sequence numbers are expected to be assigned monotonically on a
* per-target basis, hence two targets can observe the same sequence number.
*
+ * <p>
* This class includes explicit versioning for forward- and backward- compatibility of serialization format. This is
* achieved by using the serialization proxy pattern. Subclasses are in complete control of what proxy is used to
* serialize a particular object on the wire. This class can serve as an explicit version marker, hence no further
* action is necessary in the deserialization path.
*
+ * <p>
* For the serialization path an explicit call from the user is required to select the appropriate serialization
* version. This is done via {@link #toVersion(ABIVersion)} method, which should return a copy of this object with
* the requested ABI version recorded and should return the appropriate serialization proxy.
*
+ * <p>
* This workflow allows least disturbance across ABI versions, as all messages not affected by a ABI version bump
* will remain working with the same serialization format for the new ABI version.
*
+ * <p>
* Note that this class specifies the {@link Immutable} contract, which means that all subclasses must follow this API
* contract.
*
- * @author Robert Varga
- *
* @param <T> Target identifier type
* @param <C> Message type
*/
-@Beta
-public abstract class Message<T extends WritableIdentifier, C extends Message<T, C>> implements Immutable,
- Serializable {
+public abstract class Message<T extends WritableIdentifier, C extends Message<T, C>>
+ implements Immutable, Serializable {
+ /**
+ * Externalizable proxy for use with {@link Message} subclasses.
+ *
+ * @param <T> Target identifier type
+ * @param <C> Message class
+ */
+ protected interface SerialForm<T extends WritableIdentifier, C extends Message<T, C>> extends Externalizable {
+
+ @NonNull C message();
+
+ void setMessage(@NonNull C message);
+
+ @Override
+ default void writeExternal(final ObjectOutput out) throws IOException {
+ final var message = message();
+ message.getTarget().writeTo(out);
+ WritableObjects.writeLong(out, message.getSequence());
+ writeExternal(out, message);
+ }
+
+ void writeExternal(@NonNull ObjectOutput out, @NonNull C msg) throws IOException;
+
+ @Override
+ default void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+ final var target = verifyNotNull(readTarget(in));
+ final var sequence = WritableObjects.readLong(in);
+ setMessage(verifyNotNull(readExternal(in, target, sequence)));
+ }
+
+ @NonNull C readExternal(@NonNull ObjectInput in, @NonNull T target, long sequence)
+ throws IOException, ClassNotFoundException;
+
+ Object readResolve();
+
+ @NonNull T readTarget(@NonNull DataInput in) throws IOException;
+ }
+
+ @java.io.Serial
private static final long serialVersionUID = 1L;
- private final ABIVersion version;
+ private final @NonNull ABIVersion version;
private final long sequence;
- private final T target;
+ private final @NonNull T target;
private Message(final ABIVersion version, final T target, final long sequence) {
- this.target = Preconditions.checkNotNull(target);
- this.version = Preconditions.checkNotNull(version);
+ this.target = requireNonNull(target);
+ this.version = requireNonNull(version);
this.sequence = sequence;
}
*
* @return Target identifier
*/
- public final @Nonnull T getTarget() {
+ public final @NonNull T getTarget() {
return target;
}
}
@VisibleForTesting
- public final @Nonnull ABIVersion getVersion() {
+ public final @NonNull ABIVersion getVersion() {
return version;
}
/**
* Return a message which will end up being serialized in the specified {@link ABIVersion}.
*
- * @param version Request {@link ABIVersion}
+ * @param toVersion Request {@link ABIVersion}
* @return A new message which will use ABIVersion as its serialization.
*/
@SuppressWarnings("unchecked")
- public final @Nonnull C toVersion(final @Nonnull ABIVersion version) {
- if (this.version == version) {
+ public final @NonNull C toVersion(final @NonNull ABIVersion toVersion) {
+ if (this.version == toVersion) {
return (C)this;
}
- switch (version) {
- case BORON:
- return Verify.verifyNotNull(cloneAsVersion(version));
- case TEST_PAST_VERSION:
- case TEST_FUTURE_VERSION:
- // Fall-through to throw
- break;
- }
-
- throw new IllegalArgumentException("Unhandled ABI version " + version);
+ return switch (toVersion) {
+ case CHLORINE_SR2 -> verifyNotNull(cloneAsVersion(toVersion));
+ default -> throw new IllegalArgumentException("Unhandled ABI version " + toVersion);
+ };
}
/**
* method should be implemented by the concrete final message class and should invoke the equivalent of
* {@link #Message(Message, ABIVersion)}.
*
- * @param version target ABI version
+ * @param targetVersion target ABI version
* @return A message with the specified serialization stream
* @throws IllegalArgumentException if this message does not support the target ABI
*/
- protected abstract @Nonnull C cloneAsVersion(@Nonnull ABIVersion version);
+ protected abstract @NonNull C cloneAsVersion(@NonNull ABIVersion targetVersion);
@Override
public final String toString() {
* @return The {@link ToStringHelper} passed in as argument
* @throws NullPointerException if toStringHelper is null
*/
- protected @Nonnull ToStringHelper addToStringAttributes(final @Nonnull ToStringHelper toStringHelper) {
+ protected @NonNull ToStringHelper addToStringAttributes(final @NonNull ToStringHelper toStringHelper) {
return toStringHelper.add("target", target).add("sequence", Long.toUnsignedString(sequence));
}
* different objects for incompatible {@link ABIVersion}s. This method should never fail, as any compatibility
* checks should have been done by {@link #cloneAsVersion(ABIVersion)}.
*
- * @param version Requested ABI version
+ * @param reqVersion Requested ABI version
* @return Proxy for this object
*/
- abstract @Nonnull AbstractMessageProxy<T, C> externalizableProxy(@Nonnull ABIVersion version);
+ protected abstract @NonNull SerialForm<T, C> externalizableProxy(@NonNull ABIVersion reqVersion);
+ @java.io.Serial
protected final Object writeReplace() {
return externalizableProxy(version);
}