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}