package ca.uhn.fhir.rest.method; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.PushbackReader; import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.*; import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.method.OperationParameter.IOperationParamConverter; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.ResourceParameter.Mode; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; import ca.uhn.fhir.rest.server.SearchParameterMap; import ca.uhn.fhir.util.DateUtils; import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; /* * #%L * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ @SuppressWarnings("deprecation") public class MethodUtil { private static final String LABEL = "label=\""; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); private static final Set<String> ourServletRequestTypes = new HashSet<String>(); private static final Set<String> ourServletResponseTypes = new HashSet<String>(); private static final String SCHEME = "scheme=\""; static { ourServletRequestTypes.add("javax.servlet.ServletRequest"); ourServletResponseTypes.add("javax.servlet.ServletResponse"); ourServletRequestTypes.add("javax.servlet.http.HttpServletRequest"); ourServletResponseTypes.add("javax.servlet.http.HttpServletResponse"); } /** Non instantiable */ private MethodUtil() { // nothing } static void addTagsToPostOrPut(FhirContext theContext, IBaseResource resource, BaseHttpClientInvocation retVal) { if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { TagList list = (TagList) ((IResource)resource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); if (list != null) { for (Tag tag : list) { if (StringUtils.isNotBlank(tag.getTerm())) { retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); } } } } } @SuppressWarnings("unchecked") public static <T extends IIdType> T convertIdToType(IIdType value, Class<T> theIdParamType) { if (value != null && !theIdParamType.isAssignableFrom(value.getClass())) { IIdType newValue = ReflectionUtil.newInstance(theIdParamType); newValue.setValue(value.getValue()); value = newValue; } return (T) value; } public static HttpGetClientInvocation createConformanceInvocation(FhirContext theContext) { return new HttpGetClientInvocation(theContext, "metadata"); } public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) { return createCreateInvocation(theResource, null, null, theContext); } public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); String resourceName = def.getName(); StringBuilder urlExtension = new StringBuilder(); urlExtension.append(resourceName); boolean dstu1 = theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1); if (dstu1) { /* * This was allowable at one point, but as of DSTU2 it isn't. */ if (StringUtils.isNotBlank(theId)) { urlExtension.append('/'); urlExtension.append(theId); } } HttpPostClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); } else { retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); } addTagsToPostOrPut(theContext, theResource, retVal); if (!dstu1) { retVal.setOmitResourceId(true); } // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); return retVal; } public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, Map<String, List<String>> theIfNoneExistParams) { HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); retVal.setIfNoneExistParams(theIfNoneExistParams); return retVal; } public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, String theResourceBody, String theId, FhirContext theContext, String theIfNoneExistUrl) { HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); retVal.setIfNoneExistString(theIfNoneExistUrl); return retVal; } public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, IIdType theId, PatchTypeEnum thePatchType, String theBody) { return PatchMethodBinding.createPatchInvocation(theContext, theId, thePatchType, theBody); } public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, String theUrl, PatchTypeEnum thePatchType, String theBody) { return PatchMethodBinding.createPatchInvocation(theContext, theUrl, thePatchType, theBody); } public static HttpPatchClientInvocation createPatchInvocation(FhirContext theContext, PatchTypeEnum thePatchType, String theBody, String theResourceType, Map<String, List<String>> theMatchParams) { return PatchMethodBinding.createPatchInvocation(theContext, thePatchType, theBody, theResourceType, theMatchParams); } public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map<String, List<String>> theMatchParams) { String resourceType = theContext.getResourceDefinition(theResource).getName(); StringBuilder b = createUrl(resourceType, theMatchParams); HttpPutClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPutClientInvocation(theContext, theResource, b.toString()); } else { retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString()); } addTagsToPostOrPut(theContext, theResource, retVal); return retVal; } public static StringBuilder createUrl(String theResourceType, Map<String, List<String>> theMatchParams) { StringBuilder b = new StringBuilder(); b.append(theResourceType); boolean haveQuestionMark = false; for (Entry<String, List<String>> nextEntry : theMatchParams.entrySet()) { for (String nextValue : nextEntry.getValue()) { b.append(haveQuestionMark ? '&' : '?'); haveQuestionMark = true; b.append(UrlUtil.escape(nextEntry.getKey())); b.append('='); b.append(UrlUtil.escape(nextValue)); } } return b; } public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, String theMatchUrl) { HttpPutClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl); } else { retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl); } addTagsToPostOrPut(theContext, theResource, retVal); return retVal; } public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) { String resourceName = theContext.getResourceDefinition(theResource).getName(); StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append(resourceName); urlBuilder.append('/'); urlBuilder.append(theId.getIdPart()); String urlExtension = urlBuilder.toString(); HttpPutClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension); } else { retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, urlExtension); } retVal.setForceResourceId(theId); if (theId.hasVersionIdPart()) { if (theContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { retVal.addHeader(Constants.HEADER_IF_MATCH, '"' + theId.getVersionIdPart() + '"'); } else { String versionId = theId.getVersionIdPart(); if (StringUtils.isNotBlank(versionId)) { urlBuilder.append('/'); urlBuilder.append(Constants.PARAM_HISTORY); urlBuilder.append('/'); urlBuilder.append(versionId); retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, urlBuilder.toString()); } } } addTagsToPostOrPut(theContext, theResource, retVal); // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); return retVal; } public static EncodingEnum detectEncoding(String theBody) { EncodingEnum retVal = detectEncodingNoDefault(theBody); retVal = ObjectUtils.defaultIfNull(retVal, EncodingEnum.XML); return retVal; } public static EncodingEnum detectEncodingNoDefault(String theBody) { EncodingEnum retVal = null; for (int i = 0; i < theBody.length() && retVal == null; i++) { switch (theBody.charAt(i)) { case '<': retVal = EncodingEnum.XML; break; case '{': retVal = EncodingEnum.JSON; break; } } return retVal; } public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { for (Annotation annotation : theAnnotations) { if (annotation instanceof Description) { Description desc = (Description) annotation; if (isNotBlank(desc.formalDefinition())) { theParameter.setDescription(desc.formalDefinition()); } else { theParameter.setDescription(desc.shortDefinition()); } } } } public static Integer findIdParameterIndex(Method theMethod, FhirContext theContext) { Integer index = MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); if (index != null) { Class<?> paramType = theMethod.getParameterTypes()[index]; if (IIdType.class.equals(paramType)) { return index; } boolean isRi = theContext.getVersion().getVersion().isRi(); boolean usesHapiId = IdDt.class.equals(paramType); if (isRi == usesHapiId) { throw new ConfigurationException("Method uses the wrong Id datatype (IdDt / IdType) for the given context FHIR version: " + theMethod.toString()); } } return index; } @SuppressWarnings("GetClassOnAnnotation") public static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) { int paramIndex = 0; for (Annotation[] annotations : theMethod.getParameterAnnotations()) { for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) { Annotation nextAnnotation = annotations[annotationIndex]; Class<? extends Annotation> class1 = nextAnnotation.getClass(); if (toFind.isAssignableFrom(class1)) { return paramIndex; } } paramIndex++; } return null; } public static Integer findTagListParameterIndex(Method theMethod) { return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); } public static Integer findVersionIdParameterIndex(Method theMethod) { return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class); } @SuppressWarnings("unchecked") public static List<IParameter> getResourceParameters(final FhirContext theContext, Method theMethod, Object theProvider, RestOperationTypeEnum theRestfulOperationTypeEnum) { List<IParameter> parameters = new ArrayList<IParameter>(); Class<?>[] parameterTypes = theMethod.getParameterTypes(); int paramIndex = 0; for (Annotation[] annotations : theMethod.getParameterAnnotations()) { IParameter param = null; Class<?> parameterType = parameterTypes[paramIndex]; Class<? extends java.util.Collection<?>> outerCollectionType = null; Class<? extends java.util.Collection<?>> innerCollectionType = null; if (SearchParameterMap.class.equals(parameterType)) { if (theProvider instanceof IDynamicSearchResourceProvider) { Search searchAnnotation = theMethod.getAnnotation(Search.class); if (searchAnnotation != null && searchAnnotation.dynamic()) { param = new DynamicSearchParameter((IDynamicSearchResourceProvider) theProvider); } } } else if (TagList.class.isAssignableFrom(parameterType)) { // TagList is handled directly within the method bindings param = new NullParameter(); } else { if (Collection.class.isAssignableFrom(parameterType)) { innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType; parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex); } if (Collection.class.isAssignableFrom(parameterType)) { outerCollectionType = innerCollectionType; innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType; parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex); } if (Collection.class.isAssignableFrom(parameterType)) { 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)"); } } /* * Note: for the first two here, we're using strings instead of static binding * so that we don't need the java.servlet JAR on the classpath in order to use * this class */ if (ourServletRequestTypes.contains(parameterType.getName())) { param = new ServletRequestParameter(); } else if (ourServletResponseTypes.contains(parameterType.getName())) { param = new ServletResponseParameter(); } else if (parameterType.equals(RequestDetails.class)) { param = new RequestDetailsParameter(); } else if (parameterType.equals(IRequestOperationCallback.class)) { param = new RequestOperationCallbackParameter(); } else if (parameterType.equals(SummaryEnum.class)) { param = new SummaryEnumParameter(); } else if (parameterType.equals(PatchTypeEnum.class)) { param = new PatchTypeParameter(); } else { for (int i = 0; i < annotations.length && param == null; i++) { Annotation nextAnnotation = annotations[i]; if (nextAnnotation instanceof RequiredParam) { SearchParameter parameter = new SearchParameter(); parameter.setName(((RequiredParam) nextAnnotation).name()); parameter.setRequired(true); parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes()); parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes()); parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist()); parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; } else if (nextAnnotation instanceof OptionalParam) { SearchParameter parameter = new SearchParameter(); parameter.setName(((OptionalParam) nextAnnotation).name()); parameter.setRequired(false); parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes()); parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes()); parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist()); parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; } else if (nextAnnotation instanceof RawParam) { param = new RawParamsParmeter(parameters); } else if (nextAnnotation instanceof IncludeParam) { Class<? extends Collection<Include>> instantiableCollectionType; Class<?> specType; if (parameterType == String.class) { instantiableCollectionType = null; specType = String.class; } else if ((parameterType != Include.class) || innerCollectionType == null || outerCollectionType != null) { throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + Include.class.getSimpleName() + ">"); } else { instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'"); specType = parameterType; } param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType); } else if (nextAnnotation instanceof ResourceParam) { Mode mode; if (IBaseResource.class.isAssignableFrom(parameterType)) { mode = Mode.RESOURCE; } else if (String.class.equals(parameterType)) { mode = ResourceParameter.Mode.BODY; } else if (byte[].class.equals(parameterType)) { mode = ResourceParameter.Mode.BODY_BYTE_ARRAY; } else if (EncodingEnum.class.equals(parameterType)) { mode = Mode.ENCODING; } else { StringBuilder b = new StringBuilder(); b.append("Method '"); b.append(theMethod.getName()); b.append("' is annotated with @"); b.append(ResourceParam.class.getSimpleName()); b.append(" but has a type that is not an implemtation of "); b.append(IBaseResource.class.getCanonicalName()); b.append(" or String or byte[]"); throw new ConfigurationException(b.toString()); } param = new ResourceParameter((Class<? extends IResource>) parameterType, theProvider, mode); } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { param = new NullParameter(); } else if (nextAnnotation instanceof ServerBase) { param = new ServerBaseParamBinder(); } else if (nextAnnotation instanceof Elements) { param = new ElementsParameter(); } else if (nextAnnotation instanceof Since) { param = new SinceParameter(); ((SinceParameter)param).setType(theContext, parameterType, innerCollectionType, outerCollectionType); } else if (nextAnnotation instanceof At) { param = new AtParameter(); ((AtParameter)param).setType(theContext, parameterType, innerCollectionType, outerCollectionType); } else if (nextAnnotation instanceof Count) { param = new CountParameter(); } else if (nextAnnotation instanceof Sort) { param = new SortParameter(theContext); } else if (nextAnnotation instanceof TransactionParam) { param = new TransactionParameter(theContext); } else if (nextAnnotation instanceof ConditionalUrlParam) { param = new ConditionalParamBinder(theRestfulOperationTypeEnum, ((ConditionalUrlParam)nextAnnotation).supportsMultiple()); } else if (nextAnnotation instanceof OperationParam) { Operation op = theMethod.getAnnotation(Operation.class); param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation)); } else if (nextAnnotation instanceof Validate.Mode) { if (parameterType.equals(ValidationModeEnum.class) == false) { throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName()); } param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() { @Override public Object incomingServer(Object theObject) { if (isNotBlank(theObject.toString())) { ValidationModeEnum retVal = ValidationModeEnum.forCode(theObject.toString()); if (retVal == null) { OperationParameter.throwInvalidMode(theObject.toString()); } return retVal; } return null; } @Override public Object outgoingClient(Object theObject) { return ParametersUtil.createString(theContext, ((ValidationModeEnum)theObject).getCode()); } }); } else if (nextAnnotation instanceof Validate.Profile) { if (parameterType.equals(String.class) == false) { throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName()); } param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() { @Override public Object incomingServer(Object theObject) { return theObject.toString(); } @Override public Object outgoingClient(Object theObject) { return ParametersUtil.createString(theContext, theObject.toString()); } }); } else { continue; } } } if (param == null) { throw new ConfigurationException("Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter"); } param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType); parameters.add(param); paramIndex++; } return parameters; } public static void parseClientRequestResourceHeaders(IIdType theRequestedId, Map<String, List<String>> theHeaders, IBaseResource resource) { List<String> lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { String headerValue = lmHeaders.get(0); Date headerDateValue; try { headerDateValue = DateUtils.parseDate(headerValue); if (resource instanceof IResource) { IResource iResource = (IResource) resource; InstantDt existing = ResourceMetadataKeyEnum.UPDATED.get(iResource); if (existing == null || existing.isEmpty()) { InstantDt lmValue = new InstantDt(headerDateValue); iResource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); } } else if (resource instanceof IAnyResource) { IAnyResource anyResource = (IAnyResource) resource; if (anyResource.getMeta().getLastUpdated() == null) { anyResource.getMeta().setLastUpdated(headerDateValue); } } } catch (Exception e) { ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); } } List<String> clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { String headerValue = clHeaders.get(0); if (isNotBlank(headerValue)) { new IdDt(headerValue).applyTo(resource); } } List<String> locationHeaders = theHeaders.get(Constants.HEADER_LOCATION_LC); if (locationHeaders != null && locationHeaders.size() > 0 && StringUtils.isNotBlank(locationHeaders.get(0))) { String headerValue = locationHeaders.get(0); if (isNotBlank(headerValue)) { new IdDt(headerValue).applyTo(resource); } } IdDt existing = IdDt.of(resource); List<String> eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); String eTagVersion = null; if (eTagHeaders != null && eTagHeaders.size() > 0) { eTagVersion = parseETagValue(eTagHeaders.get(0)); } if (isNotBlank(eTagVersion)) { if (existing == null || existing.isEmpty()) { if (theRequestedId != null) { theRequestedId.withVersion(eTagVersion).applyTo(resource); } } else if (existing.hasVersionIdPart() == false) { existing.withVersion(eTagVersion).applyTo(resource); } } else if (existing == null || existing.isEmpty()) { if (theRequestedId != null) { theRequestedId.applyTo(resource); } } List<String> categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { TagList tagList = new TagList(); for (String header : categoryHeaders) { parseTagValue(tagList, header); } if (resource instanceof IResource) { ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); } else if (resource instanceof IAnyResource) { IBaseMetaType meta = ((IAnyResource) resource).getMeta(); for (Tag next : tagList) { meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); } } } } public static String parseETagValue(String value) { String eTagVersion; value = value.trim(); if (value.length() > 1) { if (value.charAt(value.length() - 1) == '"') { if (value.charAt(0) == '"') { eTagVersion = value.substring(1, value.length() - 1); } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { eTagVersion = value.substring(3, value.length() - 1); } else { eTagVersion = value; } } else { eTagVersion = value; } } else { eTagVersion = value; } return eTagVersion; } /** * This is a utility method intended provided to help the JPA module. */ public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters); } /** * This is a utility method intended provided to help the JPA module. */ public static IQueryParameterAnd<?> parseQueryParams(FhirContext theContext, RestSearchParameterTypeEnum paramType, String theUnqualifiedParamName, List<QualifiedParamList> theParameters) { QueryParameterAndBinder binder = null; switch (paramType) { case COMPOSITE: throw new UnsupportedOperationException(); case DATE: binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case NUMBER: binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case QUANTITY: binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case REFERENCE: binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case STRING: binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case TOKEN: binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case URI: binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; case HAS: binder = new QueryParameterAndBinder(HasAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); break; } //FIXME null access return binder.parse(theContext, theUnqualifiedParamName, theParameters); } public static void parseTagValue(TagList tagList, String nextTagComplete) { StringBuilder next = new StringBuilder(nextTagComplete); parseTagValue(tagList, nextTagComplete, next); } private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { int firstSemicolon = theBuffer.indexOf(";"); int deleteTo; if (firstSemicolon == -1) { firstSemicolon = theBuffer.indexOf(","); if (firstSemicolon == -1) { firstSemicolon = theBuffer.length(); deleteTo = theBuffer.length(); } else { deleteTo = firstSemicolon; } } else { deleteTo = firstSemicolon + 1; } String term = theBuffer.substring(0, firstSemicolon); String scheme = null; String label = null; if (isBlank(term)) { return; } theBuffer.delete(0, deleteTo); while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { theBuffer.deleteCharAt(0); } while (theBuffer.length() > 0) { boolean foundSomething = false; if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); scheme = theBuffer.substring(SCHEME.length(), closeIdx); theBuffer.delete(0, closeIdx + 1); foundSomething = true; } if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { int closeIdx = theBuffer.indexOf("\"", LABEL.length()); label = theBuffer.substring(LABEL.length(), closeIdx); theBuffer.delete(0, closeIdx + 1); foundSomething = true; } // TODO: support enc2231-string as described in // http://tools.ietf.org/html/draft-johnston-http-category-header-02 // TODO: support multiple tags in one header as described in // http://hl7.org/implement/standards/fhir/http.html#tags while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { theBuffer.deleteCharAt(0); } if (!foundSomething) { break; } } if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { theBuffer.deleteCharAt(0); while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { theBuffer.deleteCharAt(0); } theTagList.add(new Tag(scheme, term, label)); parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); } else { theTagList.add(new Tag(scheme, term, label)); } if (theBuffer.length() > 0) { ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); } } public static MethodOutcome process2xxResponse(FhirContext theContext, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) { List<String> locationHeaders = new ArrayList<String>(); List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC); if (lh != null) { locationHeaders.addAll(lh); } List<String> clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); if (clh != null) { locationHeaders.addAll(clh); } MethodOutcome retVal = new MethodOutcome(); if (locationHeaders != null && locationHeaders.size() > 0) { String locationHeader = locationHeaders.get(0); BaseOutcomeReturningMethodBinding.parseContentLocation(theContext, retVal, locationHeader); } if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); if (ct != null) { PushbackReader reader = new PushbackReader(theResponseReader); try { int firstByte = reader.read(); if (firstByte == -1) { BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); reader = null; } else { reader.unread(firstByte); } } catch (IOException e) { BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); reader = null; } if (reader != null) { IParser parser = ct.newParser(theContext); IBaseResource outcome = parser.parseResource(reader); if (outcome instanceof IBaseOperationOutcome) { retVal.setOperationOutcome((IBaseOperationOutcome) outcome); } else { retVal.setResource(outcome); } } } else { BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); } } return retVal; } public static IQueryParameterOr<?> singleton(final IQueryParameterType theParam, final String theParamName) { return new IQueryParameterOr<IQueryParameterType>() { @Override public List<IQueryParameterType> getValuesAsQueryTokens() { return Collections.singletonList(theParam); } @Override public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { if (theParameters.isEmpty()) { return; } if (theParameters.size() > 1) { throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); } theParam.setValueAsQueryToken(theContext, theParamName, theParameters.getQualifier(), theParameters.get(0)); } }; } }