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.simp.stomp.StompHeaders; 18 19 import hunt.collection; 20 import hunt.Exceptions; 21 import hunt.Long; 22 import hunt.Object; 23 import hunt.text.Common; 24 import hunt.text.StringUtils; 25 import hunt.util.AcceptMimeType; 26 import hunt.util.MimeType; 27 import hunt.util.MimeTypeUtils; 28 import hunt.util.ObjectUtils; 29 30 import std.conv; 31 import std.range; 32 33 /** 34 * Represents STOMP frame headers. 35 * 36 * <p>In addition to the normal methods defined by {@link Map}, this class offers 37 * the following convenience methods: 38 * <ul> 39 * <li>{@link #getFirst(string)} return the first value for a header name</li> 40 * <li>{@link #add(string, string)} add to the list of values for a header name</li> 41 * <li>{@link #set(string, string)} set a header name to a single string value</li> 42 * </ul> 43 * 44 * @author Rossen Stoyanchev 45 * @since 4.2 46 * @see <a href="http://stomp.github.io/stomp-specification-1.2.html#Frames_and_Headers"> 47 * http://stomp.github.io/stomp-specification-1.2.html#Frames_and_Headers</a> 48 */ 49 class StompHeaders : MultiValueMap!(string, string) { 50 51 // Standard headers (as defined in the spec) 52 53 enum string CONTENT_TYPE = "content-type"; // SEND, MESSAGE, ERROR 54 55 enum string CONTENT_LENGTH = "content-length"; // SEND, MESSAGE, ERROR 56 57 enum string RECEIPT = "receipt"; // any client frame other than CONNECT 58 59 // CONNECT 60 61 enum string HOST = "host"; 62 63 enum string ACCEPT_VERSION = "accept-version"; 64 65 enum string LOGIN = "login"; 66 67 enum string PASSCODE = "passcode"; 68 69 enum string HEARTBEAT = "heart-beat"; 70 71 // CONNECTED 72 73 enum string SESSION = "session"; 74 75 enum string SERVER = "server"; 76 77 // SEND 78 79 enum string DESTINATION = "destination"; 80 81 // SUBSCRIBE, UNSUBSCRIBE 82 83 enum string ID = "id"; 84 85 enum string ACK = "ack"; 86 87 // MESSAGE 88 89 enum string SUBSCRIPTION = "subscription"; 90 91 enum string MESSAGE_ID = "message-id"; 92 93 // RECEIPT 94 95 enum string RECEIPT_ID = "receipt-id"; 96 97 98 private MultiStringsMap headers; 99 100 101 /** 102 * Create a new instance to be populated with new header values. 103 */ 104 this() { 105 this(new LinkedMultiValueMap!(string, string)(4), false); 106 } 107 108 private this(MultiStringsMap headers, bool readOnly) { 109 assert(headers, "'headers' must not be null"); 110 if (readOnly) { 111 MultiStringsMap map = new LinkedMultiValueMap!(string, string)(headers.size()); 112 foreach(string key, List!(string) value; map) 113 map.put(key, value); 114 // map.put(key, Collections.unmodifiableList(value)); 115 this.headers = map; // Collections.unmodifiableMap(map); 116 } 117 else { 118 this.headers = headers; 119 } 120 } 121 122 123 /** 124 * Set the content-type header. 125 * Applies to the SEND, MESSAGE, and ERROR frames. 126 */ 127 void setContentType(MimeType mimeType) { 128 if (mimeType !is null) { 129 // assert(!mimeType.isWildcardType(), "'Content-Type' cannot contain wildcard type '*'"); 130 // assert(!mimeType.isWildcardSubtype(), "'Content-Type' cannot contain wildcard subtype '*'"); 131 set(CONTENT_TYPE, mimeType.toString()); 132 } 133 else { 134 set(CONTENT_TYPE, null); 135 } 136 } 137 138 /** 139 * Return the content-type header value. 140 */ 141 142 AcceptMimeType[] getContentType() { 143 string value = getFirst(CONTENT_TYPE); 144 // AcceptMimeType[] list = MimeTypes.parseAcceptMIMETypes(value); 145 // return value.empty ? null : MimeTypeUtils.parseMimeType(value); 146 return value.empty ? null : MimeTypeUtils.parseAcceptMIMETypes(value); 147 } 148 149 /** 150 * Set the content-length header. 151 * Applies to the SEND, MESSAGE, and ERROR frames. 152 */ 153 void setContentLength(long contentLength) { 154 set(CONTENT_LENGTH, to!string(contentLength)); 155 } 156 157 /** 158 * Return the content-length header or -1 if unknown. 159 */ 160 long getContentLength() { 161 string value = getFirst(CONTENT_LENGTH); 162 return (value !is null ? Long.parseLong(value) : -1); 163 } 164 165 /** 166 * Set the receipt header. 167 * Applies to any client frame other than CONNECT. 168 */ 169 void setReceipt(string receipt) { 170 set(RECEIPT, receipt); 171 } 172 173 /** 174 * Get the receipt header. 175 */ 176 177 string getReceipt() { 178 return getFirst(RECEIPT); 179 } 180 181 /** 182 * Set the host header. 183 * Applies to the CONNECT frame. 184 */ 185 void setHost(string host) { 186 set(HOST, host); 187 } 188 189 /** 190 * Get the host header. 191 */ 192 193 string getHost() { 194 return getFirst(HOST); 195 } 196 197 /** 198 * Set the accept-version header. Must be one of "1.1", "1.2", or both. 199 * Applies to the CONNECT frame. 200 * @since 5.0.7 201 */ 202 void setAcceptVersion(string[] acceptVersions...) { 203 if (acceptVersions.length == 0) { 204 set(ACCEPT_VERSION, null); 205 return; 206 } 207 foreach(string ver; acceptVersions) { 208 assert(ver !is null && (ver == ("1.1") || ver == ("1.2")), 209 "Invalid version: " ~ ver); 210 } 211 set(ACCEPT_VERSION, StringUtils.toCommaDelimitedString(acceptVersions.dup)); 212 } 213 214 /** 215 * Get the accept-version header. 216 * @since 5.0.7 217 */ 218 219 string[] getAcceptVersion() { 220 string value = getFirst(ACCEPT_VERSION); 221 return value !is null ? StringUtils.commaDelimitedListToStringArray(value) : null; 222 } 223 224 /** 225 * Set the login header. 226 * Applies to the CONNECT frame. 227 */ 228 void setLogin(string login) { 229 set(LOGIN, login); 230 } 231 232 /** 233 * Get the login header. 234 */ 235 236 string getLogin() { 237 return getFirst(LOGIN); 238 } 239 240 /** 241 * Set the passcode header. 242 * Applies to the CONNECT frame. 243 */ 244 void setPasscode(string passcode) { 245 set(PASSCODE, passcode); 246 } 247 248 /** 249 * Get the passcode header. 250 */ 251 252 string getPasscode() { 253 return getFirst(PASSCODE); 254 } 255 256 /** 257 * Set the heartbeat header. 258 * Applies to the CONNECT and CONNECTED frames. 259 */ 260 void setHeartbeat(long[] heartbeat) { 261 if (heartbeat is null || heartbeat.length != 2) { 262 throw new IllegalArgumentException("Heart-beat array must be of length 2, not " ~ 263 (heartbeat !is null ? to!string(heartbeat.length) : "null")); 264 } 265 string value = heartbeat[0].to!string() ~ "," ~ heartbeat[1].to!string(); 266 if (heartbeat[0] < 0 || heartbeat[1] < 0) { 267 throw new IllegalArgumentException("Heart-beat values cannot be negative: " ~ value); 268 } 269 set(HEARTBEAT, value); 270 } 271 272 /** 273 * Get the heartbeat header. 274 */ 275 276 long[] getHeartbeat() { 277 string rawValue = getFirst(HEARTBEAT); 278 string[] rawValues = StringUtils.split(rawValue, ","); 279 if (rawValues is null) { 280 return null; 281 } 282 // return [Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])]; 283 return [rawValues[0].to!long(), rawValues[1].to!long()]; 284 } 285 286 /** 287 * Whether heartbeats are enabled. Returns {@code false} if 288 * {@link #setHeartbeat} is set to "0,0", and {@code true} otherwise. 289 */ 290 bool isHeartbeatEnabled() { 291 long[] heartbeat = getHeartbeat(); 292 return (heartbeat !is null && heartbeat[0] != 0 && heartbeat[1] != 0); 293 } 294 295 /** 296 * Set the session header. 297 * Applies to the CONNECTED frame. 298 */ 299 void setSession(string session) { 300 set(SESSION, session); 301 } 302 303 /** 304 * Get the session header. 305 */ 306 307 string getSession() { 308 return getFirst(SESSION); 309 } 310 311 /** 312 * Set the server header. 313 * Applies to the CONNECTED frame. 314 */ 315 void setServer(string server) { 316 set(SERVER, server); 317 } 318 319 /** 320 * Get the server header. 321 * Applies to the CONNECTED frame. 322 */ 323 324 string getServer() { 325 return getFirst(SERVER); 326 } 327 328 /** 329 * Set the destination header. 330 */ 331 void setDestination(string destination) { 332 set(DESTINATION, destination); 333 } 334 335 /** 336 * Get the destination header. 337 * Applies to the SEND, SUBSCRIBE, and MESSAGE frames. 338 */ 339 340 string getDestination() { 341 return getFirst(DESTINATION); 342 } 343 344 /** 345 * Set the id header. 346 * Applies to the SUBSCR0BE, UNSUBSCRIBE, and ACK or NACK frames. 347 */ 348 void setId(string id) { 349 set(ID, id); 350 } 351 352 /** 353 * Get the id header. 354 */ 355 356 string getId() { 357 return getFirst(ID); 358 } 359 360 /** 361 * Set the ack header to one of "auto", "client", or "client-individual". 362 * Applies to the SUBSCRIBE and MESSAGE frames. 363 */ 364 void setAck(string ack) { 365 set(ACK, ack); 366 } 367 368 /** 369 * Get the ack header. 370 */ 371 372 string getAck() { 373 return getFirst(ACK); 374 } 375 376 /** 377 * Set the login header. 378 * Applies to the MESSAGE frame. 379 */ 380 void setSubscription(string subscription) { 381 set(SUBSCRIPTION, subscription); 382 } 383 384 /** 385 * Get the subscription header. 386 */ 387 388 string getSubscription() { 389 return getFirst(SUBSCRIPTION); 390 } 391 392 /** 393 * Set the message-id header. 394 * Applies to the MESSAGE frame. 395 */ 396 void setMessageId(string messageId) { 397 set(MESSAGE_ID, messageId); 398 } 399 400 /** 401 * Get the message-id header. 402 */ 403 404 string getMessageId() { 405 return getFirst(MESSAGE_ID); 406 } 407 408 /** 409 * Set the receipt-id header. 410 * Applies to the RECEIPT frame. 411 */ 412 void setReceiptId(string receiptId) { 413 set(RECEIPT_ID, receiptId); 414 } 415 416 /** 417 * Get the receipt header. 418 */ 419 420 string getReceiptId() { 421 return getFirst(RECEIPT_ID); 422 } 423 424 /** 425 * Return the first header value for the given header name, if any. 426 * @param headerName the header name 427 * @return the first header value, or {@code null} if none 428 */ 429 override 430 431 string getFirst(string headerName) { 432 List!(string) headerValues = this.headers.get(headerName); 433 return headerValues !is null ? headerValues.get(0) : null; 434 } 435 436 /** 437 * Add the given, single header value under the given name. 438 * @param headerName the header name 439 * @param headerValue the header value 440 * @throws UnsupportedOperationException if adding headers is not supported 441 * @see #put(string, List) 442 * @see #set(string, string) 443 */ 444 override 445 void add(string headerName, string headerValue) { 446 List!(string) headerValues = this.headers.computeIfAbsent(headerName, k => new LinkedList!string()); 447 headerValues.add(headerValue); 448 } 449 450 override 451 void addAll(string headerName, List!(string) headerValues) { 452 List!(string) currentValues = this.headers.computeIfAbsent(headerName, k => new LinkedList!string()); 453 currentValues.addAll(headerValues); 454 } 455 456 // override 457 void addAll(MultiStringsMap values) { 458 foreach(string k, List!(string) v; values) 459 this.addAll(k, v); 460 } 461 462 /** 463 * Set the given, single header value under the given name. 464 * @param headerName the header name 465 * @param headerValue the header value 466 * @throws UnsupportedOperationException if adding headers is not supported 467 * @see #put(string, List) 468 * @see #add(string, string) 469 */ 470 override 471 void set(string headerName, string headerValue) { 472 List!(string) headerValues = new LinkedList!(string)(); 473 headerValues.add(headerValue); 474 this.headers.put(headerName, headerValues); 475 } 476 477 override 478 void setAll(Map!(string, string) values) { 479 foreach(string k, string v; values) 480 this.set(k, v); 481 } 482 483 override 484 Map!(string, string) toSingleValueMap() { 485 LinkedHashMap!(string, string) singleValueMap = new LinkedHashMap!(string, string)(this.headers.size()); 486 foreach(string key, List!(string) value; this.headers) { 487 singleValueMap.put(key, value.get(0)); 488 } 489 return singleValueMap; 490 } 491 492 493 // Map implementation 494 495 override 496 int size() { 497 return this.headers.size(); 498 } 499 500 override 501 bool isEmpty() { 502 return this.headers.isEmpty(); 503 } 504 505 override 506 bool containsKey(string key) { 507 return this.headers.containsKey(key); 508 } 509 510 override 511 bool containsValue(List!(string) value) { 512 return this.headers.containsValue(value); 513 } 514 515 List!(string) opIndex(string key) { 516 return get(key); 517 } 518 519 override 520 List!(string) get(string key) { 521 return this.headers.get(key); 522 } 523 524 override 525 List!(string) put(string key, List!(string) value) { 526 return this.headers.put(key, value); 527 } 528 529 override 530 void putAll(MultiStringsMap map) { 531 this.headers.putAll(map); 532 } 533 534 List!(string) putIfAbsent(string key, List!(string) value) { 535 List!(string) v; 536 537 if(!containsKey(key)) 538 v = put(key, value); 539 540 return v; 541 } 542 543 override 544 List!(string) remove(string key) { 545 return this.headers.remove(key); 546 } 547 548 bool remove(string key, List!(string) value){ 549 List!(string) curValue = get(key); 550 if(curValue != value || !containsKey(key)) 551 return false; 552 remove(key); 553 return true; 554 } 555 556 override 557 void clear() { 558 this.headers.clear(); 559 } 560 561 562 bool replace(string key, List!(string) oldValue, List!(string) newValue) { 563 List!(string) curValue = get(key); 564 if(curValue != oldValue || !containsKey(key)){ 565 return false; 566 } 567 put(key, newValue); 568 return true; 569 } 570 571 List!(string) replace(string key, List!(string) value) { 572 List!(string) curValue; 573 if (containsKey(key)) { 574 curValue = put(key, value); 575 } 576 return curValue; 577 } 578 579 // override 580 // Set!(string) keySet() { 581 // return this.headers.keySet(); 582 // } 583 584 585 List!(string)[] values() { 586 List!(string)[] arr; 587 foreach(List!(string) value; byValue()) { 588 arr ~= value; 589 } 590 return arr; 591 } 592 593 // override 594 // Set!(Entry!(string, List!(string))) entrySet() { 595 // return this.headers.entrySet(); 596 // } 597 598 599 int opApply(scope int delegate(ref string, ref List!(string)) dg) { 600 return this.headers.opApply(dg); 601 } 602 603 int opApply(scope int delegate(MapEntry!(string, List!(string)) entry) dg) { 604 return this.headers.opApply(dg); 605 } 606 607 InputRange!string byKey() { 608 return this.headers.byKey(); 609 } 610 611 InputRange!(List!(string)) byValue() { 612 return this.headers.byValue(); 613 } 614 615 bool opEquals(IObject o) { 616 return opEquals(cast(Object)o); 617 } 618 619 override 620 bool opEquals(Object other) { 621 if(this is other) 622 return true; 623 StompHeaders sh = cast(StompHeaders) other; 624 if(sh is null) return false; 625 return this.headers == sh.headers; 626 } 627 628 override 629 size_t toHash() @trusted nothrow { 630 return this.headers.toHash(); 631 } 632 633 override 634 string toString() { 635 return this.headers.toString(); 636 } 637 638 639 /** 640 * Return a {@code StompHeaders} object that can only be read, not written to. 641 */ 642 static StompHeaders readOnlyStompHeaders(MultiStringsMap headers) { 643 return new StompHeaders((headers !is null ? headers : Collections.emptyMap!(string, List!(string))()), true); 644 } 645 646 mixin CloneMemberTemplate!(typeof(this)); 647 648 }