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