2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.cluster.access.concepts;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.base.MoreObjects.ToStringHelper;
16 import java.io.DataInput;
17 import java.io.Externalizable;
18 import java.io.IOException;
19 import java.io.NotSerializableException;
20 import java.io.ObjectInput;
21 import java.io.ObjectInputStream;
22 import java.io.ObjectOutput;
23 import java.io.ObjectOutputStream;
24 import java.io.ObjectStreamException;
25 import java.io.Serializable;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.controller.cluster.access.ABIVersion;
28 import org.opendaylight.yangtools.concepts.Immutable;
29 import org.opendaylight.yangtools.concepts.WritableIdentifier;
30 import org.opendaylight.yangtools.concepts.WritableObjects;
33 * An abstract concept of a Message. This class cannot be instantiated directly, use its specializations {@link Request}
34 * and {@link Response}.
37 * Messages have a target and a sequence number. Sequence numbers are expected to be assigned monotonically on a
38 * per-target basis, hence two targets can observe the same sequence number.
41 * This class includes explicit versioning for forward- and backward- compatibility of serialization format. This is
42 * achieved by using the serialization proxy pattern. Subclasses are in complete control of what proxy is used to
43 * serialize a particular object on the wire. This class can serve as an explicit version marker, hence no further
44 * action is necessary in the deserialization path.
47 * For the serialization path an explicit call from the user is required to select the appropriate serialization
48 * version. This is done via {@link #toVersion(ABIVersion)} method, which should return a copy of this object with
49 * the requested ABI version recorded and should return the appropriate serialization proxy.
52 * This workflow allows least disturbance across ABI versions, as all messages not affected by a ABI version bump
53 * will remain working with the same serialization format for the new ABI version.
56 * Note that this class specifies the {@link Immutable} contract, which means that all subclasses must follow this API
59 * @param <T> Target identifier type
60 * @param <C> Message type
62 public abstract class Message<T extends WritableIdentifier, C extends Message<T, C>>
63 implements Immutable, Serializable {
65 * Externalizable proxy for use with {@link Message} subclasses.
67 * @param <T> Target identifier type
68 * @param <C> Message class
70 protected interface SerialForm<T extends WritableIdentifier, C extends Message<T, C>> extends Externalizable {
74 void setMessage(@NonNull C message);
77 default void writeExternal(final ObjectOutput out) throws IOException {
78 final var message = message();
79 message.getTarget().writeTo(out);
80 WritableObjects.writeLong(out, message.getSequence());
81 writeExternal(out, message);
84 void writeExternal(@NonNull ObjectOutput out, @NonNull C msg) throws IOException;
87 default void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
88 final var target = verifyNotNull(readTarget(in));
89 final var sequence = WritableObjects.readLong(in);
90 setMessage(verifyNotNull(readExternal(in, target, sequence)));
93 @NonNull C readExternal(@NonNull ObjectInput in, @NonNull T target, long sequence)
94 throws IOException, ClassNotFoundException;
98 @NonNull T readTarget(@NonNull DataInput in) throws IOException;
102 private static final long serialVersionUID = 1L;
104 private final @NonNull ABIVersion version;
105 private final long sequence;
106 private final @NonNull T target;
108 private Message(final ABIVersion version, final T target, final long sequence) {
109 this.target = requireNonNull(target);
110 this.version = requireNonNull(version);
111 this.sequence = sequence;
114 Message(final T target, final long sequence) {
115 this(ABIVersion.current(), target, sequence);
118 Message(final C msg, final ABIVersion version) {
119 this(version, msg.getTarget(), msg.getSequence());
123 * Get the target identifier for this message.
125 * @return Target identifier
127 public final @NonNull T getTarget() {
132 * Get the logical sequence number.
134 * @return logical sequence number
136 public final long getSequence() {
141 public final @NonNull ABIVersion getVersion() {
146 * Return a message which will end up being serialized in the specified {@link ABIVersion}.
148 * @param toVersion Request {@link ABIVersion}
149 * @return A new message which will use ABIVersion as its serialization.
151 @SuppressWarnings("unchecked")
152 public final @NonNull C toVersion(final @NonNull ABIVersion toVersion) {
153 if (version == toVersion) {
157 return switch (toVersion) {
158 case POTASSIUM -> verifyNotNull(cloneAsVersion(toVersion));
159 default -> throw new IllegalArgumentException("Unhandled ABI version " + toVersion);
164 * Create a copy of this message which will serialize to a stream corresponding to the specified method. This
165 * method should be implemented by the concrete final message class and should invoke the equivalent of
166 * {@link #Message(Message, ABIVersion)}.
168 * @param targetVersion target ABI version
169 * @return A message with the specified serialization stream
170 * @throws IllegalArgumentException if this message does not support the target ABI
172 protected abstract @NonNull C cloneAsVersion(@NonNull ABIVersion targetVersion);
175 public final String toString() {
176 return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
180 * Add attributes to the output of {@link #toString()}. Subclasses wanting to contribute additional information
181 * should override this method. Any null attributes will be omitted from the output.
183 * @param toStringHelper a {@link ToStringHelper} instance
184 * @return The {@link ToStringHelper} passed in as argument
185 * @throws NullPointerException if toStringHelper is null
187 protected @NonNull ToStringHelper addToStringAttributes(final @NonNull ToStringHelper toStringHelper) {
188 return toStringHelper.add("target", target).add("sequence", Long.toUnsignedString(sequence));
192 * Instantiate a serialization proxy for this object for the target ABI version. Implementations should return
193 * different objects for incompatible {@link ABIVersion}s. This method should never fail, as any compatibility
194 * checks should have been done by {@link #cloneAsVersion(ABIVersion)}.
196 * @param reqVersion Requested ABI version
197 * @return Proxy for this object
199 protected abstract @NonNull SerialForm<T, C> externalizableProxy(@NonNull ABIVersion reqVersion);
202 protected final Object writeReplace() {
203 return externalizableProxy(version);
206 protected final void throwNSE() throws NotSerializableException {
207 throw new NotSerializableException(getClass().getName());
211 private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
216 private void readObjectNoData() throws ObjectStreamException {
221 private void writeObject(final ObjectOutputStream stream) throws IOException {