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