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}