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