001package org.hl7.fhir.dstu3.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    return StringUtils.equals(getValueAsString(), ((IdType)theArg0).getValueAsString());
253  }
254
255  /**
256   * Returns true if this IdType matches the given IdType in terms of resource
257   * type and ID, but ignores the URL base
258   */
259  @SuppressWarnings("deprecation")
260  public boolean equalsIgnoreBase(IdType theId) {
261    if (theId == null) {
262      return false;
263    }
264    if (theId.isEmpty()) {
265      return isEmpty();
266    }
267    return ObjectUtils.equals(getResourceType(), theId.getResourceType())
268        && ObjectUtils.equals(getIdPart(), theId.getIdPart())
269        && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
270  }
271
272  /**
273   * Returns the portion of this resource ID which corresponds to the server
274   * base URL. For example given the resource ID
275   * <code>http://example.com/fhir/Patient/123</code> the base URL would be
276   * <code>http://example.com/fhir</code>.
277   * <p>
278   * This method may return null if the ID contains no base (e.g. "Patient/123")
279   * </p>
280   */
281  @Override
282  public String getBaseUrl() {
283    return myBaseUrl;
284  }
285
286  /**
287   * Returns only the logical ID part of this ID. For example, given the ID
288   * "http://example,.com/fhir/Patient/123/_history/456", this method would
289   * return "123".
290   */
291  @Override
292  public String getIdPart() {
293    return myUnqualifiedId;
294  }
295
296  /**
297   * Returns the unqualified portion of this ID as a big decimal, or
298   * <code>null</code> if the value is null
299   * 
300   * @throws NumberFormatException
301   *           If the value is not a valid BigDecimal
302   */
303  public BigDecimal getIdPartAsBigDecimal() {
304    String val = getIdPart();
305    if (isBlank(val)) {
306      return null;
307    }
308    return new BigDecimal(val);
309  }
310
311  /**
312   * Returns the unqualified portion of this ID as a {@link Long}, or
313   * <code>null</code> if the value is null
314   * 
315   * @throws NumberFormatException
316   *           If the value is not a valid Long
317   */
318  @Override
319  public Long getIdPartAsLong() {
320    String val = getIdPart();
321    if (isBlank(val)) {
322      return null;
323    }
324    return Long.parseLong(val);
325  }
326
327  @Override
328  public String getResourceType() {
329    return myResourceType;
330  }
331
332  /**
333   * Returns the value of this ID. Note that this value may be a fully qualified
334   * URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to
335   * get just the ID portion.
336   * 
337   * @see #getIdPart()
338   */
339  @Override
340  public String getValue() {
341    String retVal = super.getValue();
342    if (retVal == null && myHaveComponentParts) {
343
344      if (isLocal() || isUrn()) {
345        return myUnqualifiedId;
346      }
347
348      StringBuilder b = new StringBuilder();
349      if (isNotBlank(myBaseUrl)) {
350        b.append(myBaseUrl);
351        if (myBaseUrl.charAt(myBaseUrl.length() - 1) != '/') {
352          b.append('/');
353        }
354      }
355
356      if (isNotBlank(myResourceType)) {
357        b.append(myResourceType);
358      }
359
360      if (b.length() > 0) {
361        b.append('/');
362      }
363
364      b.append(myUnqualifiedId);
365      if (isNotBlank(myUnqualifiedVersionId)) {
366        b.append('/');
367        b.append("_history");
368        b.append('/');
369        b.append(myUnqualifiedVersionId);
370      }
371      retVal = b.toString();
372      super.setValue(retVal);
373    }
374    return retVal;
375  }
376
377  @Override
378  public String getValueAsString() {
379    return getValue();
380  }
381
382  @Override
383  public String getVersionIdPart() {
384    return myUnqualifiedVersionId;
385  }
386
387  public Long getVersionIdPartAsLong() {
388    if (!hasVersionIdPart()) {
389      return null;
390    } else {
391      return Long.parseLong(getVersionIdPart());
392    }
393  }
394
395  /**
396   * Returns true if this ID has a base url
397   * 
398   * @see #getBaseUrl()
399   */
400  public boolean hasBaseUrl() {
401    return isNotBlank(myBaseUrl);
402  }
403
404  @Override
405  public int hashCode() {
406    HashCodeBuilder b = new HashCodeBuilder();
407    b.append(getValueAsString());
408    return b.toHashCode();
409  }
410
411  @Override
412  public boolean hasIdPart() {
413    return isNotBlank(getIdPart());
414  }
415
416  @Override
417  public boolean hasResourceType() {
418    return isNotBlank(myResourceType);
419  }
420
421  @Override
422  public boolean hasVersionIdPart() {
423    return isNotBlank(getVersionIdPart());
424  }
425
426  /**
427   * Returns <code>true</code> if this ID contains an absolute URL (in other
428   * words, a URL starting with "http://" or "https://"
429   */
430  @Override
431  public boolean isAbsolute() {
432    if (StringUtils.isBlank(getValue())) {
433      return false;
434    }
435    return isUrlAbsolute(getValue());
436  }
437
438  @Override
439  public boolean isEmpty() {
440    return isBlank(getValue());
441  }
442
443  @Override
444  public boolean isIdPartValid() {
445    String id = getIdPart();
446    if (StringUtils.isBlank(id)) {
447      return false;
448    }
449    if (id.length() > 64) {
450      return false;
451    }
452    for (int i = 0; i < id.length(); i++) {
453      char nextChar = id.charAt(i);
454      if (nextChar >= 'a' && nextChar <= 'z') {
455        continue;
456      }
457      if (nextChar >= 'A' && nextChar <= 'Z') {
458        continue;
459      }
460      if (nextChar >= '0' && nextChar <= '9') {
461        continue;
462      }
463      if (nextChar == '-' || nextChar == '.') {
464        continue;
465      }
466      return false;
467    }
468    return true;
469  }
470
471        /**
472   * Returns <code>true</code> if the unqualified ID is a valid {@link Long}
473   * value (in other words, it consists only of digits)
474         */
475  @Override
476  public boolean isIdPartValidLong() {
477    return isValidLong(getIdPart());
478  }
479
480  /**
481   * Returns <code>true</code> if the ID is a local reference (in other words,
482   * it begins with the '#' character)
483   */
484  @Override
485  public boolean isLocal() {
486    return defaultString(myUnqualifiedId).startsWith("#");
487  }
488
489  private boolean isUrn() {
490    return defaultString(myUnqualifiedId).startsWith("urn:");
491  }
492
493  @Override
494  public boolean isVersionIdPartValidLong() {
495    return isValidLong(getVersionIdPart());
496  }
497
498  /**
499   * Set the value
500   * 
501   * <p>
502   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
503   * represented in hex), a uuid, an oid, or any other combination of lowercase
504   * letters, numerals, "-" and ".", with a length limit of 36 characters.
505   * </p>
506   * <p>
507   * regex: [a-z0-9\-\.]{1,36}
508   * </p>
509   */
510  @Override
511  public IdType setValue(String theValue) {
512    // TODO: add validation
513    super.setValue(theValue);
514    myHaveComponentParts = false;
515
516    if (StringUtils.isBlank(theValue)) {
517      myBaseUrl = null;
518      super.setValue(null);
519      myUnqualifiedId = null;
520      myUnqualifiedVersionId = null;
521      myResourceType = null;
522    } else if (theValue.charAt(0) == '#' && theValue.length() > 1) {
523      super.setValue(theValue);
524      myBaseUrl = null;
525      myUnqualifiedId = theValue;
526      myUnqualifiedVersionId = null;
527      myResourceType = null;
528      myHaveComponentParts = true;
529    } else if (theValue.startsWith("urn:")) {
530      myBaseUrl = null;
531      myUnqualifiedId = theValue;
532      myUnqualifiedVersionId = null;
533      myResourceType = null;
534      myHaveComponentParts = true;
535    } else {
536      int vidIndex = theValue.indexOf("/_history/");
537      int idIndex;
538      if (vidIndex != -1) {
539        myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
540        idIndex = theValue.lastIndexOf('/', vidIndex - 1);
541        myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
542      } else {
543        idIndex = theValue.lastIndexOf('/');
544        myUnqualifiedId = theValue.substring(idIndex + 1);
545        myUnqualifiedVersionId = null;
546      }
547
548      myBaseUrl = null;
549      if (idIndex <= 0) {
550        myResourceType = null;
551      } else {
552        int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
553        if (typeIndex == -1) {
554          myResourceType = theValue.substring(0, idIndex);
555        } else {
556          myResourceType = theValue.substring(typeIndex + 1, idIndex);
557
558          if (typeIndex > 4) {
559            myBaseUrl = theValue.substring(0, typeIndex);
560          }
561
562        }
563      }
564
565    }
566    return this;
567  }
568
569  /**
570   * Set the value
571   * 
572   * <p>
573   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally
574   * represented in hex), a uuid, an oid, or any other combination of lowercase
575   * letters, numerals, "-" and ".", with a length limit of 36 characters.
576   * </p>
577   * <p>
578   * regex: [a-z0-9\-\.]{1,36}
579   * </p>
580   */
581  @Override
582  public void setValueAsString(String theValue) {
583    setValue(theValue);
584  }
585
586  @Override
587  public String toString() {
588    return getValue();
589  }
590
591  /**
592   * Returns a new IdType containing this IdType's values but with no server
593   * base URL if one is present in this IdType. For example, if this IdType
594   * contains the ID "http://foo/Patient/1", this method will return a new
595   * IdType containing ID "Patient/1".
596   */
597  @Override
598  public IdType toUnqualified() {
599    if (isLocal() || isUrn()) {
600       return new IdType(getValueAsString());
601    }
602    return new IdType(getResourceType(), getIdPart(), getVersionIdPart());
603  }
604
605  @Override
606  public IdType toUnqualifiedVersionless() {
607    if (isLocal() || isUrn()) {
608       return new IdType(getValueAsString());
609    }
610    return new IdType(getResourceType(), getIdPart());
611  }
612
613  @Override
614  public IdType toVersionless() {
615    if (isLocal() || isUrn()) {
616       return new IdType(getValueAsString());
617    }
618    return new IdType(getBaseUrl(), getResourceType(), getIdPart(), null);
619  }
620
621  @Override
622  public IdType withResourceType(String theResourceName) {
623    if (isLocal() || isUrn()) {
624       return new IdType(getValueAsString());
625    }
626    return new IdType(theResourceName, getIdPart(), getVersionIdPart());
627  }
628
629  /**
630   * Returns a view of this ID as a fully qualified URL, given a server base and
631   * resource name (which will only be used if the ID does not already contain
632   * those respective parts). Essentially, because IdType can contain either a
633   * complete URL or a partial one (or even jut a simple ID), this method may be
634   * used to translate into a complete URL.
635   * 
636   * @param theServerBase
637   *          The server base (e.g. "http://example.com/fhir")
638   * @param theResourceType
639   *          The resource name (e.g. "Patient")
640   * @return A fully qualified URL for this ID (e.g.
641   *         "http://example.com/fhir/Patient/1")
642   */
643  @Override
644  public IdType withServerBase(String theServerBase, String theResourceType) {
645    if (isLocal() || isUrn()) {
646       return new IdType(getValueAsString());
647    }
648    return new IdType(theServerBase, theResourceType, getIdPart(), getVersionIdPart());
649  }
650
651  /**
652   * Creates a new instance of this ID which is identical, but refers to the
653   * specific version of this resource ID noted by theVersion.
654   * 
655   * @param theVersion
656   *          The actual version string, e.g. "1"
657   * @return A new instance of IdType which is identical, but refers to the
658   *         specific version of this resource ID noted by theVersion.
659   */
660  @Override
661  public IdType withVersion(String theVersion) {
662    Validate.notBlank(theVersion, "Version may not be null or empty");
663
664    if (isLocal() || isUrn()) {
665       return new IdType(getValueAsString());
666    }
667
668    String existingValue = getValue();
669
670    int i = existingValue.indexOf("_history");
671    String value;
672    if (i > 1) {
673      value = existingValue.substring(0, i - 1);
674    } else {
675      value = existingValue;
676    }
677
678    return new IdType(value + '/' + "_history" + '/' + theVersion);
679  }
680
681  private static boolean isUrlAbsolute(String theValue) {
682    String value = theValue.toLowerCase();
683    return value.startsWith("http://") || value.startsWith("https://");
684  }
685
686  private static boolean isValidLong(String id) {
687    if (StringUtils.isBlank(id)) {
688      return false;
689    }
690    for (int i = 0; i < id.length(); i++) {
691      if (Character.isDigit(id.charAt(i)) == false) {
692        return false;
693      }
694    }
695    return true;
696  }
697
698  /**
699   * Construct a new ID with with form "urn:uuid:[UUID]" where [UUID] is a new,
700   * randomly created UUID generated by {@link UUID#randomUUID()}
701   */
702  public static IdType newRandomUuid() {
703    return new IdType("urn:uuid:" + UUID.randomUUID().toString());
704  }
705
706  /**
707   * Retrieves the ID from the given resource instance
708   */
709  public static IdType of(IBaseResource theResouce) {
710    if (theResouce == null) {
711      throw new NullPointerException("theResource can not be null");
712    } else {
713      IIdType retVal = theResouce.getIdElement();
714      if (retVal == null) {
715        return null;
716      } else if (retVal instanceof IdType) {
717        return (IdType) retVal;
718      } else {
719        return new IdType(retVal.getValue());
720      }
721    }
722  }
723
724  private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
725    if (theIdPart == null) {
726      throw new NullPointerException("BigDecimal ID can not be null");
727    }
728    return theIdPart.toPlainString();
729  }
730
731  private static String toPlainStringWithNpeThrowIfNeeded(Long theIdPart) {
732    if (theIdPart == null) {
733      throw new NullPointerException("Long ID can not be null");
734    }
735    return theIdPart.toString();
736  }
737
738        public String fhirType() {
739                return "id";
740        }
741        
742        public IIdType setParts(String theBaseUrl, String theResourceType, String theIdPart, String theVersionIdPart) {
743                if (isNotBlank(theVersionIdPart)) {
744                        Validate.notBlank(theResourceType, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
745                        Validate.notBlank(theIdPart, "If theVersionIdPart is populated, theResourceType and theIdPart must be populated");
746                }
747                if (isNotBlank(theBaseUrl) && isNotBlank(theIdPart)) {
748                        Validate.notBlank(theResourceType, "If theBaseUrl is populated and theIdPart is populated, theResourceType must be populated");
749                }
750                
751                setValue(null);
752                
753                myBaseUrl = theBaseUrl;
754                myResourceType = theResourceType;
755                myUnqualifiedId = theIdPart;
756                myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionIdPart, null);
757                myHaveComponentParts = true;
758                
759                return this;
760        }
761        
762}