001package ca.uhn.fhir.model.api;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
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 */
022import static org.apache.commons.lang3.StringUtils.isNotBlank;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.lang3.StringUtils;
030import org.apache.commons.lang3.Validate;
031import org.apache.commons.lang3.builder.ToStringBuilder;
032import org.apache.commons.lang3.builder.ToStringStyle;
033
034import ca.uhn.fhir.context.FhirContext;
035import ca.uhn.fhir.context.RuntimeResourceDefinition;
036import ca.uhn.fhir.model.base.resource.ResourceMetadataMap;
037import ca.uhn.fhir.model.primitive.BoundCodeDt;
038import ca.uhn.fhir.model.primitive.DecimalDt;
039import ca.uhn.fhir.model.primitive.IdDt;
040import ca.uhn.fhir.model.primitive.InstantDt;
041import ca.uhn.fhir.model.primitive.IntegerDt;
042import ca.uhn.fhir.model.primitive.StringDt;
043import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
044import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
045import ca.uhn.fhir.model.valueset.BundleTypeEnum;
046import ca.uhn.fhir.rest.server.Constants;
047import ca.uhn.fhir.util.UrlUtil;
048
049public class Bundle extends BaseBundle /* implements IBase implements IElement */{
050
051        private static final long serialVersionUID = 5811989173275366745L;
052        private ResourceMetadataMap myResourceMetadata;
053        private BoundCodeDt<BundleTypeEnum> myType;
054        private StringDt myBundleId;
055        private TagList myCategories;
056        private List<BundleEntry> myEntries;
057        private volatile transient Map<IdDt, IResource> myIdToEntries;
058        private StringDt myLinkBase;
059        private StringDt myLinkFirst;
060        private StringDt myLinkLast;
061        private StringDt myLinkNext;
062        private StringDt myLinkPrevious;
063        private StringDt myLinkSelf;
064        private StringDt myTitle;
065        private IntegerDt myTotalResults;
066
067        /**
068         * @deprecated Tags wil become immutable in a future release of HAPI, so
069         *             {@link #addCategory(String, String, String)} should be used instead
070         */
071        @Deprecated
072        public Tag addCategory() {
073                Tag retVal = new Tag();
074                getCategories().add(retVal);
075                return retVal;
076        }
077
078        public void addCategory(String theScheme, String theTerm, String theLabel) {
079                getCategories().add(new Tag(theScheme, theTerm, theLabel));
080        }
081
082        public void addCategory(Tag theTag) {
083                getCategories().add(theTag);
084        }
085
086        /**
087         * Adds and returns a new bundle entry
088         */
089        public BundleEntry addEntry() {
090                BundleEntry retVal = new BundleEntry();
091                getEntries().add(retVal);
092                return retVal;
093        }
094
095        /**
096         * Adds a new entry
097         * 
098         * @param theBundleEntry
099         *            The entry to add
100         */
101        public void addEntry(BundleEntry theBundleEntry) {
102                Validate.notNull(theBundleEntry, "theBundleEntry can not be null");
103                getEntries().add(theBundleEntry);
104        }
105
106        /**
107         * Creates a new entry using the given resource and populates it accordingly
108         * 
109         * @param theResource
110         *            The resource to add
111         * @return Returns the newly created bundle entry that was added to the bundle
112         */
113        public BundleEntry addResource(IResource theResource, FhirContext theContext, String theServerBase) {
114                BundleEntry entry = addEntry();
115                entry.setResource(theResource);
116
117                RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
118
119                String title = ResourceMetadataKeyEnum.TITLE.get(theResource);
120                if (title != null) {
121                        entry.getTitle().setValue(title);
122                } else {
123                        entry.getTitle().setValue(def.getName() + " " + StringUtils.defaultString(theResource.getId().getValue(), "(no ID)"));
124                }
125
126                if (theResource.getId() != null) {
127                        if (theResource.getId().isAbsolute()) {
128
129                                entry.getLinkSelf().setValue(theResource.getId().getValue());
130                                //TODO: Use of a deprecated method should be resolved.
131                                entry.getId().setValue(theResource.getId().toVersionless().getValue());
132
133                        } else if (StringUtils.isNotBlank(theResource.getId().getValue())) {
134
135                                StringBuilder b = new StringBuilder();
136                                b.append(theServerBase);
137                                if (b.length() > 0 && b.charAt(b.length() - 1) != '/') {
138                                        b.append('/');
139                                }
140                                b.append(def.getName());
141                                b.append('/');
142                                String resId = theResource.getId().getIdPart();
143                                b.append(resId);
144
145                                //TODO: Use of a deprecated method should be resolved.
146                                entry.getId().setValue(b.toString());
147
148                                if (isNotBlank(theResource.getId().getVersionIdPart())) {
149                                        b.append('/');
150                                        b.append(Constants.PARAM_HISTORY);
151                                        b.append('/');
152                                        b.append(theResource.getId().getVersionIdPart());
153                                } else {
154                                        //TODO: Use of a deprecated method should be resolved.
155                                        IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(theResource);
156                                        if (versionId != null) {
157                                                b.append('/');
158                                                b.append(Constants.PARAM_HISTORY);
159                                                b.append('/');
160                                                b.append(versionId.getValue());
161                                        }
162                                }
163
164                                String qualifiedId = b.toString();
165                                entry.getLinkSelf().setValue(qualifiedId);
166
167                                // String resourceType = theContext.getResourceDefinition(theResource).getName();
168
169
170                        }
171                }
172
173                InstantDt published = ResourceMetadataKeyEnum.PUBLISHED.get(theResource);
174                if (published == null) {
175                        entry.getPublished().setToCurrentTimeInLocalTimeZone();
176                } else {
177                        entry.setPublished(published);
178                }
179
180                InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
181                if (updated != null) {
182                        //TODO: Use of a deprecated method should be resolved.
183                        entry.setUpdated(updated);
184                }
185
186                InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(theResource);
187                if (deleted != null) {
188                        entry.setDeleted(deleted);
189                }
190
191                IdDt previous = ResourceMetadataKeyEnum.PREVIOUS_ID.get(theResource);
192                if (previous != null) {
193                        entry.getLinkAlternate().setValue(previous.withServerBase(theServerBase, def.getName()).getValue());
194                }
195
196                TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
197                if (tagList != null) {
198                        for (Tag nextTag : tagList) {
199                                entry.addCategory(nextTag);
200                        }
201                }
202
203                String linkSearch = ResourceMetadataKeyEnum.LINK_SEARCH.get(theResource);
204                if (isNotBlank(linkSearch)) {
205                        if (!UrlUtil.isAbsolute(linkSearch)) {
206                                linkSearch = (theServerBase + "/" + linkSearch);
207                        }
208                        entry.getLinkSearch().setValue(linkSearch);
209                }
210
211                String linkAlternate = ResourceMetadataKeyEnum.LINK_ALTERNATE.get(theResource);
212                if (isNotBlank(linkAlternate)) {
213                        if (!UrlUtil.isAbsolute(linkAlternate)) {
214                                linkSearch = (theServerBase + "/" + linkAlternate);
215                        }
216                        entry.getLinkAlternate().setValue(linkSearch);
217                }
218                
219                BundleEntrySearchModeEnum entryStatus = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(theResource);
220                if (entryStatus != null) {
221                        entry.getSearchMode().setValueAsEnum(entryStatus);
222                }
223
224                BundleEntryTransactionMethodEnum entryTransactionOperation = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(theResource);
225                if (entryTransactionOperation != null) {
226                        entry.getTransactionMethod().setValueAsEnum(entryTransactionOperation);
227                }
228
229                DecimalDt entryScore = ResourceMetadataKeyEnum.ENTRY_SCORE.get(theResource);
230                if (entryScore != null) {
231                        entry.setScore(entryScore);
232                }
233
234                return entry;
235        }
236
237        public StringDt getBundleId() {
238                if (myBundleId == null) {
239                        myBundleId = new StringDt();
240                }
241                return myBundleId;
242        }
243
244        public TagList getCategories() {
245                if (myCategories == null) {
246                        myCategories = new TagList();
247                }
248                return myCategories;
249        }
250
251        public List<BundleEntry> getEntries() {
252                if (myEntries == null) {
253                        myEntries = new ArrayList<BundleEntry>();
254                }
255                return myEntries;
256        }
257
258        public StringDt getLinkBase() {
259                if (myLinkBase == null) {
260                        myLinkBase = new StringDt();
261                }
262                return myLinkBase;
263        }
264
265        public StringDt getLinkFirst() {
266                if (myLinkFirst == null) {
267                        myLinkFirst = new StringDt();
268                }
269                return myLinkFirst;
270        }
271
272        public StringDt getLinkLast() {
273                if (myLinkLast == null) {
274                        myLinkLast = new StringDt();
275                }
276                return myLinkLast;
277        }
278
279        public StringDt getLinkNext() {
280                if (myLinkNext == null) {
281                        myLinkNext = new StringDt();
282                }
283                return myLinkNext;
284        }
285
286        public StringDt getLinkPrevious() {
287                if (myLinkPrevious == null) {
288                        myLinkPrevious = new StringDt();
289                }
290                return myLinkPrevious;
291        }
292
293        public StringDt getLinkSelf() {
294                if (myLinkSelf == null) {
295                        myLinkSelf = new StringDt();
296                }
297                return myLinkSelf;
298        }
299
300        /*
301        public InstantDt getPublished() {
302                InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED);
303                if (retVal == null) {
304                        retVal= new InstantDt();
305                        getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, retVal);
306                }
307                return retVal;
308        }
309        */
310
311        /**
312         * Retrieves a resource from a bundle given its logical ID.
313         * <p>
314         * <b>Important usage notes</b>: This method ignores base URLs (so passing in an ID of
315         * <code>http://foo/Patient/123</code> will return a resource if it has the logical ID of
316         * <code>http://bar/Patient/123</code>. Also, this method is intended to be used for bundles which have already been
317         * populated. It will cache its results for fast performance, but that means that modifications to the bundle after
318         * this method is called may not be accurately reflected.
319         * </p>
320         * 
321         * @param theId
322         *            The resource ID
323         * @return Returns the resource with the given ID, or <code>null</code> if none is found
324         */
325        public IResource getResourceById(IdDt theId) {
326                Map<IdDt, IResource> map = myIdToEntries;
327                if (map == null) {
328                        map = new HashMap<IdDt, IResource>();
329                        for (BundleEntry next : this.getEntries()) {
330                                //TODO: Use of a deprecated method should be resolved.
331                                if (next.getId().isEmpty() == false) {
332                                        map.put(next.getId().toUnqualified(), next.getResource());
333                                }
334                        }
335                        myIdToEntries = map;
336                }
337                return map.get(theId.toUnqualified());
338        }
339
340        public ResourceMetadataMap getResourceMetadata() {
341                if (myResourceMetadata == null) {
342                        myResourceMetadata = new ResourceMetadataMap();
343                }
344                return myResourceMetadata;
345        }
346
347        /**
348         * Returns a list containing all resources of the given type from this bundle
349         */
350        public <T extends IResource> List<T> getResources(Class<T> theClass) {
351                ArrayList<T> retVal = new ArrayList<T>();
352                for (BundleEntry next : getEntries()) {
353                        if (next.getResource() != null && theClass.isAssignableFrom(next.getResource().getClass())) {
354                                @SuppressWarnings("unchecked")
355                                T resource = (T) next.getResource();
356                                retVal.add(resource);
357                        }
358                }
359                return retVal;
360        }
361
362        public StringDt getTitle() {
363                if (myTitle == null) {
364                        myTitle = new StringDt();
365                }
366                return myTitle;
367        }
368
369        public IntegerDt getTotalResults() {
370                if (myTotalResults == null) {
371                        myTotalResults = new IntegerDt();
372                }
373                return myTotalResults;
374        }
375
376        public BoundCodeDt<BundleTypeEnum> getType() {
377                if (myType == null) {
378                        myType = new BoundCodeDt<BundleTypeEnum>(BundleTypeEnum.VALUESET_BINDER);
379                }
380                return myType;
381        }
382
383        public InstantDt getUpdated() {
384                InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
385                if (retVal == null) {
386                        retVal= new InstantDt();
387                        getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, retVal);
388                }
389                return retVal;
390        }
391
392        /**
393         * Returns true if this bundle contains zero entries
394         */
395        @Override
396        public boolean isEmpty() {
397                return getEntries().isEmpty();
398        }
399
400        public void setCategories(TagList theCategories) {
401                myCategories = theCategories;
402        }
403
404        /*
405        public void setPublished(InstantDt thePublished) {
406                getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, thePublished);
407        }
408        */
409
410        public void setType(BoundCodeDt<BundleTypeEnum> theType) {
411                myType = theType;
412        }
413
414        /**
415         * Returns the number of entries in this bundle
416         */
417        public int size() {
418                return getEntries().size();
419        }
420
421        public List<IResource> toListOfResources() {
422                ArrayList<IResource> retVal = new ArrayList<IResource>();
423                for (BundleEntry next : getEntries()) {
424                        if (next.getResource() != null) {
425                                retVal.add(next.getResource());
426                        }
427                }
428                return retVal;
429        }
430
431        @Override
432        public String toString() {
433                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
434                b.append(getEntries().size() + " entries");
435                b.append("id", getId());
436                return b.toString();
437        }
438
439        public static Bundle withResources(List<IResource> theResources, FhirContext theContext, String theServerBase) {
440                Bundle retVal = new Bundle();
441                for (IResource next : theResources) {
442                        retVal.addResource(next, theContext, theServerBase);
443                }
444                return retVal;
445        }
446
447        public static Bundle withSingleResource(IResource theResource) {
448                Bundle retVal = new Bundle();
449                retVal.addEntry().setResource(theResource);
450                return retVal;
451        }
452
453}