1 /*
2  * Copyright 2002-2018 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 module hunt.stomp.MessageHeaders;
18 
19 import hunt.stomp.IdGenerator;
20 
21 // import hunt.io.ObjectInputStream;
22 // import hunt.io.ObjectOutputStream;
23 
24 // import hunt.collection.Collection;
25 // import hunt.collection.Collections;
26 // import hunt.collection.HashMap;
27 // import hunt.collection.HashSet;
28 // import hunt.collection.Map;
29 // import hunt.collection.Set;
30 
31 import hunt.collection;
32 import hunt.util.DateTime;
33 import hunt.logging;
34 import hunt.Long;
35 import hunt.Nullable;
36 import hunt.Exceptions;
37 import hunt.Object;
38 
39 import std.uuid;
40 import std.range;
41 
42 
43 /**
44  * The headers for a {@link Message}.
45  *
46  * <p><b>IMPORTANT</b>: This class is immutable. Any mutating operation such as
47  * {@code put(..)}, {@code putAll(..)} and others will throw
48  * {@link UnsupportedOperationException}.
49  * <p>Subclasses do have access to the raw headers, however, via {@link #getRawHeaders()}.
50  *
51  * <p>One way to create message headers is to use the
52  * {@link hunt.stomp.support.MessageBuilder MessageBuilder}:
53  * <pre class="code">
54  * MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
55  * </pre>
56  *
57  * A second option is to create {@link hunt.stomp.support.GenericMessage}
58  * passing a payload as {@link Object} and headers as a {@link Map java.util.Map}:
59  * <pre class="code">
60  * Map headers = new HashMap();
61  * headers.put("key1", "value1");
62  * headers.put("key2", "value2");
63  * new GenericMessage("foo", headers);
64  * </pre>
65  *
66  * A third option is to use {@link hunt.stomp.support.MessageHeaderAccessor}
67  * or one of its subclasses to create specific categories of headers.
68  *
69  * @author Arjen Poutsma
70  * @author Mark Fisher
71  * @author Gary Russell
72  * @author Juergen Hoeller
73  * @since 4.0
74  * @see hunt.stomp.support.MessageBuilder
75  * @see hunt.stomp.support.MessageHeaderAccessor
76  */
77 class MessageHeaders : AbstractMap!(string, Object) {
78 
79 	/**
80 	 * UUID for none.
81 	 */
82 	__gshared static UUID ID_VALUE_NONE;
83 
84 	/**
85 	 * The key for the Message ID. This is an automatically generated UUID and
86 	 * should never be explicitly set in the header map <b>except</b> in the
87 	 * case of Message deserialization where the serialized Message's generated
88 	 * UUID is being restored.
89 	 */
90 	enum string ID = "id";
91 
92 	/**
93 	 * The key for the message timestamp.
94 	 */
95 	enum string TIMESTAMP = "timestamp";
96 
97 	/**
98 	 * The key for the message content type.
99 	 */
100 	enum string CONTENT_TYPE = "contentType";
101 
102 	/**
103 	 * The key for the message reply channel.
104 	 */
105 	enum string REPLY_CHANNEL = "replyChannel";
106 
107 	/**
108 	 * The key for the message error channel.
109 	 */
110 	enum string ERROR_CHANNEL = "errorChannel";
111 
112 
113 	private __gshared static IdGenerator defaultIdGenerator;
114 
115 	
116 	private static IdGenerator idGenerator;
117 
118 
119 	private Map!(string, Object) headers;
120 
121 	shared static this() {
122 		ID_VALUE_NONE = UUID.init;
123 		defaultIdGenerator = new class IdGenerator {
124 			UUID generateId() {
125 				return randomUUID();
126 			}
127 		};
128 		// defaultIdGenerator = new AlternativeJdkIdGenerator();
129 	}
130 
131 
132 	/**
133 	 * Construct a {@link MessageHeaders} with the given headers. An {@link #ID} and
134 	 * {@link #TIMESTAMP} headers will also be added, overriding any existing values.
135 	 * @param headers a map with headers to add
136 	 */
137 	this(Map!(string, Object) headers) {
138 		this(headers, null, null);
139 	}
140 
141 	this(Map!(string, Object) headers, UUID* id, long timestamp) {
142 		this(headers, id, Long.valueOf(timestamp));
143 	}
144 
145 	/**
146 	 * Constructor providing control over the ID and TIMESTAMP header values.
147 	 * @param headers a map with headers to add
148 	 * @param id the {@link #ID} header value
149 	 * @param timestamp the {@link #TIMESTAMP} header value
150 	 */
151 	this(Map!(string, Object) headers, UUID* id, Long timestamp) {
152 		this.headers = (headers !is null ? 
153 			new HashMap!(string, Object)(headers) 
154 			: new HashMap!(string, Object)());
155 
156 		if (id is null) {
157 			this.headers.put(ID, new Nullable!UUID(randomUUID()));
158 		}
159 		else if (*id == ID_VALUE_NONE) {
160 			this.headers.remove(ID);
161 		}
162 		else {
163 			this.headers.put(ID, new Nullable!UUID(*id));
164 		}
165 
166 		if (timestamp is null) {
167 			timestamp = new Long(DateTime.currentTimeMillis());
168 			this.headers.put(TIMESTAMP, timestamp);
169 		}
170 		else if (timestamp < 0) {
171 			this.headers.remove(TIMESTAMP);
172 		}
173 		else {
174 			this.headers.put(TIMESTAMP, timestamp);
175 		}
176 	}
177 
178 	/**
179 	 * Copy constructor which allows for ignoring certain entries.
180 	 * Used for serialization without non-serializable entries.
181 	 * @param original the MessageHeaders to copy
182 	 * @param keysToIgnore the keys of the entries to ignore
183 	 */
184 	private this(MessageHeaders original, Set!(string) keysToIgnore) {
185 		this.headers = new HashMap!(string, Object)(original.headers.size());
186 		foreach(string key, Object value; original.headers) {
187 			if (!keysToIgnore.contains(key))
188 				this.headers.put(key, value);
189 		}
190 	}
191 
192 
193 	protected Map!(string, Object) getRawHeaders() {
194 		return this.headers;
195 	}
196 
197 	static IdGenerator getIdGenerator() {
198 		IdGenerator generator = idGenerator;
199 		return (generator !is null ? generator : defaultIdGenerator);
200 	}
201 
202 	
203 	UUID getId() {
204 		return getAs!(UUID)(ID);
205 	}
206 
207 	
208 	Long getTimestamp() {
209 		return getAs!(Long)(TIMESTAMP);
210 	}
211 
212 	
213 	Object getReplyChannel() {
214 		return get(REPLY_CHANNEL);
215 	}
216 
217 	
218 	Object getErrorChannel() {
219 		return get(ERROR_CHANNEL);
220 	}
221 
222 	
223 	T getAs(T=Object)(string key) {
224 		Object value = this.headers.get(key);
225 		if (value is null) {
226 			// version(HUNT_DEBUG) warningf("header does not exist: %s", key);
227 			return T.init;
228 		}
229 		// if (!type.isAssignableFrom(value.getClass())) {
230 		// 	throw new IllegalArgumentException("Incorrect type specified for header '" ~
231 		// 			key ~ "'. Expected [" ~ type ~ "] but actual type is [" ~ value.getClass() ~ "]");
232 		// }
233 		// static if(is(T == Nullable!T)) {
234 		// 	Nullable!T o = cast(Nullable!T)value;
235 		// 	return o.value;
236 		// } else {
237 		// 	return cast(T) value;
238 		// }
239 		Nullable!T o = cast(Nullable!T)value;
240 		if(o is null) {
241 			static if(is(T == class)) {
242 				version(HUNT_DEBUG) tracef("header[%s] = %s", key, value);
243 				return cast(T) value;
244 			} else {
245 				assert(false, "wrong type");
246 			}
247 		} else {
248 			version(HUNT_DEBUG) tracef("header[%s] = %s", key, o.value);
249 			return o.value;
250 		}
251 	}
252 
253 
254 	// Delegating Map implementation
255 
256 	override bool containsKey(string key) {
257 		return this.headers.containsKey(key);
258 	}
259 
260 	override bool containsValue(Object value) {
261 		return this.headers.containsValue(value);
262 	}
263 
264 	// Set<Map.Entry!(string, Object)> entrySet() {
265 	// 	return Collections.unmodifiableMap(this.headers).entrySet();
266 	// }
267 
268 	
269 	override Object get(string key) {
270 		return this.headers.get(key);
271 	}
272 
273 	override bool isEmpty() {
274 		return this.headers.isEmpty();
275 	}
276 
277 	override int size() {
278 		return this.headers.size();
279 	}
280 
281     override int opApply(scope int delegate(ref string, ref Object) dg)  {
282         return this.headers.opApply(dg);
283     }
284     
285     override int opApply(scope int delegate(MapEntry!(string, Object) entry) dg) {
286         return this.headers.opApply(dg);
287     }
288     
289     override InputRange!string byKey() {
290         return this.headers.byKey();
291     }
292 
293     override InputRange!Object byValue() {
294         return this.headers.byValue();
295     }	
296 
297 	// Unsupported Map operations
298 
299 	/**
300 	 * Since MessageHeaders are immutable, the call to this method
301 	 * will result in {@link UnsupportedOperationException}.
302 	 */
303 	override Object put(string key, Object value) {
304 		throw new UnsupportedOperationException("MessageHeaders is immutable");
305 	}
306 
307 	/**
308 	 * Since MessageHeaders are immutable, the call to this method
309 	 * will result in {@link UnsupportedOperationException}.
310 	 */
311 	override void putAll(Map!(string, Object) map) {
312 		throw new UnsupportedOperationException("MessageHeaders is immutable");
313 	}
314 
315 	/**
316 	 * Since MessageHeaders are immutable, the call to this method
317 	 * will result in {@link UnsupportedOperationException}.
318 	 */
319 	override Object remove(string key) {
320 		throw new UnsupportedOperationException("MessageHeaders is immutable");
321 	}
322 
323 	/**
324 	 * Since MessageHeaders are immutable, the call to this method
325 	 * will result in {@link UnsupportedOperationException}.
326 	 */
327 	override void clear() {
328 		throw new UnsupportedOperationException("MessageHeaders is immutable");
329 	}
330 
331 
332 	// equals, toHash, toString
333 
334 	override
335 	bool opEquals(Object other) {
336 		if(this is other)
337 			return true;
338 
339 		MessageHeaders ot = cast(MessageHeaders)other;
340 		if(ot !is null && this.headers == ot.headers)
341 			return true;
342 		return false;
343 	}
344 
345 	override
346 	size_t toHash() @trusted nothrow {
347 		return this.headers.toHash();
348 	}
349 
350 	override
351 	string toString() {
352 		return this.headers.toString();
353 	}
354 
355 }