001package ca.uhn.fhir.model.api;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 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&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.
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}