001package org.hl7.fhir.dstu2016may.model;
002
003import static org.apache.commons.lang3.StringUtils.defaultString;
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.ObjectUtils;
041import org.apache.commons.lang3.StringUtils;
042import org.apache.commons.lang3.Validate;
043import org.apache.commons.lang3.builder.HashCodeBuilder;
044import org.hl7.fhir.instance.model.api.IBaseResource;
045import org.hl7.fhir.instance.model.api.IIdType;
046import org.hl7.fhir.instance.model.api.IPrimitiveType;
047
048import ca.uhn.fhir.model.api.annotation.DatatypeDef;
049
050/**
051 * This class represents the logical identity for a resource, or as much of that
052 * identity is known. In FHIR, every resource must have a "logical ID" which is
053 * defined by the FHIR specification as:
054 * <p>
055 * <code>A whole number in the range 0 to 2^64-1 (optionally represented in hex), 
056 * a uuid, an oid, or any other combination of lowercase letters, numerals, "-" 
057 * and ".", with a length limit of 36 characters</code>
058 * </p>
059 * <p>
060 * This class contains that logical ID, and can optionally also contain a
061 * relative or absolute URL representing the resource identity. For example, the
062 * following are all valid values for IdType, and all might represent the same
063 * resource:
064 * </p>
065 * <ul>
066 * <li><code>123</code> (just a resource's ID)</li>
067 * <li><code>Patient/123</code> (a relative identity)</li>
068 * <li><code>http://example.com/Patient/123 (an absolute identity)</code></li>
069 * <li>
070 * <code>http://example.com/Patient/123/_history/1 (an absolute identity with a version id)</code>
071 * </li>
072 * <li>
073 * <code>Patient/123/_history/1 (a relative identity with a version id)</code>
074 * </li>
075 * </ul>
076 * <p>
077 * In most situations, you only need to populate the resource's ID (e.g.
078 * <code>123</code>) in resources you are constructing and the encoder will
079 * infer the rest from the context in which the object is being used. On the
080 * other hand, the parser will always try to populate the complete absolute
081 * identity on objects it creates as a convenience.
082 * </p>
083 * <p>
084 * Regex for ID: [a-z0-9\-\.]{1,36}
085 * </p>
086 */
087@DatatypeDef(name = "id", profileOf=StringType.class)
088public final class IdType extends UriType implements IPrimitiveType<String>, IIdType {
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 theResouce) {
226    if (theResouce == null) {
227      throw new NullPointerException("theResource can not be null");
228    } else {
229      theResouce.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    IdType id = (IdType) theArg0;
253    return StringUtils.equals(getValueAsString(), id.getValueAsString());
254  }
255
256  /**
257   * Returns true if this IdType matches the given IdType in terms of resource
258   * type and ID, but ignores the URL base
259   */
260  @SuppressWarnings("deprecation")
261  public boolean equalsIgnoreBase(IdType theId) {
262    if (theId == null) {
263      return false;
264    }
265    if (theId.isEmpty()) {
266      return isEmpty();
267    }
268    return ObjectUtils.equals(getResourceType(), theId.getResourceType())
269        && ObjectUtils.equals(getIdPart(), theId.getIdPart())
270        && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
271  }
272
273  /**
274   * Returns the portion of this resource ID which corresponds to the server
275   * base URL. For example given the resource ID
276   * <code>http://example.com/fhir/Patient/123</code> the base URL would be
277   * <code>http://example.com/fhir</code>.
278   * <p>
279   * This method may return null if the ID contains no base (e.g. "Patient/123")
280   * </p>
281   */
282  @Override
283  public String getBaseUrl() {
284    return myBaseUrl;
285  }
286
287  /**
288   * Returns only the logical ID part of this ID. For example, given the ID
289   * "http://example,.com/fhir/Patient/123/_history/456", this method would
290   * return "123".
291   */
292  @Override
293  public String getIdPart() {
294    return myUnqualifiedId;
295  }
296
297  /**
298   * Returns the unqualified portion of this ID as a big decimal, or
299   * <code>null</code> if the value is null
300   * 
301   * @throws NumberFormatException
302   *           If the value is not a valid BigDecimal
303   */
304  public BigDecimal getIdPartAsBigDecimal() {
305    String val = getIdPart();
306    if (isBlank(val)) {
307      return null;
308    }
309    return new BigDecimal(val);
310  }
311
312  /**
313   * Returns the unqualified portion of this ID as a {@link Long}, or
314   * <code>null</code> if the value is null
315   * 
316   * @throws NumberFormatException
317   *           If the value is not a valid Long
318   */
319  @Override
320  public Long getIdPartAsLong() {
321    String val = getIdPart();
322    if (isBlank(val)) {
323      return null;
324    }
325    return Long.parseLong(val);
326  }
327
328  @Override
329  public String getResourceType() {
330    return myResourceType;
331  }
332
333  /**
334   * Returns the value of this ID. Note that this value may be a fully qualified
335   * URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to
336   * get just the ID portion.
337   * 
338   * @see #getIdPart()
339   */
340  @Override
341  public String getValue() {
342    String retVal = super.getValue();
343    if (retVal == null && myHaveComponentParts) {
344
345      if (isLocal() || isUrn()) {
346        return myUnqualifiedId;
347      }
348
349      StringBuilder b = new StringBuilder();
350      if (isNotBlank(myBaseUrl)) {
351        b.append(myBaseUrl);
352        if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') {
353          b.append('/');
354        }
355      }
356
357      if (isNotBlank(myResourceType)) {
358        b.append(myResourceType);
359      }
360
361      if (b.length() > 0 && isNotBlank(myUnqualifiedId)) {
362        b.append('/');
363      }
364
365      if (isNotBlank(myUnqualifiedId)) {
366        b.append(myUnqualifiedId);
367      } else if (isNotBlank(myUnqualifiedVersionId)) {
368        b.append('/');
369      }
370
371      if (isNotBlank(myUnqualifiedVersionId)) {
372        b.append('/');
373        b.append("_history");
374        b.append('/');
375        b.append(myUnqualifiedVersionId);
376      }
377      retVal = b.toString();
378      super.setValue(retVal);
379    }
380    return retVal;
381  }
382
383  @Override
384  public String getValueAsString() {
385    return getValue();
386  }
387
388  @Override
389  public String getVersionIdPart() {
390    return myUnqualifiedVersionId;
391  }
392
393  public Long getVersionIdPartAsLong() {
394    if (!hasVersionIdPart()) {
395      return null;
396    } else {
397      return Long.parseLong(getVersionIdPart());
398    }
399  }
400
401  /**
402   * Returns true if this ID has a base url
403   * 
404   * @see #getBaseUrl()
405   */
406  public boolean hasBaseUrl() {
407    return isNotBlank(myBaseUrl);
408  }
409
410  @Override
411  public int hashCode() {
412    HashCodeBuilder b = new HashCodeBuilder();
413    b.append(getValueAsString());
414    return b.toHashCode();
415  }
416
417  @Override
418  public boolean hasIdPart() {
419    return isNotBlank(getIdPart());
420  }
421
422  @Override
423  public boolean hasResourceType() {
424    return isNotBlank(myResourceType);
425  }
426
427  @Override
428  public boolean hasVersionIdPart() {
429    return isNotBlank(getVersionIdPart());
430  }
431
432  /**
433   * Returns <code>true</code> if this ID contains an absolute URL (in other
434   * words, a URL starting with "http://" or "https://"
435   */
436  @Override
437  public boolean isAbsolute() {
438    if (StringUtils.isBlank(getValue())) {
439      return false;
440    }
441    return isUrlAbsolute(getValue());
442  }
443
444  @Override
445  public boolean isEmpty() {
446    return isBlank(getValue());
447  }
448
449  @Override
450  public boolean isIdPartValid() {
451    String id = getIdPart();
452    if (StringUtils.isBlank(id)) {
453      return false;
454    }
455    if (id.length() > 64) {
456      return false;
457    }
458    for (int i = 0; i < id.length(); i++) {
459      char nextChar = id.charAt(i);
460      if (nextChar >= 'a' && nextChar <= 'z') {
461        continue;
462      }
463      if (nextChar >= 'A' && nextChar <= 'Z') {
464        continue;
465      }
466      if (nextChar >= '0' && nextChar <= '9') {
467        continue;
468      }
469      if (nextChar == '-' || nextChar == '.') {
470        continue;
471      }
472      return false;
473    }
474    return true;
475  }
476
477        /**
478   * Returns <code>true</code> if the unqualified ID is a valid {@link Long}
479   * value (in other words, it consists only of digits)
480         */
481  @Override
482  public boolean isIdPartValidLong() {
483    return isValidLong(getIdPart());
484  }
485
486  /**
487   * Returns <code>true</code> if the ID is a local reference (in other words,
488   * it begins with the '#' character)
489   */
490  @Override
491  public boolean isLocal() {
492    return defaultString(myUnqualifiedId).startsWith("#");
493  }
494
495  private boolean isUrn() {
496    return defaultString(myUnqualifiedId).startsWith("urn:");
497  }
498
499  @Override
500  public boolean isVersionIdPartValidLong() {
501    return isValidLong(getVersionIdPart());
502  }
503
504  /**
505   * Set the value
506   * 
507   * <p>
508   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
509   * represented in hex), a uuid, an oid, or any other combination of lowercase
510   * letters, numerals, "-" and ".", with a length limit of 36 characters.
511   * </p>
512   * <p>
513   * regex: [a-z0-9\-\.]{1,36}
514   * </p>
515   */
516  @Override
517  public IdType setValue(String theValue) {
518    // TODO: add validation
519    super.setValue(theValue);
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:")) {
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
676   *          The actual version string, e.g. "1"
677   * @return A new instance of IdType which is identical, but refers to the
678   *         specific version of this resource ID noted by theVersion.
679   */
680  public IdType withVersion(String theVersion) {
681    Validate.notBlank(theVersion, "Version may not be null or empty");
682
683    if (isLocal() || isUrn()) {
684       return new IdType(getValueAsString());
685    }
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        public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
762                if (isNotBlank(theVersionIdPart)) {
763                        Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
764                        Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
765                }
766                if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
767                        Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
768                }
769                
770                setValue(null);
771                
772                myBaseUrl = theBaseUrl;
773                myResourceType = theResourceType;
774                myUnqualifiedId = theIdPart;
775                myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
776                myHaveComponentParts = true;
777                
778                return this;
779        }
780        
781}