
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.i18n.Msg; 024import ca.uhn.fhir.model.base.composite.BaseCodingDt; 025import ca.uhn.fhir.model.primitive.IdDt; 026import ca.uhn.fhir.model.primitive.InstantDt; 027import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 028import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; 029import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 030import org.apache.commons.lang3.StringUtils; 031import org.hl7.fhir.instance.model.api.IAnyResource; 032import org.hl7.fhir.instance.model.api.IPrimitiveType; 033 034import java.io.Serializable; 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.Date; 038import java.util.List; 039import java.util.Map; 040 041import static org.apache.commons.lang3.StringUtils.isNotBlank; 042 043/** 044 * 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 045 * data elements which are sent/receieved in HTTP Headers along with read/create resource requests, or properties which can be found in bundle entries. 046 * <p> 047 * 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 048 * {@link #UPDATED} value, which is the "last updated" time for that resource, use the following code: 049 * </p> 050 * <p> 051 * <code>InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(resource);</code> 052 * <p> 053 * <p> 054 * To set this value, use the following: 055 * </p> 056 * <p> 057 * <code>InstantDt update = new InstantDt("2011-01-02T11:22:33.0000Z"); // populate with the actual time<br> 058 * ResourceMetadataKeyEnum.UPDATED.put(resource, update);</code> 059 * </p> 060 * <p> 061 * 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 062 * define their own keys for storage in resource metadata if needed. 063 * </p> 064 */ 065public abstract class ResourceMetadataKeyEnum<T> implements Serializable { 066 067 /** 068 * 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 069 * of scenarios, such as POSTing transaction bundles to a server, or returning resource history. 070 * <p> 071 * Values for this key are of type <b>{@link InstantDt}</b> 072 * </p> 073 */ 074 public static final ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>> DELETED_AT = new ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>>("DELETED_AT") { 075 private static final long serialVersionUID = 1L; 076 077 @Override 078 public InstantDt get(IResource theResource) { 079 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), DELETED_AT); 080 } 081 082 @SuppressWarnings("unchecked") 083 @Override 084 public IPrimitiveType<Date> get(IAnyResource theResource) { 085 return (IPrimitiveType<Date>) theResource.getUserData(DELETED_AT.name()); 086 } 087 088 @Override 089 public void put(IResource theResource, InstantDt theObject) { 090 theResource.getResourceMetadata().put(DELETED_AT, theObject); 091 } 092 093 @Override 094 public void put(IAnyResource theResource, IPrimitiveType<Date> theObject) { 095 theResource.setUserData(DELETED_AT.name(), theObject); 096 } 097 }; 098 099 /** 100 * 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. 101 * 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 102 * server, or to "match" to indicate that the resource was returned because it matched the given search criteria. 103 * <p> 104 * Note that status is only used in FHIR DSTU2 and later. 105 * </p> 106 * <p> 107 * Values for this key are of type <b>{@link BundleEntrySearchModeEnum}</b> 108 * </p> 109 */ 110 public static final ResourceMetadataKeySupportingAnyResource<BundleEntrySearchModeEnum, String> ENTRY_SEARCH_MODE = new ResourceMetadataKeySupportingAnyResource<BundleEntrySearchModeEnum, String>("ENTRY_SEARCH_MODE") { 111 private static final long serialVersionUID = 1L; 112 113 @Override 114 public BundleEntrySearchModeEnum get(IResource theResource) { 115 return getEnumFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SEARCH_MODE, BundleEntrySearchModeEnum.class, BundleEntrySearchModeEnum.VALUESET_BINDER); 116 } 117 118 @Override 119 public String get(IAnyResource theResource) { 120 return (String) theResource.getUserData(ENTRY_SEARCH_MODE.name()); 121 } 122 123 @Override 124 public void put(IResource theResource, BundleEntrySearchModeEnum theObject) { 125 theResource.getResourceMetadata().put(ENTRY_SEARCH_MODE, theObject); 126 } 127 128 @Override 129 public void put(IAnyResource theResource, String theObject) { 130 theResource.setUserData(ENTRY_SEARCH_MODE.name(), theObject); 131 } 132 }; 133 /** 134 * 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 135 * 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 136 * 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 137 * result of a transaction to indicate to the client what operation was actually performed. 138 * <p> 139 * Note that status is only used in FHIR DSTU2 and later. 140 * </p> 141 * <p> 142 * Values for this key are of type <b>{@link BundleEntryTransactionMethodEnum}</b> 143 * </p> 144 */ 145 public static final ResourceMetadataKeySupportingAnyResource<BundleEntryTransactionMethodEnum, String> ENTRY_TRANSACTION_METHOD = new ResourceMetadataKeySupportingAnyResource<BundleEntryTransactionMethodEnum, String>( 146 "ENTRY_TRANSACTION_OPERATION") { 147 private static final long serialVersionUID = 1L; 148 149 @Override 150 public BundleEntryTransactionMethodEnum get(IResource theResource) { 151 return getEnumFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_TRANSACTION_METHOD, BundleEntryTransactionMethodEnum.class, 152 BundleEntryTransactionMethodEnum.VALUESET_BINDER); 153 } 154 155 @Override 156 public String get(IAnyResource theResource) { 157 return (String) theResource.getUserData(ENTRY_TRANSACTION_METHOD.name()); 158 } 159 160 @Override 161 public void put(IResource theResource, BundleEntryTransactionMethodEnum theObject) { 162 theResource.getResourceMetadata().put(ENTRY_TRANSACTION_METHOD, theObject); 163 } 164 165 @Override 166 public void put(IAnyResource theResource, String theObject) { 167 theResource.setUserData(ENTRY_TRANSACTION_METHOD.name(), theObject); 168 } 169 170 }; 171 172 /** 173 * The value for this key represents a {@link List} of profile IDs that this resource claims to conform to. 174 * <p> 175 * <p> 176 * 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. 177 * </p> 178 */ 179 public static final ResourceMetadataKeyEnum<List<IdDt>> PROFILES = new ResourceMetadataKeyEnum<List<IdDt>>("PROFILES") { 180 private static final long serialVersionUID = 1L; 181 182 @Override 183 public List<IdDt> get(IResource theResource) { 184 return getIdListFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PROFILES); 185 } 186 187 @Override 188 public void put(IResource theResource, List<IdDt> theObject) { 189 theResource.getResourceMetadata().put(PROFILES, theObject); 190 } 191 }; 192 /** 193 * 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. 194 * <p> 195 * Values for this key are of type <b>{@link InstantDt}</b> 196 * </p> 197 * <p> 198 * <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. 199 * </p> 200 * 201 * @see InstantDt 202 */ 203 public static final ResourceMetadataKeyEnum<InstantDt> PUBLISHED = new ResourceMetadataKeyEnum<InstantDt>("PUBLISHED") { 204 private static final long serialVersionUID = 1L; 205 206 @Override 207 public InstantDt get(IResource theResource) { 208 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PUBLISHED); 209 } 210 211 @Override 212 public void put(IResource theResource, InstantDt theObject) { 213 theResource.getResourceMetadata().put(PUBLISHED, theObject); 214 } 215 }; 216 public static final ResourceMetadataKeyEnum<List<BaseCodingDt>> SECURITY_LABELS = new ResourceMetadataKeyEnum<List<BaseCodingDt>>("SECURITY_LABELS") { 217 private static final long serialVersionUID = 1L; 218 219 @Override 220 public List<BaseCodingDt> get(IResource resource) { 221 Object obj = resource.getResourceMetadata().get(SECURITY_LABELS); 222 if (obj == null) { 223 return null; 224 } 225 //noinspection unchecked 226 return (List<BaseCodingDt>) obj; 227 } 228 229 @Override 230 public void put(IResource iResource, List<BaseCodingDt> labels) { 231 iResource.getResourceMetadata().put(SECURITY_LABELS, labels); 232 } 233 234 }; 235 /** 236 * The value for this key is the list of tags associated with this resource 237 * <p> 238 * Values for this key are of type <b>{@link TagList}</b> 239 * </p> 240 * 241 * @see TagList 242 */ 243 public static final ResourceMetadataKeyEnum<TagList> TAG_LIST = new ResourceMetadataKeyEnum<TagList>("TAG_LIST") { 244 private static final long serialVersionUID = 1L; 245 246 @Override 247 public TagList get(IResource theResource) { 248 Object retValObj = theResource.getResourceMetadata().get(TAG_LIST); 249 if (retValObj == null) { 250 return null; 251 } else { 252 return (TagList) retValObj; 253 } 254 } 255 256 @Override 257 public void put(IResource theResource, TagList theObject) { 258 theResource.getResourceMetadata().put(TAG_LIST, theObject); 259 } 260 }; 261 /** 262 * 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 263 * case of methods that return a single resource (read, vread, etc.) 264 * <p> 265 * Values for this key are of type <b>{@link InstantDt}</b> 266 * </p> 267 * 268 * @see InstantDt 269 */ 270 public static final ResourceMetadataKeyEnum<InstantDt> UPDATED = new ResourceMetadataKeyEnum<InstantDt>("UPDATED") { 271 private static final long serialVersionUID = 1L; 272 273 @Override 274 public InstantDt get(IResource theResource) { 275 return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), UPDATED); 276 } 277 278 @Override 279 public void put(IResource theResource, InstantDt theObject) { 280 theResource.getResourceMetadata().put(UPDATED, theObject); 281 } 282 }; 283 /** 284 * The value for this key is the version ID of the resource object. 285 * <p> 286 * Values for this key are of type <b>{@link String}</b> 287 * </p> 288 * 289 * @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method 290 */ 291 @Deprecated 292 public static final ResourceMetadataKeyEnum<String> VERSION = new ResourceMetadataKeyEnum<String>("VERSION") { 293 private static final long serialVersionUID = 1L; 294 295 @Override 296 public String get(IResource theResource) { 297 return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION); 298 } 299 300 @Override 301 public void put(IResource theResource, String theObject) { 302 theResource.getResourceMetadata().put(VERSION, theObject); 303 } 304 }; 305 /** 306 * The value for this key is the version ID of the resource object. 307 * <p> 308 * Values for this key are of type <b>{@link IdDt}</b> 309 * </p> 310 * 311 * @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method 312 */ 313 @Deprecated 314 public static final ResourceMetadataKeyEnum<IdDt> VERSION_ID = new ResourceMetadataKeyEnum<IdDt>("VERSION_ID") { 315 private static final long serialVersionUID = 1L; 316 317 @Override 318 public IdDt get(IResource theResource) { 319 return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata()); 320 } 321 322 @Override 323 public void put(IResource theResource, IdDt theObject) { 324 theResource.getResourceMetadata().put(VERSION_ID, theObject); 325 } 326 }; 327 private static final long serialVersionUID = 1L; 328 private final String myValue; 329 330 public ResourceMetadataKeyEnum(String theValue) { 331 myValue = theValue; 332 } 333 334 @Override 335 public boolean equals(Object obj) { 336 if (this == obj) 337 return true; 338 if (obj == null) 339 return false; 340 if (getClass() != obj.getClass()) 341 return false; 342 ResourceMetadataKeyEnum<?> other = (ResourceMetadataKeyEnum<?>) obj; 343 if (myValue == null) { 344 if (other.myValue != null) 345 return false; 346 } else if (!myValue.equals(other.myValue)) 347 return false; 348 return true; 349 } 350 351 public abstract T get(IResource theResource); 352 353 @Override 354 public int hashCode() { 355 final int prime = 31; 356 int result = 1; 357 result = prime * result + ((myValue == null) ? 0 : myValue.hashCode()); 358 return result; 359 } 360 361 public String name() { 362 return myValue; 363 } 364 365 public abstract void put(IResource theResource, T theObject); 366 367 public static abstract class ResourceMetadataKeySupportingAnyResource<T, T2> extends ResourceMetadataKeyEnum<T> { 368 369 private static final long serialVersionUID = 1L; 370 371 public ResourceMetadataKeySupportingAnyResource(String theValue) { 372 super(theValue); 373 } 374 375 public abstract T2 get(IAnyResource theResource); 376 377 public abstract void put(IAnyResource theResource, T2 theObject); 378 379 } 380 381 public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum<ExtensionDt> { 382 public ExtensionResourceMetadataKey(String theUrl) { 383 super(theUrl); 384 } 385 386 @Override 387 public ExtensionDt get(IResource theResource) { 388 Object retValObj = theResource.getResourceMetadata().get(this); 389 if (retValObj == null) { 390 return null; 391 } else if (retValObj instanceof ExtensionDt) { 392 return (ExtensionDt) retValObj; 393 } 394 throw new InternalErrorException(Msg.code(1890) + "Found an object of type '" + retValObj.getClass().getCanonicalName() 395 + "' in resource metadata for key " + this.name() + " - Expected " 396 + ExtensionDt.class.getCanonicalName()); 397 } 398 399 @Override 400 public void put(IResource theResource, ExtensionDt theObject) { 401 theResource.getResourceMetadata().put(this, theObject); 402 } 403 } 404 405 406 @SuppressWarnings("unchecked") 407 private static <T extends Enum<?>> T getEnumFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<T> theKey, Class<T> theEnumType, 408 IValueSetEnumBinder<T> theBinder) { 409 Object retValObj = theResourceMetadata.get(theKey); 410 if (retValObj == null) { 411 return null; 412 } else if (theEnumType.equals(retValObj.getClass())) { 413 return (T) retValObj; 414 } else if (retValObj instanceof String) { 415 return theBinder.fromCodeString((String) retValObj); 416 } 417 throw new InternalErrorException(Msg.code(1891) + "Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 418 + InstantDt.class.getCanonicalName()); 419 } 420 421 private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata) { 422 return toId(ResourceMetadataKeyEnum.VERSION_ID, theResourceMetadata.get(ResourceMetadataKeyEnum.VERSION_ID)); 423 } 424 425 private static List<IdDt> getIdListFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) { 426 Object retValObj = theResourceMetadata.get(theKey); 427 if (retValObj == null) { 428 return null; 429 } else if (retValObj instanceof List) { 430 List<?> retValList = (List<?>) retValObj; 431 for (Object next : retValList) { 432 if (!(next instanceof IdDt)) { 433 List<IdDt> retVal = new ArrayList<IdDt>(); 434 for (Object nextVal : retValList) { 435 retVal.add(toId(theKey, nextVal)); 436 } 437 return Collections.unmodifiableList(retVal); 438 } 439 } 440 @SuppressWarnings("unchecked") 441 List<IdDt> retVal = (List<IdDt>) retValList; 442 return Collections.unmodifiableList(retVal); 443 } else { 444 return Collections.singletonList(toId(theKey, retValObj)); 445 } 446 } 447 448 private static InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) { 449 Object retValObj = theResourceMetadata.get(theKey); 450 if (retValObj == null) { 451 return null; 452 } else if (retValObj instanceof Date) { 453 return new InstantDt((Date) retValObj); 454 } else if (retValObj instanceof InstantDt) { 455 if (((InstantDt) retValObj).isEmpty()) { 456 return null; 457 } 458 return (InstantDt) retValObj; 459 } 460 throw new InternalErrorException(Msg.code(1892) + "Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 461 + InstantDt.class.getCanonicalName()); 462 } 463 464 private static String getStringFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<String> theKey) { 465 Object retValObj = theResourceMetadata.get(theKey); 466 if (retValObj == null) { 467 return null; 468 } else if (retValObj instanceof String) { 469 if (StringUtils.isBlank(((String) retValObj))) { 470 return null; 471 } 472 return (String) retValObj; 473 } 474 throw new InternalErrorException(Msg.code(1893) + "Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 475 + String.class.getCanonicalName()); 476 } 477 478 private static IdDt toId(ResourceMetadataKeyEnum<?> theKey, Object retValObj) { 479 if (retValObj == null) { 480 return null; 481 } else if (retValObj instanceof String) { 482 if (isNotBlank((String) retValObj)) { 483 return new IdDt((String) retValObj); 484 } 485 return null; 486 } else if (retValObj instanceof IdDt) { 487 if (((IdDt) retValObj).isEmpty()) { 488 return null; 489 } 490 return (IdDt) retValObj; 491 } else if (retValObj instanceof Number) { 492 return new IdDt(retValObj.toString()); 493 } 494 throw new InternalErrorException(Msg.code(1894) + "Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " 495 + IdDt.class.getCanonicalName()); 496 } 497 498 499}