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}