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