001package org.hl7.fhir.dstu3.model; 002 003import static org.apache.commons.lang3.StringUtils.defaultString; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032*/ 033 034import static org.apache.commons.lang3.StringUtils.isBlank; 035import static org.apache.commons.lang3.StringUtils.isNotBlank; 036 037import java.math.BigDecimal; 038import java.util.UUID; 039 040import org.apache.commons.lang3.ObjectUtils; 041import org.apache.commons.lang3.StringUtils; 042import org.apache.commons.lang3.Validate; 043import org.apache.commons.lang3.builder.HashCodeBuilder; 044import org.hl7.fhir.instance.model.api.IBaseResource; 045import org.hl7.fhir.instance.model.api.IIdType; 046import org.hl7.fhir.instance.model.api.IPrimitiveType; 047 048import ca.uhn.fhir.model.api.annotation.DatatypeDef; 049 050/** 051 * This class represents the logical identity for a resource, or as much of that 052 * identity is known. In FHIR, every resource must have a "logical ID" which is 053 * defined by the FHIR specification as: 054 * <p> 055 * <code>A whole number in the range 0 to 2^64-1 (optionally represented in hex), 056 * a uuid, an oid, or any other combination of lowercase letters, numerals, "-" 057 * and ".", with a length limit of 36 characters</code> 058 * </p> 059 * <p> 060 * This class contains that logical ID, and can optionally also contain a 061 * relative or absolute URL representing the resource identity. For example, the 062 * following are all valid values for IdType, and all might represent the same 063 * resource: 064 * </p> 065 * <ul> 066 * <li><code>123</code> (just a resource's ID)</li> 067 * <li><code>Patient/123</code> (a relative identity)</li> 068 * <li><code>http://example.com/Patient/123 (an absolute identity)</code></li> 069 * <li> 070 * <code>http://example.com/Patient/123/_history/1 (an absolute identity with a version id)</code> 071 * </li> 072 * <li> 073 * <code>Patient/123/_history/1 (a relative identity with a version id)</code> 074 * </li> 075 * </ul> 076 * <p> 077 * In most situations, you only need to populate the resource's ID (e.g. 078 * <code>123</code>) in resources you are constructing and the encoder will 079 * infer the rest from the context in which the object is being used. On the 080 * other hand, the parser will always try to populate the complete absolute 081 * identity on objects it creates as a convenience. 082 * </p> 083 * <p> 084 * Regex for ID: [a-z0-9\-\.]{1,36} 085 * </p> 086 */ 087@DatatypeDef(name = "id", profileOf=StringType.class) 088public final class IdType extends UriType implements IPrimitiveType<String>, IIdType { 089 /** 090 * This is the maximum length for the ID 091 */ 092 public static final int MAX_LENGTH = 64; // maximum length 093 094 private static final long serialVersionUID = 2L; 095 private String myBaseUrl; 096 private boolean myHaveComponentParts; 097 private String myResourceType; 098 private String myUnqualifiedId; 099 private String myUnqualifiedVersionId; 100 101 /** 102 * Create a new empty ID 103 */ 104 public IdType() { 105 super(); 106 } 107 108 /** 109 * Create a new ID, using a BigDecimal input. Uses 110 * {@link BigDecimal#toPlainString()} to generate the string representation. 111 */ 112 public IdType(BigDecimal thePid) { 113 if (thePid != null) { 114 setValue(toPlainStringWithNpeThrowIfNeeded(thePid)); 115 } else { 116 setValue(null); 117 } 118 } 119 120 /** 121 * Create a new ID using a long 122 */ 123 public IdType(long theId) { 124 setValue(Long.toString(theId)); 125 } 126 127 /** 128 * Create a new ID using a string. This String may contain a simple ID (e.g. 129 * "1234") or it may contain a complete URL 130 * (http://example.com/fhir/Patient/1234). 131 * 132 * <p> 133 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally 134 * represented in hex), a uuid, an oid, or any other combination of lowercase 135 * letters, numerals, "-" and ".", with a length limit of 36 characters. 136 * </p> 137 * <p> 138 * regex: [a-z0-9\-\.]{1,36} 139 * </p> 140 */ 141 public IdType(String theValue) { 142 setValue(theValue); 143 } 144 145 /** 146 * Constructor 147 * 148 * @param theResourceType 149 * The resource type (e.g. "Patient") 150 * @param theIdPart 151 * The ID (e.g. "123") 152 */ 153 public IdType(String theResourceType, BigDecimal theIdPart) { 154 this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); 155 } 156 157 /** 158 * Constructor 159 * 160 * @param theResourceType 161 * The resource type (e.g. "Patient") 162 * @param theIdPart 163 * The ID (e.g. "123") 164 */ 165 public IdType(String theResourceType, Long theIdPart) { 166 this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); 167 } 168 169 /** 170 * Constructor 171 * 172 * @param theResourceType 173 * The resource type (e.g. "Patient") 174 * @param theId 175 * The ID (e.g. "123") 176 */ 177 public IdType(String theResourceType, String theId) { 178 this(theResourceType, theId, null); 179 } 180 181 /** 182 * Constructor 183 * 184 * @param theResourceType 185 * The resource type (e.g. "Patient") 186 * @param theId 187 * The ID (e.g. "123") 188 * @param theVersionId 189 * The version ID ("e.g. "456") 190 */ 191 public IdType(String theResourceType, String theId, String theVersionId) { 192 this(null, theResourceType, theId, theVersionId); 193 } 194 195 /** 196 * Constructor 197 * 198 * @param theBaseUrl 199 * The server base URL (e.g. "http://example.com/fhir") 200 * @param theResourceType 201 * The resource type (e.g. "Patient") 202 * @param theId 203 * The ID (e.g. "123") 204 * @param theVersionId 205 * The version ID ("e.g. "456") 206 */ 207 public IdType(String theBaseUrl, String theResourceType, String theId, String theVersionId) { 208 myBaseUrl = theBaseUrl; 209 myResourceType = theResourceType; 210 myUnqualifiedId = theId; 211 myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null); 212 myHaveComponentParts = true; 213 if (isBlank(myBaseUrl) && isBlank(myResourceType) && isBlank(myUnqualifiedId) && isBlank(myUnqualifiedVersionId)) { 214 myHaveComponentParts = false; 215 } 216 } 217 218 /** 219 * Creates an ID based on a given URL 220 */ 221 public IdType(UriType theUrl) { 222 setValue(theUrl.getValueAsString()); 223 } 224 225 public void applyTo(IBaseResource theResouce) { 226 if (theResouce == null) { 227 throw new NullPointerException("theResource can not be null"); 228 } else { 229 theResouce.setId(new IdType(getValue())); 230 } 231 } 232 233 /** 234 * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was 235 * deprocated because its name is ambiguous) 236 */ 237 @Deprecated 238 public BigDecimal asBigDecimal() { 239 return getIdPartAsBigDecimal(); 240 } 241 242 @Override 243 public IdType copy() { 244 return new IdType(getValue()); 245 } 246 247 @Override 248 public boolean equals(Object theArg0) { 249 if (!(theArg0 instanceof IdType)) { 250 return false; 251 } 252 return StringUtils.equals(getValueAsString(), ((IdType)theArg0).getValueAsString()); 253 } 254 255 /** 256 * Returns true if this IdType matches the given IdType in terms of resource 257 * type and ID, but ignores the URL base 258 */ 259 @SuppressWarnings("deprecation") 260 public boolean equalsIgnoreBase(IdType theId) { 261 if (theId == null) { 262 return false; 263 } 264 if (theId.isEmpty()) { 265 return isEmpty(); 266 } 267 return ObjectUtils.equals(getResourceType(), theId.getResourceType()) 268 && ObjectUtils.equals(getIdPart(), theId.getIdPart()) 269 && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart()); 270 } 271 272 /** 273 * Returns the portion of this resource ID which corresponds to the server 274 * base URL. For example given the resource ID 275 * <code>http://example.com/fhir/Patient/123</code> the base URL would be 276 * <code>http://example.com/fhir</code>. 277 * <p> 278 * This method may return null if the ID contains no base (e.g. "Patient/123") 279 * </p> 280 */ 281 @Override 282 public String getBaseUrl() { 283 return myBaseUrl; 284 } 285 286 /** 287 * Returns only the logical ID part of this ID. For example, given the ID 288 * "http://example,.com/fhir/Patient/123/_history/456", this method would 289 * return "123". 290 */ 291 @Override 292 public String getIdPart() { 293 return myUnqualifiedId; 294 } 295 296 /** 297 * Returns the unqualified portion of this ID as a big decimal, or 298 * <code>null</code> if the value is null 299 * 300 * @throws NumberFormatException 301 * If the value is not a valid BigDecimal 302 */ 303 public BigDecimal getIdPartAsBigDecimal() { 304 String val = getIdPart(); 305 if (isBlank(val)) { 306 return null; 307 } 308 return new BigDecimal(val); 309 } 310 311 /** 312 * Returns the unqualified portion of this ID as a {@link Long}, or 313 * <code>null</code> if the value is null 314 * 315 * @throws NumberFormatException 316 * If the value is not a valid Long 317 */ 318 @Override 319 public Long getIdPartAsLong() { 320 String val = getIdPart(); 321 if (isBlank(val)) { 322 return null; 323 } 324 return Long.parseLong(val); 325 } 326 327 @Override 328 public String getResourceType() { 329 return myResourceType; 330 } 331 332 /** 333 * Returns the value of this ID. Note that this value may be a fully qualified 334 * URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to 335 * get just the ID portion. 336 * 337 * @see #getIdPart() 338 */ 339 @Override 340 public String getValue() { 341 String retVal = super.getValue(); 342 if (retVal == null && myHaveComponentParts) { 343 344 if (isLocal() || isUrn()) { 345 return myUnqualifiedId; 346 } 347 348 StringBuilder b = new StringBuilder(); 349 if (isNotBlank(myBaseUrl)) { 350 b.append(myBaseUrl); 351 if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') { 352 b.append('/'); 353 } 354 } 355 356 if (isNotBlank(myResourceType)) { 357 b.append(myResourceType); 358 } 359 360 if (b.length() > 0) { 361 b.append('/'); 362 } 363 364 b.append(myUnqualifiedId); 365 if (isNotBlank(myUnqualifiedVersionId)) { 366 b.append('/'); 367 b.append("_history"); 368 b.append('/'); 369 b.append(myUnqualifiedVersionId); 370 } 371 retVal = b.toString(); 372 super.setValue(retVal); 373 } 374 return retVal; 375 } 376 377 @Override 378 public String getValueAsString() { 379 return getValue(); 380 } 381 382 @Override 383 public String getVersionIdPart() { 384 return myUnqualifiedVersionId; 385 } 386 387 public Long getVersionIdPartAsLong() { 388 if (!hasVersionIdPart()) { 389 return null; 390 } else { 391 return Long.parseLong(getVersionIdPart()); 392 } 393 } 394 395 /** 396 * Returns true if this ID has a base url 397 * 398 * @see #getBaseUrl() 399 */ 400 public boolean hasBaseUrl() { 401 return isNotBlank(myBaseUrl); 402 } 403 404 @Override 405 public int hashCode() { 406 HashCodeBuilder b = new HashCodeBuilder(); 407 b.append(getValueAsString()); 408 return b.toHashCode(); 409 } 410 411 @Override 412 public boolean hasIdPart() { 413 return isNotBlank(getIdPart()); 414 } 415 416 @Override 417 public boolean hasResourceType() { 418 return isNotBlank(myResourceType); 419 } 420 421 @Override 422 public boolean hasVersionIdPart() { 423 return isNotBlank(getVersionIdPart()); 424 } 425 426 /** 427 * Returns <code>true</code> if this ID contains an absolute URL (in other 428 * words, a URL starting with "http://" or "https://" 429 */ 430 @Override 431 public boolean isAbsolute() { 432 if (StringUtils.isBlank(getValue())) { 433 return false; 434 } 435 return isUrlAbsolute(getValue()); 436 } 437 438 @Override 439 public boolean isEmpty() { 440 return isBlank(getValue()); 441 } 442 443 @Override 444 public boolean isIdPartValid() { 445 String id = getIdPart(); 446 if (StringUtils.isBlank(id)) { 447 return false; 448 } 449 if (id.length() > 64) { 450 return false; 451 } 452 for (int i = 0; i < id.length(); i++) { 453 char nextChar = id.charAt(i); 454 if (nextChar >= 'a' && nextChar <= 'z') { 455 continue; 456 } 457 if (nextChar >= 'A' && nextChar <= 'Z') { 458 continue; 459 } 460 if (nextChar >= '0' && nextChar <= '9') { 461 continue; 462 } 463 if (nextChar == '-' || nextChar == '.') { 464 continue; 465 } 466 return false; 467 } 468 return true; 469 } 470 471 /** 472 * Returns <code>true</code> if the unqualified ID is a valid {@link Long} 473 * value (in other words, it consists only of digits) 474 */ 475 @Override 476 public boolean isIdPartValidLong() { 477 return isValidLong(getIdPart()); 478 } 479 480 /** 481 * Returns <code>true</code> if the ID is a local reference (in other words, 482 * it begins with the '#' character) 483 */ 484 @Override 485 public boolean isLocal() { 486 return defaultString(myUnqualifiedId).startsWith("#"); 487 } 488 489 private boolean isUrn() { 490 return defaultString(myUnqualifiedId).startsWith("urn:"); 491 } 492 493 @Override 494 public boolean isVersionIdPartValidLong() { 495 return isValidLong(getVersionIdPart()); 496 } 497 498 /** 499 * Set the value 500 * 501 * <p> 502 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally 503 * represented in hex), a uuid, an oid, or any other combination of lowercase 504 * letters, numerals, "-" and ".", with a length limit of 36 characters. 505 * </p> 506 * <p> 507 * regex: [a-z0-9\-\.]{1,36} 508 * </p> 509 */ 510 @Override 511 public IdType setValue(String theValue) { 512 // TODO: add validation 513 super.setValue(theValue); 514 myHaveComponentParts = false; 515 516 if (StringUtils.isBlank(theValue)) { 517 myBaseUrl = null; 518 super.setValue(null); 519 myUnqualifiedId = null; 520 myUnqualifiedVersionId = null; 521 myResourceType = null; 522 } else if (theValue.charAt(0) == '#' && theValue.length() > 1) { 523 super.setValue(theValue); 524 myBaseUrl = null; 525 myUnqualifiedId = theValue; 526 myUnqualifiedVersionId = null; 527 myResourceType = null; 528 myHaveComponentParts = true; 529 } else if (theValue.startsWith("urn:")) { 530 myBaseUrl = null; 531 myUnqualifiedId = theValue; 532 myUnqualifiedVersionId = null; 533 myResourceType = null; 534 myHaveComponentParts = true; 535 } else { 536 int vidIndex = theValue.indexOf("/_history/"); 537 int idIndex; 538 if (vidIndex != -1) { 539 myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length()); 540 idIndex = theValue.lastIndexOf('/', vidIndex - 1); 541 myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex); 542 } else { 543 idIndex = theValue.lastIndexOf('/'); 544 myUnqualifiedId = theValue.substring(idIndex + 1); 545 myUnqualifiedVersionId = null; 546 } 547 548 myBaseUrl = null; 549 if (idIndex <= 0) { 550 myResourceType = null; 551 } else { 552 int typeIndex = theValue.lastIndexOf('/', idIndex - 1); 553 if (typeIndex == -1) { 554 myResourceType = theValue.substring(0, idIndex); 555 } else { 556 myResourceType = theValue.substring(typeIndex + 1, idIndex); 557 558 if (typeIndex > 4) { 559 myBaseUrl = theValue.substring(0, typeIndex); 560 } 561 562 } 563 } 564 565 } 566 return this; 567 } 568 569 /** 570 * Set the value 571 * 572 * <p> 573 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally 574 * represented in hex), a uuid, an oid, or any other combination of lowercase 575 * letters, numerals, "-" and ".", with a length limit of 36 characters. 576 * </p> 577 * <p> 578 * regex: [a-z0-9\-\.]{1,36} 579 * </p> 580 */ 581 @Override 582 public void setValueAsString(String theValue) { 583 setValue(theValue); 584 } 585 586 @Override 587 public String toString() { 588 return getValue(); 589 } 590 591 /** 592 * Returns a new IdType containing this IdType's values but with no server 593 * base URL if one is present in this IdType. For example, if this IdType 594 * contains the ID "http://foo/Patient/1", this method will return a new 595 * IdType containing ID "Patient/1". 596 */ 597 @Override 598 public IdType toUnqualified() { 599 if (isLocal() || isUrn()) { 600 return new IdType(getValueAsString()); 601 } 602 return new IdType(getResourceType(), getIdPart(), getVersionIdPart()); 603 } 604 605 @Override 606 public IdType toUnqualifiedVersionless() { 607 if (isLocal() || isUrn()) { 608 return new IdType(getValueAsString()); 609 } 610 return new IdType(getResourceType(), getIdPart()); 611 } 612 613 @Override 614 public IdType toVersionless() { 615 if (isLocal() || isUrn()) { 616 return new IdType(getValueAsString()); 617 } 618 return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null); 619 } 620 621 @Override 622 public IdType withResourceType(String theResourceName) { 623 if (isLocal() || isUrn()) { 624 return new IdType(getValueAsString()); 625 } 626 return new IdType(theResourceName, getIdPart(), getVersionIdPart()); 627 } 628 629 /** 630 * Returns a view of this ID as a fully qualified URL, given a server base and 631 * resource name (which will only be used if the ID does not already contain 632 * those respective parts). Essentially, because IdType can contain either a 633 * complete URL or a partial one (or even jut a simple ID), this method may be 634 * used to translate into a complete URL. 635 * 636 * @param theServerBase 637 * The server base (e.g. "http://example.com/fhir") 638 * @param theResourceType 639 * The resource name (e.g. "Patient") 640 * @return A fully qualified URL for this ID (e.g. 641 * "http://example.com/fhir/Patient/1") 642 */ 643 @Override 644 public IdType withServerBase(String theServerBase, String theResourceType) { 645 if (isLocal() || isUrn()) { 646 return new IdType(getValueAsString()); 647 } 648 return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart()); 649 } 650 651 /** 652 * Creates a new instance of this ID which is identical, but refers to the 653 * specific version of this resource ID noted by theVersion. 654 * 655 * @param theVersion 656 * The actual version string, e.g. "1" 657 * @return A new instance of IdType which is identical, but refers to the 658 * specific version of this resource ID noted by theVersion. 659 */ 660 @Override 661 public IdType withVersion(String theVersion) { 662 Validate.notBlank(theVersion, "Version may not be null or empty"); 663 664 if (isLocal() || isUrn()) { 665 return new IdType(getValueAsString()); 666 } 667 668 String existingValue = getValue(); 669 670 int i = existingValue.indexOf("_history"); 671 String value; 672 if (i > 1) { 673 value = existingValue.substring(0, i - 1); 674 } else { 675 value = existingValue; 676 } 677 678 return new IdType(value + '/' + "_history" + '/' + theVersion); 679 } 680 681 private static boolean isUrlAbsolute(String theValue) { 682 String value = theValue.toLowerCase(); 683 return value.startsWith("http://") || value.startsWith("https://"); 684 } 685 686 private static boolean isValidLong(String id) { 687 if (StringUtils.isBlank(id)) { 688 return false; 689 } 690 for (int i = 0; i < id.length(); i++) { 691 if (Character.isDigit(id.charAt(i)) == false) { 692 return false; 693 } 694 } 695 return true; 696 } 697 698 /** 699 * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new, 700 * randomly created UUID generated by {@link UUID#randomUUID()} 701 */ 702 public static IdType newRandomUuid() { 703 return new IdType("urn:uuid:" + UUID.randomUUID().toString()); 704 } 705 706 /** 707 * Retrieves the ID from the given resource instance 708 */ 709 public static IdType of(IBaseResource theResouce) { 710 if (theResouce == null) { 711 throw new NullPointerException("theResource can not be null"); 712 } else { 713 IIdType retVal = theResouce.getIdElement(); 714 if (retVal == null) { 715 return null; 716 } else if (retVal instanceof IdType) { 717 return (IdType) retVal; 718 } else { 719 return new IdType(retVal.getValue()); 720 } 721 } 722 } 723 724 private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) { 725 if (theIdPart == null) { 726 throw new NullPointerException("BigDecimal ID can not be null"); 727 } 728 return theIdPart.toPlainString(); 729 } 730 731 private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) { 732 if (theIdPart == null) { 733 throw new NullPointerException("Long ID can not be null"); 734 } 735 return theIdPart.toString(); 736 } 737 738 public String fhirType() { 739 return "id"; 740 } 741 742 public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) { 743 if (isNotBlank(theVersionIdPart)) { 744 Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated"); 745 Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated"); 746 } 747 if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) { 748 Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated"); 749 } 750 751 setValue(null); 752 753 myBaseUrl = theBaseUrl; 754 myResourceType = theResourceType; 755 myUnqualifiedId = theIdPart; 756 myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null); 757 myHaveComponentParts = true; 758 759 return this; 760 } 761 762}