001package ca.uhn.fhir.rest.server.provider.dstu2;
002
003/*
004 * #%L
005 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
006 * %%
007 * Copyright (C) 2014 - 2018 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.*;
025
026import org.hl7.fhir.instance.model.api.*;
027
028import ca.uhn.fhir.context.FhirContext;
029import ca.uhn.fhir.context.api.BundleInclusionRule;
030import ca.uhn.fhir.model.api.*;
031import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
032import ca.uhn.fhir.model.dstu2.resource.Bundle;
033import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
034import ca.uhn.fhir.model.dstu2.resource.Bundle.Link;
035import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
036import ca.uhn.fhir.model.dstu2.valueset.SearchEntryModeEnum;
037import ca.uhn.fhir.model.primitive.IdDt;
038import ca.uhn.fhir.model.primitive.InstantDt;
039import ca.uhn.fhir.model.valueset.*;
040import ca.uhn.fhir.rest.api.Constants;
041import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
042import ca.uhn.fhir.util.ResourceReferenceInfo;
043
044public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
045        private String myBase;
046        private Bundle myBundle;
047        private FhirContext myContext;
048
049        public Dstu2BundleFactory(FhirContext theContext) {
050                myContext = theContext;
051        }
052
053        private void addResourcesForSearch(List<? extends IBaseResource> theResult) {
054                List<IBaseResource> includedResources = new ArrayList<IBaseResource>();
055                Set<IIdType> addedResourceIds = new HashSet<IIdType>();
056
057                for (IBaseResource next : theResult) {
058                        if (next.getIdElement().isEmpty() == false) {
059                                addedResourceIds.add(next.getIdElement());
060                        }
061                }
062
063                for (IBaseResource nextBaseRes : theResult) {
064                        IResource next = (IResource) nextBaseRes;
065                        Set<String> containedIds = new HashSet<String>();
066                        for (IResource nextContained : next.getContained().getContainedResources()) {
067                                if (nextContained.getId().isEmpty() == false) {
068                                        containedIds.add(nextContained.getId().getValue());
069                                }
070                        }
071
072                        List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
073                        do {
074                                List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
075
076                                for (BaseResourceReferenceDt nextRef : references) {
077                                        IResource nextRes = (IResource) nextRef.getResource();
078                                        if (nextRes != null) {
079                                                if (nextRes.getId().hasIdPart()) {
080                                                        if (containedIds.contains(nextRes.getId().getValue())) {
081                                                                // Don't add contained IDs as top level resources
082                                                                continue;
083                                                        }
084
085                                                        IdDt id = nextRes.getId();
086                                                        if (id.hasResourceType() == false) {
087                                                                String resName = myContext.getResourceDefinition(nextRes).getName();
088                                                                id = id.withResourceType(resName);
089                                                        }
090
091                                                        if (!addedResourceIds.contains(id)) {
092                                                                addedResourceIds.add(id);
093                                                                addedResourcesThisPass.add(nextRes);
094                                                        }
095
096                                                }
097                                        }
098                                }
099
100                                // Linked resources may themselves have linked resources
101                                references = new ArrayList<BaseResourceReferenceDt>();
102                                for (IResource iResource : addedResourcesThisPass) {
103                                        List<BaseResourceReferenceDt> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class);
104                                        references.addAll(newReferences);
105                                }
106
107                                includedResources.addAll(addedResourcesThisPass);
108
109                        } while (references.isEmpty() == false);
110
111                        Entry entry = myBundle.addEntry().setResource(next);
112                        if (next.getId().hasBaseUrl()) {
113                                entry.setFullUrl(next.getId().getValue());
114                        }
115                        BundleEntryTransactionMethodEnum httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next);
116                        if (httpVerb != null) {
117                                entry.getRequest().getMethodElement().setValueAsString(httpVerb.getCode());
118                        }
119                }
120
121                /*
122                 * Actually add the resources to the bundle
123                 */
124                for (IBaseResource next : includedResources) {
125                        Entry entry = myBundle.addEntry();
126                        entry.setResource((IResource) next).getSearch().setMode(SearchEntryModeEnum.INCLUDE);
127                        if (next.getIdElement().hasBaseUrl()) {
128                                entry.setFullUrl(next.getIdElement().getValue());
129                        }
130                }
131        }
132
133        @Override
134        public void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
135                ensureBundle();
136
137                List<IResource> includedResources = new ArrayList<IResource>();
138                Set<IdDt> addedResourceIds = new HashSet<IdDt>();
139
140                for (IBaseResource next : theResult) {
141                        if (next.getIdElement().isEmpty() == false) {
142                                addedResourceIds.add((IdDt) next.getIdElement());
143                        }
144                }
145
146                for (IBaseResource nextBaseRes : theResult) {
147                        IResource next = (IResource) nextBaseRes;
148
149                        Set<String> containedIds = new HashSet<String>();
150                        for (IResource nextContained : next.getContained().getContainedResources()) {
151                                if (nextContained.getId().isEmpty() == false) {
152                                        containedIds.add(nextContained.getId().getValue());
153                                }
154                        }
155
156                        List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
157                        do {
158                                List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
159
160                                for (ResourceReferenceInfo nextRefInfo : references) {
161                                        if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
162                                                continue;
163
164                                        IResource nextRes = (IResource) nextRefInfo.getResourceReference().getResource();
165                                        if (nextRes != null) {
166                                                if (nextRes.getId().hasIdPart()) {
167                                                        if (containedIds.contains(nextRes.getId().getValue())) {
168                                                                // Don't add contained IDs as top level resources
169                                                                continue;
170                                                        }
171
172                                                        IdDt id = nextRes.getId();
173                                                        if (id.hasResourceType() == false) {
174                                                                String resName = myContext.getResourceDefinition(nextRes).getName();
175                                                                id = id.withResourceType(resName);
176                                                        }
177
178                                                        if (!addedResourceIds.contains(id)) {
179                                                                addedResourceIds.add(id);
180                                                                addedResourcesThisPass.add(nextRes);
181                                                        }
182
183                                                }
184                                        }
185                                }
186
187                                includedResources.addAll(addedResourcesThisPass);
188
189                                // Linked resources may themselves have linked resources
190                                references = new ArrayList<ResourceReferenceInfo>();
191                                for (IResource iResource : addedResourcesThisPass) {
192                                        List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
193                                        references.addAll(newReferences);
194                                }
195                        } while (references.isEmpty() == false);
196
197                        Entry entry = myBundle.addEntry().setResource(next);
198                        BundleEntryTransactionMethodEnum httpVerb = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next);
199                        if (httpVerb != null) {
200                                entry.getRequest().getMethodElement().setValueAsString(httpVerb.getCode());
201                        }
202                        populateBundleEntryFullUrl(next, entry);
203
204                        BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next);
205                        if (searchMode != null) {
206                                entry.getSearch().getModeElement().setValue(searchMode.getCode());
207                        }
208                }
209
210                /*
211                 * Actually add the resources to the bundle
212                 */
213                for (IResource next : includedResources) {
214                        Entry entry = myBundle.addEntry();
215                        entry.setResource(next).getSearch().setMode(SearchEntryModeEnum.INCLUDE);
216                        populateBundleEntryFullUrl(next, entry);
217                }
218
219        }
220
221        @Override
222        public void addRootPropertiesToBundle(String theId, String theServerBase, String theLinkSelf, String theLinkPrev, String theLinkNext, Integer theTotalResults, BundleTypeEnum theBundleType,
223                                                                                                          IPrimitiveType<Date> theLastUpdated) {
224                ensureBundle();
225
226                myBase = theServerBase;
227
228                if (myBundle.getIdElement().isEmpty()) {
229                        myBundle.setId(theId);
230                }
231                if (myBundle.getId().isEmpty()) {
232                        myBundle.setId(UUID.randomUUID().toString());
233                }
234
235                if (ResourceMetadataKeyEnum.UPDATED.get(myBundle) == null) {
236                        ResourceMetadataKeyEnum.UPDATED.put(myBundle, (InstantDt) theLastUpdated);
237                }
238
239                if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theLinkSelf)) {
240                        myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theLinkSelf);
241                }
242                if (!hasLink(Constants.LINK_NEXT, myBundle) && isNotBlank(theLinkNext)) {
243                        myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(theLinkNext);
244                }
245                if (!hasLink(Constants.LINK_PREVIOUS, myBundle) && isNotBlank(theLinkPrev)) {
246                        myBundle.addLink().setRelation(Constants.LINK_PREVIOUS).setUrl(theLinkPrev);
247                }
248
249                if (myBundle.getTypeElement().isEmpty() && theBundleType != null) {
250                        myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
251                }
252
253                if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) {
254                        myBundle.getTotalElement().setValue(theTotalResults);
255                }
256        }
257
258        private void ensureBundle() {
259                if (myBundle == null) {
260                        myBundle = new Bundle();
261                }
262        }
263
264        @Override
265        public IResource getResourceBundle() {
266                return myBundle;
267        }
268
269        private boolean hasLink(String theLinkType, Bundle theBundle) {
270                for (Link next : theBundle.getLink()) {
271                        if (theLinkType.equals(next.getRelation())) {
272                                return true;
273                        }
274                }
275                return false;
276        }
277
278        @Override
279        public void initializeBundleFromResourceList(String theAuthor, List<? extends IBaseResource> theResources, String theServerBase, String theCompleteUrl, int theTotalResults,
280                        BundleTypeEnum theBundleType) {
281                ensureBundle();
282
283                myBundle.setId(UUID.randomUUID().toString());
284
285                ResourceMetadataKeyEnum.PUBLISHED.put(myBundle, InstantDt.withCurrentTime());
286
287                myBundle.addLink().setRelation(Constants.LINK_FHIR_BASE).setUrl(theServerBase);
288                myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theCompleteUrl);
289                myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
290
291                if (theBundleType.equals(BundleTypeEnum.TRANSACTION)) {
292                        for (IBaseResource nextBaseRes : theResources) {
293                                IResource next = (IResource) nextBaseRes;
294                                Entry nextEntry = myBundle.addEntry();
295
296                                nextEntry.setResource(next);
297                                if (next.getId().isEmpty()) {
298                                        nextEntry.getRequest().setMethod(HTTPVerbEnum.POST);
299                                } else {
300                                        nextEntry.getRequest().setMethod(HTTPVerbEnum.PUT);
301                                        if (next.getId().isAbsolute()) {
302                                                nextEntry.getRequest().setUrl(next.getId());
303                                        } else {
304                                                String resourceType = myContext.getResourceDefinition(next).getName();
305                                                nextEntry.getRequest().setUrl(new IdDt(theServerBase, resourceType, next.getId().getIdPart(), next.getId().getVersionIdPart()).getValue());
306                                        }
307                                }
308                        }
309                } else {
310                        addResourcesForSearch(theResources);
311                }
312
313                myBundle.getTotalElement().setValue(theTotalResults);
314        }
315
316        @Override
317        public void initializeWithBundleResource(IBaseResource theBundle) {
318                myBundle = (Bundle) theBundle;
319        }
320
321        private void populateBundleEntryFullUrl(IResource next, Entry entry) {
322                if (next.getId().hasBaseUrl()) {
323                        entry.setFullUrl(next.getId().toVersionless().getValue());
324                } else {
325                        if (isNotBlank(myBase) && next.getId().hasIdPart()) {
326                                IdDt id = next.getId().toVersionless();
327                                id = id.withServerBase(myBase, myContext.getResourceDefinition(next).getName());
328                                entry.setFullUrl(id.getValue());
329                        }
330                }
331        }
332
333        @Override
334        public List<IBaseResource> toListOfResources() {
335                ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
336                for (Entry next : myBundle.getEntry()) {
337                        if (next.getResource() != null) {
338                                retVal.add(next.getResource());
339                        } else if (next.getResponse().getLocationElement().isEmpty() == false) {
340                                IdDt id = new IdDt(next.getResponse().getLocation());
341                                String resourceType = id.getResourceType();
342                                if (isNotBlank(resourceType)) {
343                                        IResource res = (IResource) myContext.getResourceDefinition(resourceType).newInstance();
344                                        res.setId(id);
345                                        retVal.add(res);
346                                }
347                        }
348                }
349                return retVal;
350        }
351
352}