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