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.support.MessageHeaderAccessor;
18 
19 import hunt.stomp.IdGenerator;
20 import hunt.stomp.Message;
21 import hunt.stomp.MessageChannel;
22 import hunt.stomp.MessageHeaders;
23 
24 import hunt.collection;
25 import hunt.Exceptions;
26 import hunt.Nullable;
27 import hunt.Long;
28 import hunt.Object;
29 import hunt.text.Charset;
30 import hunt.text.PatternMatchUtils;
31 import hunt.util.DateTime;
32 import hunt.util.MimeType;
33 import hunt.util.ObjectUtils;
34 
35 import std.algorithm;
36 import std.conv;
37 import std.range.primitives;
38 import std.string;
39 import std.uuid;
40 
41 
42 /**
43  * Callback interface for initializing a {@link MessageHeaderAccessor}.
44  *
45  * @author Rossen Stoyanchev
46  * @since 4.1
47  */
48 interface MessageHeaderInitializer {
49 
50 	/**
51 	 * Initialize the given {@code MessageHeaderAccessor}.
52 	 * @param headerAccessor the MessageHeaderAccessor to initialize
53 	 */
54 	void initHeaders(MessageHeaderAccessor headerAccessor);
55 
56 }
57 
58 alias Charset = string;
59 
60 /**
61  * A base for classes providing strongly typed getters and setters as well as
62  * behavior around specific categories of headers (e.g. STOMP headers).
63  * Supports creating new headers, modifying existing headers (when still mutable),
64  * or copying and modifying existing headers.
65  *
66  * <p>The method {@link #getMessageHeaders()} provides access to the underlying,
67  * fully-prepared {@link MessageHeaders} that can then be used as-is (i.e.
68  * without copying) to create a single message as follows:
69  *
70  * <pre class="code">
71  * MessageHeaderAccessor accessor = new MessageHeaderAccessor();
72  * accessor.setHeader("foo", "bar");
73  * Message message = MessageHelper.createMessage("payload", accessor.getMessageHeaders());
74  * </pre>
75  *
76  * <p>After the above, by default the {@code MessageHeaderAccessor} becomes
77  * immutable. However it is possible to leave it mutable for further initialization
78  * in the same thread, for example:
79  *
80  * <pre class="code">
81  * MessageHeaderAccessor accessor = new MessageHeaderAccessor();
82  * accessor.setHeader("foo", "bar");
83  * accessor.setLeaveMutable(true);
84  * Message message = MessageHelper.createMessage("payload", accessor.getMessageHeaders());
85  *
86  * // later on in the same thread...
87  *
88  * MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message);
89  * accessor.setHeader("bar", "baz");
90  * accessor.setImmutable();
91  * </pre>
92  *
93  * <p>The method {@link #toMap()} returns a copy of the underlying headers. It can
94  * be used to prepare multiple messages from the same {@code MessageHeaderAccessor}
95  * instance:
96  * <pre class="code">
97  * MessageHeaderAccessor accessor = new MessageHeaderAccessor();
98  * MessageBuilder builder = MessageBuilder.withPayload("payload").setHeaders(accessor);
99  *
100  * accessor.setHeader("foo", "bar1");
101  * Message message1 = builder.build();
102  *
103  * accessor.setHeader("foo", "bar2");
104  * Message message2 = builder.build();
105  *
106  * accessor.setHeader("foo", "bar3");
107  * Message  message3 = builder.build();
108  * </pre>
109  *
110  * <p>However note that with the above style, the header accessor is shared and
111  * cannot be re-obtained later on. Alternatively it is also possible to create
112  * one {@code MessageHeaderAccessor} per message:
113  *
114  * <pre class="code">
115  * MessageHeaderAccessor accessor1 = new MessageHeaderAccessor();
116  * accessor.set("foo", "bar1");
117  * Message message1 = MessageHelper.createMessage("payload", accessor1.getMessageHeaders());
118  *
119  * MessageHeaderAccessor accessor2 = new MessageHeaderAccessor();
120  * accessor.set("foo", "bar2");
121  * Message message2 = MessageHelper.createMessage("payload", accessor2.getMessageHeaders());
122  *
123  * MessageHeaderAccessor accessor3 = new MessageHeaderAccessor();
124  * accessor.set("foo", "bar3");
125  * Message message3 = MessageHelper.createMessage("payload", accessor3.getMessageHeaders());
126  * </pre>
127  *
128  * <p>Note that the above examples aim to demonstrate the general idea of using
129  * header accessors. The most likely usage however is through subclasses.
130  *
131  * @author Rossen Stoyanchev
132  * @author Juergen Hoeller
133  * @since 4.0
134  */
135 class MessageHeaderAccessor {
136 
137 	/**
138 	 * The default charset used for headers.
139 	 */
140 	enum Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
141 
142 	private __gshared MimeType[] READABLE_MIME_TYPES;
143 
144 	private MutableMessageHeaders headers;
145 
146 	private bool leaveMutable = false;
147 
148 	private bool modified = false;
149 
150 	private bool enableTimestamp = false;
151 
152 	private IdGenerator idGenerator;
153 
154 	shared static this() {
155 		READABLE_MIME_TYPES = [
156 			MimeType.APPLICATION_JSON, MimeType.APPLICATION_XML,
157 			new MimeType("text", new MimeType("*")), 
158 			new MimeType("application", new MimeType("*+json")), 
159 			new MimeType("application", new MimeType("*+xml"))
160 		];
161 	}
162 
163 
164 	/**
165 	 * A constructor to create new headers.
166 	 */
167 	this() {
168 		this(null);
169 	}
170 
171 	/**
172 	 * A constructor accepting the headers of an existing message to copy.
173 	 * @param message a message to copy the headers from, or {@code null} if none
174 	 */
175 	this(MessageBase message) {
176 		this.headers = new MutableMessageHeaders(message !is null ? message.getHeaders() : null);
177 	}
178 
179 
180 	/**
181 	 * Build a 'nested' accessor for the given message.
182 	 * @param message the message to build a new accessor for
183 	 * @return the nested accessor (typically a specific subclass)
184 	 */
185 	protected MessageHeaderAccessor createAccessor(MessageBase message) {
186 		return new MessageHeaderAccessor(message);
187 	}
188 
189 
190 	// Configuration properties
191 
192 	/**
193 	 * By default when {@link #getMessageHeaders()} is called, {@code "this"}
194 	 * {@code MessageHeaderAccessor} instance can no longer be used to modify the
195 	 * underlying message headers and the returned {@code MessageHeaders} is immutable.
196 	 * <p>However when this is set to {@code true}, the returned (underlying)
197 	 * {@code MessageHeaders} instance remains mutable. To make further modifications
198 	 * continue to use the same accessor instance or re-obtain it via:<br>
199 	 * {@link MessageHeaderAccessor#getAccessor(Message, Class)
200 	 * MessageHeaderAccessor.getAccessor(Message, Class)}
201 	 * <p>When modifications are complete use {@link #setImmutable()} to prevent
202 	 * further changes. The intended use case for this mechanism is initialization
203 	 * of a Message within a single thread.
204 	 * <p>By default this is set to {@code false}.
205 	 * @since 4.1
206 	 */
207 	void setLeaveMutable(bool leaveMutable) {
208 		assert(this.headers.isMutable(), "Already immutable");
209 		this.leaveMutable = leaveMutable;
210 	}
211 
212 	/**
213 	 * By default when {@link #getMessageHeaders()} is called, {@code "this"}
214 	 * {@code MessageHeaderAccessor} instance can no longer be used to modify the
215 	 * underlying message headers. However if {@link #setLeaveMutable()}
216 	 * is used, this method is necessary to indicate explicitly when the
217 	 * {@code MessageHeaders} instance should no longer be modified.
218 	 * @since 4.1
219 	 */
220 	void setImmutable() {
221 		this.headers.setImmutable();
222 	}
223 
224 	/**
225 	 * Whether the underlying headers can still be modified.
226 	 * @since 4.1
227 	 */
228 	bool isMutable() {
229 		return this.headers.isMutable();
230 	}
231 
232 	/**
233 	 * Mark the underlying message headers as modified.
234 	 * @param modified typically {@code true}, or {@code false} to reset the flag
235 	 * @since 4.1
236 	 */
237 	protected void setModified(bool modified) {
238 		this.modified = modified;
239 	}
240 
241 	/**
242 	 * Check whether the underlying message headers have been marked as modified.
243 	 * @return {@code true} if the flag has been set, {@code false} otherwise
244 	 */
245 	bool isModified() {
246 		return this.modified;
247 	}
248 
249 	/**
250 	 * A package private mechanism to enables the automatic addition of the
251 	 * {@link hunt.stomp.MessageHeaders#TIMESTAMP} header.
252 	 * <p>By default, this property is set to {@code false}.
253 	 * @see IdTimestampMessageHeaderInitializer
254 	 */
255 	void setEnableTimestamp(bool enableTimestamp) {
256 		this.enableTimestamp = enableTimestamp;
257 	}
258 
259 	/**
260 	 * A package-private mechanism to configure the IdGenerator strategy to use.
261 	 * <p>By default this property is not set in which case the default IdGenerator
262 	 * in {@link hunt.stomp.MessageHeaders} is used.
263 	 * @see IdTimestampMessageHeaderInitializer
264 	 */
265 	void setIdGenerator(IdGenerator idGenerator) {
266 		this.idGenerator = idGenerator;
267 	}
268 
269 
270 	// Accessors for the resulting MessageHeaders
271 
272 	/**
273 	 * Return the underlying {@code MessageHeaders} instance.
274 	 * <p>Unless {@link #setLeaveMutable()} was set to {@code true}, after
275 	 * this call, the headers are immutable and this accessor can no longer
276 	 * modify them.
277 	 * <p>This method always returns the same {@code MessageHeaders} instance if
278 	 * invoked multiples times. To obtain a copy of the underlying headers, use
279 	 * {@link #toMessageHeaders()} or {@link #toMap()} instead.
280 	 * @since 4.1
281 	 */
282 	MessageHeaders getMessageHeaders() {
283 		if (!this.leaveMutable) {
284 			setImmutable();
285 		}
286 		return this.headers;
287 	}
288 
289 	/**
290 	 * Return a copy of the underlying header values as a {@link MessageHeaders} object.
291 	 * <p>This method can be invoked many times, with modifications in between
292 	 * where each new call returns a fresh copy of the current header values.
293 	 * @since 4.1
294 	 */
295 	MessageHeaders toMessageHeaders() {
296 		return new MessageHeaders(this.headers);
297 	}
298 
299 	/**
300 	 * Return a copy of the underlying header values as a plain {@link Map} object.
301 	 * <p>This method can be invoked many times, with modifications in between
302 	 * where each new call returns a fresh copy of the current header values.
303 	 */
304 	Map!(string, Object) toMap() {
305 		return new HashMap!(string, Object)(this.headers);
306 	}
307 
308 
309 	// Generic header accessors
310 
311 	/**
312 	 * Retrieve the value for the header with the given name.
313 	 * @param headerName the name of the header
314 	 * @return the associated value, or {@code null} if none found
315 	 */
316 	
317 	Object getHeader(string headerName) {
318 		return this.headers.get(headerName);
319 	}
320 
321 	T getHeaderAs(T)(string headerName) {
322 		return this.headers.getAs!(T)(headerName);
323 	}
324 
325 	void setHeader(T)(string name, T value) if(!is(T == class)) {
326 		setHeader(name, new Nullable!T(value));
327 	}
328 
329 	/**
330 	 * Set the value for the given header name.
331 	 * <p>If the provided value is {@code null}, the header will be removed.
332 	 */
333 	void setHeader(string name, Object value) {
334 		if (isReadOnly(name)) {
335 			throw new IllegalArgumentException("'" ~ name ~ "' header is read-only");
336 		}
337 		verifyType(name, value);
338 		if (value !is null) {
339 			// Modify header if necessary
340 			if (!ObjectUtils.nullSafeEquals(value, getHeader(name))) {
341 				this.modified = true;
342 				this.headers.getRawHeaders().put(name, value);
343 			}
344 		}
345 		else {
346 			// Remove header if available
347 			if (this.headers.containsKey(name)) {
348 				this.modified = true;
349 				this.headers.getRawHeaders().remove(name);
350 			}
351 		}
352 	}
353 
354 	protected void verifyType(string headerName, Object headerValue) {
355 		if (!headerName.empty && headerValue !is null) {
356 			if (MessageHeaders.ERROR_CHANNEL == headerName ||
357 					MessageHeaders.REPLY_CHANNEL.endsWith(headerName)) {
358 				MessageChannel mc = cast(MessageChannel)headerValue;
359 				Nullable!string str = cast(Nullable!string)headerValue;
360 				
361 				if (mc is null && str is null) {
362 					throw new IllegalArgumentException(
363 							"'" ~ headerName ~ "' header value must be a MessageChannel or string");
364 				}
365 			}
366 		}
367 	}
368 
369 	/**
370 	 * Set the value for the given header name only if the header name is not
371 	 * already associated with a value.
372 	 */
373 	void setHeaderIfAbsent(string name, Object value) {
374 		if (getHeader(name) is null) {
375 			setHeader(name, value);
376 		}
377 	}
378 
379 	/**
380 	 * Remove the value for the given header name.
381 	 */
382 	void removeHeader(string headerName) {
383 		if (!headerName.empty && !isReadOnly(headerName)) {
384 			setHeader(headerName, null);
385 		}
386 	}
387 
388 	/**
389 	 * Removes all headers provided via array of 'headerPatterns'.
390 	 * <p>As the name suggests, array may contain simple matching patterns for header
391 	 * names. Supported pattern styles are: "xxx*", "*xxx", "*xxx*" and "xxx*yyy".
392 	 */
393 	void removeHeaders(string[] headerPatterns...) {
394 		List!(string) headersToRemove = new ArrayList!string();
395 		foreach (string pattern ; headerPatterns) {
396 			if (!pattern.empty()){
397 				if (pattern.canFind("*")){
398 					headersToRemove.addAll(getMatchingHeaderNames(pattern, this.headers));
399 				}
400 				else {
401 					headersToRemove.add(pattern);
402 				}
403 			}
404 		}
405 		foreach (string headerToRemove ; headersToRemove) {
406 			removeHeader(headerToRemove);
407 		}
408 	}
409 
410 	private List!(string) getMatchingHeaderNames(string pattern, Map!(string, Object) headers) {
411 		List!(string) matchingHeaderNames = new ArrayList!string();
412 		if (headers !is null) {
413 			foreach (string key ; headers.byKey) {
414 				if (PatternMatchUtils.simpleMatch(pattern, key)) {
415 					matchingHeaderNames.add(key);
416 				}
417 			}
418 		}
419 		return matchingHeaderNames;
420 	}
421 
422 	/**
423 	 * Copy the name-value pairs from the provided Map.
424 	 * <p>This operation will overwrite any existing values. Use
425 	 * {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values.
426 	 */
427 	void copyHeaders(Map!(string, Object) headersToCopy) {
428 		if (headersToCopy !is null) {
429 			foreach(string key, Object value; headersToCopy) {
430 				if (!isReadOnly(key)) setHeader(key, value);
431 			}
432 		}
433 	}
434 
435 	/**
436 	 * Copy the name-value pairs from the provided Map.
437 	 * <p>This operation will <em>not</em> overwrite any existing values.
438 	 */
439 	void copyHeadersIfAbsent(Map!(string, Object) headersToCopy) {
440 		if (headersToCopy !is null) {
441 			foreach(string key, Object value; headersToCopy) {
442 				if (!isReadOnly(key)) setHeaderIfAbsent(key, value);
443 			}
444 		}
445 	}
446 
447 	protected bool isReadOnly(string headerName) {
448 		return (MessageHeaders.ID == (headerName) || MessageHeaders.TIMESTAMP == (headerName));
449 	}
450 
451 
452 	// Specific header accessors
453 	
454 	UUID getId() {
455 		Object value = getHeader(MessageHeaders.ID);
456 		if (value is null) {
457 			return UUID.init;
458 		}
459 
460 		auto uu = cast(Nullable!UUID)value;
461 		if(uu is null) {
462 			auto us = cast(Nullable!string)value;
463 			if(us is null)
464 				throw new Exception("bad type");
465 			else 
466 				return UUID(us.value);
467 		} else {
468 			return uu.value;
469 		}
470 
471 		// return (value instanceof UUID ? (UUID) value : UUID.fromString(value.toString()));
472 	}
473 
474 	
475 	Long getTimestamp() {
476 		Object value = getHeader(MessageHeaders.TIMESTAMP);
477 		if (value is null) {
478 			return null;
479 		}
480 
481 		auto vl = cast(Long)value;
482 		if(vl is null) {
483 			auto vs = cast(Nullable!string)value;
484 			if(vs is null)
485 				throw new Exception("bad type");
486 			else 
487 				return new Long(vs.value);
488 		} else {
489 			return vl;
490 		}
491 
492 		// return (value instanceof Long ? (Long) value : Long.parseLong(value.toString()));
493 	}
494 
495 	void setContentType(MimeType contentType) {
496 		setHeader(MessageHeaders.CONTENT_TYPE, contentType);
497 	}
498 
499 	
500 	MimeType getContentType() {
501 		Object value = getHeader(MessageHeaders.CONTENT_TYPE);
502 		if (value is null) {
503 			return null;
504 		}
505 		MimeType mt = cast(MimeType) value;
506 		if(mt !is null)
507 			return mt;
508 
509 		auto vs = cast(Nullable!string)value;
510 		if(vs is null)
511 			throw new Exception("bad type");
512 		else 
513 			return new MimeType(vs.value);
514 		// return (value instanceof MimeType ? (MimeType) value : MimeType.valueOf(value.toString()));
515 	}
516 
517 	private Charset getCharset() {
518 		MimeType contentType = getContentType();
519 		Charset charset = (contentType !is null ? contentType.getCharsetString() : null);
520 		return (charset !is null ? charset : DEFAULT_CHARSET);
521 	}
522 
523 	void setReplyChannelName(string replyChannelName) {
524 		setHeader(MessageHeaders.REPLY_CHANNEL, new Nullable!string(replyChannelName));
525 	}
526 
527 	void setReplyChannel(MessageChannel replyChannel) {
528 		setHeader(MessageHeaders.REPLY_CHANNEL, cast(Object)replyChannel);
529 	}
530 
531 	
532 	Object getReplyChannel() {
533 		return getHeader(MessageHeaders.REPLY_CHANNEL);
534 	}
535 
536 	void setErrorChannelName(string errorChannelName) {
537 		setHeader(MessageHeaders.ERROR_CHANNEL, new Nullable!string(errorChannelName));
538 	}
539 
540 	void setErrorChannel(MessageChannel errorChannel) {
541 		setHeader(MessageHeaders.ERROR_CHANNEL, cast(Object)errorChannel);
542 	}
543 
544 	
545 	Object getErrorChannel() {
546 		return getHeader(MessageHeaders.ERROR_CHANNEL);
547 	}
548 
549 
550 	// Log message stuff
551 
552 	/**
553 	 * Return a concise message for logging purposes.
554 	 * @param payload the payload that corresponds to the headers.
555 	 * @return the message
556 	 */
557 	string getShortLogMessage(Object payload) {
558 		return "headers=" ~ this.headers.toString() ~ getShortPayloadLogMessage(payload);
559 	}
560 
561 	/**
562 	 * Return a more detailed message for logging purposes.
563 	 * @param payload the payload that corresponds to the headers.
564 	 * @return the message
565 	 */
566 	string getDetailedLogMessage(Object payload) {
567 		return "headers=" ~ this.headers.toString() ~ getDetailedPayloadLogMessage(payload);
568 	}
569 
570 	protected string getShortPayloadLogMessage(Object payload) {
571 		auto textPayload = cast(Nullable!string)payload;
572 		if (textPayload !is null) {
573 			string payloadText = textPayload.value;
574 			return (payloadText.length < 80) ?
575 				" payload=" ~ payloadText :
576 				" payload=" ~ payloadText[0 ..80] ~ "...(truncated)";
577 		}
578 		
579 		auto bytesPayload = cast(Nullable!(byte[]))payload;
580 		if (bytesPayload !is null) {
581 			byte[] bytes = bytesPayload.value;
582 			if (isReadableContentType()) {
583 				return (bytes.length < 80) ?
584 						" payload=" ~ cast(string)(bytes) :
585 						" payload=" ~ cast(string)(bytes[0..80]) ~ "...(truncated)";
586 			} else {
587 				return " payload=byte[" ~ to!string(bytes.length) ~ "]";
588 			}
589 		}
590 		else {
591 			string payloadText = payload.toString();
592 			return (payloadText.length < 80) ?
593 					" payload=" ~ payloadText :
594 					" payload=" ~ ObjectUtils.identityToString(payload);
595 		}
596 	}
597 
598 	protected string getDetailedPayloadLogMessage(Object payload) {
599 		auto textPayload = cast(Nullable!string)payload;
600 		if (textPayload !is null) {
601 			return " payload=" ~ textPayload.value;
602 		}
603 		
604 		auto bytesPayload = cast(Nullable!(byte[]))payload;
605 		if (bytesPayload !is null) {
606 			byte[] bytes = bytesPayload.value;
607 			if (isReadableContentType()) {
608 				return " payload=" ~ cast(string)(bytes);
609 			} else {
610 				return " payload=byte[" ~ to!string(bytes.length) ~ "]";
611 			}
612 		}
613 		else {
614 			return " payload=" ~ payload.toString();
615 		}
616 	}
617 
618 	protected bool isReadableContentType() {
619 		MimeType contentType = getContentType();
620 		// implementationMissing(false);
621 		// TODO: Tasks pending completion -@zxp at 11/13/2018, 3:19:07 PM
622 		// 
623 		foreach (MimeType mimeType ; READABLE_MIME_TYPES) {
624 			// if (mimeType.includes(contentType)) {
625 			// 	return true;
626 			// }
627 		}
628 		return false;
629 	}
630 
631 	override
632 	string toString() {
633 		return typeid(this).name ~ " [headers=" ~ this.headers.toString() ~ "]";
634 	}
635 
636 
637 	// Static factory methods
638 
639 	/**
640 	 * Return the original {@code MessageHeaderAccessor} used to create the headers
641 	 * of the given {@code Message}, or {@code null} if that's not available or if
642 	 * its type does not match the required type.
643 	 * <p>This is for cases where the existence of an accessor is strongly expected
644 	 * (followed up with an assertion) or where an accessor will be created otherwise.
645 	 * @param message the message to get an accessor for
646 	 * @param requiredType the required accessor type (or {@code null} for any)
647 	 * @return an accessor instance of the specified type, or {@code null} if none
648 	 * @since 4.1
649 	 */
650 	
651 	static T getAccessor(T)(MessageBase message) {
652 		return getAccessor!T(message.getHeaders());
653 	}
654 
655 	/**
656 	 * A variation of {@link #getAccessor(hunt.stomp.Message, Class)}
657 	 * with a {@code MessageHeaders} instance instead of a {@code Message}.
658 	 * <p>This is for cases when a full message may not have been created yet.
659 	 * @param messageHeaders the message headers to get an accessor for
660 	 * @param requiredType the required accessor type (or {@code null} for any)
661 	 * @return an accessor instance of the specified type, or {@code null} if none
662 	 * @since 4.1
663 	 */
664 	
665 	
666 	static T getAccessor(T)(MessageHeaders messageHeaders) {
667 		MutableMessageHeaders mutableHeaders = cast(MutableMessageHeaders) messageHeaders;
668 
669 		if (mutableHeaders !is null) {
670 			MessageHeaderAccessor headerAccessor = mutableHeaders.getAccessor();
671 			if (typeid(T) == typeid(headerAccessor))  {
672 				return cast(T) headerAccessor;
673 			}
674 		}
675 		return null;
676 	}
677 
678 	/**
679 	 * Return a mutable {@code MessageHeaderAccessor} for the given message attempting
680 	 * to match the type of accessor used to create the message headers, or otherwise
681 	 * wrapping the message with a {@code MessageHeaderAccessor} instance.
682 	 * <p>This is for cases where a header needs to be updated in generic code
683 	 * while preserving the accessor type for downstream processing.
684 	 * @return an accessor of the required type (never {@code null})
685 	 * @since 4.1
686 	 */
687 	static MessageHeaderAccessor getMutableAccessor(MessageBase message) {
688 			MutableMessageHeaders mutableHeaders = cast(MutableMessageHeaders) message.getHeaders();
689 		if (mutableHeaders !is null) {
690 			MessageHeaderAccessor accessor = mutableHeaders.getAccessor();
691 			return (accessor.isMutable() ? accessor : accessor.createAccessor(message));
692 		}
693 		return new MessageHeaderAccessor(message);
694 	}
695 
696 
697 	
698 	private class MutableMessageHeaders : MessageHeaders {
699 
700 		private bool mutable = true;
701 
702 		this(Map!(string, Object) headers) {
703 			super(headers, &MessageHeaders.ID_VALUE_NONE, (-1L));
704 		}
705 
706 		override
707 		Map!(string, Object) getRawHeaders() {
708 			assert(this.mutable, "Already immutable");
709 			return super.getRawHeaders();
710 		}
711 
712 		void setImmutable() {
713 			if (!this.mutable) {
714 				return;
715 			}
716 
717 			if (getId().empty) {
718 				// UUID id = randomUUID();
719 
720 				IdGenerator idGenerator = this.outer.idGenerator;
721 				if(idGenerator is null)
722 					idGenerator = MessageHeaders.getIdGenerator();
723 				UUID id = idGenerator.generateId();
724 				if (id != MessageHeaders.ID_VALUE_NONE) {
725 					getRawHeaders().put(ID, new Nullable!UUID(id));
726 				}
727 			}
728 
729 			if (getTimestamp() is null) {
730 				if (this.outer.enableTimestamp) {
731 					Long timestamp = new Long(DateTime.currentTimeMillis());
732 					getRawHeaders().put(TIMESTAMP, timestamp);
733 				}
734 			}
735 
736 			this.mutable = false;
737 		}
738 
739 		bool isMutable() {
740 			return this.mutable;
741 		}
742 
743 		MessageHeaderAccessor getAccessor() {
744 			return this.outer;
745 		}
746 
747 		protected Object writeReplace() {
748 			// Serialize as regular MessageHeaders (without MessageHeaderAccessor reference)
749 			return new MessageHeaders(this);
750 		}
751 	}
752 
753 }