001package ca.uhn.fhir.model.api; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.model.base.composite.BaseCodingDt; 024import ca.uhn.fhir.model.primitive.IdDt; 025import ca.uhn.fhir.model.primitive.InstantDt; 026import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 027import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; 028import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 029import org.apache.commons.lang3.StringUtils; 030import org.hl7.fhir.instance.model.api.IAnyResource; 031import org.hl7.fhir.instance.model.api.IPrimitiveType; 032 033import java.io.Serializable; 034import java.util.*; 035 036import static org.apache.commons.lang3.StringUtils.isNotBlank; 037 038/** 039 * Keys in this map refer to <b>resource metadata keys</b>, which are keys used to access information about specific resource instances that live outside of the resource body. Typically, these are 040 * data elements which are sent/receieved in HTTP Headers along with read/create resource requests, or properties which can be found in bundle entries. 041 * <p> 042 * To access or set resource metadata values, every resource has a metadata map, and this class provides convenient getters/setters for interacting with that map. For example, to get a resource's 043 * {@link #UPDATED} value, which is the "last updated" time for that resource, use the following code: 044 * </p> 045 * <p> 046 * <code>InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(resource);</code> 047 * <p> 048 * <p> 049 * To set this value, use the following: 050 * </p> 051 * <p> 052 * <code>InstantDt update = new InstantDt("2011-01-02T11:22:33.0000Z"); // populate with the actual time<br> 053 * ResourceMetadataKeyEnum.UPDATED.put(resource, update);</code> 054 * </p> 055 * <p> 056 * Note that this class is not a Java Enum, and can therefore be extended (this is why it is not actually an Enum). Users of HAPI-FHIR are able to create their own classes extending this class to 057 * define their own keys for storage in resource metadata if needed. 058 * </p> 059 */ 060public abstract class ResourceMetadataKeyEnum<T> implements Serializable { 061 062 /** 063 * If present and populated with a date/time (as an instance of {@link InstantDt}), this value is an indication that the resource is in the deleted state. This key is only used in a limited number 064 * of scenarios, such as POSTing transaction bundles to a server, or returning resource history. 065 * <p> 066 * Values for this key are of type <b>{@link InstantDt}</b> 067 * </p> 068 */ 069 public static final ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>> DELETED_AT = new ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>>("DELETED_AT") { 070 private static final long serialVersionUID = 1L; 071 072 @Override 073 public InstantDt get(IResource theResource) { 074 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), DELETED_AT); 075 } 076 077 @SuppressWarnings("unchecked") 078 @Override 079 public IPrimitiveType<Date> get(IAnyResource theResource) { 080 return (IPrimitiveType<Date>) theResource.getUserData(DELETED_AT.name()); 081 } 082 083 @Override 084 public void put(IResource theResource, InstantDt theObject) { 085 theResource.getResourceMetadata().put(DELETED_AT, theObject); 086 } 087 088 @Override 089 public void put(IAnyResource theResource, IPrimitiveType<Date> theObject) { 090 theResource.setUserData(DELETED_AT.name(), theObject); 091 } 092 }; 093 094 /** 095 * If present and populated with a {@link BundleEntrySearchModeEnum}, contains the "bundle entry search mode", which is the value of the status field in the Bundle entry containing this resource. 096 * The value for this key corresponds to field <code>Bundle.entry.search.mode</code>. This value can be set to provide a status value of "include" for included resources being returned by a 097 * server, or to "match" to indicate that the resource was returned because it matched the given search criteria. 098 * <p> 099 * Note that status is only used in FHIR DSTU2 and later. 100 * </p> 101 * <p> 102 * Values for this key are of type <b>{@link BundleEntrySearchModeEnum}</b> 103 * </p> 104 */ 105 public static final ResourceMetadataKeySupportingAnyResource<BundleEntrySearchModeEnum, String> ENTRY_SEARCH_MODE = new ResourceMetadataKeySupportingAnyResource<BundleEntrySearchModeEnum, String>("ENTRY_SEARCH_MODE") { 106 private static final long serialVersionUID = 1L; 107 108 @Override 109 public BundleEntrySearchModeEnum get(IResource theResource) { 110 return getEnumFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SEARCH_MODE, BundleEntrySearchModeEnum.class, BundleEntrySearchModeEnum.VALUESET_BINDER); 111 } 112 113 @Override 114 public String get(IAnyResource theResource) { 115 return (String) theResource.getUserData(ENTRY_SEARCH_MODE.name()); 116 } 117 118 @Override 119 public void put(IResource theResource, BundleEntrySearchModeEnum theObject) { 120 theResource.getResourceMetadata().put(ENTRY_SEARCH_MODE, theObject); 121 } 122 123 @Override 124 public void put(IAnyResource theResource, String theObject) { 125 theResource.setUserData(ENTRY_SEARCH_MODE.name(), theObject); 126 } 127 }; 128 /** 129 * If present and populated with a {@link BundleEntryTransactionMethodEnum}, contains the "bundle entry transaction operation", which is the value of the status field in the Bundle entry 130 * containing this resource. The value for this key corresponds to field <code>Bundle.entry.transaction.operation</code>. This value can be set in resources being transmitted to a server to 131 * provide a status value of "create" or "update" to indicate behaviour the server should observe. It may also be set to similar values (or to "noop") in resources being returned by a server as a 132 * result of a transaction to indicate to the client what operation was actually performed. 133 * <p> 134 * Note that status is only used in FHIR DSTU2 and later. 135 * </p> 136 * <p> 137 * Values for this key are of type <b>{@link BundleEntryTransactionMethodEnum}</b> 138 * </p> 139 */ 140 public static final ResourceMetadataKeySupportingAnyResource<BundleEntryTransactionMethodEnum, String> ENTRY_TRANSACTION_METHOD = new ResourceMetadataKeySupportingAnyResource<BundleEntryTransactionMethodEnum, String>( 141 "ENTRY_TRANSACTION_OPERATION") { 142 private static final long serialVersionUID = 1L; 143 144 @Override 145 public BundleEntryTransactionMethodEnum get(IResource theResource) { 146 return getEnumFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_TRANSACTION_METHOD, BundleEntryTransactionMethodEnum.class, 147 BundleEntryTransactionMethodEnum.VALUESET_BINDER); 148 } 149 150 @Override 151 public String get(IAnyResource theResource) { 152 return (String) theResource.getUserData(ENTRY_TRANSACTION_METHOD.name()); 153 } 154 155 @Override 156 public void put(IResource theResource, BundleEntryTransactionMethodEnum theObject) { 157 theResource.getResourceMetadata().put(ENTRY_TRANSACTION_METHOD, theObject); 158 } 159 160 @Override 161 public void put(IAnyResource theResource, String theObject) { 162 theResource.setUserData(ENTRY_TRANSACTION_METHOD.name(), theObject); 163 } 164 165 }; 166 167 /** 168 * The value for this key represents a {@link List} of profile IDs that this resource claims to conform to. 169 * <p> 170 * <p> 171 * Values for this key are of type <b>List<IdDt></b>. Note that the returned list is <i>unmodifiable</i>, so you need to create a new list and call <code>put</code> to change its value. 172 * </p> 173 */ 174 public static final ResourceMetadataKeyEnum<List<IdDt>> PROFILES = new ResourceMetadataKeyEnum<List<IdDt>>("PROFILES") { 175 private static final long serialVersionUID = 1L; 176 177 @Override 178 public List<IdDt> get(IResource theResource) { 179 return getIdListFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PROFILES); 180 } 181 182 @Override 183 public void put(IResource theResource, List<IdDt> theObject) { 184 theResource.getResourceMetadata().put(PROFILES, theObject); 185 } 186 }; 187 /** 188 * The value for this key is the bundle entry <b>Published</b> time. This is defined by FHIR as "Time resource copied into the feed", which is generally best left to the current time. 189 * <p> 190 * Values for this key are of type <b>{@link InstantDt}</b> 191 * </p> 192 * <p> 193 * <b>Server Note</b>: In servers, it is generally advisable to leave this value <code>null</code>, in which case the server will substitute the current time automatically. 194 * </p> 195 * 196 * @see InstantDt 197 */ 198 public static final ResourceMetadataKeyEnum<InstantDt> PUBLISHED = new ResourceMetadataKeyEnum<InstantDt>("PUBLISHED") { 199 private static final long serialVersionUID = 1L; 200 201 @Override 202 public InstantDt get(IResource theResource) { 203 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PUBLISHED); 204 } 205 206 @Override 207 public void put(IResource theResource, InstantDt theObject) { 208 theResource.getResourceMetadata().put(PUBLISHED, theObject); 209 } 210 }; 211 public static final ResourceMetadataKeyEnum<List<BaseCodingDt>> SECURITY_LABELS = new ResourceMetadataKeyEnum<List<BaseCodingDt>>("SECURITY_LABELS") { 212 private static final long serialVersionUID = 1L; 213 214 @Override 215 public List<BaseCodingDt> get(IResource resource) { 216 Object obj = resource.getResourceMetadata().get(SECURITY_LABELS); 217 if (obj == null) { 218 return null; 219 } 220 //noinspection unchecked 221 return (List<BaseCodingDt>) obj; 222 } 223 224 @Override 225 public void put(IResource iResource, List<BaseCodingDt> labels) { 226 iResource.getResourceMetadata().put(SECURITY_LABELS, labels); 227 } 228 229 }; 230 /** 231 * The value for this key is the list of tags associated with this resource 232 * <p> 233 * Values for this key are of type <b>{@link TagList}</b> 234 * </p> 235 * 236 * @see TagList 237 */ 238 public static final ResourceMetadataKeyEnum<TagList> TAG_LIST = new ResourceMetadataKeyEnum<TagList>("TAG_LIST") { 239 private static final long serialVersionUID = 1L; 240 241 @Override 242 public TagList get(IResource theResource) { 243 Object retValObj = theResource.getResourceMetadata().get(TAG_LIST); 244 if (retValObj == null) { 245 return null; 246 } else { 247 return (TagList) retValObj; 248 } 249 } 250 251 @Override 252 public void put(IResource theResource, TagList theObject) { 253 theResource.getResourceMetadata().put(TAG_LIST, theObject); 254 } 255 }; 256 /** 257 * The value for this key is the bundle entry <b>Updated</b> time. This is defined by FHIR as "Last Updated for resource". This value is also used for populating the "Last-Modified" header in the 258 * case of methods that return a single resource (read, vread, etc.) 259 * <p> 260 * Values for this key are of type <b>{@link InstantDt}</b> 261 * </p> 262 * 263 * @see InstantDt 264 */ 265 public static final ResourceMetadataKeyEnum<InstantDt> UPDATED = new ResourceMetadataKeyEnum<InstantDt>("UPDATED") { 266 private static final long serialVersionUID = 1L; 267 268 @Override 269 public InstantDt get(IResource theResource) { 270 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), UPDATED); 271 } 272 273 @Override 274 public void put(IResource theResource, InstantDt theObject) { 275 theResource.getResourceMetadata().put(UPDATED, theObject); 276 } 277 }; 278 /** 279 * The value for this key is the version ID of the resource object. 280 * <p> 281 * Values for this key are of type <b>{@link String}</b> 282 * </p> 283 * 284 * @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method 285 */ 286 @Deprecated 287 public static final ResourceMetadataKeyEnum<String> VERSION = new ResourceMetadataKeyEnum<String>("VERSION") { 288 private static final long serialVersionUID = 1L; 289 290 @Override 291 public String get(IResource theResource) { 292 return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION); 293 } 294 295 @Override 296 public void put(IResource theResource, String theObject) { 297 theResource.getResourceMetadata().put(VERSION, theObject); 298 } 299 }; 300 /** 301 * The value for this key is the version ID of the resource object. 302 * <p> 303 * Values for this key are of type <b>{@link IdDt}</b> 304 * </p> 305 * 306 * @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method 307 */ 308 @Deprecated 309 public static final ResourceMetadataKeyEnum<IdDt> VERSION_ID = new ResourceMetadataKeyEnum<IdDt>("VERSION_ID") { 310 private static final long serialVersionUID = 1L; 311 312 @Override 313 public IdDt get(IResource theResource) { 314 return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata()); 315 } 316 317 @Override 318 public void put(IResource theResource, IdDt theObject) { 319 theResource.getResourceMetadata().put(VERSION_ID, theObject); 320 } 321 }; 322 private static final long serialVersionUID = 1L; 323 private final String myValue; 324 325 public ResourceMetadataKeyEnum(String theValue) { 326 myValue = theValue; 327 } 328 329 @Override 330 public boolean equals(Object obj) { 331 if (this == obj) 332 return true; 333 if (obj == null) 334 return false; 335 if (getClass() != obj.getClass()) 336 return false; 337 ResourceMetadataKeyEnum<?> other = (ResourceMetadataKeyEnum<?>) obj; 338 if (myValue == null) { 339 if (other.myValue != null) 340 return false; 341 } else if (!myValue.equals(other.myValue)) 342 return false; 343 return true; 344 } 345 346 public abstract T get(IResource theResource); 347 348 @Override 349 public int hashCode() { 350 final int prime = 31; 351 int result = 1; 352 result = prime * result + ((myValue == null) ? 0 : myValue.hashCode()); 353 return result; 354 } 355 356 public String name() { 357 return myValue; 358 } 359 360 public abstract void put(IResource theResource, T theObject); 361 362 public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> { 363 364 private static final long serialVersionUID = 1L; 365 366 public ResourceMetadataKeySupportingAnyResource(String theValue) { 367 super(theValue); 368 } 369 370 public abstract T2 get(IAnyResource theResource); 371 372 public abstract void put(IAnyResource theResource, T2 theObject); 373 374 } 375 376 public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> { 377 public ExtensionResourceMetadataKey(String theUrl) { 378 super(theUrl); 379 } 380 381 @Override 382 public ExtensionDt get(IResource theResource) { 383 Object retValObj = theResource.getResourceMetadata().get(this); 384 if (retValObj == null) { 385 return null; 386 } else if (retValObj instanceof ExtensionDt) { 387 return (ExtensionDt) retValObj; 388 } 389 throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() 390 + "' in resource metadata for key " + this.name() + " - Expected " 391 + ExtensionDt.class.getCanonicalName()); 392 } 393 394 @Override 395 public void put(IResource theResource, ExtensionDt theObject) { 396 theResource.getResourceMetadata().put(this, theObject); 397 } 398 } 399 400 401 @SuppressWarnings("unchecked") 402 private static <T extends Enum<?>> T getEnumFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<T> theKey, Class<T> theEnumType, 403 IValueSetEnumBinder<T> theBinder) { 404 Object retValObj = theResourceMetadata.get(theKey); 405 if (retValObj == null) { 406 return null; 407 } else if (theEnumType.equals(retValObj.getClass())) { 408 return (T) retValObj; 409 } else if (retValObj instanceof String) { 410 return theBinder.fromCodeString((String) retValObj); 411 } 412 throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 413 + InstantDt.class.getCanonicalName()); 414 } 415 416 private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata) { 417 return toId(ResourceMetadataKeyEnum.VERSION_ID, theResourceMetadata.get(ResourceMetadataKeyEnum.VERSION_ID)); 418 } 419 420 private static List<IdDt> getIdListFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) { 421 Object retValObj = theResourceMetadata.get(theKey); 422 if (retValObj == null) { 423 return null; 424 } else if (retValObj instanceof List) { 425 List<?> retValList = (List<?>) retValObj; 426 for (Object next : retValList) { 427 if (!(next instanceof IdDt)) { 428 List<IdDt> retVal = new ArrayList<IdDt>(); 429 for (Object nextVal : retValList) { 430 retVal.add(toId(theKey, nextVal)); 431 } 432 return Collections.unmodifiableList(retVal); 433 } 434 } 435 @SuppressWarnings("unchecked") 436 List<IdDt> retVal = (List<IdDt>) retValList; 437 return Collections.unmodifiableList(retVal); 438 } else { 439 return Collections.singletonList(toId(theKey, retValObj)); 440 } 441 } 442 443 private static InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) { 444 Object retValObj = theResourceMetadata.get(theKey); 445 if (retValObj == null) { 446 return null; 447 } else if (retValObj instanceof Date) { 448 return new InstantDt((Date) retValObj); 449 } else if (retValObj instanceof InstantDt) { 450 if (((InstantDt) retValObj).isEmpty()) { 451 return null; 452 } 453 return (InstantDt) retValObj; 454 } 455 throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 456 + InstantDt.class.getCanonicalName()); 457 } 458 459 private static String getStringFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<String> theKey) { 460 Object retValObj = theResourceMetadata.get(theKey); 461 if (retValObj == null) { 462 return null; 463 } else if (retValObj instanceof String) { 464 if (StringUtils.isBlank(((String) retValObj))) { 465 return null; 466 } 467 return (String) retValObj; 468 } 469 throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 470 + String.class.getCanonicalName()); 471 } 472 473 private static IdDt toId(ResourceMetadataKeyEnum<?> theKey, Object retValObj) { 474 if (retValObj == null) { 475 return null; 476 } else if (retValObj instanceof String) { 477 if (isNotBlank((String) retValObj)) { 478 return new IdDt((String) retValObj); 479 } 480 return null; 481 } else if (retValObj instanceof IdDt) { 482 if (((IdDt) retValObj).isEmpty()) { 483 return null; 484 } 485 return (IdDt) retValObj; 486 } else if (retValObj instanceof Number) { 487 return new IdDt(retValObj.toString()); 488 } 489 throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 490 + IdDt.class.getCanonicalName()); 491 } 492 493 494}