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