001/*
002 * Copyright 2002-2018 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;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.io.Serializable;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029import java.util.UUID;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033
034import org.springframework.lang.Nullable;
035import org.springframework.util.AlternativeJdkIdGenerator;
036import org.springframework.util.IdGenerator;
037
038/**
039 * The headers for a {@link Message}.
040 *
041 * <p><b>IMPORTANT</b>: This class is immutable. Any mutating operation such as
042 * {@code put(..)}, {@code putAll(..)} and others will throw
043 * {@link UnsupportedOperationException}.
044 * <p>Subclasses do have access to the raw headers, however, via {@link #getRawHeaders()}.
045 *
046 * <p>One way to create message headers is to use the
047 * {@link org.springframework.messaging.support.MessageBuilder MessageBuilder}:
048 * <pre class="code">
049 * MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2");
050 * </pre>
051 *
052 * A second option is to create {@link org.springframework.messaging.support.GenericMessage}
053 * passing a payload as {@link Object} and headers as a {@link Map java.util.Map}:
054 * <pre class="code">
055 * Map headers = new HashMap();
056 * headers.put("key1", "value1");
057 * headers.put("key2", "value2");
058 * new GenericMessage("foo", headers);
059 * </pre>
060 *
061 * A third option is to use {@link org.springframework.messaging.support.MessageHeaderAccessor}
062 * or one of its subclasses to create specific categories of headers.
063 *
064 * @author Arjen Poutsma
065 * @author Mark Fisher
066 * @author Gary Russell
067 * @author Juergen Hoeller
068 * @since 4.0
069 * @see org.springframework.messaging.support.MessageBuilder
070 * @see org.springframework.messaging.support.MessageHeaderAccessor
071 */
072public class MessageHeaders implements Map<String, Object>, Serializable {
073
074        /**
075         * UUID for none.
076         */
077        public static final UUID ID_VALUE_NONE = new UUID(0,0);
078
079        /**
080         * The key for the Message ID. This is an automatically generated UUID and
081         * should never be explicitly set in the header map <b>except</b> in the
082         * case of Message deserialization where the serialized Message's generated
083         * UUID is being restored.
084         */
085        public static final String ID = "id";
086
087        /**
088         * The key for the message timestamp.
089         */
090        public static final String TIMESTAMP = "timestamp";
091
092        /**
093         * The key for the message content type.
094         */
095        public static final String CONTENT_TYPE = "contentType";
096
097        /**
098         * The key for the message reply channel.
099         */
100        public static final String REPLY_CHANNEL = "replyChannel";
101
102        /**
103         * The key for the message error channel.
104         */
105        public static final String ERROR_CHANNEL = "errorChannel";
106
107
108        private static final long serialVersionUID = 7035068984263400920L;
109
110        private static final Log logger = LogFactory.getLog(MessageHeaders.class);
111
112        private static final IdGenerator defaultIdGenerator = new AlternativeJdkIdGenerator();
113
114        @Nullable
115        private static volatile IdGenerator idGenerator;
116
117
118        private final Map<String, Object> headers;
119
120
121        /**
122         * Construct a {@link MessageHeaders} with the given headers. An {@link #ID} and
123         * {@link #TIMESTAMP} headers will also be added, overriding any existing values.
124         * @param headers a map with headers to add
125         */
126        public MessageHeaders(@Nullable Map<String, Object> headers) {
127                this(headers, null, null);
128        }
129
130        /**
131         * Constructor providing control over the ID and TIMESTAMP header values.
132         * @param headers a map with headers to add
133         * @param id the {@link #ID} header value
134         * @param timestamp the {@link #TIMESTAMP} header value
135         */
136        protected MessageHeaders(@Nullable Map<String, Object> headers, @Nullable UUID id, @Nullable Long timestamp) {
137                this.headers = (headers != null ? new HashMap<>(headers) : new HashMap<>());
138
139                if (id == null) {
140                        this.headers.put(ID, getIdGenerator().generateId());
141                }
142                else if (id == ID_VALUE_NONE) {
143                        this.headers.remove(ID);
144                }
145                else {
146                        this.headers.put(ID, id);
147                }
148
149                if (timestamp == null) {
150                        this.headers.put(TIMESTAMP, System.currentTimeMillis());
151                }
152                else if (timestamp < 0) {
153                        this.headers.remove(TIMESTAMP);
154                }
155                else {
156                        this.headers.put(TIMESTAMP, timestamp);
157                }
158        }
159
160        /**
161         * Copy constructor which allows for ignoring certain entries.
162         * Used for serialization without non-serializable entries.
163         * @param original the MessageHeaders to copy
164         * @param keysToIgnore the keys of the entries to ignore
165         */
166        private MessageHeaders(MessageHeaders original, Set<String> keysToIgnore) {
167                this.headers = new HashMap<>(original.headers.size());
168                original.headers.forEach((key, value) -> {
169                        if (!keysToIgnore.contains(key)) {
170                                this.headers.put(key, value);
171                        }
172                });
173        }
174
175
176        protected Map<String, Object> getRawHeaders() {
177                return this.headers;
178        }
179
180        protected static IdGenerator getIdGenerator() {
181                IdGenerator generator = idGenerator;
182                return (generator != null ? generator : defaultIdGenerator);
183        }
184
185        @Nullable
186        public UUID getId() {
187                return get(ID, UUID.class);
188        }
189
190        @Nullable
191        public Long getTimestamp() {
192                return get(TIMESTAMP, Long.class);
193        }
194
195        @Nullable
196        public Object getReplyChannel() {
197                return get(REPLY_CHANNEL);
198        }
199
200        @Nullable
201        public Object getErrorChannel() {
202                return get(ERROR_CHANNEL);
203        }
204
205
206        @SuppressWarnings("unchecked")
207        @Nullable
208        public <T> T get(Object key, Class<T> type) {
209                Object value = this.headers.get(key);
210                if (value == null) {
211                        return null;
212                }
213                if (!type.isAssignableFrom(value.getClass())) {
214                        throw new IllegalArgumentException("Incorrect type specified for header '" +
215                                        key + "'. Expected [" + type + "] but actual type is [" + value.getClass() + "]");
216                }
217                return (T) value;
218        }
219
220
221        // Delegating Map implementation
222
223        @Override
224        public boolean containsKey(Object key) {
225                return this.headers.containsKey(key);
226        }
227
228        @Override
229        public boolean containsValue(Object value) {
230                return this.headers.containsValue(value);
231        }
232
233        @Override
234        public Set<Map.Entry<String, Object>> entrySet() {
235                return Collections.unmodifiableMap(this.headers).entrySet();
236        }
237
238        @Override
239        @Nullable
240        public Object get(Object key) {
241                return this.headers.get(key);
242        }
243
244        @Override
245        public boolean isEmpty() {
246                return this.headers.isEmpty();
247        }
248
249        @Override
250        public Set<String> keySet() {
251                return Collections.unmodifiableSet(this.headers.keySet());
252        }
253
254        @Override
255        public int size() {
256                return this.headers.size();
257        }
258
259        @Override
260        public Collection<Object> values() {
261                return Collections.unmodifiableCollection(this.headers.values());
262        }
263
264
265        // Unsupported Map operations
266
267        /**
268         * Since MessageHeaders are immutable, the call to this method
269         * will result in {@link UnsupportedOperationException}.
270         */
271        @Override
272        public Object put(String key, Object value) {
273                throw new UnsupportedOperationException("MessageHeaders is immutable");
274        }
275
276        /**
277         * Since MessageHeaders are immutable, the call to this method
278         * will result in {@link UnsupportedOperationException}.
279         */
280        @Override
281        public void putAll(Map<? extends String, ? extends Object> map) {
282                throw new UnsupportedOperationException("MessageHeaders is immutable");
283        }
284
285        /**
286         * Since MessageHeaders are immutable, the call to this method
287         * will result in {@link UnsupportedOperationException}.
288         */
289        @Override
290        public Object remove(Object key) {
291                throw new UnsupportedOperationException("MessageHeaders is immutable");
292        }
293
294        /**
295         * Since MessageHeaders are immutable, the call to this method
296         * will result in {@link UnsupportedOperationException}.
297         */
298        @Override
299        public void clear() {
300                throw new UnsupportedOperationException("MessageHeaders is immutable");
301        }
302
303
304        // Serialization methods
305
306        private void writeObject(ObjectOutputStream out) throws IOException {
307                Set<String> keysToIgnore = new HashSet<>();
308                this.headers.forEach((key, value) -> {
309                        if (!(value instanceof Serializable)) {
310                                keysToIgnore.add(key);
311                        }
312                });
313
314                if (keysToIgnore.isEmpty()) {
315                        // All entries are serializable -> serialize the regular MessageHeaders instance
316                        out.defaultWriteObject();
317                }
318                else {
319                        // Some non-serializable entries -> serialize a temporary MessageHeaders copy
320                        if (logger.isDebugEnabled()) {
321                                logger.debug("Ignoring non-serializable message headers: " + keysToIgnore);
322                        }
323                        out.writeObject(new MessageHeaders(this, keysToIgnore));
324                }
325        }
326
327        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
328                in.defaultReadObject();
329        }
330
331
332        // equals, hashCode, toString
333
334        @Override
335        public boolean equals(@Nullable Object other) {
336                return (this == other ||
337                                (other instanceof MessageHeaders && this.headers.equals(((MessageHeaders) other).headers)));
338        }
339
340        @Override
341        public int hashCode() {
342                return this.headers.hashCode();
343        }
344
345        @Override
346        public String toString() {
347                return this.headers.toString();
348        }
349
350}