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