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}