001package ca.uhn.fhir.rest.method; 002 003import static org.apache.commons.lang3.StringUtils.isBlank; 004import static org.apache.commons.lang3.StringUtils.isNotBlank; 005 006import java.io.IOException; 007import java.io.PushbackReader; 008import java.io.Reader; 009import java.lang.annotation.Annotation; 010import java.lang.reflect.Method; 011import java.util.*; 012import java.util.Map.Entry; 013 014import org.apache.commons.lang3.ObjectUtils; 015import org.apache.commons.lang3.StringUtils; 016import org.hl7.fhir.instance.model.api.*; 017 018import ca.uhn.fhir.context.*; 019import ca.uhn.fhir.model.api.*; 020import ca.uhn.fhir.model.api.annotation.Description; 021import ca.uhn.fhir.model.primitive.IdDt; 022import ca.uhn.fhir.model.primitive.InstantDt; 023import ca.uhn.fhir.parser.IParser; 024import ca.uhn.fhir.rest.annotation.*; 025import ca.uhn.fhir.rest.api.*; 026import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; 027import ca.uhn.fhir.rest.method.OperationParameter.IOperationParamConverter; 028import ca.uhn.fhir.rest.param.*; 029import ca.uhn.fhir.rest.param.ResourceParameter.Mode; 030import ca.uhn.fhir.rest.server.Constants; 031import ca.uhn.fhir.rest.server.EncodingEnum; 032import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; 033import ca.uhn.fhir.rest.server.SearchParameterMap; 034import ca.uhn.fhir.util.DateUtils; 035import ca.uhn.fhir.util.ParametersUtil; 036import ca.uhn.fhir.util.ReflectionUtil; 037import ca.uhn.fhir.util.UrlUtil; 038 039/* 040 * #%L 041 * HAPI FHIR - Core Library 042 * %% 043 * Copyright (C) 2014 - 2017 University Health Network 044 * %% 045 * Licensed under the Apache License, Version 2.0 (the "License"); 046 * you may not use this file except in compliance with the License. 047 * You may obtain a copy of the License at 048 * 049 * http://www.apache.org/licenses/LICENSE-2.0 050 * 051 * Unless required by applicable law or agreed to in writing, software 052 * distributed under the License is distributed on an "AS IS" BASIS, 053 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 054 * See the License for the specific language governing permissions and 055 * limitations under the License. 056 * #L% 057 */ 058 059@SuppressWarnings("deprecation") 060public class MethodUtil { 061 062 private static final String LABEL = "label=\""; 063 064 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); 065 private static final Set<String> ourServletRequestTypes = new HashSet<String>(); 066 private static final Set<String> ourServletResponseTypes = new HashSet<String>(); 067 private static final String SCHEME = "scheme=\""; 068 static { 069 ourServletRequestTypes.add("javax.servlet.ServletRequest"); 070 ourServletResponseTypes.add("javax.servlet.ServletResponse"); 071 ourServletRequestTypes.add("javax.servlet.http.HttpServletRequest"); 072 ourServletResponseTypes.add("javax.servlet.http.HttpServletResponse"); 073 } 074 075 /** Non instantiable */ 076 private MethodUtil() { 077 // nothing 078 } 079 080 081 static void addTagsToPostOrPut(FhirContext theContext, IBaseResource resource, BaseHttpClientInvocation retVal) { 082 if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { 083 TagList list = (TagList) ((IResource)resource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); 084 if (list != null) { 085 for (Tag tag : list) { 086 if (StringUtils.isNotBlank(tag.getTerm())) { 087 retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); 088 } 089 } 090 } 091 } 092 } 093 094 095 @SuppressWarnings("unchecked") 096 public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) { 097 if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) { 098 IIdType newValue = ReflectionUtil.newInstance(theIdParamType); 099 newValue.setValue(value.getValue()); 100 value = newValue; 101 } 102 return (T) value; 103 } 104 105 public static HttpGetClientInvocation createConformanceInvocation(FhirContext theContext) { 106 return new HttpGetClientInvocation(theContext, "metadata"); 107 } 108 109 public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) { 110 return createCreateInvocation(theResource, null, null, theContext); 111 } 112 113 public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext) { 114 RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); 115 String resourceName = def.getName(); 116 117 StringBuilder urlExtension = new StringBuilder(); 118 urlExtension.append(resourceName); 119 120 boolean dstu1 = theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1); 121 if (dstu1) { 122 /* 123 * This was allowable at one point, but as of DSTU2 it isn't. 124 */ 125 if (StringUtils.isNotBlank(theId)) { 126 urlExtension.append('/'); 127 urlExtension.append(theId); 128 } 129 } 130 131 HttpPostClientInvocation retVal; 132 if (StringUtils.isBlank(theResourceBody)) { 133 retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); 134 } else { 135 retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); 136 } 137 addTagsToPostOrPut(theContext, theResource, retVal); 138 139 if (!dstu1) { 140 retVal.setOmitResourceId(true); 141 } 142 // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); 143 144 return retVal; 145 } 146 147 public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, Map<String, List<String>> theIfNoneExistParams) { 148 HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); 149 retVal.setIfNoneExistParams(theIfNoneExistParams); 150 return retVal; 151 } 152 153 public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, String theIfNoneExistUrl) { 154 HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); 155 retVal.setIfNoneExistString(theIfNoneExistUrl); 156 return retVal; 157 } 158 159 public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IIdType theId, PatchTypeEnum thePatchType, String theBody) { 160 return PatchMethodBinding.createPatchInvocation(theContext, theId, thePatchType, theBody); 161 } 162 163 public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, String theUrl, PatchTypeEnum thePatchType, String theBody) { 164 return PatchMethodBinding.createPatchInvocation(theContext, theUrl, thePatchType, theBody); 165 } 166 167 public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, PatchTypeEnum thePatchType, String theBody, String theResourceType, Map<String, List<String>> theMatchParams) { 168 return PatchMethodBinding.createPatchInvocation(theContext, thePatchType, theBody, theResourceType, theMatchParams); 169 } 170 171 public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map<String, List<String>> theMatchParams) { 172 String resourceType = theContext.getResourceDefinition(theResource).getName(); 173 174 StringBuilder b = createUrl(resourceType, theMatchParams); 175 176 HttpPutClientInvocation retVal; 177 if (StringUtils.isBlank(theResourceBody)) { 178 retVal = new HttpPutClientInvocation(theContext, theResource, b.toString()); 179 } else { 180 retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString()); 181 } 182 183 addTagsToPostOrPut(theContext, theResource, retVal); 184 185 return retVal; 186 } 187 188 189 public static StringBuilder createUrl(String theResourceType, Map<String, List<String>> theMatchParams) { 190 StringBuilder b = new StringBuilder(); 191 192 b.append(theResourceType); 193 194 boolean haveQuestionMark = false; 195 for (Entry<String, List<String>> nextEntry : theMatchParams.entrySet()) { 196 for (String nextValue : nextEntry.getValue()) { 197 b.append(haveQuestionMark ? '&' : '?'); 198 haveQuestionMark = true; 199 b.append(UrlUtil.escape(nextEntry.getKey())); 200 b.append('='); 201 b.append(UrlUtil.escape(nextValue)); 202 } 203 } 204 return b; 205 } 206 207 208 public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) { 209 HttpPutClientInvocation retVal; 210 if (StringUtils.isBlank(theResourceBody)) { 211 retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl); 212 } else { 213 retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl); 214 } 215 216 addTagsToPostOrPut(theContext, theResource, retVal); 217 218 return retVal; 219 } 220 221 public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) { 222 String resourceName = theContext.getResourceDefinition(theResource).getName(); 223 StringBuilder urlBuilder = new StringBuilder(); 224 urlBuilder.append(resourceName); 225 urlBuilder.append('/'); 226 urlBuilder.append(theId.getIdPart()); 227 String urlExtension = urlBuilder.toString(); 228 229 HttpPutClientInvocation retVal; 230 if (StringUtils.isBlank(theResourceBody)) { 231 retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension); 232 } else { 233 retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension); 234 } 235 236 retVal.setForceResourceId(theId); 237 238 if (theId.hasVersionIdPart()) { 239 if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { 240 retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"'); 241 } else { 242 String versionId = theId.getVersionIdPart(); 243 if (StringUtils.isNotBlank(versionId)) { 244 urlBuilder.append('/'); 245 urlBuilder.append(Constants.PARAM_HISTORY); 246 urlBuilder.append('/'); 247 urlBuilder.append(versionId); 248 retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, urlBuilder.toString()); 249 } 250 } 251 } 252 253 addTagsToPostOrPut(theContext, theResource, retVal); 254 // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); 255 256 return retVal; 257 } 258 259 public static EncodingEnum detectEncoding(String theBody) { 260 EncodingEnum retVal = detectEncodingNoDefault(theBody); 261 retVal = ObjectUtils.defaultIfNull(retVal, EncodingEnum.XML); 262 return retVal; 263 } 264 265 public static EncodingEnum detectEncodingNoDefault(String theBody) { 266 EncodingEnum retVal = null; 267 for (int i = 0; i < theBody.length() && retVal == null; i++) { 268 switch (theBody.charAt(i)) { 269 case '<': 270 retVal = EncodingEnum.XML; 271 break; 272 case '{': 273 retVal = EncodingEnum.JSON; 274 break; 275 } 276 } 277 return retVal; 278 } 279 280 public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { 281 for (Annotation annotation : theAnnotations) { 282 if (annotation instanceof Description) { 283 Description desc = (Description) annotation; 284 if (isNotBlank(desc.formalDefinition())) { 285 theParameter.setDescription(desc.formalDefinition()); 286 } else { 287 theParameter.setDescription(desc.shortDefinition()); 288 } 289 } 290 } 291 } 292 293 public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { 294 Integer index = MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); 295 if (index != null) { 296 Class<?> paramType = theMethod.getParameterTypes()[index]; 297 if (IIdType.class.equals(paramType)) { 298 return index; 299 } 300 boolean isRi = theContext.getVersion().getVersion().isRi(); 301 boolean usesHapiId = IdDt.class.equals(paramType); 302 if (isRi == usesHapiId) { 303 throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString()); 304 } 305 } 306 return index; 307 } 308 309 @SuppressWarnings("GetClassOnAnnotation") 310 public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { 311 int paramIndex = 0; 312 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 313 for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) { 314 Annotation nextAnnotation = annotations[annotationIndex]; 315 Class<? extends Annotation> class1 = nextAnnotation.getClass(); 316 if (toFind.isAssignableFrom(class1)) { 317 return paramIndex; 318 } 319 } 320 paramIndex++; 321 } 322 return null; 323 } 324 325 public static Integer findTagListParameterIndex(Method theMethod) { 326 return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); 327 } 328 329 public static Integer findVersionIdParameterIndex(Method theMethod) { 330 return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class); 331 } 332 333 @SuppressWarnings("unchecked") 334 public static List<IParameter> getResourceParameters(final FhirContext theContext, Method theMethod, Object theProvider, RestOperationTypeEnum theRestfulOperationTypeEnum) { 335 List<IParameter> parameters = new ArrayList<IParameter>(); 336 337 Class<?>[] parameterTypes = theMethod.getParameterTypes(); 338 int paramIndex = 0; 339 for (Annotation[] annotations : theMethod.getParameterAnnotations()) { 340 341 IParameter param = null; 342 Class<?> parameterType = parameterTypes[paramIndex]; 343 Class<? extends java.util.Collection<?>> outerCollectionType = null; 344 Class<? extends java.util.Collection<?>> innerCollectionType = null; 345 if (SearchParameterMap.class.equals(parameterType)) { 346 if (theProvider instanceof IDynamicSearchResourceProvider) { 347 Search searchAnnotation = theMethod.getAnnotation(Search.class); 348 if (searchAnnotation != null && searchAnnotation.dynamic()) { 349 param = new DynamicSearchParameter((IDynamicSearchResourceProvider) theProvider); 350 } 351 } 352 } else if (TagList.class.isAssignableFrom(parameterType)) { 353 // TagList is handled directly within the method bindings 354 param = new NullParameter(); 355 } else { 356 if (Collection.class.isAssignableFrom(parameterType)) { 357 innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType; 358 parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex); 359 } 360 if (Collection.class.isAssignableFrom(parameterType)) { 361 outerCollectionType = innerCollectionType; 362 innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType; 363 parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex); 364 } 365 if (Collection.class.isAssignableFrom(parameterType)) { 366 throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is of an invalid generic type (can not be a collection of a collection of a collection)"); 367 } 368 } 369 370 /* 371 * Note: for the first two here, we're using strings instead of static binding 372 * so that we don't need the java.servlet JAR on the classpath in order to use 373 * this class 374 */ 375 if (ourServletRequestTypes.contains(parameterType.getName())) { 376 param = new ServletRequestParameter(); 377 } else if (ourServletResponseTypes.contains(parameterType.getName())) { 378 param = new ServletResponseParameter(); 379 } else if (parameterType.equals(RequestDetails.class)) { 380 param = new RequestDetailsParameter(); 381 } else if (parameterType.equals(IRequestOperationCallback.class)) { 382 param = new RequestOperationCallbackParameter(); 383 } else if (parameterType.equals(SummaryEnum.class)) { 384 param = new SummaryEnumParameter(); 385 } else if (parameterType.equals(PatchTypeEnum.class)) { 386 param = new PatchTypeParameter(); 387 } else { 388 for (int i = 0; i < annotations.length && param == null; i++) { 389 Annotation nextAnnotation = annotations[i]; 390 391 if (nextAnnotation instanceof RequiredParam) { 392 SearchParameter parameter = new SearchParameter(); 393 parameter.setName(((RequiredParam) nextAnnotation).name()); 394 parameter.setRequired(true); 395 parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes()); 396 parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes()); 397 parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist()); 398 parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); 399 MethodUtil.extractDescription(parameter, annotations); 400 param = parameter; 401 } else if (nextAnnotation instanceof OptionalParam) { 402 SearchParameter parameter = new SearchParameter(); 403 parameter.setName(((OptionalParam) nextAnnotation).name()); 404 parameter.setRequired(false); 405 parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes()); 406 parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes()); 407 parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist()); 408 parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); 409 MethodUtil.extractDescription(parameter, annotations); 410 param = parameter; 411 } else if (nextAnnotation instanceof RawParam) { 412 param = new RawParamsParmeter(parameters); 413 } else if (nextAnnotation instanceof IncludeParam) { 414 Class<? extends Collection<Include>> instantiableCollectionType; 415 Class<?> specType; 416 417 if (parameterType == String.class) { 418 instantiableCollectionType = null; 419 specType = String.class; 420 } else if ((parameterType != Include.class) || innerCollectionType == null || outerCollectionType != null) { 421 throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + Include.class.getSimpleName() + ">"); 422 } else { 423 instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'"); 424 specType = parameterType; 425 } 426 427 param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType); 428 } else if (nextAnnotation instanceof ResourceParam) { 429 Mode mode; 430 if (IBaseResource.class.isAssignableFrom(parameterType)) { 431 mode = Mode.RESOURCE; 432 } else if (String.class.equals(parameterType)) { 433 mode = ResourceParameter.Mode.BODY; 434 } else if (byte[].class.equals(parameterType)) { 435 mode = ResourceParameter.Mode.BODY_BYTE_ARRAY; 436 } else if (EncodingEnum.class.equals(parameterType)) { 437 mode = Mode.ENCODING; 438 } else { 439 StringBuilder b = new StringBuilder(); 440 b.append("Method '"); 441 b.append(theMethod.getName()); 442 b.append("' is annotated with @"); 443 b.append(ResourceParam.class.getSimpleName()); 444 b.append(" but has a type that is not an implemtation of "); 445 b.append(IBaseResource.class.getCanonicalName()); 446 b.append(" or String or byte[]"); 447 throw new ConfigurationException(b.toString()); 448 } 449 param = new ResourceParameter((Class<? extends IResource>) parameterType, theProvider, mode); 450 } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { 451 param = new NullParameter(); 452 } else if (nextAnnotation instanceof ServerBase) { 453 param = new ServerBaseParamBinder(); 454 } else if (nextAnnotation instanceof Elements) { 455 param = new ElementsParameter(); 456 } else if (nextAnnotation instanceof Since) { 457 param = new SinceParameter(); 458 ((SinceParameter)param).setType(theContext, parameterType, innerCollectionType, outerCollectionType); 459 } else if (nextAnnotation instanceof At) { 460 param = new AtParameter(); 461 ((AtParameter)param).setType(theContext, parameterType, innerCollectionType, outerCollectionType); 462 } else if (nextAnnotation instanceof Count) { 463 param = new CountParameter(); 464 } else if (nextAnnotation instanceof Sort) { 465 param = new SortParameter(theContext); 466 } else if (nextAnnotation instanceof TransactionParam) { 467 param = new TransactionParameter(theContext); 468 } else if (nextAnnotation instanceof ConditionalUrlParam) { 469 param = new ConditionalParamBinder(theRestfulOperationTypeEnum, ((ConditionalUrlParam)nextAnnotation).supportsMultiple()); 470 } else if (nextAnnotation instanceof OperationParam) { 471 Operation op = theMethod.getAnnotation(Operation.class); 472 param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation)); 473 } else if (nextAnnotation instanceof Validate.Mode) { 474 if (parameterType.equals(ValidationModeEnum.class) == false) { 475 throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName()); 476 } 477 param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() { 478 @Override 479 public Object incomingServer(Object theObject) { 480 if (isNotBlank(theObject.toString())) { 481 ValidationModeEnum retVal = ValidationModeEnum.forCode(theObject.toString()); 482 if (retVal == null) { 483 OperationParameter.throwInvalidMode(theObject.toString()); 484 } 485 return retVal; 486 } 487 return null; 488 } 489 490 @Override 491 public Object outgoingClient(Object theObject) { 492 return ParametersUtil.createString(theContext, ((ValidationModeEnum)theObject).getCode()); 493 } 494 }); 495 } else if (nextAnnotation instanceof Validate.Profile) { 496 if (parameterType.equals(String.class) == false) { 497 throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName()); 498 } 499 param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() { 500 @Override 501 public Object incomingServer(Object theObject) { 502 return theObject.toString(); 503 } 504 505 @Override 506 public Object outgoingClient(Object theObject) { 507 return ParametersUtil.createString(theContext, theObject.toString()); 508 } 509 }); 510 } else { 511 continue; 512 } 513 514 } 515 516 } 517 518 if (param == null) { 519 throw new ConfigurationException("Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() 520 + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter"); 521 } 522 523 param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType); 524 parameters.add(param); 525 526 paramIndex++; 527 } 528 return parameters; 529 } 530 531 public static void parseClientRequestResourceHeaders(IIdType theRequestedId, Map<String, List<String>> theHeaders, IBaseResource resource) { 532 List<String> lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); 533 if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { 534 String headerValue = lmHeaders.get(0); 535 Date headerDateValue; 536 try { 537 headerDateValue = DateUtils.parseDate(headerValue); 538 if (resource instanceof IResource) { 539 IResource iResource = (IResource) resource; 540 InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource); 541 if (existing == null || existing.isEmpty()) { 542 InstantDt lmValue = new InstantDt(headerDateValue); 543 iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); 544 } 545 } else if (resource instanceof IAnyResource) { 546 IAnyResource anyResource = (IAnyResource) resource; 547 if (anyResource.getMeta().getLastUpdated() == null) { 548 anyResource.getMeta().setLastUpdated(headerDateValue); 549 } 550 } 551 } catch (Exception e) { 552 ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); 553 } 554 } 555 556 List<String> clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); 557 if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { 558 String headerValue = clHeaders.get(0); 559 if (isNotBlank(headerValue)) { 560 new IdDt(headerValue).applyTo(resource); 561 } 562 } 563 564 List<String> locationHeaders = theHeaders.get(Constants.HEADER_LOCATION_LC); 565 if (locationHeaders != null && locationHeaders.size() > 0 && StringUtils.isNotBlank(locationHeaders.get(0))) { 566 String headerValue = locationHeaders.get(0); 567 if (isNotBlank(headerValue)) { 568 new IdDt(headerValue).applyTo(resource); 569 } 570 } 571 572 IdDt existing = IdDt.of(resource); 573 574 List<String> eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); 575 String eTagVersion = null; 576 if (eTagHeaders != null && eTagHeaders.size() > 0) { 577 eTagVersion = parseETagValue(eTagHeaders.get(0)); 578 } 579 if (isNotBlank(eTagVersion)) { 580 if (existing == null || existing.isEmpty()) { 581 if (theRequestedId != null) { 582 theRequestedId.withVersion(eTagVersion).applyTo(resource); 583 } 584 } else if (existing.hasVersionIdPart() == false) { 585 existing.withVersion(eTagVersion).applyTo(resource); 586 } 587 } else if (existing == null || existing.isEmpty()) { 588 if (theRequestedId != null) { 589 theRequestedId.applyTo(resource); 590 } 591 } 592 593 List<String> categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); 594 if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { 595 TagList tagList = new TagList(); 596 for (String header : categoryHeaders) { 597 parseTagValue(tagList, header); 598 } 599 if (resource instanceof IResource) { 600 ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); 601 } else if (resource instanceof IAnyResource) { 602 IBaseMetaType meta = ((IAnyResource) resource).getMeta(); 603 for (Tag next : tagList) { 604 meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); 605 } 606 } 607 } 608 } 609 610 public static String parseETagValue(String value) { 611 String eTagVersion; 612 value = value.trim(); 613 if (value.length() > 1) { 614 if (value.charAt(value.length() - 1) == '"') { 615 if (value.charAt(0) == '"') { 616 eTagVersion = value.substring(1, value.length() - 1); 617 } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { 618 eTagVersion = value.substring(3, value.length() - 1); 619 } else { 620 eTagVersion = value; 621 } 622 } else { 623 eTagVersion = value; 624 } 625 } else { 626 eTagVersion = value; 627 } 628 return eTagVersion; 629 } 630 631 /** 632 * This is a utility method intended provided to help the JPA module. 633 */ 634 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 635 RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); 636 return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); 637 } 638 639 640 /** 641 * This is a utility method intended provided to help the JPA module. 642 */ 643 public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType, String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { 644 QueryParameterAndBinder binder = null; 645 switch (paramType) { 646 case COMPOSITE: 647 throw new UnsupportedOperationException(); 648 case DATE: 649 binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 650 break; 651 case NUMBER: 652 binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 653 break; 654 case QUANTITY: 655 binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 656 break; 657 case REFERENCE: 658 binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 659 break; 660 case STRING: 661 binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 662 break; 663 case TOKEN: 664 binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 665 break; 666 case URI: 667 binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 668 break; 669 case HAS: 670 binder = new QueryParameterAndBinder(HasAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); 671 break; 672 } 673 674 //FIXME null access 675 return binder.parse(theContext, theUnqualifiedParamName, theParameters); 676 } 677 678 public static void parseTagValue(TagList tagList, String nextTagComplete) { 679 StringBuilder next = new StringBuilder(nextTagComplete); 680 parseTagValue(tagList, nextTagComplete, next); 681 } 682 683 private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { 684 int firstSemicolon = theBuffer.indexOf(";"); 685 int deleteTo; 686 if (firstSemicolon == -1) { 687 firstSemicolon = theBuffer.indexOf(","); 688 if (firstSemicolon == -1) { 689 firstSemicolon = theBuffer.length(); 690 deleteTo = theBuffer.length(); 691 } else { 692 deleteTo = firstSemicolon; 693 } 694 } else { 695 deleteTo = firstSemicolon + 1; 696 } 697 698 String term = theBuffer.substring(0, firstSemicolon); 699 String scheme = null; 700 String label = null; 701 if (isBlank(term)) { 702 return; 703 } 704 705 theBuffer.delete(0, deleteTo); 706 while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { 707 theBuffer.deleteCharAt(0); 708 } 709 710 while (theBuffer.length() > 0) { 711 boolean foundSomething = false; 712 if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { 713 int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); 714 scheme = theBuffer.substring(SCHEME.length(), closeIdx); 715 theBuffer.delete(0, closeIdx + 1); 716 foundSomething = true; 717 } 718 if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { 719 int closeIdx = theBuffer.indexOf("\"", LABEL.length()); 720 label = theBuffer.substring(LABEL.length(), closeIdx); 721 theBuffer.delete(0, closeIdx + 1); 722 foundSomething = true; 723 } 724 // TODO: support enc2231-string as described in 725 // http://tools.ietf.org/html/draft-johnston-http-category-header-02 726 // TODO: support multiple tags in one header as described in 727 // http://hl7.org/implement/standards/fhir/http.html#tags 728 729 while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { 730 theBuffer.deleteCharAt(0); 731 } 732 733 if (!foundSomething) { 734 break; 735 } 736 } 737 738 if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { 739 theBuffer.deleteCharAt(0); 740 while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { 741 theBuffer.deleteCharAt(0); 742 } 743 theTagList.add(new Tag(scheme, term, label)); 744 parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); 745 } else { 746 theTagList.add(new Tag(scheme, term, label)); 747 } 748 749 if (theBuffer.length() > 0) { 750 ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); 751 } 752 753 } 754 755 public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) { 756 List<String> locationHeaders = new ArrayList<String>(); 757 List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC); 758 if (lh != null) { 759 locationHeaders.addAll(lh); 760 } 761 List<String> clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); 762 if (clh != null) { 763 locationHeaders.addAll(clh); 764 } 765 766 MethodOutcome retVal = new MethodOutcome(); 767 if (locationHeaders != null && locationHeaders.size() > 0) { 768 String locationHeader = locationHeaders.get(0); 769 BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader); 770 } 771 if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { 772 EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); 773 if (ct != null) { 774 PushbackReader reader = new PushbackReader(theResponseReader); 775 776 try { 777 int firstByte = reader.read(); 778 if (firstByte == -1) { 779 BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); 780 reader = null; 781 } else { 782 reader.unread(firstByte); 783 } 784 } catch (IOException e) { 785 BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); 786 reader = null; 787 } 788 789 if (reader != null) { 790 IParser parser = ct.newParser(theContext); 791 IBaseResource outcome = parser.parseResource(reader); 792 if (outcome instanceof IBaseOperationOutcome) { 793 retVal.setOperationOutcome((IBaseOperationOutcome) outcome); 794 } else { 795 retVal.setResource(outcome); 796 } 797 } 798 799 } else { 800 BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); 801 } 802 } 803 return retVal; 804 } 805 806 public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { 807 return new IQueryParameterOr<IQueryParameterType>() { 808 809 @Override 810 public List<IQueryParameterType> getValuesAsQueryTokens() { 811 return Collections.singletonList(theParam); 812 } 813 814 @Override 815 public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { 816 if (theParameters.isEmpty()) { 817 return; 818 } 819 if (theParameters.size() > 1) { 820 throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); 821 } 822 theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), theParameters.get(0)); 823 } 824 }; 825 } 826 827}