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