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}