001package ca.uhn.fhir.rest.method;
002
003import static org.apache.commons.lang3.StringUtils.isNotBlank;
004
005/*
006 * #%L
007 * HAPI FHIR - Core Library
008 * %%
009 * Copyright (C) 2014 - 2017 University Health Network
010 * %%
011 * Licensed under the Apache License, Version 2.0 (the "License");
012 * you may not use this file except in compliance with the License.
013 * You may obtain a copy of the License at
014 * 
015 *      http://www.apache.org/licenses/LICENSE-2.0
016 * 
017 * Unless required by applicable law or agreed to in writing, software
018 * distributed under the License is distributed on an "AS IS" BASIS,
019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 * See the License for the specific language governing permissions and
021 * limitations under the License.
022 * #L%
023 */
024
025import java.io.IOException;
026import java.io.InputStream;
027import java.lang.reflect.Method;
028import java.util.*;
029
030import org.apache.commons.io.IOUtils;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.commons.lang3.Validate;
033import org.hl7.fhir.instance.model.api.*;
034
035import ca.uhn.fhir.context.ConfigurationException;
036import ca.uhn.fhir.context.FhirContext;
037import ca.uhn.fhir.model.api.*;
038import ca.uhn.fhir.model.primitive.IdDt;
039import ca.uhn.fhir.model.primitive.InstantDt;
040import ca.uhn.fhir.model.valueset.BundleTypeEnum;
041import ca.uhn.fhir.rest.annotation.*;
042import ca.uhn.fhir.rest.api.RequestTypeEnum;
043import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
044import ca.uhn.fhir.rest.server.*;
045import ca.uhn.fhir.rest.server.exceptions.*;
046import ca.uhn.fhir.util.DateUtils;
047
048public class ReadMethodBinding extends BaseResourceReturningMethodBinding implements IClientResponseHandlerHandlesBinary<Object> {
049        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
050
051        private Integer myIdIndex;
052        private boolean mySupportsVersion;
053        private Integer myVersionIdIndex;
054        private Class<? extends IIdType> myIdParameterType;
055
056        @SuppressWarnings("unchecked")
057        public ReadMethodBinding(Class<? extends IBaseResource> theAnnotatedResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
058                super(theAnnotatedResourceType, theMethod, theContext, theProvider);
059
060                Validate.notNull(theMethod, "Method must not be null");
061
062                Integer idIndex = MethodUtil.findIdParameterIndex(theMethod, getContext());
063                Integer versionIdIndex = MethodUtil.findVersionIdParameterIndex(theMethod);
064
065                Class<?>[] parameterTypes = theMethod.getParameterTypes();
066
067                mySupportsVersion = theMethod.getAnnotation(Read.class).version();
068                myIdIndex = idIndex;
069                myVersionIdIndex = versionIdIndex;
070
071                if (myIdIndex == null) {
072                        throw new ConfigurationException("@" + Read.class.getSimpleName() + " method " + theMethod.getName() + " on type \"" + theMethod.getDeclaringClass().getName() + "\" does not have a parameter annotated with @" + IdParam.class.getSimpleName());
073                }
074                myIdParameterType = (Class<? extends IIdType>) parameterTypes[myIdIndex];
075
076                if (!IIdType.class.isAssignableFrom(myIdParameterType)) {
077                        throw new ConfigurationException("ID parameter must be of type IdDt or IdType - Found: " + myIdParameterType);
078                }
079                if (myVersionIdIndex != null && !IdDt.class.equals(parameterTypes[myVersionIdIndex])) {
080                        throw new ConfigurationException("Version ID parameter must be of type: " + IdDt.class.getCanonicalName() + " - Found: " + parameterTypes[myVersionIdIndex]);
081                }
082
083        }
084
085        @Override
086        public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
087                if (mySupportsVersion && theRequestDetails.getId().hasVersionIdPart()) {
088                        return RestOperationTypeEnum.VREAD;
089                }
090                return RestOperationTypeEnum.READ;
091        }
092
093        @Override
094        public List<Class<?>> getAllowableParamAnnotations() {
095                ArrayList<Class<?>> retVal = new ArrayList<Class<?>>();
096                retVal.add(IdParam.class);
097                retVal.add(Elements.class);
098                return retVal;
099        }
100
101        @Override
102        public RestOperationTypeEnum getRestOperationType() {
103                return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
104        }
105
106        @Override
107        public ReturnTypeEnum getReturnType() {
108                return ReturnTypeEnum.RESOURCE;
109        }
110
111        @Override
112        public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
113                if (!theRequest.getResourceName().equals(getResourceName())) {
114                        return false;
115                }
116                for (String next : theRequest.getParameters().keySet()) {
117                        if (!ALLOWED_PARAMS.contains(next)) {
118                                return false;
119                        }
120                }
121                if (theRequest.getId() == null) {
122                        return false;
123                }
124                if (mySupportsVersion == false) {
125                        if (theRequest.getId().hasVersionIdPart()) {
126                                return false;
127                        }
128                }
129                if (isNotBlank(theRequest.getCompartmentName())) {
130                        return false;
131                }
132                if (theRequest.getRequestType() != RequestTypeEnum.GET) {
133                        ourLog.trace("Method {} doesn't match because request type is not GET: {}", theRequest.getId(), theRequest.getRequestType());
134                        return false;
135                }
136                if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
137                        if (mySupportsVersion == false && myVersionIdIndex == null) {
138                                return false;
139                        }
140                        if (theRequest.getId().hasVersionIdPart() == false) {
141                                return false;
142                        }
143                } else if (!StringUtils.isBlank(theRequest.getOperation())) {
144                        return false;
145                }
146                return true;
147        }
148
149        @Override
150        public HttpGetClientInvocation invokeClient(Object[] theArgs) {
151                HttpGetClientInvocation retVal;
152                IIdType id = ((IIdType) theArgs[myIdIndex]);
153                if (myVersionIdIndex == null) {
154                        String resourceName = getResourceName();
155                        if (id.hasVersionIdPart()) {
156                                retVal = createVReadInvocation(getContext(), new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()), resourceName);
157                        } else {
158                                retVal = createReadInvocation(getContext(), id, resourceName);
159                        }
160                } else {
161                        IdDt vid = ((IdDt) theArgs[myVersionIdIndex]);
162                        String resourceName = getResourceName();
163
164                        retVal = createVReadInvocation(getContext(), new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()), resourceName);
165                }
166
167                for (int idx = 0; idx < theArgs.length; idx++) {
168                        IParameter nextParam = getParameters().get(idx);
169                        nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
170                }
171
172                return retVal;
173        }
174
175        @Override
176        public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
177                byte[] contents = IOUtils.toByteArray(theResponseReader);
178
179                IBaseBinary resource = (IBaseBinary) getContext().getResourceDefinition("Binary").newInstance();
180                resource.setContentType(theResponseMimeType);
181                resource.setContent(contents);
182
183                switch (getMethodReturnType()) {
184                case BUNDLE:
185                        return Bundle.withSingleResource((IResource) resource);
186                case LIST_OF_RESOURCES:
187                        return Collections.singletonList(resource);
188                case RESOURCE:
189                        return resource;
190                case BUNDLE_PROVIDER:
191                        return new SimpleBundleProvider(resource);
192                case BUNDLE_RESOURCE:
193                case METHOD_OUTCOME:
194                        break;
195                }
196
197                throw new IllegalStateException("" + getMethodReturnType()); // should not happen
198        }
199
200        @Override
201        public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
202                theMethodParams[myIdIndex] = MethodUtil.convertIdToType(theRequest.getId(), myIdParameterType);
203                if (myVersionIdIndex != null) {
204                        theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart());
205                }
206
207                Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
208                IBundleProvider retVal = toResourceList(response);
209
210
211                if (retVal.size() == 1) {
212                        List<IBaseResource> responseResources = retVal.getResources(0, 1);
213                        IBaseResource responseResource = responseResources.get(0);
214
215                        // If-None-Match
216                        if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {
217                                String ifNoneMatch = theRequest.getHeader(Constants.HEADER_IF_NONE_MATCH_LC);
218                                if (StringUtils.isNotBlank(ifNoneMatch)) {
219                                        ifNoneMatch = MethodUtil.parseETagValue(ifNoneMatch);
220                                        if (responseResource.getIdElement() != null && responseResource.getIdElement().hasVersionIdPart()) {
221                                                if (responseResource.getIdElement().getVersionIdPart().equals(ifNoneMatch)) {
222                                                        ourLog.debug("Returning HTTP 301 because request specified {}={}", Constants.HEADER_IF_NONE_MATCH, ifNoneMatch);
223                                                        throw new NotModifiedException("Not Modified");
224                                                }
225                                        }
226                                }
227                        }
228                                
229                        // If-Modified-Since
230                        String ifModifiedSince = theRequest.getHeader(Constants.HEADER_IF_MODIFIED_SINCE_LC);
231                        if (isNotBlank(ifModifiedSince)) {
232                                Date ifModifiedSinceDate = DateUtils.parseDate(ifModifiedSince);
233                                Date lastModified = null;
234                                if (responseResource instanceof IResource) {
235                                        InstantDt lastModifiedDt = ResourceMetadataKeyEnum.UPDATED.get((IResource) responseResource);
236                                        if (lastModifiedDt != null) {
237                                                lastModified = lastModifiedDt.getValue();
238                                        }
239                                } else {
240                                        lastModified = ((IAnyResource)responseResource).getMeta().getLastUpdated();
241                                }
242                                
243                                if (lastModified != null && lastModified.getTime() > ifModifiedSinceDate.getTime()) {
244                                        ourLog.debug("Returning HTTP 301 because If-Modified-Since does not match");
245                                        throw new NotModifiedException("Not Modified");
246                                }
247                        }
248                                
249                } // if we have at least 1 result
250                
251                
252                return retVal;
253        }
254
255        @Override
256        public boolean isBinary() {
257                return "Binary".equals(getResourceName());
258        }
259
260        public boolean isVread() {
261                return mySupportsVersion || myVersionIdIndex != null;
262        }
263
264        public static HttpGetClientInvocation createAbsoluteReadInvocation(FhirContext theContext, IIdType theId) {
265                return new HttpGetClientInvocation(theContext, theId.toVersionless().getValue());
266        }
267
268        public static HttpGetClientInvocation createAbsoluteVReadInvocation(FhirContext theContext, IIdType theId) {
269                return new HttpGetClientInvocation(theContext, theId.getValue());
270        }
271
272        public static HttpGetClientInvocation createReadInvocation(FhirContext theContext, IIdType theId, String theResourceName) {
273                return new HttpGetClientInvocation(theContext, new IdDt(theResourceName, theId.getIdPart()).getValue());
274        }
275
276        public static HttpGetClientInvocation createVReadInvocation(FhirContext theContext, IIdType theId, String theResourceName) {
277                return new HttpGetClientInvocation(theContext, new IdDt(theResourceName, theId.getIdPart(), theId.getVersionIdPart()).getValue());
278        }
279
280        @Override
281        protected BundleTypeEnum getResponseBundleType() {
282                return null;
283        }
284
285}