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&lt;IdDt&gt;</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}