001package ca.uhn.fhir.rest.client.method; 002 003/*- 004 * #%L 005 * HAPI FHIR - Client Framework 006 * %% 007 * Copyright (C) 2014 - 2018 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 java.io.Reader; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.*; 027 028import org.hl7.fhir.instance.model.api.*; 029 030import ca.uhn.fhir.context.ConfigurationException; 031import ca.uhn.fhir.context.FhirContext; 032import ca.uhn.fhir.model.api.IResource; 033import ca.uhn.fhir.model.valueset.BundleTypeEnum; 034import ca.uhn.fhir.parser.IParser; 035import ca.uhn.fhir.rest.api.Constants; 036import ca.uhn.fhir.rest.api.MethodOutcome; 037import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; 038import ca.uhn.fhir.util.BundleUtil; 039import ca.uhn.fhir.util.ReflectionUtil; 040 041public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> { 042 protected static final Set<String> ALLOWED_PARAMS; 043 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReturningMethodBinding.class); 044 045 static { 046 HashSet<String> set = new HashSet<String>(); 047 set.add(Constants.PARAM_FORMAT); 048 set.add(Constants.PARAM_NARRATIVE); 049 set.add(Constants.PARAM_PRETTY); 050 set.add(Constants.PARAM_SORT); 051 set.add(Constants.PARAM_SORT_ASC); 052 set.add(Constants.PARAM_SORT_DESC); 053 set.add(Constants.PARAM_COUNT); 054 set.add(Constants.PARAM_SUMMARY); 055 set.add(Constants.PARAM_ELEMENTS); 056 ALLOWED_PARAMS = Collections.unmodifiableSet(set); 057 } 058 059 private MethodReturnTypeEnum myMethodReturnType; 060 private Class<?> myResourceListCollectionType; 061 private String myResourceName; 062 private Class<? extends IBaseResource> myResourceType; 063 private List<Class<? extends IBaseResource>> myPreferTypesList; 064 065 @SuppressWarnings("unchecked") 066 public BaseResourceReturningMethodBinding(Class<?> theReturnResourceType, Method theMethod, FhirContext theContext, Object theProvider) { 067 super(theMethod, theContext, theProvider); 068 069 Class<?> methodReturnType = theMethod.getReturnType(); 070 if (Collection.class.isAssignableFrom(methodReturnType)) { 071 072 myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES; 073 Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod); 074 if (collectionType != null) { 075 if (!Object.class.equals(collectionType) && !IBaseResource.class.isAssignableFrom(collectionType)) { 076 throw new ConfigurationException( 077 "Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " + collectionType); 078 } 079 } 080 myResourceListCollectionType = collectionType; 081 082 } else if (IBaseResource.class.isAssignableFrom(methodReturnType)) { 083 if (Modifier.isAbstract(methodReturnType.getModifiers()) == false && theContext.getResourceDefinition((Class<? extends IBaseResource>) methodReturnType).isBundle()) { 084 myMethodReturnType = MethodReturnTypeEnum.BUNDLE_RESOURCE; 085 } else { 086 myMethodReturnType = MethodReturnTypeEnum.RESOURCE; 087 } 088 } else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) { 089 myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME; 090 } else { 091 throw new ConfigurationException( 092 "Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); 093 } 094 095 if (theReturnResourceType != null) { 096 if (IBaseResource.class.isAssignableFrom(theReturnResourceType)) { 097 if (Modifier.isAbstract(theReturnResourceType.getModifiers()) || Modifier.isInterface(theReturnResourceType.getModifiers())) { 098 // If we're returning an abstract type, that's ok 099 } else { 100 myResourceType = (Class<? extends IResource>) theReturnResourceType; 101 myResourceName = theContext.getResourceDefinition(myResourceType).getName(); 102 } 103 } 104 } 105 106 myPreferTypesList = createPreferTypesList(); 107 } 108 109 public MethodReturnTypeEnum getMethodReturnType() { 110 return myMethodReturnType; 111 } 112 113 @Override 114 public String getResourceName() { 115 return myResourceName; 116 } 117 118 /** 119 * If the response is a bundle, this type will be placed in the root of the bundle (can be null) 120 */ 121 protected abstract BundleTypeEnum getResponseBundleType(); 122 123 public abstract ReturnTypeEnum getReturnType(); 124 125 @Override 126 public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) { 127 128 if (Constants.STATUS_HTTP_204_NO_CONTENT == theResponseStatusCode) { 129 return toReturnType(null); 130 } 131 132 IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode, myPreferTypesList); 133 134 switch (getReturnType()) { 135 case BUNDLE: { 136 137 IBaseBundle bundle = null; 138 List<? extends IBaseResource> listOfResources = null; 139 Class<? extends IBaseResource> type = getContext().getResourceDefinition("Bundle").getImplementingClass(); 140 bundle = (IBaseBundle) parser.parseResource(type, theResponseReader); 141 listOfResources = BundleUtil.toListOfResources(getContext(), bundle); 142 143 switch (getMethodReturnType()) { 144 case BUNDLE_RESOURCE: 145 return bundle; 146 case LIST_OF_RESOURCES: 147 if (myResourceListCollectionType != null) { 148 for (Iterator<? extends IBaseResource> iter = listOfResources.iterator(); iter.hasNext();) { 149 IBaseResource next = iter.next(); 150 if (!myResourceListCollectionType.isAssignableFrom(next.getClass())) { 151 ourLog.debug("Not returning resource of type {} because it is not a subclass or instance of {}", next.getClass(), myResourceListCollectionType); 152 iter.remove(); 153 } 154 } 155 } 156 return listOfResources; 157 case RESOURCE: 158 List<IBaseResource> list = BundleUtil.toListOfResources(getContext(), bundle); 159 if (list.size() == 0) { 160 return null; 161 } else if (list.size() == 1) { 162 return list.get(0); 163 } else { 164 throw new InvalidResponseException(theResponseStatusCode, "FHIR server call returned a bundle with multiple resources, but this method is only able to returns one."); 165 } 166 default: 167 break; 168 } 169 break; 170 } 171 case RESOURCE: { 172 IBaseResource resource; 173 if (myResourceType != null) { 174 resource = parser.parseResource(myResourceType, theResponseReader); 175 } else { 176 resource = parser.parseResource(theResponseReader); 177 } 178 179 MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, resource); 180 181 return toReturnType(resource); 182 } 183 } 184 185 throw new IllegalStateException("Should not get here!"); 186 } 187 188 private Object toReturnType(IBaseResource resource) { 189 Object retVal = null; 190 191 switch (getMethodReturnType()) { 192 case LIST_OF_RESOURCES: 193 retVal = Collections.emptyList(); 194 if (resource != null) { 195 retVal = Collections.singletonList(resource); 196 } 197 break; 198 case RESOURCE: 199 retVal = resource; 200 break; 201 case BUNDLE_RESOURCE: 202 retVal = resource; 203 break; 204 case METHOD_OUTCOME: 205 MethodOutcome outcome = new MethodOutcome(); 206 outcome.setOperationOutcome((IBaseOperationOutcome) resource); 207 retVal = outcome; 208 break; 209 } 210 return retVal; 211 } 212 213 @SuppressWarnings("unchecked") 214 private List<Class<? extends IBaseResource>> createPreferTypesList() { 215 List<Class<? extends IBaseResource>> preferTypes = null; 216 if (myResourceType != null && !BaseMethodBinding.isResourceInterface(myResourceType)) { 217 preferTypes = new ArrayList<Class<? extends IBaseResource>>(1); 218 preferTypes.add(myResourceType); 219 } else if (myResourceListCollectionType != null && IBaseResource.class.isAssignableFrom(myResourceListCollectionType) && !BaseMethodBinding.isResourceInterface(myResourceListCollectionType)) { 220 preferTypes = new ArrayList<Class<? extends IBaseResource>>(1); 221 preferTypes.add((Class<? extends IBaseResource>) myResourceListCollectionType); 222 } 223 return preferTypes; 224 } 225 226 /** 227 * Should the response include a Content-Location header. Search method bunding (and any others?) may override this to disable the content-location, since it doesn't make sense 228 */ 229 protected boolean isAddContentLocationHeader() { 230 return true; 231 } 232 233 protected void setResourceName(String theResourceName) { 234 myResourceName = theResourceName; 235 } 236 237 public enum MethodReturnTypeEnum { 238 BUNDLE_RESOURCE, 239 LIST_OF_RESOURCES, 240 METHOD_OUTCOME, 241 RESOURCE 242 } 243 244 public static class ResourceOrDstu1Bundle { 245 246 private final IBaseResource myResource; 247 248 public ResourceOrDstu1Bundle(IBaseResource theResource) { 249 myResource = theResource; 250 } 251 252 public IBaseResource getResource() { 253 return myResource; 254 } 255 256 } 257 258 public enum ReturnTypeEnum { 259 BUNDLE, 260 RESOURCE 261 } 262 263}