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