Prune old ABI versions
[controller.git] / opendaylight / md-sal / cds-access-api / src / main / java / org / opendaylight / controller / cluster / access / concepts / Message.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.cluster.access.concepts;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
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.ObjectInput;
20 import java.io.ObjectOutput;
21 import java.io.Serializable;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.controller.cluster.access.ABIVersion;
24 import org.opendaylight.yangtools.concepts.Immutable;
25 import org.opendaylight.yangtools.concepts.WritableIdentifier;
26 import org.opendaylight.yangtools.concepts.WritableObjects;
27
28 /**
29  * An abstract concept of a Message. This class cannot be instantiated directly, use its specializations {@link Request}
30  * and {@link Response}.
31  *
32  * <p>
33  * Messages have a target and a sequence number. Sequence numbers are expected to be assigned monotonically on a
34  * per-target basis, hence two targets can observe the same sequence number.
35  *
36  * <p>
37  * This class includes explicit versioning for forward- and backward- compatibility of serialization format. This is
38  * achieved by using the serialization proxy pattern. Subclasses are in complete control of what proxy is used to
39  * serialize a particular object on the wire. This class can serve as an explicit version marker, hence no further
40  * action is necessary in the deserialization path.
41  *
42  * <p>
43  * For the serialization path an explicit call from the user is required to select the appropriate serialization
44  * version. This is done via {@link #toVersion(ABIVersion)} method, which should return a copy of this object with
45  * the requested ABI version recorded and should return the appropriate serialization proxy.
46  *
47  * <p>
48  * This workflow allows least disturbance across ABI versions, as all messages not affected by a ABI version bump
49  * will remain working with the same serialization format for the new ABI version.
50  *
51  * <p>
52  * Note that this class specifies the {@link Immutable} contract, which means that all subclasses must follow this API
53  * contract.
54  *
55  * @param <T> Target identifier type
56  * @param <C> Message type
57  */
58 public abstract class Message<T extends WritableIdentifier, C extends Message<T, C>>
59         implements Immutable, Serializable {
60     /**
61      * Externalizable proxy for use with {@link Message} subclasses.
62      *
63      * @param <T> Target identifier type
64      * @param <C> Message class
65      */
66     protected interface SerialForm<T extends WritableIdentifier, C extends Message<T, C>> extends Externalizable {
67
68         @NonNull C message();
69
70         void setMessage(@NonNull C message);
71
72         @Override
73         default void writeExternal(final ObjectOutput out) throws IOException {
74             final var message = message();
75             message.getTarget().writeTo(out);
76             WritableObjects.writeLong(out, message.getSequence());
77             writeExternal(out, message);
78         }
79
80         void writeExternal(@NonNull ObjectOutput out, @NonNull C msg) throws IOException;
81
82         @Override
83         default void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
84             final var target = verifyNotNull(readTarget(in));
85             final var sequence = WritableObjects.readLong(in);
86             setMessage(verifyNotNull(readExternal(in, target, sequence)));
87         }
88
89         @NonNull C readExternal(@NonNull ObjectInput in, @NonNull T target, long sequence)
90             throws IOException, ClassNotFoundException;
91
92         Object readResolve();
93
94         @NonNull T readTarget(@NonNull DataInput in) throws IOException;
95     }
96
97     @java.io.Serial
98     private static final long serialVersionUID = 1L;
99
100     private final @NonNull ABIVersion version;
101     private final long sequence;
102     private final @NonNull T target;
103
104     private Message(final ABIVersion version, final T target, final long sequence) {
105         this.target = requireNonNull(target);
106         this.version = requireNonNull(version);
107         this.sequence = sequence;
108     }
109
110     Message(final T target, final long sequence) {
111         this(ABIVersion.current(), target, sequence);
112     }
113
114     Message(final C msg, final ABIVersion version) {
115         this(version, msg.getTarget(), msg.getSequence());
116     }
117
118     /**
119      * Get the target identifier for this message.
120      *
121      * @return Target identifier
122      */
123     public final @NonNull T getTarget() {
124         return target;
125     }
126
127     /**
128      * Get the logical sequence number.
129      *
130      * @return logical sequence number
131      */
132     public final long getSequence() {
133         return sequence;
134     }
135
136     @VisibleForTesting
137     public final @NonNull ABIVersion getVersion() {
138         return version;
139     }
140
141     /**
142      * Return a message which will end up being serialized in the specified {@link ABIVersion}.
143      *
144      * @param toVersion Request {@link ABIVersion}
145      * @return A new message which will use ABIVersion as its serialization.
146      */
147     @SuppressWarnings("unchecked")
148     public final @NonNull C toVersion(final @NonNull ABIVersion toVersion) {
149         if (this.version == toVersion) {
150             return (C)this;
151         }
152
153         return switch (toVersion) {
154             case MAGNESIUM, CHLORINE_SR2 -> verifyNotNull(cloneAsVersion(toVersion));
155             default -> throw new IllegalArgumentException("Unhandled ABI version " + toVersion);
156         };
157     }
158
159     /**
160      * Create a copy of this message which will serialize to a stream corresponding to the specified method. This
161      * method should be implemented by the concrete final message class and should invoke the equivalent of
162      * {@link #Message(Message, ABIVersion)}.
163      *
164      * @param targetVersion target ABI version
165      * @return A message with the specified serialization stream
166      * @throws IllegalArgumentException if this message does not support the target ABI
167      */
168     protected abstract @NonNull C cloneAsVersion(@NonNull ABIVersion targetVersion);
169
170     @Override
171     public final String toString() {
172         return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
173     }
174
175     /**
176      * Add attributes to the output of {@link #toString()}. Subclasses wanting to contribute additional information
177      * should override this method. Any null attributes will be omitted from the output.
178      *
179      * @param toStringHelper a {@link ToStringHelper} instance
180      * @return The {@link ToStringHelper} passed in as argument
181      * @throws NullPointerException if toStringHelper is null
182      */
183     protected @NonNull ToStringHelper addToStringAttributes(final @NonNull ToStringHelper toStringHelper) {
184         return toStringHelper.add("target", target).add("sequence", Long.toUnsignedString(sequence));
185     }
186
187     /**
188      * Instantiate a serialization proxy for this object for the target ABI version. Implementations should return
189      * different objects for incompatible {@link ABIVersion}s. This method should never fail, as any compatibility
190      * checks should have been done by {@link #cloneAsVersion(ABIVersion)}.
191      *
192      * @param reqVersion Requested ABI version
193      * @return Proxy for this object
194      */
195     protected abstract @NonNull SerialForm<T, C> externalizableProxy(@NonNull ABIVersion reqVersion);
196
197     @java.io.Serial
198     protected final Object writeReplace() {
199         return externalizableProxy(version);
200     }
201 }