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