001/*
002 * #%L
003 * HAPI FHIR - Client Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.client.method;
021
022import ca.uhn.fhir.context.ConfigurationException;
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.rest.annotation.Patch;
026import ca.uhn.fhir.rest.annotation.ResourceParam;
027import ca.uhn.fhir.rest.api.PatchTypeEnum;
028import ca.uhn.fhir.rest.api.RequestTypeEnum;
029import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
030import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
031import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
033import org.hl7.fhir.instance.model.api.IBaseResource;
034import org.hl7.fhir.instance.model.api.IIdType;
035
036import java.lang.annotation.Annotation;
037import java.lang.reflect.Method;
038import java.util.Arrays;
039import java.util.Collections;
040import java.util.List;
041import java.util.ListIterator;
042import java.util.Map;
043import java.util.Set;
044
045/**
046 * Base class for an operation that has a resource type but not a resource body in the
047 * request body
048 *
049 */
050public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceIdButNoResourceBody {
051
052        private int myPatchTypeParameterIndex = -1;
053        private int myResourceParamIndex;
054
055        public PatchMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
056                super(
057                                theMethod,
058                                theContext,
059                                theProvider,
060                                Patch.class,
061                                theMethod.getAnnotation(Patch.class).type());
062
063                for (ListIterator<Class<?>> iter =
064                                                Arrays.asList(theMethod.getParameterTypes()).listIterator();
065                                iter.hasNext(); ) {
066                        int nextIndex = iter.nextIndex();
067                        Class<?> next = iter.next();
068                        if (next.equals(PatchTypeEnum.class)) {
069                                myPatchTypeParameterIndex = nextIndex;
070                        }
071                        for (Annotation nextAnnotation : theMethod.getParameterAnnotations()[nextIndex]) {
072                                if (nextAnnotation instanceof ResourceParam) {
073                                        myResourceParamIndex = nextIndex;
074                                }
075                        }
076                }
077
078                if (myPatchTypeParameterIndex == -1) {
079                        throw new ConfigurationException(Msg.code(1414) + "Method has no parameter of type "
080                                        + PatchTypeEnum.class.getName() + " - " + theMethod.toString());
081                }
082                if (myResourceParamIndex == -1) {
083                        throw new ConfigurationException(Msg.code(1415) + "Method has no parameter with @"
084                                        + ResourceParam.class.getSimpleName() + " annotation - " + theMethod.toString());
085                }
086        }
087
088        @Override
089        public RestOperationTypeEnum getRestOperationType() {
090                return RestOperationTypeEnum.PATCH;
091        }
092
093        @Override
094        protected Set<RequestTypeEnum> provideAllowableRequestTypes() {
095                return Collections.singleton(RequestTypeEnum.PATCH);
096        }
097
098        @Override
099        protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IBaseResource theResource) {
100                StringBuilder urlExtension = new StringBuilder();
101                urlExtension.append(getContext().getResourceType(theResource));
102
103                return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString());
104        }
105
106        @Override
107        protected boolean allowVoidReturnType() {
108                return true;
109        }
110
111        @Override
112        public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
113                IIdType idDt = (IIdType) theArgs[getIdParameterIndex()];
114                if (idDt == null) {
115                        throw new NullPointerException(Msg.code(1416) + "ID can not be null");
116                }
117
118                if (idDt.hasResourceType() == false) {
119                        idDt = idDt.withResourceType(getResourceName());
120                } else if (getResourceName().equals(idDt.getResourceType()) == false) {
121                        throw new InvalidRequestException(Msg.code(1417) + "ID parameter has the wrong resource type, expected '"
122                                        + getResourceName() + "', found: " + idDt.getResourceType());
123                }
124
125                PatchTypeEnum patchType = (PatchTypeEnum) theArgs[myPatchTypeParameterIndex];
126                String body = (String) theArgs[myResourceParamIndex];
127
128                HttpPatchClientInvocation retVal = createPatchInvocation(getContext(), idDt, patchType, body);
129
130                for (int idx = 0; idx < theArgs.length; idx++) {
131                        IParameter nextParam = getParameters().get(idx);
132                        nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
133                }
134
135                return retVal;
136        }
137
138        public static HttpPatchClientInvocation createPatchInvocation(
139                        FhirContext theContext, IIdType theId, PatchTypeEnum thePatchType, String theBody) {
140                HttpPatchClientInvocation retVal =
141                                new HttpPatchClientInvocation(theContext, theId, thePatchType.getContentType(), theBody);
142                return retVal;
143        }
144
145        public static HttpPatchClientInvocation createPatchInvocation(
146                        FhirContext theContext, String theUrlPath, PatchTypeEnum thePatchType, String theBody) {
147                HttpPatchClientInvocation retVal =
148                                new HttpPatchClientInvocation(theContext, theUrlPath, thePatchType.getContentType(), theBody);
149                return retVal;
150        }
151
152        @Override
153        protected String getMatchingOperation() {
154                return null;
155        }
156
157        public static HttpPatchClientInvocation createPatchInvocation(
158                        FhirContext theContext,
159                        PatchTypeEnum thePatchType,
160                        String theBody,
161                        String theResourceType,
162                        Map<String, List<String>> theMatchParams) {
163                StringBuilder urlBuilder = MethodUtil.createUrl(theResourceType, theMatchParams);
164                String url = urlBuilder.toString();
165                HttpPatchClientInvocation retVal =
166                                new HttpPatchClientInvocation(theContext, url, thePatchType.getContentType(), theBody);
167                return retVal;
168        }
169}