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}