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}