001package ca.uhn.fhir.rest.server.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
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 ca.uhn.fhir.context.ConfigurationException;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
026import ca.uhn.fhir.model.valueset.BundleTypeEnum;
027import ca.uhn.fhir.rest.annotation.Transaction;
028import ca.uhn.fhir.rest.annotation.TransactionParam;
029import ca.uhn.fhir.rest.api.RequestTypeEnum;
030import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
031import ca.uhn.fhir.rest.api.server.IBundleProvider;
032import ca.uhn.fhir.rest.api.server.IRestfulServer;
033import ca.uhn.fhir.rest.api.server.RequestDetails;
034import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
035import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
036import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
037import ca.uhn.fhir.rest.server.method.TransactionParameter.ParamStyle;
038import org.hl7.fhir.instance.model.api.IBaseResource;
039
040import javax.annotation.Nonnull;
041import java.lang.reflect.Method;
042import java.util.List;
043
044import static org.apache.commons.lang3.StringUtils.isNotBlank;
045
046public class TransactionMethodBinding extends BaseResourceReturningMethodBinding {
047
048        private int myTransactionParamIndex;
049        private ParamStyle myTransactionParamStyle;
050
051        public TransactionMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
052                super(null, theMethod, theContext, theProvider);
053
054                myTransactionParamIndex = -1;
055                int index = 0;
056                for (IParameter next : getParameters()) {
057                        if (next instanceof TransactionParameter) {
058                                if (myTransactionParamIndex != -1) {
059                                        throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " has multiple parameters annotated with the @"
060                                                        + TransactionParam.class + " annotation, exactly one is required for @" + Transaction.class
061                                                        + " methods");
062                                }
063                                myTransactionParamIndex = index;
064                                myTransactionParamStyle = ((TransactionParameter) next).getParamStyle();
065                        }
066                        index++;
067                }
068
069                if (myTransactionParamIndex == -1) {
070                        throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with the @"
071                                        + TransactionParam.class + " annotation");
072                }
073        }
074
075        @Nonnull
076        @Override
077        public RestOperationTypeEnum getRestOperationType() {
078                return RestOperationTypeEnum.TRANSACTION;
079        }
080
081        @Override
082        protected BundleTypeEnum getResponseBundleType() {
083                return BundleTypeEnum.TRANSACTION_RESPONSE;
084        }
085
086        @Override
087        public ReturnTypeEnum getReturnType() {
088                return ReturnTypeEnum.BUNDLE;
089        }
090
091        @Override
092        public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
093                if (theRequest.getRequestType() != RequestTypeEnum.POST) {
094                        return MethodMatchEnum.NONE;
095                }
096                if (isNotBlank(theRequest.getOperation())) {
097                        return MethodMatchEnum.NONE;
098                }
099                if (isNotBlank(theRequest.getResourceName())) {
100                        return MethodMatchEnum.NONE;
101                }
102                return MethodMatchEnum.EXACT;
103        }
104
105        @SuppressWarnings("unchecked")
106        @Override
107        public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
108
109                /*
110                 * The design of HAPI's transaction method for DSTU1 support assumed that a transaction was just an update on a
111                 * bunch of resources (because that's what it was), but in DSTU2 transaction has become much more broad, so we
112                 * no longer hold the user's hand much here.
113                 */
114                if (myTransactionParamStyle == ParamStyle.RESOURCE_BUNDLE) {
115                        // This is the DSTU2 style
116                        Object response = invokeServerMethod(theRequest, theMethodParams);
117                        return response;
118                }
119
120                // Call the server implementation method
121                Object response = invokeServerMethod(theRequest, theMethodParams);
122                IBundleProvider retVal = toResourceList(response);
123
124                /*
125                 * int offset = 0; if (retVal.size() != resources.size()) { if (retVal.size() > 0 && retVal.getResources(0,
126                 * 1).get(0) instanceof OperationOutcome) { offset = 1; } else { throw new
127                 * InternalErrorException("Transaction bundle contained " + resources.size() +
128                 * " entries, but server method response contained " + retVal.size() + " entries (must be the same)"); } }
129                 */
130
131                List<IBaseResource> retResources = retVal.getAllResources();
132                for (int i = 0; i < retResources.size(); i++) {
133                        IBaseResource newRes = retResources.get(i);
134                        if (newRes.getIdElement() == null || newRes.getIdElement().isEmpty()) {
135                                if (!(newRes instanceof BaseOperationOutcome)) {
136                                        throw new InternalErrorException("Transaction method returned resource at index " + i + " with no id specified - IResource#setId(IdDt)");
137                                }
138                        }
139                }
140
141                return retVal;
142        }
143
144        @Override
145        protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
146                super.populateActionRequestDetailsForInterceptor(theRequestDetails, theDetails, theMethodParams);
147
148                /*
149                 * If the method has no parsed resource parameter, we parse here in order to have something for the interceptor.
150                 */
151                IBaseResource resource;
152                if (myTransactionParamIndex != -1) {
153                        resource = (IBaseResource) theMethodParams[myTransactionParamIndex];
154                } else {
155                        Class<? extends IBaseResource> resourceType = getContext().getResourceDefinition("Bundle").getImplementingClass();
156                        resource = ResourceParameter.parseResourceFromRequest(theRequestDetails, this, resourceType);
157                }
158
159                theRequestDetails.setResource(resource);
160                if (theDetails != null) {
161                        theDetails.setResource(resource);
162                }
163
164        }
165
166}