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