diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
index e190a60ed4d..d3df0dd67e2 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.context;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
import java.util.*;
@@ -50,7 +51,12 @@ public class RuntimeSearchParam {
}
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf,
- Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) {
+ Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) {
+ this(theId, theUri, theName, theDescription, thePath, theParamType, theCompositeOf, theProvidesMembershipInCompartments, theTargets, theStatus, null);
+ }
+
+ public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf,
+ Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus, Collection theBase) {
super();
myId = theId;
myUri = theUri;
@@ -70,13 +76,19 @@ public class RuntimeSearchParam {
} else {
myTargets = null;
}
-
- HashSet base = new HashSet();
- int indexOf = thePath.indexOf('.');
- if (indexOf != -1) {
- base.add(trim(thePath.substring(0, indexOf)));
+
+ if (theBase == null || theBase.isEmpty()) {
+ HashSet base = new HashSet<>();
+ if (isNotBlank(thePath)) {
+ int indexOf = thePath.indexOf('.');
+ if (indexOf != -1) {
+ base.add(trim(thePath.substring(0, indexOf)));
+ }
+ }
+ myBase = Collections.unmodifiableSet(base);
+ } else {
+ myBase = Collections.unmodifiableSet(new HashSet<>(theBase));
}
- myBase = Collections.unmodifiableSet(base);
}
public Set getBase() {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 90414d1b0f0..d95e7c77a92 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -33,6 +33,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index 28686bbadb1..102d51aa3f5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -19,19 +19,49 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License.
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.*;
-import java.io.UnsupportedEncodingException;
-import java.text.Normalizer;
-import java.util.*;
-import java.util.Map.Entry;
-
-import javax.persistence.*;
-import javax.persistence.criteria.*;
-import javax.xml.stream.events.Characters;
-import javax.xml.stream.events.XMLEvent;
-
-import org.apache.commons.lang3.*;
+import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.jpa.dao.data.*;
+import ca.uhn.fhir.jpa.entity.*;
+import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
+import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
+import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
+import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
+import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
+import ca.uhn.fhir.jpa.util.DeleteConflict;
+import ca.uhn.fhir.model.api.*;
+import ca.uhn.fhir.model.base.composite.BaseCodingDt;
+import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.InstantDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.XhtmlDt;
+import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
+import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.parser.LenientErrorHandler;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.QualifiedParamList;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.*;
+import ca.uhn.fhir.rest.server.exceptions.*;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.CoverageIgnore;
+import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.OperationOutcomeUtil;
+import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Sets;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.*;
@@ -40,49 +70,30 @@ import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Sets;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
+import javax.persistence.*;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.XMLEvent;
+import java.io.UnsupportedEncodingException;
+import java.text.Normalizer;
+import java.util.*;
+import java.util.Map.Entry;
-import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.jpa.dao.data.*;
-import ca.uhn.fhir.jpa.entity.*;
-import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
-import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
-import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
-import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
-import ca.uhn.fhir.jpa.util.DeleteConflict;
-import ca.uhn.fhir.model.api.*;
-import ca.uhn.fhir.model.base.composite.BaseCodingDt;
-import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
-import ca.uhn.fhir.model.primitive.*;
-import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
-import ca.uhn.fhir.parser.*;
-import ca.uhn.fhir.rest.api.*;
-import ca.uhn.fhir.rest.api.Constants;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import ca.uhn.fhir.rest.param.*;
-import ca.uhn.fhir.rest.server.exceptions.*;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
-import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
-import ca.uhn.fhir.util.*;
+import static org.apache.commons.lang3.StringUtils.*;
public abstract class BaseHapiFhirDao implements IDao {
- static final Set EXCLUDE_ELEMENTS_IN_ENCODED;
-
public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L);
public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L);
public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
public static final String OO_SEVERITY_ERROR = "error";
public static final String OO_SEVERITY_INFO = "information";
public static final String OO_SEVERITY_WARN = "warning";
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
- private static final Map ourRetrievalContexts = new HashMap();
- private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
+ public static final String UCUM_NS = "http://unitsofmeasure.org";
+ static final Set EXCLUDE_ELEMENTS_IN_ENCODED;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
@@ -91,7 +102,9 @@ public abstract class BaseHapiFhirDao implements IDao {
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
static final Map> RESOURCE_META_PARAMS;
- public static final String UCUM_NS = "http://unitsofmeasure.org";
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
+ private static final Map ourRetrievalContexts = new HashMap();
+ private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
static {
Map> resourceMetaParams = new HashMap>();
@@ -115,52 +128,47 @@ public abstract class BaseHapiFhirDao implements IDao {
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
}
- @Autowired(required = true)
- private DaoConfig myConfig;
- private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired(required = false)
protected IFulltextSearchSvc myFulltextSearchSvc;
- @Autowired
- private PlatformTransactionManager myPlatformTransactionManager;
-
- @Autowired
- private List> myResourceDaos;
-
- @Autowired
- private IResourceHistoryTableDao myResourceHistoryTableDao;
-
@Autowired()
protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
-
- private Map, IFhirResourceDao>> myResourceTypeToDao;
-
@Autowired
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
-
- @Autowired
- private ISearchDao mySearchDao;
-
- @Autowired
- private ISearchParamExtractor mySearchParamExtractor;
-
- @Autowired
- private ISearchParamPresenceSvc mySearchParamPresenceSvc;
-
- @Autowired
- private ISearchParamRegistry mySearchParamRegistry;
-
- @Autowired
- private ISearchResultDao mySearchResultDao;
-
@Autowired
protected ISearchParamRegistry mySerarchParamRegistry;
-
@Autowired()
protected IHapiTerminologySvc myTerminologySvc;
+ @Autowired(required = true)
+ private DaoConfig myConfig;
+ private FhirContext myContext;
+ @Autowired
+ private PlatformTransactionManager myPlatformTransactionManager;
+ @Autowired
+ private List> myResourceDaos;
+ @Autowired
+ private IResourceHistoryTableDao myResourceHistoryTableDao;
+ private Map, IFhirResourceDao>> myResourceTypeToDao;
+ @Autowired
+ private ISearchDao mySearchDao;
+ @Autowired
+ private ISearchParamExtractor mySearchParamExtractor;
+ @Autowired
+ private ISearchParamPresenceSvc mySearchParamPresenceSvc;
+ @Autowired
+ private ISearchParamRegistry mySearchParamRegistry;
+ @Autowired
+ private ISearchResultDao mySearchResultDao;
+ @Autowired
+ private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
+
+ private void autoCreateResource(T theResource) {
+ IFhirResourceDao dao = (IFhirResourceDao) getDao(theResource.getClass());
+ dao.create(theResource);
+ }
protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
@@ -188,13 +196,84 @@ public abstract class BaseHapiFhirDao implements IDao {
return InstantDt.withCurrentTime();
}
+ private Set extractCompositeStringUniques(ResourceTable theEntity, Set theStringParams, Set theTokenParams, Set theNumberParams, Set theQuantityParams, Set theDateParams, Set theUriParams, Set theLinks) {
+ Set compositeStringUniques;
+ compositeStringUniques = new HashSet<>();
+ List uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(theEntity.getResourceType());
+ for (JpaRuntimeSearchParam next : uniqueSearchParams) {
+
+ List> partsChoices = new ArrayList<>();
+
+ for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
+ Set extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
+ Set linksForCompositePart = null;
+ switch (nextCompositeOf.getParamType()) {
+ case NUMBER:
+ paramsListForCompositePart = theNumberParams;
+ break;
+ case DATE:
+ paramsListForCompositePart = theDateParams;
+ break;
+ case STRING:
+ paramsListForCompositePart = theStringParams;
+ break;
+ case TOKEN:
+ paramsListForCompositePart = theTokenParams;
+ break;
+ case REFERENCE:
+ linksForCompositePart = theLinks;
+ break;
+ case QUANTITY:
+ paramsListForCompositePart = theQuantityParams;
+ break;
+ case URI:
+ paramsListForCompositePart = theUriParams;
+ break;
+ case COMPOSITE:
+ case HAS:
+ break;
+ }
+
+ ArrayList nextChoicesList = new ArrayList<>();
+ partsChoices.add(nextChoicesList);
+
+ String key = UrlUtil.escape(nextCompositeOf.getName());
+ if (paramsListForCompositePart != null) {
+ for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
+ if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
+ IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
+ String value = nextParamAsClientParam.getValueAsQueryToken(getContext());
+ value = UrlUtil.escape(value);
+ nextChoicesList.add(key + "=" + value);
+ }
+ }
+ }
+ if (linksForCompositePart != null) {
+ for (ResourceLink nextLink : linksForCompositePart) {
+ String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
+ value = UrlUtil.escape(value);
+ nextChoicesList.add(key + "=" + value);
+ }
+ }
+ }
+
+ Set queryStringsToPopulate = extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices);
+
+ for (String nextQueryString : queryStringsToPopulate) {
+ compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
+ }
+ }
+
+ return compositeStringUniques;
+ }
+
/**
* @return Returns a set containing all of the parameter names that
- * were found to have a value
+ * were found to have a value
*/
@SuppressWarnings("unchecked")
protected Set extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set theLinks, Date theUpdateTime) {
- HashSet retVal = new HashSet();
+ HashSet retVal = new HashSet<>();
/*
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
@@ -243,12 +322,12 @@ public abstract class BaseHapiFhirDao implements IDao {
/*
* This can only really happen if the DAO is being called
* programatically with a Bundle (not through the FHIR REST API)
- * but Smile does this
+ * but Smile does this
*/
if (nextId.isEmpty() && nextValue.getResource() != null) {
nextId = nextValue.getResource().getIdElement();
}
-
+
if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
// This is a blank or contained resource reference
continue;
@@ -291,7 +370,7 @@ public abstract class BaseHapiFhirDao implements IDao {
resourceDefinition = getContext().getResourceDefinition(typeString);
} catch (DataFormatException e) {
throw new InvalidRequestException(
- "Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
+ "Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
}
if (isNotBlank(baseUrl)) {
@@ -352,14 +431,14 @@ public abstract class BaseHapiFhirDao implements IDao {
if (!typeString.equals(target.getResourceType())) {
throw new UnprocessableEntityException(
- "Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
+ "Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
}
if (target.getDeleted() != null) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
}
-
+
if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
continue;
}
@@ -375,11 +454,6 @@ public abstract class BaseHapiFhirDao implements IDao {
return retVal;
}
- private void autoCreateResource(T theResource) {
- IFhirResourceDao dao = (IFhirResourceDao) getDao(theResource.getClass());
- dao.create(theResource);
- }
-
protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
}
@@ -509,7 +583,7 @@ public abstract class BaseHapiFhirDao implements IDao {
@SuppressWarnings("unchecked")
private
+ *
+ * So this recursive algorithm calculates those
+ *
+ *
+ * @param theResourceType E.g. Patient
+ * @param thePartsChoices E.g. [[gender=male], [name=SMITH, name=JOHN]]
+ */
+ public static Set extractCompositeStringUniquesValueChains(String theResourceType, List> thePartsChoices) {
+
+ Collections.sort(thePartsChoices, new Comparator>() {
+ @Override
+ public int compare(List o1, List o2) {
+ String str1=null;
+ String str2=null;
+ if (o1.size() > 0) {
+ str1 = o1.get(0);
+ }
+ if (o2.size() > 0) {
+ str2 = o2.get(0);
+ }
+ return StringUtils.compare(str1, str2);
+ }
+ });
+
+ List values = new ArrayList<>();
+ Set queryStringsToPopulate = new HashSet<>();
+ extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices, values, queryStringsToPopulate);
+ return queryStringsToPopulate;
+ }
+
+ private static void extractCompositeStringUniquesValueChains(String theResourceType, List> thePartsChoices, List theValues, Set theQueryStringsToPopulate) {
+ if (thePartsChoices.size() > 0) {
+ List nextList = thePartsChoices.get(0);
+ Collections.sort(nextList);
+ for (String nextChoice : nextList) {
+ theValues.add(nextChoice);
+ extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices.subList(1, thePartsChoices.size()), theValues, theQueryStringsToPopulate);
+ theValues.remove(theValues.size() - 1);
+ }
+ } else {
+ if (theValues.size() > 0) {
+ StringBuilder uniqueString = new StringBuilder();
+ uniqueString.append(theResourceType);
+
+ for (int i = 0; i < theValues.size(); i++) {
+ uniqueString.append(i == 0 ? "?" : "&");
+ uniqueString.append(theValues.get(i));
+ }
+
+ theQueryStringsToPopulate.add(uniqueString.toString());
+ }
+ }
}
protected static boolean isValidPid(IIdType theId) {
@@ -1990,7 +2149,7 @@ public abstract class BaseHapiFhirDao implements IDao {
RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
if (paramDef == null) {
throw new InvalidRequestException(
- "Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
+ "Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
}
IQueryParameterAnd> param = ParameterUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);
@@ -2023,7 +2182,7 @@ public abstract class BaseHapiFhirDao implements IDao {
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
if (!theResourceName.equals(theEntity.getResourceType())) {
throw new ResourceNotFoundException(
- "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
+ "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 4b315d2ed0c..51c4d600ad1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -34,7 +34,10 @@ import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
-import ca.uhn.fhir.model.api.*;
+import ca.uhn.fhir.model.api.IQueryParameterAnd;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.QualifiedParamList;
@@ -384,7 +387,6 @@ public abstract class BaseHapiFhirResourceDao extends B
// Notify JPA interceptors
if (theRequestDetails != null) {
- ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theResource);
theRequestDetails.getRequestOperationCallback().resourceCreated(theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
@@ -871,8 +873,7 @@ public abstract class BaseHapiFhirResourceDao extends B
throw new ResourceNotFoundException(theId);
}
- //@formatter:off
- for (BaseTag next : new ArrayList(entity.getTags())) {
+ for (BaseTag next : new ArrayList<>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) &&
ObjectUtil.equals(next.getTag().getSystem(), theScheme) &&
ObjectUtil.equals(next.getTag().getCode(), theTerm)) {
@@ -880,7 +881,6 @@ public abstract class BaseHapiFhirResourceDao extends B
entity.getTags().remove(next);
}
}
- //@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java
index 068cc8873d8..47cf35ac5da 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java
@@ -20,27 +20,28 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.annotation.PostConstruct;
-
-import org.apache.commons.lang3.Validate;
-import org.springframework.beans.factory.annotation.Autowired;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
+ private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
private Map> myBuiltInSearchParams;
+ private volatile Map> myActiveUniqueSearchParams;
+ private volatile Map, List>> myActiveParamNamesToUniqueSearchParams;
@Autowired
private FhirContext myCtx;
-
@Autowired
private Collection> myDaos;
@@ -75,18 +76,119 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
return myBuiltInSearchParams.get(theResourceName);
}
+ @Override
+ public List getActiveUniqueSearchParams(String theResourceName) {
+ refreshCacheIfNecessary();
+ List retVal = myActiveUniqueSearchParams.get(theResourceName);
+ if (retVal == null) {
+ retVal = Collections.emptyList();
+ }
+ return retVal;
+ }
+
+ @Override
+ public List getActiveUniqueSearchParams(String theResourceName, Set theParamNames) {
+ refreshCacheIfNecessary();
+
+ Map, List> paramNamesToParams = myActiveParamNamesToUniqueSearchParams.get(theResourceName);
+ if (paramNamesToParams == null) {
+ return Collections.emptyList();
+ }
+
+ List retVal = paramNamesToParams.get(theParamNames);
+ if (retVal == null) {
+ retVal = Collections.emptyList();
+ }
+ return Collections.unmodifiableList(retVal);
+ }
+
public Map> getBuiltInSearchParams() {
return myBuiltInSearchParams;
}
+ public void populateActiveSearchParams(Map> theActiveSearchParams) {
+ Map> activeUniqueSearchParams = new HashMap<>();
+ Map, List>> activeParamNamesToUniqueSearchParams = new HashMap<>();
+
+ Map idToRuntimeSearchParam = new HashMap<>();
+ List jpaSearchParams = new ArrayList<>();
+
+ /*
+ * Loop through parameters and find JPA params
+ */
+ for (Map.Entry> nextResourceNameToEntries : theActiveSearchParams.entrySet()) {
+ List uniqueSearchParams = activeUniqueSearchParams.get(nextResourceNameToEntries.getKey());
+ if (uniqueSearchParams == null) {
+ uniqueSearchParams = new ArrayList<>();
+ activeUniqueSearchParams.put(nextResourceNameToEntries.getKey(), uniqueSearchParams);
+ }
+ Collection nextSearchParamsForResourceName = nextResourceNameToEntries.getValue().values();
+ for (RuntimeSearchParam nextCandidate : nextSearchParamsForResourceName) {
+
+ if (nextCandidate.getId() != null) {
+ idToRuntimeSearchParam.put(nextCandidate.getId().toUnqualifiedVersionless().getValue(), nextCandidate);
+ }
+
+ if (nextCandidate instanceof JpaRuntimeSearchParam) {
+ JpaRuntimeSearchParam nextCandidateCasted = (JpaRuntimeSearchParam) nextCandidate;
+ jpaSearchParams.add(nextCandidateCasted);
+ if (nextCandidateCasted.isUnique()) {
+ uniqueSearchParams.add(nextCandidateCasted);
+ }
+ }
+ }
+
+ }
+
+ Set haveSeen = new HashSet<>();
+ for (JpaRuntimeSearchParam next : jpaSearchParams) {
+ if (!haveSeen.add(next.getId().toUnqualifiedVersionless().getValue())) {
+ continue;
+ }
+
+ Set paramNames = new HashSet<>();
+ for (JpaRuntimeSearchParam.Component nextComponent : next.getComponents()) {
+ String nextRef = nextComponent.getReference().getReferenceElement().toUnqualifiedVersionless().getValue();
+ RuntimeSearchParam componentTarget = idToRuntimeSearchParam.get(nextRef);
+ if (componentTarget != null) {
+ next.getCompositeOf().add(componentTarget);
+ paramNames.add(componentTarget.getName());
+ } else {
+ ourLog.warn("Search parameter {} refers to unknown component {}", next.getId().toUnqualifiedVersionless().getValue(), nextRef);
+ }
+ }
+
+ if (next.getCompositeOf() != null) {
+ Collections.sort(next.getCompositeOf(), new Comparator() {
+ @Override
+ public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) {
+ return StringUtils.compare(theO1.getName(), theO2.getName());
+ }
+ });
+ for (String nextBase : next.getBase()) {
+ if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) {
+ activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap, List>());
+ }
+ if (!activeParamNamesToUniqueSearchParams.get(nextBase).containsKey(paramNames)) {
+ activeParamNamesToUniqueSearchParams.get(nextBase).put(paramNames, new ArrayList());
+ }
+ activeParamNamesToUniqueSearchParams.get(nextBase).get(paramNames).add(next);
+ }
+ }
+ }
+
+ myActiveUniqueSearchParams = activeUniqueSearchParams;
+ myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams;
+ }
+
@PostConstruct
public void postConstruct() {
- Map> resourceNameToSearchParams = new HashMap>();
+ Map> resourceNameToSearchParams = new HashMap<>();
for (IFhirResourceDao> nextDao : myDaos) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
String nextResourceName = nextResDef.getName();
- HashMap nameToParam = new HashMap();
+ HashMap nameToParam = new HashMap<>();
resourceNameToSearchParams.put(nextResourceName, nameToParam);
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
@@ -97,4 +199,6 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams);
}
+ protected abstract void refreshCacheIfNecessary();
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java
index 611ba169e05..e02e29775b8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java
@@ -20,21 +20,27 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
-import java.util.Map;
-
import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
public interface ISearchParamRegistry {
void forceRefresh();
- Map> getActiveSearchParams();
-
- Map getActiveSearchParams(String theResourceName);
-
/**
* @return Returns {@literal null} if no match
*/
RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName);
+ Map getActiveSearchParams(String theResourceName);
+
+ Map> getActiveSearchParams();
+
+ List getActiveUniqueSearchParams(String theResourceName);
+
+ List getActiveUniqueSearchParams(String theResourceName, Set theParamNames);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index c3d9861097a..eace417ea9b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -19,19 +19,40 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License.
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.*;
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.util.*;
-import java.util.Map.Entry;
-
-import javax.persistence.EntityManager;
-import javax.persistence.TypedQuery;
-import javax.persistence.criteria.*;
-import javax.persistence.criteria.CriteriaBuilder.In;
-
-import org.apache.commons.lang3.*;
+import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
+import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
+import ca.uhn.fhir.jpa.entity.*;
+import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
+import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
+import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
+import ca.uhn.fhir.jpa.util.BaseIterator;
+import ca.uhn.fhir.jpa.util.StopWatch;
+import ca.uhn.fhir.model.api.*;
+import ca.uhn.fhir.model.base.composite.BaseCodingDt;
+import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
+import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.InstantDt;
+import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
+import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
+import ca.uhn.fhir.rest.api.SortOrderEnum;
+import ca.uhn.fhir.rest.api.SortSpec;
+import ca.uhn.fhir.rest.param.*;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.tuple.Pair;
@@ -39,28 +60,20 @@ import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.query.Query;
import org.hl7.fhir.dstu3.model.BaseResource;
-import org.hl7.fhir.instance.model.api.*;
+import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
-import com.google.common.collect.*;
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.*;
+import javax.persistence.criteria.CriteriaBuilder.In;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.*;
+import java.util.Map.Entry;
-import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
-import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
-import ca.uhn.fhir.jpa.entity.*;
-import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
-import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
-import ca.uhn.fhir.jpa.util.BaseIterator;
-import ca.uhn.fhir.jpa.util.StopWatch;
-import ca.uhn.fhir.model.api.*;
-import ca.uhn.fhir.model.base.composite.*;
-import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.model.primitive.InstantDt;
-import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
-import ca.uhn.fhir.parser.DataFormatException;
-import ca.uhn.fhir.rest.api.*;
-import ca.uhn.fhir.rest.param.*;
-import ca.uhn.fhir.rest.server.exceptions.*;
-import ca.uhn.fhir.util.UrlUtil;
+import static org.apache.commons.lang3.StringUtils.*;
/**
* The SearchBuilder is responsible for actually forming the SQL query that handles
@@ -69,8 +82,9 @@ import ca.uhn.fhir.util.UrlUtil;
public class SearchBuilder implements ISearchBuilder {
private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList());
- private static Long NO_MORE = Long.valueOf(-1);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class);
+ private static Long NO_MORE = Long.valueOf(-1);
+ private static HandlerTypeEnum ourLastHandlerMechanismForUnitTest;
private List myAlsoIncludePids;
private CriteriaBuilder myBuilder;
private BaseHapiFhirDao> myCallingDao;
@@ -94,8 +108,8 @@ public class SearchBuilder implements ISearchBuilder {
* Constructor
*/
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, IFulltextSearchSvc theFulltextSearchSvc,
- BaseHapiFhirDao> theDao,
- IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) {
+ BaseHapiFhirDao> theDao,
+ IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) {
myContext = theFhirContext;
myEntityManager = theEntityManager;
myFulltextSearchSvc = theFulltextSearchSvc;
@@ -135,7 +149,7 @@ public class SearchBuilder implements ISearchBuilder {
return;
}
- List codePredicates = new ArrayList();
+ List codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr;
Predicate p = createPredicateDate(params, theResourceName, theParamName, myBuilder, join);
@@ -358,7 +372,7 @@ public class SearchBuilder implements ISearchBuilder {
if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) {
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
- resourceTypes = new ArrayList>();
+ resourceTypes = new ArrayList<>();
Set targetTypes = param.getTargets();
@@ -457,7 +471,7 @@ public class SearchBuilder implements ISearchBuilder {
IQueryParameterType chainValue;
if (remainingChain != null) {
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
- ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", new Object[] { nextType.getSimpleName(), chain, remainingChain });
+ ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", new Object[]{nextType.getSimpleName(), chain, remainingChain});
continue;
}
@@ -785,7 +799,7 @@ public class SearchBuilder implements ISearchBuilder {
*/
ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName);
Collection candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName);
- List toFind = new ArrayList();
+ List toFind = new ArrayList<>();
for (String next : candidates) {
if (value.length() >= next.length()) {
if (value.substring(0, next.length()).equals(next)) {
@@ -798,12 +812,12 @@ public class SearchBuilder implements ISearchBuilder {
continue;
}
- predicate = join.