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