
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}