View Javadoc
1   package ca.uhn.fhir.model.primitive;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import static org.apache.commons.lang3.StringUtils.*;
24  
25  import java.math.BigDecimal;
26  
27  import org.apache.commons.lang3.ObjectUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.lang3.Validate;
30  
31  import ca.uhn.fhir.model.api.BasePrimitive;
32  import ca.uhn.fhir.model.api.annotation.DatatypeDef;
33  import ca.uhn.fhir.model.api.annotation.SimpleSetter;
34  import ca.uhn.fhir.parser.DataFormatException;
35  import ca.uhn.fhir.rest.server.Constants;
36  import ca.uhn.fhir.util.UrlUtil;
37  
38  /**
39   * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource.
40   * 
41   * <p>
42   * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
43   * limit of 36 characters.
44   * </p>
45   * <p>
46   * regex: [a-z0-9\-\.]{1,36}
47   * </p>
48   */
49  @DatatypeDef(name = "id")
50  public class IdDt extends BasePrimitive<String> {
51  
52  	private String myBaseUrl;
53  	private boolean myHaveComponentParts;
54  	private String myResourceType;
55  	private String myUnqualifiedId;
56  	private String myUnqualifiedVersionId;
57  	private volatile String myValue;
58  
59  	/**
60  	 * Create a new empty ID
61  	 */
62  	public IdDt() {
63  		super();
64  	}
65  
66  	/**
67  	 * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation.
68  	 */
69  	public IdDt(BigDecimal thePid) {
70  		if (thePid != null) {
71  			setValue(toPlainStringWithNpeThrowIfNeeded(thePid));
72  		} else {
73  			setValue(null);
74  		}
75  	}
76  
77  	/**
78  	 * Create a new ID using a long
79  	 */
80  	public IdDt(long theId) {
81  		setValue(Long.toString(theId));
82  	}
83  
84  	/**
85  	 * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete URL (http://example.com/fhir/Patient/1234).
86  	 * 
87  	 * <p>
88  	 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
89  	 * limit of 36 characters.
90  	 * </p>
91  	 * <p>
92  	 * regex: [a-z0-9\-\.]{1,36}
93  	 * </p>
94  	 */
95  	@SimpleSetter
96  	public IdDt(@SimpleSetter.Parameter(name = "theId") String theValue) {
97  		setValue(theValue);
98  	}
99  
100 	/**
101 	 * Constructor
102 	 * 
103 	 * @param theResourceType
104 	 *            The resource type (e.g. "Patient")
105 	 * @param theId
106 	 *            The ID (e.g. "123")
107 	 */
108 	public IdDt(String theResourceType, BigDecimal theIdPart) {
109 		this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart));
110 	}
111 
112 	/**
113 	 * Constructor
114 	 * 
115 	 * @param theResourceType
116 	 *            The resource type (e.g. "Patient")
117 	 * @param theId
118 	 *            The ID (e.g. "123")
119 	 */
120 	public IdDt(String theResourceType, String theId) {
121 		this(theResourceType, theId, null);
122 	}
123 
124 	/**
125 	 * Constructor
126 	 * 
127 	 * @param theResourceType
128 	 *            The resource type (e.g. "Patient")
129 	 * @param theId
130 	 *            The ID (e.g. "123")
131 	 * @param theVersionId
132 	 *            The version ID ("e.g. "456")
133 	 */
134 	public IdDt(String theResourceType, String theId, String theVersionId) {
135 		myResourceType = theResourceType;
136 		myUnqualifiedId = theId;
137 		myUnqualifiedVersionId = StringUtils.defaultIfBlank(theVersionId, null);
138 		myHaveComponentParts = true;
139 	}
140 
141 	/**
142 	 * Creates an ID based on a given URL
143 	 */
144 	public IdDt(UriDt theUrl) {
145 		setValue(theUrl.getValueAsString());
146 	}
147 
148 	/**
149 	 * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous)
150 	 */
151 	public BigDecimal asBigDecimal() {
152 		return getIdPartAsBigDecimal();
153 	}
154 
155 	/**
156 	 * Returns true if this IdDt matches the given IdDt in terms of resource type and ID, but ignores the URL base
157 	 */
158 	@SuppressWarnings("deprecation")
159 	public boolean equalsIgnoreBase(IdDt theId) {
160 		if (theId == null) {
161 			return false;
162 		}
163 		if (theId.isEmpty()) {
164 			return isEmpty();
165 		}
166 		return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
167 	}
168 
169 	/**
170 	 * Returns the portion of this resource ID which corresponds to the server base URL. For example given the resource ID <code>http://example.com/fhir/Patient/123</code> the base URL would be
171 	 * <code>http://example.com/fhir</code>.
172 	 * <p>
173 	 * This method may return null if the ID contains no base (e.g. "Patient/123")
174 	 * </p>
175 	 */
176 	public String getBaseUrl() {
177 		return myBaseUrl;
178 	}
179 
180 	public String getIdPart() {
181 		return myUnqualifiedId;
182 	}
183 
184 	/**
185 	 * Returns the unqualified portion of this ID as a big decimal, or <code>null</code> if the value is null
186 	 * 
187 	 * @throws NumberFormatException
188 	 *             If the value is not a valid BigDecimal
189 	 */
190 	public BigDecimal getIdPartAsBigDecimal() {
191 		String val = getIdPart();
192 		if (isBlank(val)) {
193 			return null;
194 		}
195 		return new BigDecimal(val);
196 	}
197 
198 	/**
199 	 * Returns the unqualified portion of this ID as a {@link Long}, or <code>null</code> if the value is null
200 	 * 
201 	 * @throws NumberFormatException
202 	 *             If the value is not a valid Long
203 	 */
204 	public Long getIdPartAsLong() {
205 		String val = getIdPart();
206 		if (isBlank(val)) {
207 			return null;
208 		}
209 		return Long.parseLong(val);
210 	}
211 
212 	public String getResourceType() {
213 		return myResourceType;
214 	}
215 
216 	/**
217 	 * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get just the ID portion.
218 	 * 
219 	 * @see #getIdPart()
220 	 */
221 	@Override
222 	public String getValue() {
223 		if (myValue == null && myHaveComponentParts) {
224 			StringBuilder b = new StringBuilder();
225 			if (isNotBlank(myResourceType)) {
226 				b.append(myResourceType);
227 				b.append('/');
228 			}
229 			b.append(myUnqualifiedId);
230 			if (isNotBlank(myUnqualifiedVersionId)) {
231 				b.append('/');
232 				b.append(Constants.PARAM_HISTORY);
233 				b.append('/');
234 				b.append(myUnqualifiedVersionId);
235 			}
236 			myValue = b.toString();
237 		}
238 		return myValue;
239 	}
240 
241 	@Override
242 	public String getValueAsString() {
243 		return getValue();
244 	}
245 
246 	public String getVersionIdPart() {
247 		return myUnqualifiedVersionId;
248 	}
249 
250 	public Long getVersionIdPartAsLong() {
251 		if (!hasVersionIdPart()) {
252 			return null;
253 		} else {
254 			return Long.parseLong(getVersionIdPart());
255 		}
256 	}
257 
258 	/**
259 	 * Returns true if this ID has a base url
260 	 * 
261 	 * @see #getBaseUrl()
262 	 */
263 	public boolean hasBaseUrl() {
264 		return isNotBlank(myBaseUrl);
265 	}
266 
267 	public boolean hasIdPart() {
268 		return isNotBlank(getIdPart());
269 	}
270 
271 	public boolean hasResourceType() {
272 		return isNotBlank(myResourceType);
273 	}
274 
275 	public boolean hasVersionIdPart() {
276 		return isNotBlank(getVersionIdPart());
277 	}
278 
279 	/**
280 	 * Returns <code>true</code> if this ID contains an absolute URL (in other words, a URL starting with "http://" or "https://"
281 	 */
282 	public boolean isAbsolute() {
283 		if (StringUtils.isBlank(getValue())) {
284 			return false;
285 		}
286 		return UrlUtil.isAbsolute(getValue());
287 	}
288 
289 	/**
290 	 * Returns <code>true</code> if the unqualified ID is a valid {@link Long} value (in other words, it consists only of digits)
291 	 */
292 	public boolean isIdPartValidLong() {
293 		String id = getIdPart();
294 		if (StringUtils.isBlank(id)) {
295 			return false;
296 		}
297 		for (int i = 0; i < id.length(); i++) {
298 			if (Character.isDigit(id.charAt(i)) == false) {
299 				return false;
300 			}
301 		}
302 		return true;
303 	}
304 
305 	/**
306 	 * Returns <code>true</code> if the ID is a local reference (in other words, it begins with the '#' character)
307 	 */
308 	public boolean isLocal() {
309 		return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
310 	}
311 
312 	/**
313 	 * Copies the value from the given IdDt to <code>this</code> IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API.
314 	 */
315 	@Override
316 	public void setId(IdDt theId) {
317 		setValue(theId.getValue());
318 	}
319 
320 	/**
321 	 * Set the value
322 	 * 
323 	 * <p>
324 	 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
325 	 * limit of 36 characters.
326 	 * </p>
327 	 * <p>
328 	 * regex: [a-z0-9\-\.]{1,36}
329 	 * </p>
330 	 */
331 	@Override
332 	public void setValue(String theValue) throws DataFormatException {
333 		// TODO: add validation
334 		myValue = theValue;
335 		myHaveComponentParts = false;
336 		if (StringUtils.isBlank(theValue)) {
337 			myValue = null;
338 			myUnqualifiedId = null;
339 			myUnqualifiedVersionId = null;
340 			myResourceType = null;
341 		} else {
342 			int vidIndex = theValue.indexOf("/_history/");
343 			int idIndex;
344 			if (vidIndex != -1) {
345 				myUnqualifiedVersionId = theValue.substring(vidIndex + "/_history/".length());
346 				idIndex = theValue.lastIndexOf('/', vidIndex - 1);
347 				myUnqualifiedId = theValue.substring(idIndex + 1, vidIndex);
348 			} else {
349 				idIndex = theValue.lastIndexOf('/');
350 				myUnqualifiedId = theValue.substring(idIndex + 1);
351 				myUnqualifiedVersionId = null;
352 			}
353 
354 			myBaseUrl = null;
355 			if (idIndex <= 0) {
356 				myResourceType = null;
357 			} else {
358 				int typeIndex = theValue.lastIndexOf('/', idIndex - 1);
359 				if (typeIndex == -1) {
360 					myResourceType = theValue.substring(0, idIndex);
361 				} else {
362 					myResourceType = theValue.substring(typeIndex + 1, idIndex);
363 
364 					if (typeIndex > 4) {
365 						myBaseUrl = theValue.substring(0, typeIndex);
366 					}
367 
368 				}
369 			}
370 
371 		}
372 	}
373 
374 	/**
375 	 * Set the value
376 	 * 
377 	 * <p>
378 	 * <b>Description</b>: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length
379 	 * limit of 36 characters.
380 	 * </p>
381 	 * <p>
382 	 * regex: [a-z0-9\-\.]{1,36}
383 	 * </p>
384 	 */
385 	@Override
386 	public void setValueAsString(String theValue) throws DataFormatException {
387 		setValue(theValue);
388 	}
389 
390 	@Override
391 	public String toString() {
392 		return getValue();
393 	}
394 
395 	public IdDt toUnqualified() {
396 		return new IdDt(getResourceType(), getIdPart(), getVersionIdPart());
397 	}
398 
399 	public IdDt toUnqualifiedVersionless() {
400 		return new IdDt(getResourceType(), getIdPart());
401 	}
402 
403 	public IdDt toVersionless() {
404 		String value = getValue();
405 		int i = value.indexOf(Constants.PARAM_HISTORY);
406 		if (i > 1) {
407 			return new IdDt(value.substring(0, i - 1));
408 		} else {
409 			return this;
410 		}
411 	}
412 
413 	public IdDt withResourceType(String theResourceName) {
414 		return new IdDt(theResourceName, getIdPart(), getVersionIdPart());
415 	}
416 
417 	/**
418 	 * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be used if the ID does not already contain those respective parts). Essentially,
419 	 * because IdDt can contain either a complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete URL.
420 	 * 
421 	 * @param theServerBase
422 	 *            The server base (e.g. "http://example.com/fhir")
423 	 * @param theResourceType
424 	 *            The resource name (e.g. "Patient")
425 	 * @return A fully qualified URL for this ID (e.g. "http://example.com/fhir/Patient/1")
426 	 */
427 	public String withServerBase(String theServerBase, String theResourceType) {
428 		if (getValue().startsWith("http")) {
429 			return getValue();
430 		}
431 		StringBuilder retVal = new StringBuilder();
432 		retVal.append(theServerBase);
433 		if (retVal.charAt(retVal.length() - 1) != '/') {
434 			retVal.append('/');
435 		}
436 		if (isNotBlank(getResourceType())) {
437 			retVal.append(getResourceType());
438 		} else {
439 			retVal.append(theResourceType);
440 		}
441 		retVal.append('/');
442 		retVal.append(getIdPart());
443 
444 		if (hasVersionIdPart()) {
445 			retVal.append('/');
446 			retVal.append(Constants.PARAM_HISTORY);
447 			retVal.append('/');
448 			retVal.append(getVersionIdPart());
449 		}
450 
451 		return retVal.toString();
452 	}
453 
454 	/**
455 	 * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion.
456 	 * 
457 	 * @param theVersion
458 	 *            The actual version string, e.g. "1"
459 	 * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion.
460 	 */
461 	public IdDt withVersion(String theVersion) {
462 		Validate.notBlank(theVersion, "Version may not be null or empty");
463 
464 		String existingValue = getValue();
465 
466 		int i = existingValue.indexOf(Constants.PARAM_HISTORY);
467 		String value;
468 		if (i > 1) {
469 			value = existingValue.substring(0, i - 1);
470 		} else {
471 			value = existingValue;
472 		}
473 
474 		return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion);
475 	}
476 
477 	private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) {
478 		if (theIdPart == null) {
479 			throw new NullPointerException("BigDecimal ID can not be null");
480 		}
481 		return theIdPart.toPlainString();
482 	}
483 
484 }