001package ca.uhn.fhir.rest.server.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
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 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.model.api.IResource;
026import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
027import ca.uhn.fhir.model.primitive.IdDt;
028import ca.uhn.fhir.model.valueset.BundleTypeEnum;
029import ca.uhn.fhir.rest.annotation.History;
030import ca.uhn.fhir.rest.api.Constants;
031import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
032import ca.uhn.fhir.rest.api.server.IBundleProvider;
033import ca.uhn.fhir.rest.api.server.IRestfulServer;
034import ca.uhn.fhir.rest.api.server.RequestDetails;
035import ca.uhn.fhir.rest.param.ParameterUtil;
036import ca.uhn.fhir.rest.server.IResourceProvider;
037import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
038import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
039import org.apache.commons.lang3.StringUtils;
040import org.hl7.fhir.instance.model.api.IBaseResource;
041import org.hl7.fhir.instance.model.api.IPrimitiveType;
042
043import javax.annotation.Nonnull;
044import java.lang.reflect.Method;
045import java.lang.reflect.Modifier;
046import java.util.Date;
047import java.util.List;
048
049import static org.apache.commons.lang3.StringUtils.isBlank;
050
051public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
052
053        private final Integer myIdParamIndex;
054        private final RestOperationTypeEnum myResourceOperationType;
055        private final String myResourceName;
056
057        public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
058                super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider);
059
060                myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
061
062                History historyAnnotation = theMethod.getAnnotation(History.class);
063                Class<? extends IBaseResource> type = historyAnnotation.type();
064                if (Modifier.isInterface(type.getModifiers())) {
065                        if (theProvider instanceof IResourceProvider) {
066                                type = ((IResourceProvider) theProvider).getResourceType();
067                                if (myIdParamIndex != null) {
068                                        myResourceOperationType = RestOperationTypeEnum.HISTORY_INSTANCE;
069                                } else {
070                                        myResourceOperationType = RestOperationTypeEnum.HISTORY_TYPE;
071                                }
072                        } else {
073                                myResourceOperationType = RestOperationTypeEnum.HISTORY_SYSTEM;
074                        }
075                } else {
076                        if (myIdParamIndex != null) {
077                                myResourceOperationType = RestOperationTypeEnum.HISTORY_INSTANCE;
078                        } else {
079                                myResourceOperationType = RestOperationTypeEnum.HISTORY_TYPE;
080                        }
081                }
082
083                if (type != IBaseResource.class && type != IResource.class) {
084                        myResourceName = theContext.getResourceType(type);
085                } else {
086                        myResourceName = null;
087                }
088
089        }
090
091        @Override
092        protected BundleTypeEnum getResponseBundleType() {
093                return BundleTypeEnum.HISTORY;
094        }
095
096        @Nonnull
097        @Override
098        public RestOperationTypeEnum getRestOperationType() {
099                return myResourceOperationType;
100        }
101
102        @Override
103        public ReturnTypeEnum getReturnType() {
104                return ReturnTypeEnum.BUNDLE;
105        }
106
107        @Override
108        protected boolean isOffsetModeHistory() {
109                return true;
110        }
111
112        // ObjectUtils.equals is replaced by a JDK7 method..
113        @Override
114        public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
115                if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
116                        return MethodMatchEnum.NONE;
117                }
118                if (theRequest.getResourceName() == null) {
119                        if (myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM) {
120                                return MethodMatchEnum.EXACT;
121                        } else {
122                                return MethodMatchEnum.NONE;
123                        }
124                }
125                if (!StringUtils.equals(theRequest.getResourceName(), myResourceName)) {
126                        return MethodMatchEnum.NONE;
127                }
128
129                boolean haveIdParam = theRequest.getId() != null && !theRequest.getId().isEmpty();
130                boolean wantIdParam = myIdParamIndex != null;
131                if (haveIdParam != wantIdParam) {
132                        return MethodMatchEnum.NONE;
133                }
134
135                if (theRequest.getId() == null) {
136                        if (myResourceOperationType != RestOperationTypeEnum.HISTORY_TYPE) {
137                                return MethodMatchEnum.NONE;
138                        }
139                } else if (theRequest.getId().hasVersionIdPart()) {
140                        return MethodMatchEnum.NONE;
141                }
142
143                return MethodMatchEnum.EXACT;
144        }
145
146
147        @Override
148        public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
149                if (myIdParamIndex != null) {
150                        theMethodParams[myIdParamIndex] = theRequest.getId();
151                }
152
153                Object response = invokeServerMethod(theRequest, theMethodParams);
154
155                final IBundleProvider resources = toResourceList(response);
156
157                /*
158                 * We wrap the response so we can verify that it has the ID and version set,
159                 * as is the contract for history
160                 */
161                return new IBundleProvider() {
162
163                        @Override
164                        public String getCurrentPageId() {
165                                return resources.getCurrentPageId();
166                        }
167
168                        @Override
169                        public String getNextPageId() {
170                                return resources.getNextPageId();
171                        }
172
173                        @Override
174                        public String getPreviousPageId() {
175                                return resources.getPreviousPageId();
176                        }
177
178                        @Override
179                        public IPrimitiveType<Date> getPublished() {
180                                return resources.getPublished();
181                        }
182
183                        @Nonnull
184                        @Override
185                        public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
186                                List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex);
187                                int index = theFromIndex;
188                                for (IBaseResource nextResource : retVal) {
189                                        if (nextResource.getIdElement() == null || isBlank(nextResource.getIdElement().getIdPart())) {
190                                                throw new InternalErrorException(Msg.code(410) + "Server provided resource at index " + index + " with no ID set (using IResource#setId(IdDt))");
191                                        }
192                                        if (isBlank(nextResource.getIdElement().getVersionIdPart()) && nextResource instanceof IResource) {
193                                                //TODO: Use of a deprecated method should be resolved.
194                                                IdDt versionId = ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource);
195                                                if (versionId == null || versionId.isEmpty()) {
196                                                        throw new InternalErrorException(Msg.code(411) + "Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))");
197                                                }
198                                        }
199                                        index++;
200                                }
201                                return retVal;
202                        }
203
204                        @Override
205                        public String getUuid() {
206                                return resources.getUuid();
207                        }
208
209                        @Override
210                        public Integer preferredPageSize() {
211                                return resources.preferredPageSize();
212                        }
213
214                        @Override
215                        public Integer size() {
216                                return resources.size();
217                        }
218                };
219        }
220
221        private static Class<? extends IBaseResource> toReturnType(Method theMethod, Object theProvider) {
222                if (theProvider instanceof IResourceProvider) {
223                        return ((IResourceProvider) theProvider).getResourceType();
224                }
225                History historyAnnotation = theMethod.getAnnotation(History.class);
226                Class<? extends IBaseResource> type = historyAnnotation.type();
227                if (type != IBaseResource.class && type != IResource.class) {
228                        return type;
229                }
230                return null;
231        }
232
233}