1 /* 2 * Copyright 2002-2018 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 module hunt.stomp.MessageHeaders; 18 19 import hunt.stomp.IdGenerator; 20 21 // import hunt.io.ObjectInputStream; 22 // import hunt.io.ObjectOutputStream; 23 24 // import hunt.collection.Collection; 25 // import hunt.collection.Collections; 26 // import hunt.collection.HashMap; 27 // import hunt.collection.HashSet; 28 // import hunt.collection.Map; 29 // import hunt.collection.Set; 30 31 import hunt.collection; 32 import hunt.util.DateTime; 33 import hunt.logging; 34 import hunt.Long; 35 import hunt.Nullable; 36 import hunt.Exceptions; 37 import hunt.Object; 38 39 import std.uuid; 40 import std.range; 41 42 43 /** 44 * The headers for a {@link Message}. 45 * 46 * <p><b>IMPORTANT</b>: This class is immutable. Any mutating operation such as 47 * {@code put(..)}, {@code putAll(..)} and others will throw 48 * {@link UnsupportedOperationException}. 49 * <p>Subclasses do have access to the raw headers, however, via {@link #getRawHeaders()}. 50 * 51 * <p>One way to create message headers is to use the 52 * {@link hunt.stomp.support.MessageBuilder MessageBuilder}: 53 * <pre class="code"> 54 * MessageBuilder.withPayload("foo").setHeader("key1", "value1").setHeader("key2", "value2"); 55 * </pre> 56 * 57 * A second option is to create {@link hunt.stomp.support.GenericMessage} 58 * passing a payload as {@link Object} and headers as a {@link Map java.util.Map}: 59 * <pre class="code"> 60 * Map headers = new HashMap(); 61 * headers.put("key1", "value1"); 62 * headers.put("key2", "value2"); 63 * new GenericMessage("foo", headers); 64 * </pre> 65 * 66 * A third option is to use {@link hunt.stomp.support.MessageHeaderAccessor} 67 * or one of its subclasses to create specific categories of headers. 68 * 69 * @author Arjen Poutsma 70 * @author Mark Fisher 71 * @author Gary Russell 72 * @author Juergen Hoeller 73 * @since 4.0 74 * @see hunt.stomp.support.MessageBuilder 75 * @see hunt.stomp.support.MessageHeaderAccessor 76 */ 77 class MessageHeaders : AbstractMap!(string, Object) { 78 79 /** 80 * UUID for none. 81 */ 82 __gshared static UUID ID_VALUE_NONE; 83 84 /** 85 * The key for the Message ID. This is an automatically generated UUID and 86 * should never be explicitly set in the header map <b>except</b> in the 87 * case of Message deserialization where the serialized Message's generated 88 * UUID is being restored. 89 */ 90 enum string ID = "id"; 91 92 /** 93 * The key for the message timestamp. 94 */ 95 enum string TIMESTAMP = "timestamp"; 96 97 /** 98 * The key for the message content type. 99 */ 100 enum string CONTENT_TYPE = "contentType"; 101 102 /** 103 * The key for the message reply channel. 104 */ 105 enum string REPLY_CHANNEL = "replyChannel"; 106 107 /** 108 * The key for the message error channel. 109 */ 110 enum string ERROR_CHANNEL = "errorChannel"; 111 112 113 private __gshared static IdGenerator defaultIdGenerator; 114 115 116 private static IdGenerator idGenerator; 117 118 119 private Map!(string, Object) headers; 120 121 shared static this() { 122 ID_VALUE_NONE = UUID.init; 123 defaultIdGenerator = new class IdGenerator { 124 UUID generateId() { 125 return randomUUID(); 126 } 127 }; 128 // defaultIdGenerator = new AlternativeJdkIdGenerator(); 129 } 130 131 132 /** 133 * Construct a {@link MessageHeaders} with the given headers. An {@link #ID} and 134 * {@link #TIMESTAMP} headers will also be added, overriding any existing values. 135 * @param headers a map with headers to add 136 */ 137 this(Map!(string, Object) headers) { 138 this(headers, null, null); 139 } 140 141 this(Map!(string, Object) headers, UUID* id, long timestamp) { 142 this(headers, id, Long.valueOf(timestamp)); 143 } 144 145 /** 146 * Constructor providing control over the ID and TIMESTAMP header values. 147 * @param headers a map with headers to add 148 * @param id the {@link #ID} header value 149 * @param timestamp the {@link #TIMESTAMP} header value 150 */ 151 this(Map!(string, Object) headers, UUID* id, Long timestamp) { 152 this.headers = (headers !is null ? 153 new HashMap!(string, Object)(headers) 154 : new HashMap!(string, Object)()); 155 156 if (id is null) { 157 this.headers.put(ID, new Nullable!UUID(randomUUID())); 158 } 159 else if (*id == ID_VALUE_NONE) { 160 this.headers.remove(ID); 161 } 162 else { 163 this.headers.put(ID, new Nullable!UUID(*id)); 164 } 165 166 if (timestamp is null) { 167 timestamp = new Long(DateTime.currentTimeMillis()); 168 this.headers.put(TIMESTAMP, timestamp); 169 } 170 else if (timestamp < 0) { 171 this.headers.remove(TIMESTAMP); 172 } 173 else { 174 this.headers.put(TIMESTAMP, timestamp); 175 } 176 } 177 178 /** 179 * Copy constructor which allows for ignoring certain entries. 180 * Used for serialization without non-serializable entries. 181 * @param original the MessageHeaders to copy 182 * @param keysToIgnore the keys of the entries to ignore 183 */ 184 private this(MessageHeaders original, Set!(string) keysToIgnore) { 185 this.headers = new HashMap!(string, Object)(original.headers.size()); 186 foreach(string key, Object value; original.headers) { 187 if (!keysToIgnore.contains(key)) 188 this.headers.put(key, value); 189 } 190 } 191 192 193 protected Map!(string, Object) getRawHeaders() { 194 return this.headers; 195 } 196 197 static IdGenerator getIdGenerator() { 198 IdGenerator generator = idGenerator; 199 return (generator !is null ? generator : defaultIdGenerator); 200 } 201 202 203 UUID getId() { 204 return getAs!(UUID)(ID); 205 } 206 207 208 Long getTimestamp() { 209 return getAs!(Long)(TIMESTAMP); 210 } 211 212 213 Object getReplyChannel() { 214 return get(REPLY_CHANNEL); 215 } 216 217 218 Object getErrorChannel() { 219 return get(ERROR_CHANNEL); 220 } 221 222 223 T getAs(T=Object)(string key) { 224 Object value = this.headers.get(key); 225 if (value is null) { 226 // version(HUNT_DEBUG) warningf("header does not exist: %s", key); 227 return T.init; 228 } 229 // if (!type.isAssignableFrom(value.getClass())) { 230 // throw new IllegalArgumentException("Incorrect type specified for header '" ~ 231 // key ~ "'. Expected [" ~ type ~ "] but actual type is [" ~ value.getClass() ~ "]"); 232 // } 233 // static if(is(T == Nullable!T)) { 234 // Nullable!T o = cast(Nullable!T)value; 235 // return o.value; 236 // } else { 237 // return cast(T) value; 238 // } 239 Nullable!T o = cast(Nullable!T)value; 240 if(o is null) { 241 static if(is(T == class)) { 242 version(HUNT_DEBUG) tracef("header[%s] = %s", key, value); 243 return cast(T) value; 244 } else { 245 assert(false, "wrong type"); 246 } 247 } else { 248 version(HUNT_DEBUG) tracef("header[%s] = %s", key, o.value); 249 return o.value; 250 } 251 } 252 253 254 // Delegating Map implementation 255 256 override bool containsKey(string key) { 257 return this.headers.containsKey(key); 258 } 259 260 override bool containsValue(Object value) { 261 return this.headers.containsValue(value); 262 } 263 264 // Set<Map.Entry!(string, Object)> entrySet() { 265 // return Collections.unmodifiableMap(this.headers).entrySet(); 266 // } 267 268 269 override Object get(string key) { 270 return this.headers.get(key); 271 } 272 273 override bool isEmpty() { 274 return this.headers.isEmpty(); 275 } 276 277 override int size() { 278 return this.headers.size(); 279 } 280 281 override int opApply(scope int delegate(ref string, ref Object) dg) { 282 return this.headers.opApply(dg); 283 } 284 285 override int opApply(scope int delegate(MapEntry!(string, Object) entry) dg) { 286 return this.headers.opApply(dg); 287 } 288 289 override InputRange!string byKey() { 290 return this.headers.byKey(); 291 } 292 293 override InputRange!Object byValue() { 294 return this.headers.byValue(); 295 } 296 297 // Unsupported Map operations 298 299 /** 300 * Since MessageHeaders are immutable, the call to this method 301 * will result in {@link UnsupportedOperationException}. 302 */ 303 override Object put(string key, Object value) { 304 throw new UnsupportedOperationException("MessageHeaders is immutable"); 305 } 306 307 /** 308 * Since MessageHeaders are immutable, the call to this method 309 * will result in {@link UnsupportedOperationException}. 310 */ 311 override void putAll(Map!(string, Object) map) { 312 throw new UnsupportedOperationException("MessageHeaders is immutable"); 313 } 314 315 /** 316 * Since MessageHeaders are immutable, the call to this method 317 * will result in {@link UnsupportedOperationException}. 318 */ 319 override Object remove(string key) { 320 throw new UnsupportedOperationException("MessageHeaders is immutable"); 321 } 322 323 /** 324 * Since MessageHeaders are immutable, the call to this method 325 * will result in {@link UnsupportedOperationException}. 326 */ 327 override void clear() { 328 throw new UnsupportedOperationException("MessageHeaders is immutable"); 329 } 330 331 332 // equals, toHash, toString 333 334 override 335 bool opEquals(Object other) { 336 if(this is other) 337 return true; 338 339 MessageHeaders ot = cast(MessageHeaders)other; 340 if(ot !is null && this.headers == ot.headers) 341 return true; 342 return false; 343 } 344 345 override 346 size_t toHash() @trusted nothrow { 347 return this.headers.toHash(); 348 } 349 350 override 351 string toString() { 352 return this.headers.toString(); 353 } 354 355 }