001package ca.uhn.fhir.model.primitive; 002 003import static org.apache.commons.lang3.StringUtils.defaultString; 004/* 005 * #%L 006 * HAPI FHIR - Core Library 007 * %% 008 * Copyright (C) 2014 - 2017 University Health Network 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * #L% 022 */ 023import static org.apache.commons.lang3.StringUtils.isBlank; 024import static org.apache.commons.lang3.StringUtils.isNotBlank; 025 026import java.math.BigDecimal; 027import java.util.UUID; 028 029import org.apache.commons.lang3.*; 030import org.apache.commons.lang3.builder.HashCodeBuilder; 031import org.hl7.fhir.instance.model.api.*; 032 033import ca.uhn.fhir.model.api.IResource; 034import ca.uhn.fhir.model.api.annotation.DatatypeDef; 035import ca.uhn.fhir.model.api.annotation.SimpleSetter; 036import ca.uhn.fhir.parser.DataFormatException; 037import ca.uhn.fhir.rest.api.Constants; 038import ca.uhn.fhir.util.UrlUtil; 039 040/** 041 * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource. 042 * 043 * <p> 044 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length 045 * limit of 36 characters. 046 * </p> 047 * <p> 048 * regex: [a-z-Z0-9\-\.]{1,36} 049 * </p> 050 */ 051@DatatypeDef(name = "id", profileOf = StringDt.class) 052public class IdDt extends UriDt implements /*IPrimitiveDatatype<String>, */IIdType { 053 054 private String myBaseUrl; 055 private boolean myHaveComponentParts; 056 private String myResourceType; 057 private String myUnqualifiedId; 058 private String myUnqualifiedVersionId; 059 060 /** 061 * Create a new empty ID 062 */ 063 public IdDt() { 064 super(); 065 } 066 067 /** 068 * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation. 069 */ 070 public IdDt(BigDecimal thePid) { 071 if (thePid != null) { 072 setValue(toPlainStringWithNpeThrowIfNeeded(thePid)); 073 } else { 074 setValue(null); 075 } 076 } 077 078 /** 079 * Create a new ID using a long 080 */ 081 public IdDt(long theId) { 082 setValue(Long.toString(theId)); 083 } 084 085 /** 086 * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete URL (http://example.com/fhir/Patient/1234). 087 * 088 * <p> 089 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length 090 * limit of 36 characters. 091 * </p> 092 * <p> 093 * regex: [a-z0-9\-\.]{1,36} 094 * </p> 095 */ 096 @SimpleSetter 097 public IdDt(@SimpleSetter.Parameter(name = "theId") String theValue) { 098 setValue(theValue); 099 } 100 101 /** 102 * Constructor 103 * 104 * @param theResourceType 105 * The resource type (e.g. "Patient") 106 * @param theIdPart 107 * The ID (e.g. "123") 108 */ 109 public IdDt(String theResourceType, BigDecimal theIdPart) { 110 this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); 111 } 112 113 /** 114 * Constructor 115 * 116 * @param theResourceType 117 * The resource type (e.g. "Patient") 118 * @param theIdPart 119 * The ID (e.g. "123") 120 */ 121 public IdDt(String theResourceType, Long theIdPart) { 122 this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); 123 } 124 125 /** 126 * Constructor 127 * 128 * @param theResourceType 129 * The resource type (e.g. "Patient") 130 * @param theId 131 * The ID (e.g. "123") 132 */ 133 public IdDt(String theResourceType, String theId) { 134 this(theResourceType, theId, null); 135 } 136 137 /** 138 * Constructor 139 * 140 * @param theResourceType 141 * The resource type (e.g. "Patient") 142 * @param theId 143 * The ID (e.g. "123") 144 * @param theVersionId 145 * The version ID ("e.g. "456") 146 */ 147 public IdDt(String theResourceType, String theId, String theVersionId) { 148 this(null, theResourceType, theId, theVersionId); 149 } 150 151 /** 152 * Constructor 153 * 154 * @param theBaseUrl 155 * The server base URL (e.g. "http://example.com/fhir") 156 * @param theResourceType 157 * The resource type (e.g. "Patient") 158 * @param theId 159 * The ID (e.g. "123") 160 * @param theVersionId 161 * The version ID ("e.g. "456") 162 */ 163 public IdDt(String theBaseUrl, String theResourceType, String theId, String theVersionId) { 164 myBaseUrl = theBaseUrl; 165 myResourceType = theResourceType; 166 myUnqualifiedId = theId; 167 myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null); 168 myHaveComponentParts = true; 169 if (isBlank(myBaseUrl) && isBlank(myResourceType) && isBlank(myUnqualifiedId) && isBlank(myUnqualifiedVersionId)) { 170 myHaveComponentParts = false; 171 } 172 } 173 174 /** 175 * Creates an ID based on a given URL 176 */ 177 public IdDt(UriDt theUrl) { 178 setValue(theUrl.getValueAsString()); 179 } 180 181 @Override 182 public void applyTo(IBaseResource theResouce) { 183 if (theResouce == null) { 184 throw new NullPointerException("theResource can not be null"); 185 } else if (theResouce instanceof IResource) { 186 ((IResource) theResouce).setId(new IdDt(getValue())); 187 } else if (theResouce instanceof IAnyResource) { 188 ((IAnyResource) theResouce).setId(getValue()); 189 } else { 190 throw new IllegalArgumentException("Unknown resource class type, does not implement IResource or extend Resource"); 191 } 192 } 193 194 /** 195 * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous) 196 */ 197 @Deprecated 198 public BigDecimal asBigDecimal() { 199 return getIdPartAsBigDecimal(); 200 } 201 202 @Override 203 public boolean equals(Object theArg0) { 204 if (!(theArg0 instanceof IdDt)) { 205 return false; 206 } 207 IdDt id = (IdDt) theArg0; 208 return StringUtils.equals(getValueAsString(), id.getValueAsString()); 209 } 210 211 /** 212 * Returns true if this IdDt matches the given IdDt in terms of resource type and ID, but ignores the URL base 213 */ 214 @SuppressWarnings("deprecation") 215 public boolean equalsIgnoreBase(IdDt theId) { 216 if (theId == null) { 217 return false; 218 } 219 if (theId.isEmpty()) { 220 return isEmpty(); 221 } 222 return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart()); 223 } 224 225 /** 226 * Returns the portion of this resource ID which corresponds to the server base URL. For example given the resource ID <code>http://example.com/fhir/Patient/123</code> the base URL would be 227 * <code>http://example.com/fhir</code>. 228 * <p> 229 * This method may return null if the ID contains no base (e.g. "Patient/123") 230 * </p> 231 */ 232 @Override 233 public String getBaseUrl() { 234 return myBaseUrl; 235 } 236 237 /** 238 * Returns only the logical ID part of this ID. For example, given the ID "http://example,.com/fhir/Patient/123/_history/456", this method would return "123". 239 */ 240 @Override 241 public String getIdPart() { 242 return myUnqualifiedId; 243 } 244 245 /** 246 * Returns the unqualified portion of this ID as a big decimal, or <code>null</code> if the value is null 247 * 248 * @throws NumberFormatException 249 * If the value is not a valid BigDecimal 250 */ 251 public BigDecimal getIdPartAsBigDecimal() { 252 String val = getIdPart(); 253 if (isBlank(val)) { 254 return null; 255 } 256 return new BigDecimal(val); 257 } 258 259 /** 260 * Returns the unqualified portion of this ID as a {@link Long}, or <code>null</code> if the value is null 261 * 262 * @throws NumberFormatException 263 * If the value is not a valid Long 264 */ 265 @Override 266 public Long getIdPartAsLong() { 267 String val = getIdPart(); 268 if (isBlank(val)) { 269 return null; 270 } 271 return Long.parseLong(val); 272 } 273 274 @Override 275 public String getResourceType() { 276 return myResourceType; 277 } 278 279 /** 280 * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get just the ID portion. 281 * 282 * @see #getIdPart() 283 */ 284 @Override 285 public String getValue() { 286 if (super.getValue() == null && myHaveComponentParts) { 287 288 if (isLocal() || isUrn()) { 289 return myUnqualifiedId; 290 } 291 292 StringBuilder b = new StringBuilder(); 293 if (isNotBlank(myBaseUrl)) { 294 b.append(myBaseUrl); 295 if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') { 296 b.append('/'); 297 } 298 } 299 300 if (isNotBlank(myResourceType)) { 301 b.append(myResourceType); 302 } 303 304 if (b.length() > 0 && isNotBlank(myUnqualifiedId)) { 305 b.append('/'); 306 } 307 308 if (isNotBlank(myUnqualifiedId)) { 309 b.append(myUnqualifiedId); 310 } else if (isNotBlank(myUnqualifiedVersionId)) { 311 b.append('/'); 312 } 313 314 if (isNotBlank(myUnqualifiedVersionId)) { 315 b.append('/'); 316 b.append(Constants.PARAM_HISTORY); 317 b.append('/'); 318 b.append(myUnqualifiedVersionId); 319 } 320 String value = b.toString(); 321 super.setValue(value); 322 } 323 return super.getValue(); 324 } 325 326 @Override 327 public String getValueAsString() { 328 return getValue(); 329 } 330 331 @Override 332 public String getVersionIdPart() { 333 return myUnqualifiedVersionId; 334 } 335 336 @Override 337 public Long getVersionIdPartAsLong() { 338 if (!hasVersionIdPart()) { 339 return null; 340 } 341 return Long.parseLong(getVersionIdPart()); 342 } 343 344 /** 345 * Returns true if this ID has a base url 346 * 347 * @see #getBaseUrl() 348 */ 349 @Override 350 public boolean hasBaseUrl() { 351 return isNotBlank(myBaseUrl); 352 } 353 354 @Override 355 public int hashCode() { 356 HashCodeBuilder b = new HashCodeBuilder(); 357 b.append(getValueAsString()); 358 return b.toHashCode(); 359 } 360 361 @Override 362 public boolean hasIdPart() { 363 return isNotBlank(getIdPart()); 364 } 365 366 @Override 367 public boolean hasResourceType() { 368 return isNotBlank(myResourceType); 369 } 370 371 @Override 372 public boolean hasVersionIdPart() { 373 return isNotBlank(getVersionIdPart()); 374 } 375 376 /** 377 * Returns <code>true</code> if this ID contains an absolute URL (in other words, a URL starting with "http://" or "https://" 378 */ 379 @Override 380 public boolean isAbsolute() { 381 if (StringUtils.isBlank(getValue())) { 382 return false; 383 } 384 return UrlUtil.isAbsolute(getValue()); 385 } 386 387 @Override 388 public boolean isEmpty() { 389 return super.isBaseEmpty() && isBlank(getValue()); 390 } 391 392 @Override 393 public boolean isIdPartValid() { 394 String id = getIdPart(); 395 if (StringUtils.isBlank(id)) { 396 return false; 397 } 398 if (id.length() > 64) { 399 return false; 400 } 401 for (int i = 0; i < id.length(); i++) { 402 char nextChar = id.charAt(i); 403 if (nextChar >= 'a' && nextChar <= 'z') { 404 continue; 405 } 406 if (nextChar >= 'A' && nextChar <= 'Z') { 407 continue; 408 } 409 if (nextChar >= '0' && nextChar <= '9') { 410 continue; 411 } 412 if (nextChar == '-' || nextChar == '.') { 413 continue; 414 } 415 return false; 416 } 417 return true; 418 } 419 420 @Override 421 public boolean isIdPartValidLong() { 422 return isValidLong(getIdPart()); 423 } 424 425 /** 426 * Returns <code>true</code> if the ID is a local reference (in other words, 427 * it begins with the '#' character) 428 */ 429 @Override 430 public boolean isLocal() { 431 return defaultString(myUnqualifiedId).startsWith("#"); 432 } 433 434 private boolean isUrn() { 435 return defaultString(myUnqualifiedId).startsWith("urn:"); 436 } 437 438 @Override 439 public boolean isVersionIdPartValidLong() { 440 return isValidLong(getVersionIdPart()); 441 } 442 443 /** 444 * Copies the value from the given IdDt to <code>this</code> IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API. 445 * @deprecated 446 */ 447 @Deprecated //override deprecated method 448 @Override 449 public void setId(IdDt theId) { 450 setValue(theId.getValue()); 451 } 452 453 @Override 454 public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) { 455 if (isNotBlank(theVersionIdPart)) { 456 Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated"); 457 Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated"); 458 } 459 if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) { 460 Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated"); 461 } 462 463 setValue(null); 464 465 myBaseUrl = theBaseUrl; 466 myResourceType = theResourceType; 467 myUnqualifiedId = theIdPart; 468 myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null); 469 myHaveComponentParts = true; 470 471 return this; 472 } 473 474 /** 475 * Set the value 476 * 477 * <p> 478 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length 479 * limit of 36 characters. 480 * </p> 481 * <p> 482 * regex: [a-z0-9\-\.]{1,36} 483 * </p> 484 */ 485 @Override 486 public IdDt setValue(String theValue) throws DataFormatException { 487 // TODO: add validation 488 super.setValue(theValue); 489 myHaveComponentParts = false; 490 491 if (StringUtils.isBlank(theValue)) { 492 myBaseUrl = null; 493 super.setValue(null); 494 myUnqualifiedId = null; 495 myUnqualifiedVersionId = null; 496 myResourceType = null; 497 } else if (theValue.charAt(0) == '#' && theValue.length() > 1) { 498 super.setValue(theValue); 499 myBaseUrl = null; 500 myUnqualifiedId = theValue; 501 myUnqualifiedVersionId = null; 502 myResourceType = null; 503 myHaveComponentParts = true; 504 } else if (theValue.startsWith("urn:")) { 505 myBaseUrl = null; 506 myUnqualifiedId = theValue; 507 myUnqualifiedVersionId = null; 508 myResourceType = null; 509 myHaveComponentParts = true; 510 } else { 511 int vidIndex = theValue.indexOf("/_history/"); 512 int idIndex; 513 if (vidIndex != -1) { 514 myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length()); 515 idIndex = theValue.lastIndexOf('/', vidIndex - 1); 516 myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex); 517 } else { 518 idIndex = theValue.lastIndexOf('/'); 519 myUnqualifiedId = theValue.substring(idIndex + 1); 520 myUnqualifiedVersionId = null; 521 } 522 523 myBaseUrl = null; 524 if (idIndex <= 0) { 525 myResourceType = null; 526 } else { 527 int typeIndex = theValue.lastIndexOf('/', idIndex - 1); 528 if (typeIndex == -1) { 529 myResourceType = theValue.substring(0, idIndex); 530 } else { 531 if (typeIndex > 0 && '/' == theValue.charAt(typeIndex - 1)) { 532 typeIndex = theValue.indexOf('/', typeIndex + 1); 533 } 534 if (typeIndex >= idIndex) { 535 // e.g. http://example.org/foo 536 // 'foo' was the id but we're making that the resource type. Nullify the id part because we don't have an id. 537 // Also set null value to the super.setValue() and enable myHaveComponentParts so it forces getValue() to properly 538 // recreate the url 539 myResourceType = myUnqualifiedId; 540 myUnqualifiedId = null; 541 super.setValue(null); 542 myHaveComponentParts = true; 543 } else { 544 myResourceType = theValue.substring(typeIndex + 1, idIndex); 545 } 546 547 if (typeIndex > 4) { 548 myBaseUrl = theValue.substring(0, typeIndex); 549 } 550 551 } 552 } 553 554 } 555 return this; 556 } 557 558 /** 559 * Set the value 560 * 561 * <p> 562 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length 563 * limit of 36 characters. 564 * </p> 565 * <p> 566 * regex: [a-z0-9\-\.]{1,36} 567 * </p> 568 */ 569 @Override 570 public void setValueAsString(String theValue) throws DataFormatException { 571 setValue(theValue); 572 } 573 574 @Override 575 public String toString() { 576 return getValue(); 577 } 578 579 /** 580 * Returns a new IdDt containing this IdDt's values but with no server base URL if one is present in this IdDt. For example, if this IdDt contains the ID "http://foo/Patient/1", this method will 581 * return a new IdDt containing ID "Patient/1". 582 */ 583 @Override 584 public IdDt toUnqualified() { 585 if (isLocal() || isUrn()) { 586 return new IdDt(getValueAsString()); 587 } 588 return new IdDt(getResourceType(), getIdPart(), getVersionIdPart()); 589 } 590 591 @Override 592 public IdDt toUnqualifiedVersionless() { 593 if (isLocal() || isUrn()) { 594 return new IdDt(getValueAsString()); 595 } 596 return new IdDt(getResourceType(), getIdPart()); 597 } 598 599 @Override 600 public IdDt toVersionless() { 601 if (isLocal() || isUrn()) { 602 return new IdDt(getValueAsString()); 603 } 604 return new IdDt(getBaseUrl(), getResourceType(), getIdPart(), null); 605 } 606 607 @Override 608 public IdDt withResourceType(String theResourceName) { 609 if (isLocal() || isUrn()) { 610 return new IdDt(getValueAsString()); 611 } 612 return new IdDt(theResourceName, getIdPart(), getVersionIdPart()); 613 } 614 615 /** 616 * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be used if the ID does not already contain those respective parts). Essentially, 617 * because IdDt can contain either a complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete URL. 618 * 619 * @param theServerBase 620 * The server base (e.g. "http://example.com/fhir") 621 * @param theResourceType 622 * The resource name (e.g. "Patient") 623 * @return A fully qualified URL for this ID (e.g. "http://example.com/fhir/Patient/1") 624 */ 625 @Override 626 public IdDt withServerBase(String theServerBase, String theResourceType) { 627 if (isLocal() || isUrn()) { 628 return new IdDt(getValueAsString()); 629 } 630 return new IdDt(theServerBase, theResourceType, getIdPart(), getVersionIdPart()); 631 } 632 633 /** 634 * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion. 635 * 636 * @param theVersion 637 * The actual version string, e.g. "1" 638 * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion. 639 */ 640 @Override 641 public IdDt withVersion(String theVersion) { 642 Validate.notBlank(theVersion, "Version may not be null or empty"); 643 644 if (isLocal() || isUrn()) { 645 return new IdDt(getValueAsString()); 646 } 647 648 String existingValue = getValue(); 649 650 int i = existingValue.indexOf(Constants.PARAM_HISTORY); 651 String value; 652 if (i > 1) { 653 value = existingValue.substring(0, i - 1); 654 } else { 655 value = existingValue; 656 } 657 658 return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion); 659 } 660 661 private static boolean isValidLong(String id) { 662 if (StringUtils.isBlank(id)) { 663 return false; 664 } 665 for (int i = 0; i < id.length(); i++) { 666 if (Character.isDigit(id.charAt(i)) == false) { 667 return false; 668 } 669 } 670 return true; 671 } 672 673 /** 674 * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new, randomly 675 * created UUID generated by {@link UUID#randomUUID()} 676 */ 677 public static IdDt newRandomUuid() { 678 return new IdDt("urn:uuid:" + UUID.randomUUID().toString()); 679 } 680 681 /** 682 * Retrieves the ID from the given resource instance 683 */ 684 public static IdDt of(IBaseResource theResouce) { 685 if (theResouce == null) { 686 throw new NullPointerException("theResource can not be null"); 687 } 688 IIdType retVal = theResouce.getIdElement(); 689 if (retVal == null) { 690 return null; 691 } else if (retVal instanceof IdDt) { 692 return (IdDt) retVal; 693 } else { 694 return new IdDt(retVal.getValue()); 695 } 696 } 697 698 private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) { 699 if (theIdPart == null) { 700 throw new NullPointerException("BigDecimal ID can not be null"); 701 } 702 return theIdPart.toPlainString(); 703 } 704 705 private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) { 706 if (theIdPart == null) { 707 throw new NullPointerException("Long ID can not be null"); 708 } 709 return theIdPart.toString(); 710 } 711 712}