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