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 static org.apache.commons.lang3.StringUtils.isNotBlank;
024
025import java.lang.reflect.Method;
026import java.util.ArrayList;
027import java.util.Date;
028import java.util.List;
029
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.Validate;
032import org.hl7.fhir.instance.model.api.IBaseResource;
033import org.hl7.fhir.instance.model.api.IIdType;
034
035import ca.uhn.fhir.context.ConfigurationException;
036import ca.uhn.fhir.context.FhirContext;
037import ca.uhn.fhir.model.api.IResource;
038import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
039import ca.uhn.fhir.model.primitive.InstantDt;
040import ca.uhn.fhir.model.valueset.BundleTypeEnum;
041import ca.uhn.fhir.rest.annotation.Elements;
042import ca.uhn.fhir.rest.annotation.IdParam;
043import ca.uhn.fhir.rest.annotation.Read;
044import ca.uhn.fhir.rest.api.Constants;
045import ca.uhn.fhir.rest.api.RequestTypeEnum;
046import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
047import ca.uhn.fhir.rest.api.server.IBundleProvider;
048import ca.uhn.fhir.rest.api.server.IRestfulServer;
049import ca.uhn.fhir.rest.api.server.RequestDetails;
050import ca.uhn.fhir.rest.param.ParameterUtil;
051import ca.uhn.fhir.rest.server.ETagSupportEnum;
052import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
053import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
054import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
055import ca.uhn.fhir.util.DateUtils;
056
057import javax.annotation.Nonnull;
058
059public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
060        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
061
062        private Integer myIdIndex;
063        private boolean mySupportsVersion;
064        private Class<? extends IIdType> myIdParameterType;
065
066        @SuppressWarnings("unchecked")
067        public ReadMethodBinding(Class<? extends IBaseResource> theAnnotatedResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
068                super(theAnnotatedResourceType, theMethod, theContext, theProvider);
069
070                Validate.notNull(theMethod, "Method must not be null");
071
072                Integer idIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
073
074                Class<?>[] parameterTypes = theMethod.getParameterTypes();
075
076                mySupportsVersion = theMethod.getAnnotation(Read.class).version();
077                myIdIndex = idIndex;
078
079                if (myIdIndex == null) {
080                        throw new ConfigurationException("@" + Read.class.getSimpleName() + " method " + theMethod.getName() + " on type \"" + theMethod.getDeclaringClass().getName() + "\" does not have a parameter annotated with @" + IdParam.class.getSimpleName());
081                }
082                myIdParameterType = (Class<? extends IIdType>) parameterTypes[myIdIndex];
083
084                if (!IIdType.class.isAssignableFrom(myIdParameterType)) {
085                        throw new ConfigurationException("ID parameter must be of type IdDt or IdType - Found: " + myIdParameterType);
086                }
087
088        }
089
090        @Override
091        public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
092                if (mySupportsVersion && theRequestDetails.getId().hasVersionIdPart()) {
093                        return RestOperationTypeEnum.VREAD;
094                }
095                return RestOperationTypeEnum.READ;
096        }
097
098        @Override
099        public List<Class<?>> getAllowableParamAnnotations() {
100                ArrayList<Class<?>> retVal = new ArrayList<Class<?>>();
101                retVal.add(IdParam.class);
102                retVal.add(Elements.class);
103                return retVal;
104        }
105
106        @Nonnull
107        @Override
108        public RestOperationTypeEnum getRestOperationType() {
109                return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
110        }
111
112        @Override
113        public ReturnTypeEnum getReturnType() {
114                return ReturnTypeEnum.RESOURCE;
115        }
116
117        @Override
118        public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
119                if (!theRequest.getResourceName().equals(getResourceName())) {
120                        return false;
121                }
122                for (String next : theRequest.getParameters().keySet()) {
123                        if (!next.startsWith("_")) {
124                                return false;
125                        }
126                }
127                if (theRequest.getId() == null) {
128                        return false;
129                }
130                if (mySupportsVersion == false) {
131                        if (theRequest.getId().hasVersionIdPart()) {
132                                return false;
133                        }
134                }
135                if (isNotBlank(theRequest.getCompartmentName())) {
136                        return false;
137                }
138                if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.HEAD ) {
139                        ourLog.trace("Method {} doesn't match because request type is not GET or HEAD: {}", theRequest.getId(), theRequest.getRequestType());
140                        return false;
141                }
142                if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
143                        if (mySupportsVersion == false) {
144                                return false;
145                        }
146                        if (theRequest.getId().hasVersionIdPart() == false) {
147                                return false;
148                        }
149                } else if (!StringUtils.isBlank(theRequest.getOperation())) {
150                        return false;
151                }
152                return true;
153        }
154
155
156        @Override
157        public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
158                IIdType requestId = theRequest.getId();
159
160                theMethodParams[myIdIndex] = ParameterUtil.convertIdToType(requestId, myIdParameterType);
161
162                Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
163                IBundleProvider retVal = toResourceList(response);
164
165
166                if (retVal.size() == 1) {
167                        List<IBaseResource> responseResources = retVal.getResources(0, 1);
168                        IBaseResource responseResource = responseResources.get(0);
169
170                        // If-None-Match
171                        if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {
172                                String ifNoneMatch = theRequest.getHeader(Constants.HEADER_IF_NONE_MATCH_LC);
173                                if (StringUtils.isNotBlank(ifNoneMatch)) {
174                                        ifNoneMatch = ParameterUtil.parseETagValue(ifNoneMatch);
175                                        if (responseResource.getIdElement() != null && responseResource.getIdElement().hasVersionIdPart()) {
176                                                if (responseResource.getIdElement().getVersionIdPart().equals(ifNoneMatch)) {
177                                                        ourLog.debug("Returning HTTP 304 because request specified {}={}", Constants.HEADER_IF_NONE_MATCH, ifNoneMatch);
178                                                        throw new NotModifiedException("Not Modified");
179                                                }
180                                        }
181                                }
182                        }
183                                
184                        // If-Modified-Since
185                        String ifModifiedSince = theRequest.getHeader(Constants.HEADER_IF_MODIFIED_SINCE_LC);
186                        if (isNotBlank(ifModifiedSince)) {
187                                Date ifModifiedSinceDate = DateUtils.parseDate(ifModifiedSince);
188                                Date lastModified = null;
189                                if (responseResource instanceof IResource) {
190                                        InstantDt lastModifiedDt = ResourceMetadataKeyEnum.UPDATED.get((IResource) responseResource);
191                                        if (lastModifiedDt != null) {
192                                                lastModified = lastModifiedDt.getValue();
193                                        }
194                                } else {
195                                        lastModified = responseResource.getMeta().getLastUpdated();
196                                }
197                                
198                                if (lastModified != null && lastModified.getTime() <= ifModifiedSinceDate.getTime()) {
199                                        ourLog.debug("Returning HTTP 304 because If-Modified-Since does not match");
200                                        throw new NotModifiedException("Not Modified");
201                                }
202                        }
203                                
204                } // if we have at least 1 result
205                
206                
207                return retVal;
208        }
209
210        public boolean isVread() {
211                return mySupportsVersion;
212        }
213
214        @Override
215        protected BundleTypeEnum getResponseBundleType() {
216                return null;
217        }
218
219}