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