001/*
002 * #%L
003 * HAPI FHIR Structures - DSTU2 (FHIR v1.0.0)
004 * %%
005 * Copyright (C) 2014 - 2015 University Health Network
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package org.hl7.fhir.dstu2016may.hapi.rest.server;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.api.BundleInclusionRule;
024import ca.uhn.fhir.model.api.Include;
025import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
026import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
027import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
028import ca.uhn.fhir.model.valueset.BundleTypeEnum;
029import ca.uhn.fhir.rest.api.BundleLinks;
030import ca.uhn.fhir.rest.api.Constants;
031import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
032import ca.uhn.fhir.util.ResourceReferenceInfo;
033import jakarta.annotation.Nonnull;
034import org.hl7.fhir.dstu2016may.model.Bundle;
035import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent;
036import org.hl7.fhir.dstu2016may.model.Bundle.BundleLinkComponent;
037import org.hl7.fhir.dstu2016may.model.Bundle.SearchEntryMode;
038import org.hl7.fhir.dstu2016may.model.DomainResource;
039import org.hl7.fhir.dstu2016may.model.IdType;
040import org.hl7.fhir.dstu2016may.model.Resource;
041import org.hl7.fhir.instance.model.api.IAnyResource;
042import org.hl7.fhir.instance.model.api.IBaseResource;
043import org.hl7.fhir.instance.model.api.IIdType;
044import org.hl7.fhir.instance.model.api.IPrimitiveType;
045
046import java.math.BigDecimal;
047import java.util.ArrayList;
048import java.util.Date;
049import java.util.HashSet;
050import java.util.List;
051import java.util.Set;
052import java.util.UUID;
053
054import static org.apache.commons.lang3.StringUtils.isNotBlank;
055
056public class Dstu2_1BundleFactory implements IVersionSpecificBundleFactory {
057
058        private String myBase;
059        private Bundle myBundle;
060        private FhirContext myContext;
061
062        public Dstu2_1BundleFactory(FhirContext theContext) {
063                myContext = theContext;
064        }
065
066        @Override
067        public void addResourcesToBundle(
068                        List<IBaseResource> theResult,
069                        BundleTypeEnum theBundleType,
070                        String theServerBase,
071                        BundleInclusionRule theBundleInclusionRule,
072                        Set<Include> theIncludes) {
073                ensureBundle();
074
075                List<IAnyResource> includedResources = new ArrayList<IAnyResource>();
076                Set<IIdType> addedResourceIds = new HashSet<IIdType>();
077
078                for (IBaseResource next : theResult) {
079                        if (next.getIdElement().isEmpty() == false) {
080                                addedResourceIds.add(next.getIdElement());
081                        }
082                }
083
084                for (IBaseResource next : theResult) {
085
086                        Set<String> containedIds = new HashSet<String>();
087
088                        if (next instanceof DomainResource) {
089                                for (Resource nextContained : ((DomainResource) next).getContained()) {
090                                        if (isNotBlank(nextContained.getId())) {
091                                                containedIds.add(nextContained.getId());
092                                        }
093                                }
094                        }
095
096                        List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
097                        do {
098                                List<IAnyResource> addedResourcesThisPass = new ArrayList<IAnyResource>();
099
100                                for (ResourceReferenceInfo nextRefInfo : references) {
101                                        if (theBundleInclusionRule != null
102                                                        && !theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes)) {
103                                                continue;
104                                        }
105
106                                        IAnyResource nextRes =
107                                                        (IAnyResource) nextRefInfo.getResourceReference().getResource();
108                                        if (nextRes != null) {
109                                                if (nextRes.getIdElement().hasIdPart()) {
110                                                        if (containedIds.contains(nextRes.getIdElement().getValue())) {
111                                                                // Don't add contained IDs as top level resources
112                                                                continue;
113                                                        }
114
115                                                        IIdType id = nextRes.getIdElement();
116                                                        if (id.hasResourceType() == false) {
117                                                                String resName = myContext.getResourceType(nextRes);
118                                                                id = id.withResourceType(resName);
119                                                        }
120
121                                                        if (!addedResourceIds.contains(id)) {
122                                                                addedResourceIds.add(id);
123                                                                addedResourcesThisPass.add(nextRes);
124                                                        }
125                                                }
126                                        }
127                                }
128
129                                includedResources.addAll(addedResourcesThisPass);
130
131                                // Linked resources may themselves have linked resources
132                                references = new ArrayList<ResourceReferenceInfo>();
133                                for (IAnyResource iResource : addedResourcesThisPass) {
134                                        List<ResourceReferenceInfo> newReferences =
135                                                        myContext.newTerser().getAllResourceReferences(iResource);
136                                        references.addAll(newReferences);
137                                }
138                        } while (references.isEmpty() == false);
139
140                        BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next);
141                        Resource nextAsResource = (Resource) next;
142                        IIdType id = populateBundleEntryFullUrl(next, entry);
143                        BundleEntryTransactionMethodEnum httpVerb =
144                                        ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextAsResource);
145                        if (httpVerb != null) {
146                                entry.getRequest().getMethodElement().setValueAsString(httpVerb.name());
147                                if (id != null) {
148                                        entry.getRequest().setUrl(id.getValue());
149                                }
150                        }
151
152                        // Populate Bundle.entry.search
153                        BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextAsResource);
154                        if (searchMode != null) {
155                                entry.getSearch().getModeElement().setValueAsString(searchMode.getCode());
156                        }
157                        BigDecimal searchScore = ResourceMetadataKeyEnum.ENTRY_SEARCH_SCORE.get(nextAsResource);
158                        if (searchScore != null) {
159                                entry.getSearch().getScoreElement().setValue(searchScore);
160                        }
161                }
162
163                /*
164                 * Actually add the resources to the bundle
165                 */
166                for (IAnyResource next : includedResources) {
167                        BundleEntryComponent entry = myBundle.addEntry();
168                        entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
169                        populateBundleEntryFullUrl(next, entry);
170                }
171        }
172
173        @Override
174        public void addRootPropertiesToBundle(
175                        String theId,
176                        @Nonnull BundleLinks theBundleLinks,
177                        Integer theTotalResults,
178                        IPrimitiveType<Date> theLastUpdated) {
179                ensureBundle();
180
181                myBase = theBundleLinks.serverBase;
182
183                if (myBundle.getIdElement().isEmpty()) {
184                        myBundle.setId(theId);
185                }
186
187                if (myBundle.getMeta().getLastUpdated() == null && theLastUpdated != null) {
188                        myBundle.getMeta().getLastUpdatedElement().setValueAsString(theLastUpdated.getValueAsString());
189                }
190
191                if (!hasLink(Constants.LINK_SELF, myBundle) && isNotBlank(theBundleLinks.getSelf())) {
192                        myBundle.addLink().setRelation(Constants.LINK_SELF).setUrl(theBundleLinks.getSelf());
193                }
194                if (!hasLink(Constants.LINK_NEXT, myBundle) && isNotBlank(theBundleLinks.getNext())) {
195                        myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(theBundleLinks.getNext());
196                }
197                if (!hasLink(Constants.LINK_PREVIOUS, myBundle) && isNotBlank(theBundleLinks.getPrev())) {
198                        myBundle.addLink().setRelation(Constants.LINK_PREVIOUS).setUrl(theBundleLinks.getPrev());
199                }
200
201                addTotalResultsToBundle(theTotalResults, theBundleLinks.bundleType);
202        }
203
204        @Override
205        public void addTotalResultsToBundle(Integer theTotalResults, BundleTypeEnum theBundleType) {
206                ensureBundle();
207
208                if (myBundle.getIdElement().isEmpty()) {
209                        myBundle.setId(UUID.randomUUID().toString());
210                }
211
212                if (myBundle.getTypeElement().isEmpty() && theBundleType != null) {
213                        myBundle.getTypeElement().setValueAsString(theBundleType.getCode());
214                }
215
216                if (myBundle.getTotalElement().isEmpty() && theTotalResults != null) {
217                        myBundle.getTotalElement().setValue(theTotalResults);
218                }
219        }
220
221        private void ensureBundle() {
222                if (myBundle == null) {
223                        myBundle = new Bundle();
224                }
225        }
226
227        @Override
228        public IBaseResource getResourceBundle() {
229                return myBundle;
230        }
231
232        private boolean hasLink(String theLinkType, Bundle theBundle) {
233                for (BundleLinkComponent next : theBundle.getLink()) {
234                        if (theLinkType.equals(next.getRelation())) {
235                                return true;
236                        }
237                }
238                return false;
239        }
240
241        @Override
242        public void initializeWithBundleResource(IBaseResource theBundle) {
243                myBundle = (Bundle) theBundle;
244        }
245
246        private IIdType populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) {
247                IIdType idElement = null;
248                if (next.getIdElement().hasBaseUrl()) {
249                        idElement = next.getIdElement();
250                        entry.setFullUrl(idElement.toVersionless().getValue());
251                } else {
252                        if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) {
253                                idElement = next.getIdElement();
254                                idElement = idElement.withServerBase(myBase, myContext.getResourceType(next));
255                                entry.setFullUrl(idElement.toVersionless().getValue());
256                        }
257                }
258                return idElement;
259        }
260
261        @Override
262        public List<IBaseResource> toListOfResources() {
263                ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
264                for (BundleEntryComponent next : myBundle.getEntry()) {
265                        if (next.getResource() != null) {
266                                retVal.add(next.getResource());
267                        } else if (next.getResponse().getLocationElement().isEmpty() == false) {
268                                IdType id = new IdType(next.getResponse().getLocation());
269                                String resourceType = id.getResourceType();
270                                if (isNotBlank(resourceType)) {
271                                        IAnyResource res = (IAnyResource)
272                                                        myContext.getResourceDefinition(resourceType).newInstance();
273                                        res.setId(id);
274                                        retVal.add(res);
275                                }
276                        }
277                }
278                return retVal;
279        }
280}