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 - 2016 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.ArrayList; 029import java.util.Collections; 030import java.util.List; 031import java.util.Map; 032 033import org.apache.commons.io.IOUtils; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.commons.lang3.Validate; 036import org.hl7.fhir.instance.model.api.IBaseBinary; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038import org.hl7.fhir.instance.model.api.IIdType; 039 040import ca.uhn.fhir.context.ConfigurationException; 041import ca.uhn.fhir.context.FhirContext; 042import ca.uhn.fhir.model.api.Bundle; 043import ca.uhn.fhir.model.api.IResource; 044import ca.uhn.fhir.model.primitive.IdDt; 045import ca.uhn.fhir.model.valueset.BundleTypeEnum; 046import ca.uhn.fhir.rest.annotation.Elements; 047import ca.uhn.fhir.rest.annotation.IdParam; 048import ca.uhn.fhir.rest.annotation.Read; 049import ca.uhn.fhir.rest.api.RequestTypeEnum; 050import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 051import ca.uhn.fhir.rest.server.Constants; 052import ca.uhn.fhir.rest.server.ETagSupportEnum; 053import ca.uhn.fhir.rest.server.IBundleProvider; 054import ca.uhn.fhir.rest.server.IRestfulServer; 055import ca.uhn.fhir.rest.server.SimpleBundleProvider; 056import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 057import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 058import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 059import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; 060 061public class ReadMethodBinding extends BaseResourceReturningMethodBinding implements IClientResponseHandlerHandlesBinary<Object> { 062 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class); 063 064 private Integer myIdIndex; 065 private boolean mySupportsVersion; 066 private Integer myVersionIdIndex; 067 private Class<? extends IIdType> myIdParameterType; 068 069 @SuppressWarnings("unchecked") 070 public ReadMethodBinding(Class<? extends IBaseResource> theAnnotatedResourceType, Method theMethod, FhirContext theContext, Object theProvider) { 071 super(theAnnotatedResourceType, theMethod, theContext, theProvider); 072 073 Validate.notNull(theMethod, "Method must not be null"); 074 075 Integer idIndex = MethodUtil.findIdParameterIndex(theMethod, getContext()); 076 Integer versionIdIndex = MethodUtil.findVersionIdParameterIndex(theMethod); 077 078 Class<?>[] parameterTypes = theMethod.getParameterTypes(); 079 080 mySupportsVersion = theMethod.getAnnotation(Read.class).version(); 081 myIdIndex = idIndex; 082 myVersionIdIndex = versionIdIndex; 083 084 if (myIdIndex == null) { 085 throw new ConfigurationException("@" + Read.class.getSimpleName() + " method " + theMethod.getName() + " on type \"" + theMethod.getDeclaringClass().getName() + "\" does not have a parameter annotated with @" + IdParam.class.getSimpleName()); 086 } 087 myIdParameterType = (Class<? extends IIdType>) parameterTypes[myIdIndex]; 088 089 if (!IIdType.class.isAssignableFrom(myIdParameterType)) { 090 throw new ConfigurationException("ID parameter must be of type IdDt or IdType - Found: " + myIdParameterType); 091 } 092 if (myVersionIdIndex != null && !IdDt.class.equals(parameterTypes[myVersionIdIndex])) { 093 throw new ConfigurationException("Version ID parameter must be of type: " + IdDt.class.getCanonicalName() + " - Found: " + parameterTypes[myVersionIdIndex]); 094 } 095 096 } 097 098 @Override 099 public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) { 100 if (mySupportsVersion && theRequestDetails.getId().hasVersionIdPart()) { 101 return RestOperationTypeEnum.VREAD; 102 } else { 103 return RestOperationTypeEnum.READ; 104 } 105 } 106 107 @Override 108 public List<Class<?>> getAllowableParamAnnotations() { 109 ArrayList<Class<?>> retVal = new ArrayList<Class<?>>(); 110 retVal.add(IdParam.class); 111 retVal.add(Elements.class); 112 return retVal; 113 } 114 115 @Override 116 public RestOperationTypeEnum getRestOperationType() { 117 return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ; 118 } 119 120 @Override 121 public ReturnTypeEnum getReturnType() { 122 return ReturnTypeEnum.RESOURCE; 123 } 124 125 @Override 126 public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { 127 if (!theRequest.getResourceName().equals(getResourceName())) { 128 return false; 129 } 130 for (String next : theRequest.getParameters().keySet()) { 131 if (!ALLOWED_PARAMS.contains(next)) { 132 return false; 133 } 134 } 135 if (theRequest.getId() == null) { 136 return false; 137 } 138 if (mySupportsVersion == false) { 139 if (theRequest.getId().hasVersionIdPart()) { 140 return false; 141 } 142 } 143 if (isNotBlank(theRequest.getCompartmentName())) { 144 return false; 145 } 146 if (theRequest.getRequestType() != RequestTypeEnum.GET) { 147 ourLog.trace("Method {} doesn't match because request type is not GET: {}", theRequest.getId(), theRequest.getRequestType()); 148 return false; 149 } 150 if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { 151 if (mySupportsVersion == false && myVersionIdIndex == null) { 152 return false; 153 } 154 if (theRequest.getId().hasVersionIdPart() == false) { 155 return false; 156 } 157 } else if (!StringUtils.isBlank(theRequest.getOperation())) { 158 return false; 159 } 160 return true; 161 } 162 163 @Override 164 public HttpGetClientInvocation invokeClient(Object[] theArgs) { 165 HttpGetClientInvocation retVal; 166 IdDt id = ((IdDt) theArgs[myIdIndex]); 167 if (myVersionIdIndex == null) { 168 String resourceName = getResourceName(); 169 if (id.hasVersionIdPart()) { 170 retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()), resourceName); 171 } else { 172 retVal = createReadInvocation(id, resourceName); 173 } 174 } else { 175 IdDt vid = ((IdDt) theArgs[myVersionIdIndex]); 176 String resourceName = getResourceName(); 177 178 retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()), resourceName); 179 } 180 181 for (int idx = 0; idx < theArgs.length; idx++) { 182 IParameter nextParam = getParameters().get(idx); 183 nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null); 184 } 185 186 return retVal; 187 } 188 189 @Override 190 public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException { 191 byte[] contents = IOUtils.toByteArray(theResponseReader); 192 193 IBaseBinary resource = (IBaseBinary) getContext().getResourceDefinition("Binary").newInstance(); 194 resource.setContentType(theResponseMimeType); 195 resource.setContent(contents); 196 197 switch (getMethodReturnType()) { 198 case BUNDLE: 199 return Bundle.withSingleResource((IResource) resource); 200 case LIST_OF_RESOURCES: 201 return Collections.singletonList(resource); 202 case RESOURCE: 203 return resource; 204 case BUNDLE_PROVIDER: 205 return new SimpleBundleProvider(resource); 206 case BUNDLE_RESOURCE: 207 case METHOD_OUTCOME: 208 break; 209 } 210 211 throw new IllegalStateException("" + getMethodReturnType()); // should not happen 212 } 213 214 @Override 215 public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { 216 theMethodParams[myIdIndex] = MethodUtil.convertIdToType(theRequest.getId(), myIdParameterType); 217 if (myVersionIdIndex != null) { 218 theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart()); 219 } 220 221 Object response = invokeServerMethod(theServer, theRequest, theMethodParams); 222 IBundleProvider retVal = toResourceList(response); 223 224 if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) { 225 String ifNoneMatch = theRequest.getHeader(Constants.HEADER_IF_NONE_MATCH_LC); 226 if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) { 227 List<IBaseResource> responseResources = retVal.getResources(0, 1); 228 IBaseResource responseResource = responseResources.get(0); 229 230 ifNoneMatch = MethodUtil.parseETagValue(ifNoneMatch); 231 if (responseResource.getIdElement() != null && responseResource.getIdElement().hasVersionIdPart()) { 232 if (responseResource.getIdElement().getVersionIdPart().equals(ifNoneMatch)) { 233 ourLog.debug("Returning HTTP 301 because request specified {}={}", Constants.HEADER_IF_NONE_MATCH, ifNoneMatch); 234 throw new NotModifiedException("Not Modified"); 235 } 236 } 237 } 238 } 239 240 return retVal; 241 } 242 243 @Override 244 public boolean isBinary() { 245 return "Binary".equals(getResourceName()); 246 } 247 248 public boolean isVread() { 249 return mySupportsVersion || myVersionIdIndex != null; 250 } 251 252 public static HttpGetClientInvocation createAbsoluteReadInvocation(IIdType theId) { 253 return new HttpGetClientInvocation(theId.toVersionless().getValue()); 254 } 255 256 public static HttpGetClientInvocation createAbsoluteVReadInvocation(IIdType theId) { 257 return new HttpGetClientInvocation(theId.getValue()); 258 } 259 260 public static HttpGetClientInvocation createReadInvocation(IIdType theId, String theResourceName) { 261 return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart()).getValue()); 262 } 263 264 public static HttpGetClientInvocation createVReadInvocation(IIdType theId, String theResourceName) { 265 return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart(), theId.getVersionIdPart()).getValue()); 266 } 267 268 @Override 269 protected BundleTypeEnum getResponseBundleType() { 270 return null; 271 } 272 273}