1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.replication;
18
19 import static com.linecorp.centraldogma.internal.Util.unsafeCast;
20 import static java.util.Objects.requireNonNull;
21
22 import java.util.Objects;
23
24 import javax.annotation.Nullable;
25
26 import com.fasterxml.jackson.annotation.JsonCreator;
27 import com.fasterxml.jackson.annotation.JsonProperty;
28 import com.fasterxml.jackson.core.JsonProcessingException;
29 import com.fasterxml.jackson.databind.JsonNode;
30
31 import com.linecorp.centraldogma.internal.Jackson;
32 import com.linecorp.centraldogma.server.command.Command;
33 import com.linecorp.centraldogma.server.command.CommandType;
34 import com.linecorp.centraldogma.server.command.ForcePushCommand;
35 import com.linecorp.centraldogma.server.command.NormalizingPushCommand;
36
37 public final class ReplicationLog<T> {
38
39 private final int replicaId;
40 private final Command<T> command;
41 private final T result;
42
43 @JsonCreator
44 static <T> ReplicationLog<T> deserialize(
45 @JsonProperty("replicaId") @Nullable Integer replicaId,
46 @JsonProperty("command") Command<T> command,
47 @JsonProperty("result") JsonNode result) throws JsonProcessingException {
48 return new ReplicationLog<>(requireNonNull(replicaId, "replicaId"),
49 command, deserializeResult(result, command));
50 }
51
52 @Nullable
53 private static <T> T deserializeResult(
54 JsonNode result, Command<T> command) throws JsonProcessingException {
55
56 requireNonNull(command, "command");
57
58
59 if (result != null && result.isNull()) {
60 result = null;
61 }
62
63 final Class<T> resultType;
64 if (command.type() == CommandType.FORCE_PUSH) {
65 resultType = unsafeCast(((ForcePushCommand<?>) command).delegate().type().resultType());
66 } else {
67 resultType = unsafeCast(command.type().resultType());
68 }
69 if (resultType == Void.class) {
70 if (result != null) {
71 rejectIncompatibleResult(result, Void.class);
72 }
73
74 return null;
75 }
76
77 assert result != null;
78 return Jackson.treeToValue(result, resultType);
79 }
80
81 ReplicationLog(int replicaId, Command<T> command, @Nullable T result) {
82 this.replicaId = replicaId;
83 assert !(command instanceof NormalizingPushCommand)
84 : NormalizingPushCommand.class.getSimpleName() + " cannot be replicated.";
85 this.command = requireNonNull(command, "command");
86
87 final Class<?> resultType;
88 if (command.type() == CommandType.FORCE_PUSH) {
89 resultType = ((ForcePushCommand<?>) command).delegate().type().resultType();
90 } else {
91 resultType = command.type().resultType();
92 }
93 if (resultType == Void.class) {
94 if (result != null) {
95 rejectIncompatibleResult(result, Void.class);
96 }
97 } else {
98 requireNonNull(result, "result");
99 if (!resultType.isInstance(result)) {
100 rejectIncompatibleResult(result, resultType);
101 }
102 }
103
104 this.result = result;
105 }
106
107 private static void rejectIncompatibleResult(Object result, Class<?> resultType) {
108 throw new IllegalArgumentException("incompatible result: " + result +
109 " (expected type: " + resultType.getName() + ')');
110 }
111
112 @JsonProperty
113 public int replicaId() {
114 return replicaId;
115 }
116
117 @JsonProperty
118 public Command<T> command() {
119 return command;
120 }
121
122 @JsonProperty
123 public T result() {
124 return result;
125 }
126
127 @Override
128 public int hashCode() {
129 return replicaId() * 31 + command().hashCode();
130 }
131
132 @Override
133 public boolean equals(Object obj) {
134 if (!(obj instanceof ReplicationLog)) {
135 return false;
136 }
137
138 if (this == obj) {
139 return true;
140 }
141
142 @SuppressWarnings("unchecked")
143 final ReplicationLog<Object> that = (ReplicationLog<Object>) obj;
144
145 return replicaId() == that.replicaId() &&
146 Objects.equals(result(), that.result()) &&
147 command().equals(that.command());
148 }
149
150 @Override
151 public String toString() {
152 final StringBuilder buf = new StringBuilder(64);
153
154 buf.append("(replicaId: ");
155 buf.append(replicaId());
156 buf.append(", command: ");
157 buf.append(command());
158 buf.append(", result: ");
159 buf.append(result());
160 buf.append(')');
161
162 return buf.toString();
163 }
164 }