1
2
3
4
5
6
7
8
9
10
11
12
13 package org.codehaus.plexus.build.connect.messages;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.ByteArrayOutputStream;
17 import java.io.DataInputStream;
18 import java.io.DataOutputStream;
19 import java.io.IOException;
20 import java.nio.charset.StandardCharsets;
21 import java.util.Collections;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.concurrent.atomic.AtomicLong;
28 import java.util.stream.Stream;
29
30
31
32
33 public class Message {
34 private static final ThreadLocal<Long> ID = new ThreadLocal<Long>() {
35 private final AtomicLong generator = new AtomicLong();
36
37 @Override
38 protected Long initialValue() {
39 return generator.getAndIncrement();
40 }
41 };
42 private final long threadId;
43 private final Map<String, String> properties;
44 private final String sessionId;
45
46 Message(Map<String, String> payload) {
47 this(null, ID.get(), payload);
48 }
49
50 Message(String sessionId, long threadId, Map<String, String> payload) {
51 this.sessionId = sessionId;
52 this.properties = Objects.requireNonNull(payload);
53 this.threadId = threadId;
54 }
55
56
57
58
59 public Stream<String> keys() {
60 return properties.keySet().stream();
61 }
62
63
64
65
66
67
68
69 public String getProperty(String key) {
70 return properties.get(key);
71 }
72
73
74
75
76
77
78
79
80 public String getProperty(String key, String defaultValue) {
81 return properties.getOrDefault(key, defaultValue);
82 }
83
84
85
86
87
88
89
90 public boolean getBooleanProperty(String key) {
91 return Boolean.parseBoolean(properties.get(key));
92 }
93
94
95
96
97
98
99
100
101 public boolean getBooleanProperty(String key, boolean defaultValue) {
102 String property = getProperty(key);
103 if (property == null) {
104 return defaultValue;
105 }
106 return Boolean.parseBoolean(property);
107 }
108
109
110
111
112
113 public String getSessionId() {
114 if (sessionId == null) {
115 throw new IllegalStateException("can not be called on a local message!");
116 }
117 return sessionId;
118 }
119
120
121
122
123 public byte[] serialize() {
124 return serialize(getSessionId());
125 }
126
127 @Override
128 public String toString() {
129 return getClass().getSimpleName() + " [" + sessionId + "][" + threadId + "] " + properties;
130 }
131
132
133
134
135
136
137
138 public byte[] serialize(String sessionId) {
139 ByteArrayOutputStream stream = new ByteArrayOutputStream();
140 DataOutputStream out = new DataOutputStream(stream);
141 try {
142 writeString(sessionId, out);
143 out.writeLong(threadId);
144 writeString(getClass().getSimpleName(), out);
145 if (properties.isEmpty()) {
146 out.writeInt(0);
147 } else {
148 Set<Entry<String, String>> set = properties.entrySet();
149 out.writeInt(set.size());
150 for (Entry<String, String> entry : set) {
151 writeString(entry.getKey(), out);
152 writeString(entry.getValue(), out);
153 }
154 }
155 } catch (IOException e) {
156
157 throw new RuntimeException("Internal Error: Write data failed", e);
158 }
159 return stream.toByteArray();
160 }
161
162
163
164
165
166
167
168
169
170 public static Message replyTo(Message message, Map<String, String> payload) {
171 if (payload == null) {
172 payload = Collections.emptyMap();
173 }
174 return new Message(message.sessionId, message.threadId, payload);
175 }
176
177
178
179
180
181
182
183 public static Message decode(byte[] bytes) {
184 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
185 DataInputStream in = new DataInputStream(stream);
186 try {
187 String sessionId = readString(in);
188 long threadId = in.readLong();
189 String messageType = readString(in);
190 int size = in.readInt();
191 Map<String, String> payload = new LinkedHashMap<>(size);
192 for (int i = 0; i < size; i++) {
193 payload.put(readString(in), readString(in));
194 }
195 if ("SessionMessage".equals(messageType)) {
196 return new SessionMessage(sessionId, threadId, payload);
197 }
198 if ("ProjectsMessage".equals(messageType)) {
199 return new ProjectsMessage(sessionId, threadId, payload);
200 }
201 if ("RefreshMessage".equals(messageType)) {
202 return new RefreshMessage(sessionId, threadId, payload);
203 }
204 if ("InitMessage".equals(messageType)) {
205 return new InitMessage(sessionId, threadId, payload);
206 }
207 if ("ProjectMessage".equals(messageType)) {
208 return new ProjectMessage(sessionId, threadId, payload);
209 }
210 if ("MojoMessage".equals(messageType)) {
211 return new MojoMessage(sessionId, threadId, payload);
212 }
213 return new Message(sessionId, threadId, payload);
214 } catch (IOException e) {
215
216 System.err.println("Internal Error: Message decoding failed: " + e);
217 }
218 return null;
219 }
220
221 private static String readString(DataInputStream in) throws IOException {
222 int length = in.readInt();
223 if (length < 0) {
224 return null;
225 }
226 if (length == 0) {
227 return "";
228 }
229 byte[] bs = new byte[length];
230 in.readFully(bs);
231 return new String(bs, StandardCharsets.UTF_8);
232 }
233
234 private static void writeString(String string, DataOutputStream stream) throws IOException {
235 if (string == null) {
236 stream.writeInt(-1);
237 } else {
238 byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
239 stream.writeInt(bytes.length);
240 stream.write(bytes);
241 }
242 }
243
244 @Override
245 public int hashCode() {
246 return Objects.hash(properties, sessionId, threadId);
247 }
248
249 @Override
250 public boolean equals(Object obj) {
251 if (this == obj) {
252 return true;
253 }
254 if (obj == null) {
255 return false;
256 }
257 if (getClass() != obj.getClass()) {
258 return false;
259 }
260 Message other = (Message) obj;
261 return Objects.equals(properties, other.properties)
262 && Objects.equals(sessionId, other.sessionId)
263 && threadId == other.threadId;
264 }
265 }