001package org.hl7.fhir.dstu3.model;
002
003
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*/
033import static org.apache.commons.lang3.StringUtils.*;
034
035import java.math.BigDecimal;
036import java.util.UUID;
037
038import org.apache.commons.lang3.*;
039import org.apache.commons.lang3.builder.HashCodeBuilder;
040import org.hl7.fhir.instance.model.api.*;
041
042import ca.uhn.fhir.model.api.annotation.DatatypeDef;
043
044/**
045 * This class represents the logical identity for a resource, or as much of that
046 * identity is known. In FHIR, every resource must have a "logical ID" which is
047 * defined by the FHIR specification as:
048 * <p>
049 * <code>
050 * 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.)
051 * </code>
052 * </p>
053 * <p>
054 * This class contains that logical ID, and can optionally also contain a
055 * relative or absolute URL representing the resource identity. For example, the
056 * following are all valid values for IdType, and all might represent the same
057 * resource:
058 * </p>
059 * <ul>
060 * <li><code>123</code> (just a resource's ID)</li>
061 * <li><code>Patient/123</code> (a relative identity)</li>
062 * <li><code>http://example.com/Patient/123 (an absolute identity)</code></li>
063 * <li>
064 * <code>http://example.com/Patient/123/_history/1 (an absolute identity with a version id)</code>
065 * </li>
066 * <li>
067 * <code>Patient/123/_history/1 (a relative identity with a version id)</code>
068 * </li>
069 * </ul>
070 * <p>
071 * Note that the 64 character
072 * limit applies only to the ID portion ("123" in the examples above).
073 * </p>
074 * <p>
075 * In most situations, you only need to populate the resource's ID (e.g.
076 * <code>123</code>) in resources you are constructing and the encoder will
077 * infer the rest from the context in which the object is being used. On the
078 * other hand, the parser will always try to populate the complete absolute
079 * identity on objects it creates as a convenience.
080 * </p>
081 * <p>
082 * Regex for ID: [a-z0-9\-\.]{1,64}
083 * </p>
084 */
085@DatatypeDef(name = "id", profileOf=StringType.class)
086public final class IdType extends UriType implements IPrimitiveType<String>, IIdType {
087  public static final String URN_PREFIX = "urn:";
088
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 theResource) {
226    if (theResource == null) {
227      throw new NullPointerException("theResource can not be null");
228    } else {
229      theResource.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 && isNotBlank(myUnqualifiedId)) {
361        b.append('/');
362      }
363
364      if (isNotBlank(myUnqualifiedId)) {
365        b.append(myUnqualifiedId);
366      } else if (isNotBlank(myUnqualifiedVersionId)) {
367        b.append('/');
368      }
369
370      if (isNotBlank(myUnqualifiedVersionId)) {
371        b.append('/');
372        b.append("_history");
373        b.append('/');
374        b.append(myUnqualifiedVersionId);
375      }
376      retVal = b.toString();
377      super.setValue(retVal);
378    }
379    return retVal;
380  }
381
382  @Override
383  public String getValueAsString() {
384    return getValue();
385  }
386
387  @Override
388  public String getVersionIdPart() {
389    return myUnqualifiedVersionId;
390  }
391
392  public Long getVersionIdPartAsLong() {
393    if (!hasVersionIdPart()) {
394      return null;
395    } else {
396      return Long.parseLong(getVersionIdPart());
397    }
398  }
399
400  /**
401   * Returns true if this ID has a base url
402   *
403   * @see #getBaseUrl()
404   */
405  public boolean hasBaseUrl() {
406    return isNotBlank(myBaseUrl);
407  }
408
409  @Override
410  public int hashCode() {
411    HashCodeBuilder b = new HashCodeBuilder();
412    b.append(getValueAsString());
413    return b.toHashCode();
414  }
415
416  @Override
417  public boolean hasIdPart() {
418    return isNotBlank(getIdPart());
419  }
420
421  @Override
422  public boolean hasResourceType() {
423    return isNotBlank(myResourceType);
424  }
425
426  @Override
427  public boolean hasVersionIdPart() {
428    return isNotBlank(getVersionIdPart());
429  }
430
431  /**
432   * Returns <code>true</code> if this ID contains an absolute URL (in other
433   * words, a URL starting with "http://" or "https://"
434   */
435  @Override
436  public boolean isAbsolute() {
437    if (StringUtils.isBlank(getValue())) {
438      return false;
439    }
440    return isUrlAbsolute(getValue());
441  }
442
443  @Override
444  public boolean isEmpty() {
445    return super.isEmpty() && isBlank(getValue());
446  }
447
448  @Override
449  public boolean isIdPartValid() {
450    String id = getIdPart();
451    if (StringUtils.isBlank(id)) {
452      return false;
453    }
454    if (id.length() > 64) {
455      return false;
456    }
457    for (int i = 0; i < id.length(); i++) {
458      char nextChar = id.charAt(i);
459      if (nextChar >= 'a' && nextChar <= 'z') {
460        continue;
461      }
462      if (nextChar >= 'A' && nextChar <= 'Z') {
463        continue;
464      }
465      if (nextChar >= '0' && nextChar <= '9') {
466        continue;
467      }
468      if (nextChar == '-' || nextChar == '.') {
469        continue;
470      }
471      return false;
472    }
473    return true;
474  }
475
476  /**
477   * Returns <code>true</code> if the unqualified ID is a valid {@link Long}
478   * value (in other words, it consists only of digits)
479   */
480  @Override
481  public boolean isIdPartValidLong() {
482    return isValidLong(getIdPart());
483  }
484
485  /**
486   * Returns <code>true</code> if the ID is a local reference (in other words,
487   * it begins with the '#' character)
488   */
489  @Override
490  public boolean isLocal() {
491    return defaultString(myUnqualifiedId).startsWith("#");
492  }
493
494  public boolean isUrn() {
495    return defaultString(myUnqualifiedId).startsWith(URN_PREFIX);
496  }
497
498  @Override
499  public boolean isVersionIdPartValidLong() {
500    return isValidLong(getVersionIdPart());
501  }
502
503  /**
504   * Set the value
505   *
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 IdType setValue(String theValue) {
517    // TODO: add validation
518    super.setValue(theValue);
519
520    myHaveComponentParts = false;
521
522    if (StringUtils.isBlank(theValue)) {
523      myBaseUrl = null;
524      super.setValue(null);
525      myUnqualifiedId = null;
526      myUnqualifiedVersionId = null;
527      myResourceType = null;
528    } else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
529      super.setValue(theValue);
530      myBaseUrl = null;
531      myUnqualifiedId = theValue;
532      myUnqualifiedVersionId = null;
533      myResourceType = null;
534      myHaveComponentParts = true;
535    } else if (theValue.startsWith(URN_PREFIX)) {
536      myBaseUrl = null;
537      myUnqualifiedId = theValue;
538      myUnqualifiedVersionId = null;
539      myResourceType = null;
540      myHaveComponentParts = true;
541    } else {
542      int vidIndex = theValue.indexOf("/_history/");
543      int idIndex;
544      if (vidIndex != -1) {
545        myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
546        idIndex = theValue.lastIndexOf('/', vidIndex - 1);
547        myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
548      } else {
549        idIndex = theValue.lastIndexOf('/');
550        myUnqualifiedId = theValue.substring(idIndex + 1);
551        myUnqualifiedVersionId = null;
552      }
553
554      myBaseUrl = null;
555      if (idIndex <= 0) {
556        myResourceType = null;
557      } else {
558        int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
559        if (typeIndex == -1) {
560          myResourceType = theValue.substring(0, idIndex);
561        } else {
562          if (typeIndex > 0 && '/' == theValue.charAt(typeIndex - 1)) {
563            typeIndex = theValue.indexOf('/', typeIndex + 1);
564          }
565          if (typeIndex >= idIndex) {
566            // e.g. http://example.org/foo
567            // 'foo' was the id but we're making that the resource type. Nullify the id part because we don't have an id.
568            // Also set null value to the super.setValue() and enable myHaveComponentParts so it forces getValue() to properly
569            // recreate the url
570            myResourceType = myUnqualifiedId;
571            myUnqualifiedId = null;
572            super.setValue(null);
573            myHaveComponentParts = true;
574          } else {
575            myResourceType = theValue.substring(typeIndex + 1, idIndex);
576          }
577
578          if (typeIndex > 4) {
579            myBaseUrl = theValue.substring(0, typeIndex);
580          }
581
582        }
583      }
584
585    }
586    return this;
587  }
588
589  /**
590   * Set the value
591   *
592   * <p>
593   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
594   * represented in hex), a uuid, an oid, or any other combination of lowercase
595   * letters, numerals, "-" and ".", with a length limit of 36 characters.
596   * </p>
597   * <p>
598   * regex: [a-z0-9\-\.]{1,36}
599   * </p>
600   */
601  @Override
602  public void setValueAsString(String theValue) {
603    setValue(theValue);
604  }
605
606  @Override
607  public String toString() {
608    return getValue();
609  }
610
611  /**
612   * Returns a new IdType containing this IdType's values but with no server
613   * base URL if one is present in this IdType. For example, if this IdType
614   * contains the ID "http://foo/Patient/1", this method will return a new
615   * IdType containing ID "Patient/1".
616   */
617  @Override
618  public IdType toUnqualified() {
619    if (isLocal() || isUrn()) {
620      return new IdType(getValueAsString());
621    }
622    return new IdType(getResourceType(), getIdPart(), getVersionIdPart());
623  }
624
625  @Override
626  public IdType toUnqualifiedVersionless() {
627    if (isLocal() || isUrn()) {
628      return new IdType(getValueAsString());
629    }
630    return new IdType(getResourceType(), getIdPart());
631  }
632
633  @Override
634  public IdType toVersionless() {
635    if (isLocal() || isUrn()) {
636      return new IdType(getValueAsString());
637    }
638    return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null);
639  }
640
641  @Override
642  public IdType withResourceType(String theResourceName) {
643    if (isLocal() || isUrn()) {
644      return new IdType(getValueAsString());
645    }
646    return new IdType(theResourceName, getIdPart(), getVersionIdPart());
647  }
648
649  /**
650   * Returns a view of this ID as a fully qualified URL, given a server base and
651   * resource name (which will only be used if the ID does not already contain
652   * those respective parts). Essentially, because IdType can contain either a
653   * complete URL or a partial one (or even jut a simple ID), this method may be
654   * used to translate into a complete URL.
655   *
656   * @param theServerBase
657   *          The server base (e.g. "http://example.com/fhir")
658   * @param theResourceType
659   *          The resource name (e.g. "Patient")
660   * @return A fully qualified URL for this ID (e.g.
661   *         "http://example.com/fhir/Patient/1")
662   */
663  @Override
664  public IdType withServerBase(String theServerBase, String theResourceType) {
665    if (isLocal() || isUrn()) {
666      return new IdType(getValueAsString());
667    }
668    return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart());
669  }
670
671  /**
672   * Creates a new instance of this ID which is identical, but refers to the
673   * specific version of this resource ID noted by theVersion.
674   *
675   * @param theVersion The actual version string, e.g. "1". If theVersion is blank or null, returns the same as {@link #toVersionless()}}
676   * @return A new instance of IdType which is identical, but refers to the
677   *         specific version of this resource ID noted by theVersion.
678   */
679  @Override
680  public IdType withVersion(String theVersion) {
681    if (isBlank(theVersion)) {
682      return toVersionless();
683    }
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  @Override
764  public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
765    if (isNotBlank(theVersionIdPart)) {
766      Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
767      Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
768    }
769    if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
770      Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
771    }
772
773    setValue(null);
774
775    myBaseUrl = theBaseUrl;
776    myResourceType = theResourceType;
777    myUnqualifiedId = theIdPart;
778    myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
779    myHaveComponentParts = true;
780
781    return this;
782  }
783
784}