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