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