001package ca.uhn.fhir.rest.method; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 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.ArrayList; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.List; 029import java.util.Map; 030 031import org.hl7.fhir.instance.model.api.IBase; 032import org.hl7.fhir.instance.model.api.IBaseDatatype; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.hl7.fhir.instance.model.api.IPrimitiveType; 035 036import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 037import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor; 038import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 039import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 040import ca.uhn.fhir.context.ConfigurationException; 041import ca.uhn.fhir.context.FhirContext; 042import ca.uhn.fhir.context.IRuntimeDatatypeDefinition; 043import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition; 044import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; 045import ca.uhn.fhir.context.RuntimeResourceDefinition; 046import ca.uhn.fhir.i18n.HapiLocalizer; 047import ca.uhn.fhir.model.api.IDatatype; 048import ca.uhn.fhir.rest.annotation.OperationParam; 049import ca.uhn.fhir.rest.api.RequestTypeEnum; 050import ca.uhn.fhir.rest.api.ValidationModeEnum; 051import ca.uhn.fhir.rest.param.CollectionBinder; 052import ca.uhn.fhir.rest.param.DateRangeParam; 053import ca.uhn.fhir.rest.param.ResourceParameter; 054import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 055import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 056import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; 057import ca.uhn.fhir.util.FhirTerser; 058import ca.uhn.fhir.util.ParametersUtil; 059 060public class OperationParameter implements IParameter { 061 062 private IConverter myConverter; 063 @SuppressWarnings("rawtypes") 064 private Class<? extends Collection> myInnerCollectionType; 065 private int myMax; 066 private int myMin; 067 private final String myName; 068 private final String myOperationName; 069 private Class<?> myParameterType; 070 private String myParamType; 071 private final FhirContext myContext; 072 private boolean myAllowGet; 073 074 public OperationParameter(FhirContext theCtx, String theOperationName, OperationParam theOperationParam) { 075 this(theCtx, theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max()); 076 } 077 078 OperationParameter(FhirContext theCtx, String theOperationName, String theParameterName, int theMin, int theMax) { 079 myOperationName = theOperationName; 080 myName = theParameterName; 081 myMin = theMin; 082 myMax = theMax; 083 myContext = theCtx; 084 } 085 086 protected FhirContext getContext() { 087 return myContext; 088 } 089 090 public int getMax() { 091 return myMax; 092 } 093 094 public int getMin() { 095 return myMin; 096 } 097 098 public String getName() { 099 return myName; 100 } 101 102 public String getParamType() { 103 return myParamType; 104 } 105 106 @SuppressWarnings("unchecked") 107 @Override 108 public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) { 109 if (getContext().getVersion().getVersion().isRi()) { 110 if (IDatatype.class.isAssignableFrom(theParameterType)) { 111 throw new ConfigurationException("Incorrect use of type " + theParameterType.getSimpleName() + " as parameter type for method when context is for version " 112 + getContext().getVersion().getVersion().name() + " in method: " + theMethod.toString()); 113 } 114 } 115 116 myParameterType = theParameterType; 117 if (theInnerCollectionType != null) { 118 myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, myName); 119 } else { 120 myMax = 1; 121 } 122 123 myAllowGet = IPrimitiveType.class.isAssignableFrom(myParameterType) || String.class.equals(myParameterType); 124 125 /* 126 * The parameter can be of type string for validation methods - This is a bit weird. See ValidateDstu2Test. We should probably clean this up.. 127 */ 128 if (!myParameterType.equals(IBase.class) && !myParameterType.equals(String.class)) { 129 if (IBaseResource.class.isAssignableFrom(myParameterType) && myParameterType.isInterface()) { 130 myParamType = "Resource"; 131 } else if (DateRangeParam.class.isAssignableFrom(myParameterType)) { 132 myParamType = "date"; 133 myMax = 2; 134 myAllowGet = true; 135 } else if (!IBase.class.isAssignableFrom(myParameterType) || myParameterType.isInterface() || Modifier.isAbstract(myParameterType.getModifiers())) { 136 throw new ConfigurationException("Invalid type for @OperationParam: " + myParameterType.getName()); 137 } else if (myParameterType.equals(ValidationModeEnum.class)) { 138 myParamType = "code"; 139 } else { 140 myParamType = myContext.getElementDefinition((Class<? extends IBase>) myParameterType).getName(); 141 } 142 } 143 144 } 145 146 public OperationParameter setConverter(IConverter theConverter) { 147 myConverter = theConverter; 148 return this; 149 } 150 151 @Override 152 public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) 153 throws InternalErrorException { 154 assert theTargetResource != null; 155 Object sourceClientArgument = theSourceClientArgument; 156 if (sourceClientArgument == null) { 157 return; 158 } 159 160 if (myConverter != null) { 161 sourceClientArgument = myConverter.outgoingClient(sourceClientArgument); 162 } 163 164 ParametersUtil.addParameterToParameters(theContext, theTargetResource, sourceClientArgument, myName); 165 } 166 167 @SuppressWarnings("unchecked") 168 @Override 169 public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException { 170 List<Object> matchingParamValues = new ArrayList<Object>(); 171 172 if (theRequest.getRequestType() == RequestTypeEnum.GET) { 173 String[] paramValues = theRequest.getParameters().get(myName); 174 if (paramValues != null && paramValues.length > 0) { 175 if (myAllowGet) { 176 177 if (DateRangeParam.class.isAssignableFrom(myParameterType)) { 178 List<QualifiedParamList> parameters = new ArrayList<QualifiedParamList>(); 179 parameters.add(QualifiedParamList.singleton(paramValues[0])); 180 if (paramValues.length > 1) { 181 parameters.add(QualifiedParamList.singleton(paramValues[1])); 182 } 183 DateRangeParam dateRangeParam = new DateRangeParam(); 184 dateRangeParam.setValuesAsQueryTokens(parameters); 185 matchingParamValues.add(dateRangeParam); 186 } else if (String.class.isAssignableFrom(myParameterType)) { 187 188 for (String next : paramValues) { 189 matchingParamValues.add(next); 190 } 191 192 } else { 193 for (String nextValue : paramValues) { 194 FhirContext ctx = theRequest.getServer().getFhirContext(); 195 RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) ctx.getElementDefinition((Class<? extends IBase>) myParameterType); 196 IPrimitiveType<?> instance = def.newInstance(); 197 instance.setValueAsString(nextValue); 198 matchingParamValues.add(instance); 199 } 200 } 201 } else { 202 HapiLocalizer localizer = theRequest.getServer().getFhirContext().getLocalizer(); 203 String msg = localizer.getMessage(OperationParameter.class, "urlParamNotPrimitive", myOperationName, myName); 204 throw new MethodNotAllowedException(msg, RequestTypeEnum.POST); 205 } 206 } 207 } else { 208 209 FhirContext ctx = theRequest.getServer().getFhirContext(); 210 211 if (theRequest.getRequestType() == RequestTypeEnum.GET) { 212 return null; 213 } 214 215 // Class<? extends IBaseResource> wantedResourceType = theMethodBinding.getContext().getResourceDefinition("Parameters").getImplementingClass(); 216 Class<IBaseResource> wantedResourceType = null; 217 IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, theMethodBinding, wantedResourceType); 218 219 RuntimeResourceDefinition def = ctx.getResourceDefinition(requestContents); 220 if (def.getName().equals("Parameters")) { 221 222 BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter"); 223 BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); 224 225 RuntimeChildPrimitiveDatatypeDefinition nameChild = (RuntimeChildPrimitiveDatatypeDefinition) paramChildElem.getChildByName("name"); 226 BaseRuntimeChildDefinition valueChild = paramChildElem.getChildByName("value[x]"); 227 BaseRuntimeChildDefinition resourceChild = paramChildElem.getChildByName("resource"); 228 229 IAccessor paramChildAccessor = paramChild.getAccessor(); 230 List<IBase> values = paramChildAccessor.getValues(requestContents); 231 for (IBase nextParameter : values) { 232 List<IBase> nextNames = nameChild.getAccessor().getValues(nextParameter); 233 if (nextNames != null && nextNames.size() > 0) { 234 IPrimitiveType<?> nextName = (IPrimitiveType<?>) nextNames.get(0); 235 if (myName.equals(nextName.getValueAsString())) { 236 237 if (myParameterType.isAssignableFrom(nextParameter.getClass())) { 238 matchingParamValues.add(nextParameter); 239 } else { 240 List<IBase> paramValues = valueChild.getAccessor().getValues(nextParameter); 241 List<IBase> paramResources = resourceChild.getAccessor().getValues(nextParameter); 242 if (paramValues != null && paramValues.size() > 0) { 243 tryToAddValues(paramValues, matchingParamValues); 244 } else if (paramResources != null && paramResources.size() > 0) { 245 tryToAddValues(paramResources, matchingParamValues); 246 } 247 } 248 249 } 250 } 251 } 252 253 } else { 254 255 if (myParameterType.isAssignableFrom(requestContents.getClass())) { 256 tryToAddValues(Arrays.asList((IBase) requestContents), matchingParamValues); 257 } 258 259 } 260 } 261 262 if (matchingParamValues.isEmpty()) { 263 return null; 264 } 265 266 if (myInnerCollectionType == null) { 267 return matchingParamValues.get(0); 268 } 269 270 try { 271 @SuppressWarnings("rawtypes") 272 Collection retVal = myInnerCollectionType.newInstance(); 273 retVal.addAll(matchingParamValues); 274 return retVal; 275 } catch (InstantiationException e) { 276 throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e); 277 } catch (IllegalAccessException e) { 278 throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e); 279 } 280 } 281 282 @SuppressWarnings("unchecked") 283 private void tryToAddValues(List<IBase> theParamValues, List<Object> theMatchingParamValues) { 284 for (Object nextValue : theParamValues) { 285 if (nextValue == null) { 286 continue; 287 } 288 if (myConverter != null) { 289 nextValue = myConverter.incomingServer(nextValue); 290 } 291 if (!myParameterType.isAssignableFrom(nextValue.getClass())) { 292 Class<? extends IBaseDatatype> sourceType = (Class<? extends IBaseDatatype>) nextValue.getClass(); 293 Class<? extends IBaseDatatype> targetType = (Class<? extends IBaseDatatype>) myParameterType; 294 BaseRuntimeElementDefinition<?> sourceTypeDef = myContext.getElementDefinition(sourceType); 295 BaseRuntimeElementDefinition<?> targetTypeDef = myContext.getElementDefinition(targetType); 296 if (targetTypeDef instanceof IRuntimeDatatypeDefinition && sourceTypeDef instanceof IRuntimeDatatypeDefinition) { 297 IRuntimeDatatypeDefinition targetTypeDtDef = (IRuntimeDatatypeDefinition) targetTypeDef; 298 if (targetTypeDtDef.isProfileOf(sourceType)) { 299 FhirTerser terser = myContext.newTerser(); 300 IBase newTarget = targetTypeDef.newInstance(); 301 terser.cloneInto((IBase) nextValue, newTarget, true); 302 theMatchingParamValues.add(newTarget); 303 continue; 304 } 305 } 306 throwWrongParamType(nextValue); 307 } 308 theMatchingParamValues.add(nextValue); 309 } 310 } 311 312 private void throwWrongParamType(Object nextValue) { 313 throw new InvalidRequestException("Request has parameter " + myName + " of type " + nextValue.getClass().getSimpleName() + " but method expects type " + myParameterType.getSimpleName()); 314 } 315 316 public interface IConverter { 317 318 Object incomingServer(Object theObject); 319 320 Object outgoingClient(Object theObject); 321 322 } 323 324}