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}