001package ca.uhn.fhir.rest.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 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.lang.annotation.Annotation;
024import java.lang.reflect.Method;
025import java.util.*;
026
027import org.hl7.fhir.instance.model.api.IIdType;
028
029import ca.uhn.fhir.context.ConfigurationException;
030import ca.uhn.fhir.context.FhirContext;
031import ca.uhn.fhir.model.api.IResource;
032import ca.uhn.fhir.rest.annotation.Patch;
033import ca.uhn.fhir.rest.annotation.ResourceParam;
034import ca.uhn.fhir.rest.api.PatchTypeEnum;
035import ca.uhn.fhir.rest.api.RequestTypeEnum;
036import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
037import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
038import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
039import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
040
041/**
042 * Base class for an operation that has a resource type but not a resource body in the
043 * request body
044 *
045 */
046public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceIdButNoResourceBody {
047
048        private int myPatchTypeParameterIndex = -1;
049        private int myResourceParamIndex;
050
051        public PatchMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
052                super(theMethod, theContext, theProvider, Patch.class, theMethod.getAnnotation(Patch.class).type());
053
054                for (ListIterator<Class<?>> iter = Arrays.asList(theMethod.getParameterTypes()).listIterator(); iter.hasNext();) {
055                        int nextIndex = iter.nextIndex();
056                        Class<?> next = iter.next();
057                        if (next.equals(PatchTypeEnum.class)) {
058                                myPatchTypeParameterIndex = nextIndex;
059                        }
060                        for (Annotation nextAnnotation : theMethod.getParameterAnnotations()[nextIndex]) {
061                                if (nextAnnotation instanceof ResourceParam) {
062                                        myResourceParamIndex = nextIndex;
063                                }
064                        }
065                }
066
067                if (myPatchTypeParameterIndex == -1) {
068                        throw new ConfigurationException("Method has no parameter of type " + PatchTypeEnum.class.getName() + " - " + theMethod.toString());
069                }
070                if (myResourceParamIndex == -1) {
071                        throw new ConfigurationException("Method has no parameter with @" + ResourceParam.class.getSimpleName() + " annotation - " + theMethod.toString());
072                }
073        }
074
075        @Override
076        public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
077                boolean retVal = super.incomingServerRequestMatchesMethod(theRequest);
078                if (retVal) {
079                        PatchTypeParameter.getTypeForRequestOrThrowInvalidRequestException(theRequest);
080                }
081                return retVal;
082        }
083
084        @Override
085        public RestOperationTypeEnum getRestOperationType() {
086                return RestOperationTypeEnum.PATCH;
087        }
088
089        @Override
090        protected Set<RequestTypeEnum> provideAllowableRequestTypes() {
091                return Collections.singleton(RequestTypeEnum.PATCH);
092        }
093
094        @Override
095        protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
096                StringBuilder urlExtension = new StringBuilder();
097                urlExtension.append(getContext().getResourceDefinition(theResource).getName());
098
099                return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString());
100        }
101
102        @Override
103        protected boolean allowVoidReturnType() {
104                return true;
105        }
106
107        @Override
108        public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
109                IIdType idDt = (IIdType) theArgs[getIdParameterIndex()];
110                if (idDt == null) {
111                        throw new NullPointerException("ID can not be null");
112                }
113
114                if (idDt.hasResourceType() == false) {
115                        idDt = idDt.withResourceType(getResourceName());
116                } else if (getResourceName().equals(idDt.getResourceType()) == false) {
117                        throw new InvalidRequestException("ID parameter has the wrong resource type, expected '" + getResourceName() + "', found: " + idDt.getResourceType());
118                }
119
120                PatchTypeEnum patchType = (PatchTypeEnum) theArgs[myPatchTypeParameterIndex];
121                String body = (String) theArgs[myResourceParamIndex];
122
123                HttpPatchClientInvocation retVal = createPatchInvocation(getContext(), idDt, patchType, body);
124
125                for (int idx = 0; idx < theArgs.length; idx++) {
126                        IParameter nextParam = getParameters().get(idx);
127                        nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
128                }
129
130                return retVal;
131        }
132
133        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IIdType theId, PatchTypeEnum thePatchType, String theBody) {
134                HttpPatchClientInvocation retVal = new HttpPatchClientInvocation(theContext, theId, thePatchType.getContentType(), theBody);
135                return retVal;
136        }
137
138        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, String theUrlPath, PatchTypeEnum thePatchType, String theBody) {
139                HttpPatchClientInvocation retVal = new HttpPatchClientInvocation(theContext, theUrlPath, thePatchType.getContentType(), theBody);
140                return retVal;
141        }
142
143        @Override
144        protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
145                IIdType id = theRequest.getId();
146                id = UpdateMethodBinding.applyETagAsVersion(theRequest, id);
147                theParams[getIdParameterIndex()] = id;
148        }
149
150        @Override
151        protected String getMatchingOperation() {
152                return null;
153        }
154
155        public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, PatchTypeEnum thePatchType, String theBody, String theResourceType, Map<String, List<String>> theMatchParams) {
156                StringBuilder urlBuilder = MethodUtil.createUrl(theResourceType, theMatchParams);
157                String url = urlBuilder.toString();
158                HttpPatchClientInvocation retVal = new HttpPatchClientInvocation(theContext, url, thePatchType.getContentType(), theBody);
159                return retVal;
160        }
161
162}