001package ca.uhn.fhir.rest.client.method;
002
003/*
004 * #%L
005 * HAPI FHIR - Client Framework
006 * %%
007 * Copyright (C) 2014 - 2022 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.i18n.Msg;
024import java.lang.reflect.Method;
025import java.lang.reflect.Modifier;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.lang3.Validate;
032import org.hl7.fhir.instance.model.api.IBase;
033import org.hl7.fhir.instance.model.api.IBaseParameters;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IPrimitiveType;
036
037import ca.uhn.fhir.context.ConfigurationException;
038import ca.uhn.fhir.context.FhirContext;
039import ca.uhn.fhir.model.api.IDatatype;
040import ca.uhn.fhir.model.api.IQueryParameterAnd;
041import ca.uhn.fhir.model.api.IQueryParameterOr;
042import ca.uhn.fhir.model.api.IQueryParameterType;
043import ca.uhn.fhir.rest.annotation.OperationParam;
044import ca.uhn.fhir.rest.api.QualifiedParamList;
045import ca.uhn.fhir.rest.api.ValidationModeEnum;
046import ca.uhn.fhir.rest.param.DateRangeParam;
047import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
048import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
049import ca.uhn.fhir.util.ParametersUtil;
050
051public class OperationParameter implements IParameter {
052
053        @SuppressWarnings("unchecked")
054        private static final Class<? extends IQueryParameterType>[] COMPOSITE_TYPES = new Class[0];
055
056        static final String REQUEST_CONTENTS_USERDATA_KEY = OperationParam.class.getName() + "_PARSED_RESOURCE";
057
058        private final FhirContext myContext;
059        private IOperationParamConverter myConverter;
060        @SuppressWarnings("rawtypes")
061        private int myMax;
062        private int myMin;
063        private final String myName;
064        private Class<?> myParameterType;
065        private String myParamType;
066        private SearchParameter mySearchParameterBinding;
067
068        public OperationParameter(FhirContext theCtx, String theOperationName, OperationParam theOperationParam) {
069                this(theCtx, theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max());
070        }
071
072        OperationParameter(FhirContext theCtx, String theOperationName, String theParameterName, int theMin, int theMax) {
073                myName = theParameterName;
074                myMin = theMin;
075                myMax = theMax;
076                myContext = theCtx;
077        }
078
079        protected FhirContext getContext() {
080                return myContext;
081        }
082
083
084        public String getName() {
085                return myName;
086        }
087
088        public String getParamType() {
089                return myParamType;
090        }
091
092        public String getSearchParamType() {
093                if (mySearchParameterBinding != null) {
094                        return mySearchParameterBinding.getParamType().getCode();
095                }
096                return null;
097        }
098
099        @SuppressWarnings("unchecked")
100        @Override
101        public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
102                if (getContext().getVersion().getVersion().isRi()) {
103                        if (IDatatype.class.isAssignableFrom(theParameterType)) {
104                                throw new ConfigurationException(Msg.code(1408) + "Incorrect use of type " + theParameterType.getSimpleName() + " as parameter type for method when context is for version " + getContext().getVersion().getVersion().name() + " in method: " + theMethod.toString());
105                        }
106                }
107
108                myParameterType = theParameterType;
109                if (theInnerCollectionType != null) {
110                        if (myMax == OperationParam.MAX_DEFAULT) {
111                                myMax = OperationParam.MAX_UNLIMITED;
112                        }
113                } else if (IQueryParameterAnd.class.isAssignableFrom(myParameterType)) {
114                        if (myMax == OperationParam.MAX_DEFAULT) {
115                                myMax = OperationParam.MAX_UNLIMITED;
116                        }
117                } else {
118                        if (myMax == OperationParam.MAX_DEFAULT) {
119                                myMax = 1;
120                        }
121                }
122
123                boolean typeIsConcrete = !myParameterType.isInterface() && !Modifier.isAbstract(myParameterType.getModifiers());
124
125                //@formatter:off
126                boolean isSearchParam = 
127                        IQueryParameterType.class.isAssignableFrom(myParameterType) || 
128                        IQueryParameterOr.class.isAssignableFrom(myParameterType) ||
129                        IQueryParameterAnd.class.isAssignableFrom(myParameterType); 
130                //@formatter:off
131
132                /*
133                 * Note: We say here !IBase.class.isAssignableFrom because a bunch of DSTU1/2 datatypes also
134                 * extend this interface. I'm not sure if they should in the end.. but they do, so we
135                 * exclude them.
136                 */
137                isSearchParam &= typeIsConcrete && !IBase.class.isAssignableFrom(myParameterType);
138
139                /*
140                 * The parameter can be of type string for validation methods - This is a bit weird. See ValidateDstu2Test. We
141                 * should probably clean this up..
142                 */
143                if (!myParameterType.equals(IBase.class) && !myParameterType.equals(String.class)) {
144                        if (IBaseResource.class.isAssignableFrom(myParameterType) && myParameterType.isInterface()) {
145                                myParamType = "Resource";
146                        } else if (DateRangeParam.class.isAssignableFrom(myParameterType)) {
147                                myParamType = "date";
148                                myMax = 2;
149                        } else if (myParameterType.equals(ValidationModeEnum.class)) {
150                                myParamType = "code";
151                        } else if (IBase.class.isAssignableFrom(myParameterType) && typeIsConcrete) {
152                                myParamType = myContext.getElementDefinition((Class<? extends IBase>) myParameterType).getName();
153                        } else if (isSearchParam) {
154                                myParamType = "string";
155                                mySearchParameterBinding = new SearchParameter(myName, myMin > 0);
156                                mySearchParameterBinding.setCompositeTypes(COMPOSITE_TYPES);
157                                mySearchParameterBinding.setType(myContext, theParameterType, theInnerCollectionType, theOuterCollectionType);
158                                myConverter = new OperationParamConverter();
159                        } else {
160                                throw new ConfigurationException(Msg.code(1409) + "Invalid type for @OperationParam: " + myParameterType.getName());
161                        }
162
163                }
164
165        }
166
167        public OperationParameter setConverter(IOperationParamConverter theConverter) {
168                myConverter = theConverter;
169                return this;
170        }
171
172        @Override
173        public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
174                assert theTargetResource != null;
175                Object sourceClientArgument = theSourceClientArgument;
176                if (sourceClientArgument == null) {
177                        return;
178                }
179
180                if (myConverter != null) {
181                        sourceClientArgument = myConverter.outgoingClient(sourceClientArgument);
182                }
183
184                ParametersUtil.addParameterToParameters(theContext, (IBaseParameters) theTargetResource, myName, sourceClientArgument);
185        }
186
187
188
189        public static void throwInvalidMode(String paramValues) {
190                throw new InvalidRequestException(Msg.code(1410) + "Invalid mode value: \"" + paramValues + "\"");
191        }
192
193        interface IOperationParamConverter {
194
195                Object outgoingClient(Object theObject);
196
197        }
198
199        class OperationParamConverter implements IOperationParamConverter {
200
201                public OperationParamConverter() {
202                        Validate.isTrue(mySearchParameterBinding != null);
203                }
204
205                @Override
206                public Object outgoingClient(Object theObject) {
207                        IQueryParameterType obj = (IQueryParameterType) theObject;
208                        IPrimitiveType<?> retVal = (IPrimitiveType<?>) myContext.getElementDefinition("string").newInstance();
209                        retVal.setValueAsString(obj.getValueAsQueryToken(myContext));
210                        return retVal;
211                }
212
213        }
214
215
216}