package ca.uhn.fhir.rest.method; /* * #%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% */ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.builder.ToStringBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; import ca.uhn.fhir.model.base.composite.BaseQuantityDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.param.CompositeAndListParam; import ca.uhn.fhir.rest.param.CompositeOrListParam; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateAndListParam; import ca.uhn.fhir.rest.param.DateOrListParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.HasAndListParam; import ca.uhn.fhir.rest.param.HasOrListParam; import ca.uhn.fhir.rest.param.HasParam; import ca.uhn.fhir.rest.param.NumberAndListParam; import ca.uhn.fhir.rest.param.NumberOrListParam; import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.QuantityAndListParam; import ca.uhn.fhir.rest.param.QuantityOrListParam; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceAndListParam; import ca.uhn.fhir.rest.param.ReferenceOrListParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriAndListParam; import ca.uhn.fhir.rest.param.UriOrListParam; import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.CollectionUtil; import ca.uhn.fhir.util.ReflectionUtil; public class SearchParameter extends BaseQueryParameter { private static final String EMPTY_STRING = ""; private static HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers; private static HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes; static final String QUALIFIER_ANY_TYPE = ":*"; static { ourParamTypes = new HashMap<Class<?>, RestSearchParameterTypeEnum>(); ourParamQualifiers = new HashMap<RestSearchParameterTypeEnum, Set<String>>(); ourParamTypes.put(StringParam.class, RestSearchParameterTypeEnum.STRING); ourParamTypes.put(StringOrListParam.class, RestSearchParameterTypeEnum.STRING); ourParamTypes.put(StringAndListParam.class, RestSearchParameterTypeEnum.STRING); ourParamQualifiers.put(RestSearchParameterTypeEnum.STRING, CollectionUtil.newSet(Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(UriParam.class, RestSearchParameterTypeEnum.URI); ourParamTypes.put(UriOrListParam.class, RestSearchParameterTypeEnum.URI); ourParamTypes.put(UriAndListParam.class, RestSearchParameterTypeEnum.URI); // TODO: are these right for URI? ourParamQualifiers.put(RestSearchParameterTypeEnum.URI, CollectionUtil.newSet(Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(TokenParam.class, RestSearchParameterTypeEnum.TOKEN); ourParamTypes.put(TokenOrListParam.class, RestSearchParameterTypeEnum.TOKEN); ourParamTypes.put(TokenAndListParam.class, RestSearchParameterTypeEnum.TOKEN); ourParamQualifiers.put(RestSearchParameterTypeEnum.TOKEN, CollectionUtil.newSet(Constants.PARAMQUALIFIER_TOKEN_TEXT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(DateParam.class, RestSearchParameterTypeEnum.DATE); ourParamTypes.put(DateOrListParam.class, RestSearchParameterTypeEnum.DATE); ourParamTypes.put(DateAndListParam.class, RestSearchParameterTypeEnum.DATE); ourParamTypes.put(DateRangeParam.class, RestSearchParameterTypeEnum.DATE); ourParamQualifiers.put(RestSearchParameterTypeEnum.DATE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(QuantityParam.class, RestSearchParameterTypeEnum.QUANTITY); ourParamTypes.put(QuantityOrListParam.class, RestSearchParameterTypeEnum.QUANTITY); ourParamTypes.put(QuantityAndListParam.class, RestSearchParameterTypeEnum.QUANTITY); ourParamQualifiers.put(RestSearchParameterTypeEnum.QUANTITY, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(NumberParam.class, RestSearchParameterTypeEnum.NUMBER); ourParamTypes.put(NumberOrListParam.class, RestSearchParameterTypeEnum.NUMBER); ourParamTypes.put(NumberAndListParam.class, RestSearchParameterTypeEnum.NUMBER); ourParamQualifiers.put(RestSearchParameterTypeEnum.NUMBER, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(ReferenceParam.class, RestSearchParameterTypeEnum.REFERENCE); ourParamTypes.put(ReferenceOrListParam.class, RestSearchParameterTypeEnum.REFERENCE); ourParamTypes.put(ReferenceAndListParam.class, RestSearchParameterTypeEnum.REFERENCE); // --vvvv-- no empty because that gets added from OptionalParam#chainWhitelist ourParamQualifiers.put(RestSearchParameterTypeEnum.REFERENCE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING)); ourParamTypes.put(CompositeParam.class, RestSearchParameterTypeEnum.COMPOSITE); ourParamTypes.put(CompositeOrListParam.class, RestSearchParameterTypeEnum.COMPOSITE); ourParamTypes.put(CompositeAndListParam.class, RestSearchParameterTypeEnum.COMPOSITE); ourParamQualifiers.put(RestSearchParameterTypeEnum.COMPOSITE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING)); ourParamTypes.put(HasParam.class, RestSearchParameterTypeEnum.HAS); ourParamTypes.put(HasOrListParam.class, RestSearchParameterTypeEnum.HAS); ourParamTypes.put(HasAndListParam.class, RestSearchParameterTypeEnum.HAS); } private List<Class<? extends IQueryParameterType>> myCompositeTypes = Collections.emptyList(); private List<Class<? extends IBaseResource>> myDeclaredTypes; private String myDescription; private String myName; private IParamBinder<?> myParamBinder; private RestSearchParameterTypeEnum myParamType; private Set<String> myQualifierBlacklist; private Set<String> myQualifierWhitelist; private boolean myRequired; private Class<?> myType; public SearchParameter() { } public SearchParameter(String theName, boolean theRequired) { this.myName = theName; this.myRequired = theRequired; } /* * (non-Javadoc) * * @see ca.uhn.fhir.rest.param.IParameter#encode(java.lang.Object) */ @Override public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException { ArrayList<QualifiedParamList> retVal = new ArrayList<QualifiedParamList>(); // TODO: declaring method should probably have a generic type.. @SuppressWarnings("rawtypes") IParamBinder paramBinder = myParamBinder; @SuppressWarnings("unchecked") List<IQueryParameterOr<?>> val = paramBinder.encode(theContext, theObject); for (IQueryParameterOr<?> nextOr : val) { retVal.add(new QualifiedParamList(nextOr, theContext)); } return retVal; } public List<Class<? extends IBaseResource>> getDeclaredTypes() { return Collections.unmodifiableList(myDeclaredTypes); } public String getDescription() { return myDescription; } /* * (non-Javadoc) * * @see ca.uhn.fhir.rest.param.IParameter#getName() */ @Override public String getName() { return myName; } @Override public RestSearchParameterTypeEnum getParamType() { return myParamType; } @Override public Set<String> getQualifierBlacklist() { return myQualifierBlacklist; } @Override public Set<String> getQualifierWhitelist() { return myQualifierWhitelist; } public Class<?> getType() { return myType; } @Override public boolean handlesMissing() { return false; } @Override public boolean isRequired() { return myRequired; } /* * (non-Javadoc) * * @see ca.uhn.fhir.rest.param.IParameter#parse(java.util.List) */ @Override public Object parse(FhirContext theContext, List<QualifiedParamList> theString) throws InternalErrorException, InvalidRequestException { return myParamBinder.parse(theContext, getName(), theString); } public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) { myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length); myQualifierWhitelist.add(QUALIFIER_ANY_TYPE); for (int i = 0; i < theChainWhitelist.length; i++) { if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) { myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY); } else if (theChainWhitelist[i].equals(EMPTY_STRING)) { myQualifierWhitelist.add("."); } else { myQualifierWhitelist.add('.' + theChainWhitelist[i]); } } if (theChainBlacklist.length > 0) { myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length); for (String next : theChainBlacklist) { if (next.equals(EMPTY_STRING)) { myQualifierBlacklist.add(EMPTY_STRING); } else { myQualifierBlacklist.add('.' + next); } } } } public void setCompositeTypes(Class<? extends IQueryParameterType>[] theCompositeTypes) { myCompositeTypes = Arrays.asList(theCompositeTypes); } public void setDeclaredTypes(Class<? extends IBaseResource>[] theTypes) { myDeclaredTypes = Arrays.asList(theTypes); } public void setDescription(String theDescription) { myDescription = theDescription; } public void setName(String name) { this.myName = name; } public void setRequired(boolean required) { this.myRequired = required; } @SuppressWarnings("unchecked") public void setType(FhirContext theContext, final Class<?> type, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) { this.myType = type; if (IQueryParameterType.class.isAssignableFrom(type)) { myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type, myCompositeTypes); } else if (IQueryParameterOr.class.isAssignableFrom(type)) { myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) type, myCompositeTypes); } else if (IQueryParameterAnd.class.isAssignableFrom(type)) { myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) type, myCompositeTypes); } else if (String.class.equals(type)) { myParamBinder = new StringBinder(); myParamType = RestSearchParameterTypeEnum.STRING; } else if (Date.class.equals(type)) { myParamBinder = new DateBinder(); myParamType = RestSearchParameterTypeEnum.DATE; } else if (Calendar.class.equals(type)) { myParamBinder = new CalendarBinder(); myParamType = RestSearchParameterTypeEnum.DATE; } else if (IPrimitiveType.class.isAssignableFrom(type) && ReflectionUtil.isInstantiable(type)) { RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) type); if (def.getNativeType() != null) { if (def.getNativeType().equals(Date.class)) { myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type); myParamType = RestSearchParameterTypeEnum.DATE; } else if (def.getNativeType().equals(String.class)) { myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type); myParamType = RestSearchParameterTypeEnum.STRING; } } } else { throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName()); } RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(type); if (typeEnum != null) { Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum); if (builtInQualifiers != null) { if (myQualifierWhitelist != null) { HashSet<String> qualifierWhitelist = new HashSet<String>(); qualifierWhitelist.addAll(myQualifierWhitelist); qualifierWhitelist.addAll(builtInQualifiers); myQualifierWhitelist = qualifierWhitelist; } else { myQualifierWhitelist = Collections.unmodifiableSet(builtInQualifiers); } } } if (myParamType == null) { myParamType = typeEnum; } if (myParamType != null) { // ok } else if (StringDt.class.isAssignableFrom(type)) { myParamType = RestSearchParameterTypeEnum.STRING; } else if (BaseIdentifierDt.class.isAssignableFrom(type)) { myParamType = RestSearchParameterTypeEnum.TOKEN; } else if (BaseQuantityDt.class.isAssignableFrom(type)) { myParamType = RestSearchParameterTypeEnum.QUANTITY; } else if (ReferenceParam.class.isAssignableFrom(type)) { myParamType = RestSearchParameterTypeEnum.REFERENCE; } else if (HasParam.class.isAssignableFrom(type)) { myParamType = RestSearchParameterTypeEnum.STRING; } else { throw new ConfigurationException("Unknown search parameter type: " + type); } // NB: Once this is enabled, we should return true from handlesMissing if // it's a collection type // if (theInnerCollectionType != null) { // this.parser = new CollectionBinder(this.parser, theInnerCollectionType); // } // // if (theOuterCollectionType != null) { // this.parser = new CollectionBinder(this.parser, theOuterCollectionType); // } } @Override public String toString() { ToStringBuilder retVal = new ToStringBuilder(this); retVal.append("name", myName); retVal.append("required", myRequired); return retVal.toString(); } }