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