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