Add ability for system to track unique constraints

This commit is contained in:
James Agnew 2017-09-05 17:33:19 -07:00
parent c6ddf89557
commit 2aa538bd56
39 changed files with 2385 additions and 1124 deletions

View File

@ -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<RuntimeSearchParam> theCompositeOf,
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus) {
Set<String> theProvidesMembershipInCompartments, Set<String> 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<RuntimeSearchParam> theCompositeOf,
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
super();
myId = theId;
myUri = theUri;
@ -70,13 +76,19 @@ public class RuntimeSearchParam {
} else {
myTargets = null;
}
HashSet<String> base = new HashSet<String>();
int indexOf = thePath.indexOf('.');
if (indexOf != -1) {
base.add(trim(thePath.substring(0, indexOf)));
if (theBase == null || theBase.isEmpty()) {
HashSet<String> 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<String> getBase() {

View File

@ -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;

View File

@ -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<T extends IBaseResource> implements IDao {
static final Set<String> 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<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
public static final String UCUM_NS = "http://unitsofmeasure.org";
static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
@ -91,7 +102,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/
static final Map<String, Class<? extends IQueryParameterType>> 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<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
@ -115,52 +128,47 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> 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<IFhirResourceDao<?>> myResourceDaos;
@Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao;
@Autowired()
protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
private Map<Class<? extends IBaseResource>, 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<IFhirResourceDao<?>> myResourceDaos;
@Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao;
private Map<Class<? extends IBaseResource>, 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 <T extends IBaseResource> void autoCreateResource(T theResource) {
IFhirResourceDao<T> dao = (IFhirResourceDao<T>) getDao(theResource.getClass());
dao.create(theResource);
}
protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
@ -188,13 +196,84 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return InstantDt.withCurrentTime();
}
private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> theStringParams, Set<ResourceIndexedSearchParamToken> theTokenParams, Set<ResourceIndexedSearchParamNumber> theNumberParams, Set<ResourceIndexedSearchParamQuantity> theQuantityParams, Set<ResourceIndexedSearchParamDate> theDateParams, Set<ResourceIndexedSearchParamUri> theUriParams, Set<ResourceLink> theLinks) {
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques;
compositeStringUniques = new HashSet<>();
List<JpaRuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(theEntity.getResourceType());
for (JpaRuntimeSearchParam next : uniqueSearchParams) {
List<List<String>> partsChoices = new ArrayList<>();
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
Set<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
Set<ResourceLink> 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<String> 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<String> 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<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) {
HashSet<String> retVal = new HashSet<String>();
HashSet<String> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> implements IDao {
return retVal;
}
private <T extends IBaseResource> void autoCreateResource(T theResource) {
IFhirResourceDao<T> dao = (IFhirResourceDao<T>) getDao(theResource.getClass());
dao.create(theResource);
}
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
}
@ -509,7 +583,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
Set<RT> paramCollection) {
Set<RT> paramCollection) {
for (Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) {
@ -569,11 +643,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return myConfig;
}
public void setConfig(DaoConfig theConfig) {
myConfig = theConfig;
}
@Override
public FhirContext getContext() {
return myContext;
}
@Autowired
public void setContext(FhirContext theContext) {
myContext = theContext;
}
public FhirContext getContext(FhirVersionEnum theVersion) {
Validate.notNull(theVersion, "theVersion must not be null");
synchronized (ourRetrievalContexts) {
@ -606,6 +689,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return dao;
}
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
return myResourceIndexedCompositeStringUniqueDao;
}
@Override
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
@ -621,23 +708,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (isBlank(theScheme) && isBlank(theTerm) && isBlank(theLabel)) {
return null;
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
Root<TagDefinition> from = cq.from(TagDefinition.class);
if (isNotBlank(theScheme)) {
cq.where(
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("mySystem"), theScheme),
builder.equal(from.get("myCode"), theTerm)));
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("mySystem"), theScheme),
builder.equal(from.get("myCode"), theTerm)));
} else {
cq.where(
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.isNull(from.get("mySystem")),
builder.equal(from.get("myCode"), theTerm)));
builder.and(
builder.equal(from.get("myTagType"), theTagType),
builder.isNull(from.get("mySystem")),
builder.equal(from.get("myCode"), theTerm)));
}
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
@ -665,7 +752,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
Set<Long> tagIds = new HashSet<Long>();
Set<Long> tagIds = new HashSet<>();
findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceTag.class);
findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceHistoryTag.class);
if (tagIds.isEmpty()) {
@ -764,8 +851,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@Override
public SearchBuilder newSearchBuilder() {
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
myForcedIdDao,
myTerminologySvc, mySerarchParamRegistry);
myForcedIdDao,
myTerminologySvc, mySerarchParamRegistry);
return builder;
}
@ -773,7 +860,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
throw new InternalErrorException(
"Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
"Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
}
}
@ -791,7 +878,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@SuppressWarnings("rawtypes")
List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
for (@SuppressWarnings("rawtypes")
IPrimitiveType nextType : childElements) {
IPrimitiveType nextType : childElements) {
if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
String nextValue = nextType.getValueAsString();
if (isNotBlank(nextValue)) {
@ -1063,11 +1150,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theResource
* The resource being persisted
*
* @param theEntity The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theResource The resource being persisted
*/
protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing
@ -1075,11 +1160,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity
* The resource
* @param theResource
* The resource being persisted
*
* @param theEntity The resource
* @param theResource The resource being persisted
*/
protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing
@ -1111,15 +1194,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
throw new NotImplementedException("");
}
public void setConfig(DaoConfig theConfig) {
myConfig = theConfig;
}
@Autowired
public void setContext(FhirContext theContext) {
myContext = theContext;
}
public void setEntityManager(EntityManager theEntityManager) {
myEntityManager = theEntityManager;
}
@ -1142,11 +1216,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* <p>
* See <a href="http://hl7.org/fhir/2015Sep/resource.html#1.11.3.7">Updates to Tags, Profiles, and Security Labels</a> for a description of the logic that the default behaviour folows.
* </p>
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag
* The tag
*
* @param theEntity The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag The tag
* @return Retturns <code>true</code> if the tag should be removed
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1156,6 +1228,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return false;
}
@Override
public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
Class<? extends IBaseResource> resourceType = type.getImplementingClass();
return toResource(resourceType, theEntity, theForHistoryOperation);
}
// protected ResourceTable toEntity(IResource theResource) {
// ResourceTable retVal = new ResourceTable();
//
@ -1164,13 +1243,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
// return retVal;
// }
@Override
public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
Class<? extends IBaseResource> resourceType = type.getImplementingClass();
return toResource(resourceType, theEntity, theForHistoryOperation);
}
@SuppressWarnings("unchecked")
@Override
public <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) {
@ -1268,7 +1340,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@SuppressWarnings("unchecked")
protected ResourceTable updateEntity(final IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ourLog.debug("Starting entity update");
/*
@ -1281,7 +1353,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
String resourceType = myContext.getResourceDefinition(theResource).getName();
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
throw new UnprocessableEntityException(
"Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
"Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
}
}
@ -1291,38 +1363,42 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setPublished(theUpdateTime);
}
Collection<ResourceIndexedSearchParamString> paramsString = new ArrayList<>();
Collection<ResourceIndexedSearchParamString> existingStringParams = new ArrayList<>();
if (theEntity.isParamsStringPopulated()) {
paramsString.addAll(theEntity.getParamsString());
existingStringParams.addAll(theEntity.getParamsString());
}
Collection<ResourceIndexedSearchParamToken> paramsToken = new ArrayList<>();
Collection<ResourceIndexedSearchParamToken> existingTokenParams = new ArrayList<>();
if (theEntity.isParamsTokenPopulated()) {
paramsToken.addAll(theEntity.getParamsToken());
existingTokenParams.addAll(theEntity.getParamsToken());
}
Collection<ResourceIndexedSearchParamNumber> paramsNumber = new ArrayList<>();
Collection<ResourceIndexedSearchParamNumber> existingNumberParams = new ArrayList<>();
if (theEntity.isParamsNumberPopulated()) {
paramsNumber.addAll(theEntity.getParamsNumber());
existingNumberParams.addAll(theEntity.getParamsNumber());
}
Collection<ResourceIndexedSearchParamQuantity> paramsQuantity = new ArrayList<>();
Collection<ResourceIndexedSearchParamQuantity> existingQuantityParams = new ArrayList<>();
if (theEntity.isParamsQuantityPopulated()) {
paramsQuantity.addAll(theEntity.getParamsQuantity());
existingQuantityParams.addAll(theEntity.getParamsQuantity());
}
Collection<ResourceIndexedSearchParamDate> paramsDate = new ArrayList<>();
Collection<ResourceIndexedSearchParamDate> existingDateParams = new ArrayList<>();
if (theEntity.isParamsDatePopulated()) {
paramsDate.addAll(theEntity.getParamsDate());
existingDateParams.addAll(theEntity.getParamsDate());
}
Collection<ResourceIndexedSearchParamUri> paramsUri = new ArrayList<>();
Collection<ResourceIndexedSearchParamUri> existingUriParams = new ArrayList<>();
if (theEntity.isParamsUriPopulated()) {
paramsUri.addAll(theEntity.getParamsUri());
existingUriParams.addAll(theEntity.getParamsUri());
}
Collection<ResourceIndexedSearchParamCoords> paramsCoords = new ArrayList<>();
Collection<ResourceIndexedSearchParamCoords> existingCoordsParams = new ArrayList<>();
if (theEntity.isParamsCoordsPopulated()) {
paramsCoords.addAll(theEntity.getParamsCoords());
existingCoordsParams.addAll(theEntity.getParamsCoords());
}
Collection<ResourceLink> existingResourceLinks = new ArrayList<>();
if (theEntity.isHasLinks()) {
existingResourceLinks.addAll(theEntity.getResourceLinks());
}
Collection<ResourceIndexedCompositeStringUnique> existingCompositeStringUniques = new ArrayList<>();
if (theEntity.isParamsCompositeStringUniquePresent()) {
existingCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
}
Set<ResourceIndexedSearchParamString> stringParams = null;
Set<ResourceIndexedSearchParamToken> tokenParams = null;
@ -1331,6 +1407,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
Set<ResourceIndexedSearchParamDate> dateParams = null;
Set<ResourceIndexedSearchParamUri> uriParams = null;
Set<ResourceIndexedSearchParamCoords> coordsParams = null;
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques = null;
Set<ResourceLink> links = null;
Set<String> populatedResourceLinkParameters = Collections.emptySet();
@ -1365,10 +1442,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
uriParams = extractSearchParamUri(theEntity, theResource);
coordsParams = extractSearchParamCoords(theEntity, theResource);
// ourLog.info("Indexing resource: {}", entity.getId());
ourLog.trace("Storing date indexes: {}", dateParams);
tokenParams = new HashSet<ResourceIndexedSearchParamToken>();
tokenParams = new HashSet<>();
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
if (next instanceof ResourceIndexedSearchParamToken) {
tokenParams.add((ResourceIndexedSearchParamToken) next);
@ -1395,8 +1471,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/*
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
* matching
* resource.
* matching resource.
*/
if (myConfig.isAllowInlineMatchUrlReferences()) {
FhirTerser terser = getContext().newTerser();
@ -1443,13 +1518,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
links = new HashSet<ResourceLink>();
links = new HashSet<>();
populatedResourceLinkParameters = extractResourceLinks(theEntity, theResource, links, theUpdateTime);
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
*/
for (Iterator<ResourceLink> existingLinkIter = existingResourceLinks.iterator(); existingLinkIter.hasNext();) {
for (Iterator<ResourceLink> existingLinkIter = existingResourceLinks.iterator(); existingLinkIter.hasNext(); ) {
ResourceLink nextExisting = existingLinkIter.next();
if (links.remove(nextExisting)) {
existingLinkIter.remove();
@ -1457,6 +1532,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
/*
* Handle composites
*/
compositeStringUniques = extractCompositeStringUniques(theEntity, stringParams, tokenParams, numberParams, quantityParams, dateParams, uriParams, links);
changed = populateResourceIntoEntity(theResource, theEntity, true);
theEntity.setUpdated(theUpdateTime);
@ -1479,6 +1560,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setParamsUriPopulated(uriParams.isEmpty() == false);
theEntity.setParamsCoords(coordsParams);
theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false);
theEntity.setParamsCompositeStringUnique(compositeStringUniques);
theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false);
theEntity.setResourceLinks(links);
theEntity.setHasLinks(links.isEmpty() == false);
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
@ -1529,7 +1612,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/*
* Update the "search param present" table which is used for the
* ?foo:missing=true queries
*
*
* Note that we're only populating this for reference params
* because the index tables for all other types have a MISSING column
* right on them for handling the :missing queries. We can't use the
@ -1567,28 +1650,28 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
*/
if (thePerformIndexing) {
for (ResourceIndexedSearchParamString next : paramsString) {
for (ResourceIndexedSearchParamString next : existingStringParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamString next : stringParams) {
myEntityManager.persist(next);
}
for (ResourceIndexedSearchParamToken next : paramsToken) {
for (ResourceIndexedSearchParamToken next : existingTokenParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamToken next : tokenParams) {
myEntityManager.persist(next);
}
for (ResourceIndexedSearchParamNumber next : paramsNumber) {
for (ResourceIndexedSearchParamNumber next : existingNumberParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamNumber next : numberParams) {
myEntityManager.persist(next);
}
for (ResourceIndexedSearchParamQuantity next : paramsQuantity) {
for (ResourceIndexedSearchParamQuantity next : existingQuantityParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamQuantity next : quantityParams) {
@ -1596,7 +1679,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
// Store date SP's
for (ResourceIndexedSearchParamDate next : paramsDate) {
for (ResourceIndexedSearchParamDate next : existingDateParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamDate next : dateParams) {
@ -1604,7 +1687,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
// Store URI SP's
for (ResourceIndexedSearchParamUri next : paramsUri) {
for (ResourceIndexedSearchParamUri next : existingUriParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamUri next : uriParams) {
@ -1612,7 +1695,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
// Store Coords SP's
for (ResourceIndexedSearchParamCoords next : paramsCoords) {
for (ResourceIndexedSearchParamCoords next : existingCoordsParams) {
myEntityManager.remove(next);
}
for (ResourceIndexedSearchParamCoords next : coordsParams) {
@ -1629,6 +1712,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
// make sure links are indexed
theEntity.setResourceLinks(links);
// Store composite string uniques
for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) {
myEntityManager.remove(next);
}
for (ResourceIndexedCompositeStringUnique next : compositeStringUniques) {
myEntityManager.persist(next);
}
theEntity.toString();
} // if thePerformIndexing
@ -1693,7 +1784,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (!referencedId.getValue().contains("?")) {
if (!validTypes.contains(referencedId.getResourceType())) {
throw new UnprocessableEntityException(
"Invalid reference found at path '" + newPath + "'. Resource type '" + referencedId.getResourceType() + "' is not valid for this path");
"Invalid reference found at path '" + newPath + "'. Resource type '" + referencedId.getResourceType() + "' is not valid for this path");
}
}
}
@ -1740,17 +1831,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* This method is invoked immediately before storing a new resource, or an update to an existing resource to allow the DAO to ensure that it is valid for persistence. By default, checks for the
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
*
* @param theResource
* The resource that is about to be persisted
* @param theEntityToSave
* TODO
*
* @param theResource The resource that is about to be persisted
* @param theEntityToSave TODO
*/
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null;
int totalMetaCount = 0;
if (theResource instanceof IResource) {
IResource res = (IResource) theResource;
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
@ -1778,7 +1867,77 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
validateChildReferences(theResource, resName);
validateMetaCount(totalMetaCount);
}
/**
* This method is used to create a set of all possible combinations of
* parameters across a set of search parameters. An example of why
* this is needed:
* <p>
* Let's say we have a unique index on (Patient:gender AND Patient:name).
* Then we pass in <code>SMITH, John</code> with a gender of <code>male</code>.
* </p>
* <p>
* In this case, because the name parameter matches both first and last name,
* we now need two unique indexes:
* <ul>
* <li>Patient?gender=male&amp;name=SMITH</li>
* <li>Patient?gender=male&amp;name=JOHN</li>
* </ul>
* </p>
* <p>
* So this recursive algorithm calculates those
* </p>
*
* @param theResourceType E.g. <code>Patient
* @param thePartsChoices E.g. <code>[[gender=male], [name=SMITH, name=JOHN]]</code>
*/
public static Set<String> extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices) {
Collections.sort(thePartsChoices, new Comparator<List<String>>() {
@Override
public int compare(List<String> o1, List<String> 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<String> values = new ArrayList<>();
Set<String> queryStringsToPopulate = new HashSet<>();
extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices, values, queryStringsToPopulate);
return queryStringsToPopulate;
}
private static void extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
if (thePartsChoices.size() > 0) {
List<String> 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<T extends IBaseResource> 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<T extends IBaseResource> 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());
}
}

View File

@ -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<T extends IBaseResource> 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<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theId);
}
//@formatter:off
for (BaseTag next : new ArrayList<BaseTag>(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<T extends IBaseResource> extends B
entity.getTags().remove(next);
}
}
//@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);

View File

@ -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<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams;
private volatile Map<String, List<JpaRuntimeSearchParam>> myActiveUniqueSearchParams;
private volatile Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams;
@Autowired
private FhirContext myCtx;
@Autowired
private Collection<IFhirResourceDao<?>> myDaos;
@ -75,18 +76,119 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
return myBuiltInSearchParams.get(theResourceName);
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
refreshCacheIfNecessary();
List<JpaRuntimeSearchParam> retVal = myActiveUniqueSearchParams.get(theResourceName);
if (retVal == null) {
retVal = Collections.emptyList();
}
return retVal;
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
refreshCacheIfNecessary();
Map<Set<String>, List<JpaRuntimeSearchParam>> paramNamesToParams = myActiveParamNamesToUniqueSearchParams.get(theResourceName);
if (paramNamesToParams == null) {
return Collections.emptyList();
}
List<JpaRuntimeSearchParam> retVal = paramNamesToParams.get(theParamNames);
if (retVal == null) {
retVal = Collections.emptyList();
}
return Collections.unmodifiableList(retVal);
}
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams;
}
public void populateActiveSearchParams(Map<String, Map<String, RuntimeSearchParam>> theActiveSearchParams) {
Map<String, List<JpaRuntimeSearchParam>> activeUniqueSearchParams = new HashMap<>();
Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> activeParamNamesToUniqueSearchParams = new HashMap<>();
Map<String, RuntimeSearchParam> idToRuntimeSearchParam = new HashMap<>();
List<JpaRuntimeSearchParam> jpaSearchParams = new ArrayList<>();
/*
* Loop through parameters and find JPA params
*/
for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextResourceNameToEntries : theActiveSearchParams.entrySet()) {
List<JpaRuntimeSearchParam> uniqueSearchParams = activeUniqueSearchParams.get(nextResourceNameToEntries.getKey());
if (uniqueSearchParams == null) {
uniqueSearchParams = new ArrayList<>();
activeUniqueSearchParams.put(nextResourceNameToEntries.getKey(), uniqueSearchParams);
}
Collection<RuntimeSearchParam> 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<String> haveSeen = new HashSet<>();
for (JpaRuntimeSearchParam next : jpaSearchParams) {
if (!haveSeen.add(next.getId().toUnqualifiedVersionless().getValue())) {
continue;
}
Set<String> 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<RuntimeSearchParam>() {
@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<Set<String>, List<JpaRuntimeSearchParam>>());
}
if (!activeParamNamesToUniqueSearchParams.get(nextBase).containsKey(paramNames)) {
activeParamNamesToUniqueSearchParams.get(nextBase).put(paramNames, new ArrayList<JpaRuntimeSearchParam>());
}
activeParamNamesToUniqueSearchParams.get(nextBase).get(paramNames).add(next);
}
}
}
myActiveUniqueSearchParams = activeUniqueSearchParams;
myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams;
}
@PostConstruct
public void postConstruct() {
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
for (IFhirResourceDao<?> nextDao : myDaos) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
String nextResourceName = nextResDef.getName();
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
HashMap<String, RuntimeSearchParam> 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();
}

View File

@ -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<String, Map<String, RuntimeSearchParam>> getActiveSearchParams();
Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName);
/**
* @return Returns {@literal null} if no match
*/
RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName);
Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName);
Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams();
List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName);
List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames);
}

View File

@ -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<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<Long>());
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<Long> 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<Predicate> codePredicates = new ArrayList<Predicate>();
List<Predicate> 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<Class<? extends IBaseResource>>();
resourceTypes = new ArrayList<>();
Set<String> 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<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName);
List<String> toFind = new ArrayList<String>();
List<String> 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.<Object> get("myUri").as(String.class).in(toFind);
predicate = join.<Object>get("myUri").as(String.class).in(toFind);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
predicate = myBuilder.like(join.<Object> get("myUri").as(String.class), createLeftMatchLikeExpression(value));
predicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
} else {
predicate = myBuilder.equal(join.<Object> get("myUri").as(String.class), value);
predicate = myBuilder.equal(join.get("myUri").as(String.class), value);
}
codePredicates.add(predicate);
} else {
@ -817,7 +831,7 @@ public class SearchBuilder implements ISearchBuilder {
* just add a predicate that can never match
*/
if (codePredicates.isEmpty()) {
Predicate predicate = myBuilder.isNull(join.<Object> get("myMissing").as(String.class));
Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class));
myPredicates.add(predicate);
return;
}
@ -840,32 +854,32 @@ public class SearchBuilder implements ISearchBuilder {
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) {
Predicate retVal = null;
switch (theParam.getParamType()) {
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin);
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case COMPOSITE:
case HAS:
case NUMBER:
case REFERENCE:
case URI:
break;
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin);
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case COMPOSITE:
case HAS:
case NUMBER:
case REFERENCE:
case URI:
break;
}
if (retVal == null) {
@ -880,27 +894,27 @@ public class SearchBuilder implements ISearchBuilder {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) {
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
}
JoinKey key = new JoinKey(theSearchParameterName, theType);
@ -938,8 +952,8 @@ public class SearchBuilder implements ISearchBuilder {
Predicate lb = null;
if (lowerBound != null) {
Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.<Date> get("myValueLow"), lowerBound);
Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.<Date> get("myValueHigh"), lowerBound);
Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueLow"), lowerBound);
Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueHigh"), lowerBound);
if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt;
} else {
@ -949,8 +963,8 @@ public class SearchBuilder implements ISearchBuilder {
Predicate ub = null;
if (upperBound != null) {
Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.<Date> get("myValueLow"), upperBound);
Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.<Date> get("myValueHigh"), upperBound);
Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueLow"), upperBound);
Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueHigh"), upperBound);
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt;
} else {
@ -968,45 +982,45 @@ public class SearchBuilder implements ISearchBuilder {
}
private Predicate createPredicateNumeric(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, CriteriaBuilder builder,
IQueryParameterType theParam, ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression<BigDecimal> thePath,
String invalidMessageName) {
IQueryParameterType theParam, ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression<BigDecimal> thePath,
String invalidMessageName) {
Predicate num;
switch (thePrefix) {
case GREATERTHAN:
num = builder.gt(thePath, theValue);
break;
case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue);
break;
case LESSTHAN:
num = builder.lt(thePath, theValue);
break;
case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue);
break;
case APPROXIMATE:
case EQUAL:
case NOT_EQUAL:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred;
Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) {
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
} else {
// Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred);
}
break;
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
case GREATERTHAN:
num = builder.gt(thePath, theValue);
break;
case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue);
break;
case LESSTHAN:
num = builder.lt(thePath, theValue);
break;
case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue);
break;
case APPROXIMATE:
case EQUAL:
case NOT_EQUAL:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred;
Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) {
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
} else {
// Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred);
}
break;
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
}
if (theParamName == null) {
@ -1016,7 +1030,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private Predicate createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom) {
From<?, ResourceIndexedSearchParamQuantity> theFrom) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue;
@ -1069,7 +1083,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) {
From<?, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm;
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
@ -1089,7 +1103,7 @@ public class SearchBuilder implements ISearchBuilder {
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
@ -1121,7 +1135,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private Predicate createPredicateToken(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom) {
From<?, ResourceIndexedSearchParamToken> theFrom) {
String code;
String system;
TokenParamModifier modifier = null;
@ -1144,12 +1158,12 @@ public class SearchBuilder implements ISearchBuilder {
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
"Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
"Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
}
/*
@ -1240,6 +1254,45 @@ public class SearchBuilder implements ISearchBuilder {
myBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid;
/*
* Check if there is a unique key associated with the set
* of parameters passed in
*/
if (myParams.getIncludes().isEmpty()) {
if (myParams.getRevIncludes().isEmpty()) {
if (myParams.getEverythingMode() == null) {
if (myParams.isAllParametersHaveNoModifier()) {
Set<String> paramNames = theParams.keySet();
if (paramNames.isEmpty() == false) {
List<JpaRuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, paramNames);
if (searchParams.size() > 0) {
List<List<String>> params = new ArrayList<>();
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamNameToValues : theParams.entrySet()) {
String nextParamName = nextParamNameToValues.getKey();
nextParamName = UrlUtil.escape(nextParamName);
for (List<? extends IQueryParameterType> nextAnd : nextParamNameToValues.getValue()) {
ArrayList<String> nextValueList = new ArrayList<>();
params.add(nextValueList);
for (IQueryParameterType nextOr : nextAnd) {
String nextOrValue = nextOr.getValueAsQueryToken(myContext);
nextOrValue = UrlUtil.escape(nextOrValue);
nextValueList.add(nextParamName + "=" + nextOrValue);
}
}
}
Set<String> uniqueQueryStrings = BaseHapiFhirDao.extractCompositeStringUniquesValueChains(myResourceName, params);
ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX;
return new UniqueIndexIterator(uniqueQueryStrings);
}
}
}
}
}
}
ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY;
return new QueryIterator();
}
@ -1334,7 +1387,7 @@ public class SearchBuilder implements ISearchBuilder {
/*
* Add a predicate to make sure we only include non-deleted resources, and only include
* resources of the right type.
*
*
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
@ -1370,7 +1423,7 @@ public class SearchBuilder implements ISearchBuilder {
/**
* @return Returns {@literal true} if any search parameter sorts were found, or false if
* no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated)
* no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated)
*/
private boolean createSort(CriteriaBuilder theBuilder, Root<ResourceTable> theFrom, SortSpec theSort, List<Order> theOrders, List<Predicate> thePredicates) {
if (theSort == null || isBlank(theSort.getParamName())) {
@ -1411,43 +1464,43 @@ public class SearchBuilder implements ISearchBuilder {
JoinEnum joinType;
switch (param.getParamType()) {
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[] { "myValueExact" };
joinType = JoinEnum.STRING;
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[] { "myValueLow" };
joinType = JoinEnum.DATE;
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[] { "myTargetResourcePid" };
joinType = JoinEnum.REFERENCE;
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[] { "mySystem", "myValue" };
joinType = JoinEnum.TOKEN;
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[] { "myValue" };
joinType = JoinEnum.NUMBER;
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[] { "myUri" };
joinType = JoinEnum.URI;
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[] { "myValue" };
joinType = JoinEnum.QUANTITY;
break;
default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[]{"myValueExact"};
joinType = JoinEnum.STRING;
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[]{"myValueLow"};
joinType = JoinEnum.DATE;
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[]{"myTargetResourcePid"};
joinType = JoinEnum.REFERENCE;
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[]{"mySystem", "myValue"};
joinType = JoinEnum.TOKEN;
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[]{"myValue"};
joinType = JoinEnum.NUMBER;
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[]{"myUri"};
joinType = JoinEnum.URI;
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[]{"myValue"};
joinType = JoinEnum.QUANTITY;
break;
default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
}
/*
@ -1513,7 +1566,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private void doLoadPids(List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao,
Map<Long, Integer> position, Collection<Long> pids) {
Map<Long, Integer> position, Collection<Long> pids) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
@ -1549,7 +1602,7 @@ public class SearchBuilder implements ISearchBuilder {
@Override
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
EntityManager entityManager, FhirContext context, IDao theDao) {
EntityManager entityManager, FhirContext context, IDao theDao) {
if (theIncludePids.isEmpty()) {
ourLog.info("The include pids are empty");
// return;
@ -1557,7 +1610,7 @@ public class SearchBuilder implements ISearchBuilder {
// Dupes will cause a crash later anyhow, but this is expensive so only do it
// when running asserts
assert new HashSet<Long>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
assert new HashSet<>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) {
@ -1572,7 +1625,7 @@ public class SearchBuilder implements ISearchBuilder {
* but this should work too. Sigh.
*/
int maxLoad = 800;
List<Long> pids = new ArrayList<Long>(theIncludePids);
List<Long> pids = new ArrayList<>(theIncludePids);
for (int i = 0; i < pids.size(); i += maxLoad) {
int to = i + maxLoad;
to = Math.min(to, pids.size());
@ -1589,7 +1642,7 @@ public class SearchBuilder implements ISearchBuilder {
*/
@Override
public HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) {
boolean theReverseMode, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<Long>();
}
@ -1599,9 +1652,9 @@ public class SearchBuilder implements ISearchBuilder {
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
Collection<Long> nextRoundMatches = theMatches;
HashSet<Long> allAdded = new HashSet<Long>();
HashSet<Long> original = new HashSet<Long>(theMatches);
ArrayList<Include> includes = new ArrayList<Include>(theRevIncludes);
HashSet<Long> allAdded = new HashSet<>();
HashSet<Long> original = new HashSet<>(theMatches);
ArrayList<Include> includes = new ArrayList<>(theRevIncludes);
int roundCounts = 0;
StopWatch w = new StopWatch();
@ -1610,10 +1663,10 @@ public class SearchBuilder implements ISearchBuilder {
do {
roundCounts++;
HashSet<Long> pidsToInclude = new HashSet<Long>();
Set<Long> nextRoundOmit = new HashSet<Long>();
HashSet<Long> pidsToInclude = new HashSet<>();
Set<Long> nextRoundOmit = new HashSet<>();
for (Iterator<Include> iter = includes.iterator(); iter.hasNext();) {
for (Iterator<Include> iter = includes.iterator(); iter.hasNext(); ) {
Include nextInclude = iter.next();
if (nextInclude.isRecurse() == false) {
iter.remove();
@ -1712,7 +1765,7 @@ public class SearchBuilder implements ISearchBuilder {
nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound);
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[] { allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart() });
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[]{allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart()});
return allAdded;
}
@ -1788,49 +1841,49 @@ public class SearchBuilder implements ISearchBuilder {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateDate(theResourceName, theParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateQuantity(theResourceName, theParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateReference(theResourceName, theParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateString(theResourceName, theParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateToken(theResourceName, theParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateNumber(theResourceName, theParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateUri(theResourceName, theParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateDate(theResourceName, theParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateQuantity(theResourceName, theParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateReference(theResourceName, theParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateString(theResourceName, theParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateToken(theResourceName, theParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateNumber(theResourceName, theParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateUri(theResourceName, theParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
}
} else {
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
@ -1851,35 +1904,35 @@ public class SearchBuilder implements ISearchBuilder {
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
}
return qp;
}
@ -1918,11 +1971,11 @@ public class SearchBuilder implements ISearchBuilder {
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
ourLog.info("LastUpdated lower bound: {}", new InstantDt(theLastUpdated.getLowerBoundAsInstant()));
Predicate predicateLower = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getLowerBoundAsInstant());
Predicate predicateLower = builder.greaterThanOrEqualTo(from.<Date>get("myUpdated"), theLastUpdated.getLowerBoundAsInstant());
lastUpdatedPredicates.add(predicateLower);
}
if (theLastUpdated.getUpperBoundAsInstant() != null) {
Predicate predicateUpper = builder.lessThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getUpperBoundAsInstant());
Predicate predicateUpper = builder.lessThanOrEqualTo(from.<Date>get("myUpdated"), theLastUpdated.getUpperBoundAsInstant());
lastUpdatedPredicates.add(predicateUpper);
}
}
@ -1934,7 +1987,7 @@ public class SearchBuilder implements ISearchBuilder {
}
private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom,
String theResourceType) {
String theResourceType) {
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType);
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName);
List<String> path = param.getPathsSplit();
@ -1958,10 +2011,37 @@ public class SearchBuilder implements ISearchBuilder {
return resultList;
}
@VisibleForTesting
public static HandlerTypeEnum getLastHandlerMechanismForUnitTest() {
ourLog.info("Retrieving last handler mechanism: {}", ourLastHandlerMechanismForUnitTest);
return ourLastHandlerMechanismForUnitTest;
}
@VisibleForTesting
public static void resetLastHandlerMechanismForUnitTest() {
ourLog.info("Clearing last handler mechanism (was {})", ourLastHandlerMechanismForUnitTest);
ourLastHandlerMechanismForUnitTest = null;
}
static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[thePredicates.size()]);
}
public enum HandlerTypeEnum {
UNIQUE_INDEX, STANDARD_QUERY
}
private enum JoinEnum {
DATE,
NUMBER,
QUANTITY,
REFERENCE,
STRING,
TOKEN,
URI
}
public class IncludesIterator extends BaseIterator<Long> implements Iterator<Long> {
private Iterator<Long> myCurrentIterator;
@ -2020,51 +2100,12 @@ public class SearchBuilder implements ISearchBuilder {
}
private enum JoinEnum {
DATE,
NUMBER,
QUANTITY,
REFERENCE,
STRING,
TOKEN,
URI
}
private static class JoinKey {
private final JoinEnum myJoinType;
private final String myParamName;
public JoinKey(String theParamName, JoinEnum theJoinType) {
super();
myParamName = theParamName;
myJoinType = theJoinType;
}
@Override
public boolean equals(Object theObj) {
JoinKey obj = (JoinKey) theObj;
return new EqualsBuilder()
.append(myParamName, obj.myParamName)
.append(myJoinType, obj.myJoinType)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(myParamName)
.append(myJoinType)
.toHashCode();
}
}
private final class QueryIterator extends BaseIterator<Long> implements Iterator<Long> {
private final Set<Long> myPidSet = new HashSet<Long>();
private boolean myFirst = true;
private IncludesIterator myIncludesIterator;
private Long myNext;
private final Set<Long> myPidSet = new HashSet<Long>();
private Iterator<Long> myPreResultsIterator;
private Iterator<Long> myResultsIterator;
private SortSpec mySort;
@ -2215,4 +2256,68 @@ public class SearchBuilder implements ISearchBuilder {
}
private class UniqueIndexIterator implements Iterator<Long> {
private final Set<String> myUniqueQueryStrings;
private Iterator<Long> myWrap = null;
public UniqueIndexIterator(Set<String> theUniqueQueryStrings) {
myUniqueQueryStrings = theUniqueQueryStrings;
}
private void ensureHaveQuery() {
if (myWrap == null) {
ourLog.info("Searching for unique index matches over {} candidate query strings");
StopWatch sw = new StopWatch();
Collection<Long> resourcePids = myCallingDao.getResourceIndexedCompositeStringUniqueDao().findResourcePidsByQueryStrings(myUniqueQueryStrings);
ourLog.info("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis());
myWrap = resourcePids.iterator();
}
}
@Override
public boolean hasNext() {
ensureHaveQuery();
return myWrap.hasNext();
}
@Override
public Long next() {
ensureHaveQuery();
return myWrap.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static class JoinKey {
private final JoinEnum myJoinType;
private final String myParamName;
public JoinKey(String theParamName, JoinEnum theJoinType) {
super();
myParamName = theParamName;
myJoinType = theJoinType;
}
@Override
public boolean equals(Object theObj) {
JoinKey obj = (JoinKey) theObj;
return new EqualsBuilder()
.append(myParamName, obj.myParamName)
.append(myJoinType, obj.myJoinType)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(myParamName)
.append(myJoinType)
.toHashCode();
}
}
}

View File

@ -116,13 +116,13 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof PeriodDt) {
PeriodDt nextValue = (PeriodDt) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.dao;
*/
public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry {
// nothing yet
@Override
protected void refreshCacheIfNecessary() {
// nothing yet
}
}

View File

@ -201,7 +201,7 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
public Set<Include> getRevIncludes() {
if (myRevIncludes == null) {
myRevIncludes = new HashSet<Include>();
myRevIncludes = new HashSet<>();
}
return myRevIncludes;
}
@ -210,6 +210,22 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return mySort;
}
/**
* This will only return true if all parameters have no modifier of any kind
*/
public boolean isAllParametersHaveNoModifier() {
for (List<List<? extends IQueryParameterType>> nextParamName : values()) {
for (List<? extends IQueryParameterType> nextAnd : nextParamName) {
for (IQueryParameterType nextOr : nextAnd) {
if (isNotBlank(nextOr.getQueryParameterQualifier())) {
return false;
}
}
}
}
return true;
}
/**
* If set, tells the server to load these results synchronously, and not to load
* more than X results

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.Optional;
public interface IResourceIndexedCompositeStringUniqueDao extends JpaRepository<ResourceIndexedCompositeStringUnique, Long> {
@Query("SELECT r FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString = :str")
ResourceIndexedCompositeStringUnique findByQueryString(@Param("str") String theQueryString);
@Query("SELECT r.myResourceId FROM ResourceIndexedCompositeStringUnique r WHERE r.myIndexString IN :str")
Collection<Long> findResourcePidsByQueryStrings(@Param("str") Collection<String> theQueryString);
}

View File

@ -1,6 +1,26 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Enumerations;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
@ -11,9 +31,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* 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.
@ -22,27 +42,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* #L%
*/
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ElementUtil;
public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class);
@ -56,19 +55,21 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
protected void markAffectedResources(SearchParameter theResource) {
if (theResource != null) {
String expression = theResource.getExpression();
final String resourceType = expression.substring(0, expression.indexOf('.'));
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", expression);
if (isNotBlank(expression)) {
final String resourceType = expression.substring(0, expression.indexOf('.'));
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", expression);
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
});
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
});
ourLog.info("Marked {} resources for reindexing", updatedCount);
ourLog.info("Marked {} resources for reindexing", updatedCount);
}
}
mySearchParamRegistry.forceRefresh();
@ -126,43 +127,47 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
}
String expression = theResource.getExpression();
if (isBlank(expression)) {
if (theResource.getType() == Enumerations.SearchParamType.COMPOSITE && isBlank(expression)) {
// this is ok
} else if (isBlank(expression)) {
throw new UnprocessableEntityException("SearchParameter.expression is missing");
}
if (ElementUtil.isEmpty(theResource.getBase())) {
throw new UnprocessableEntityException("SearchParameter.base is missing");
}
} else {
expression = expression.trim();
theResource.setExpression(expression);
expression = expression.trim();
theResource.setExpression(expression);
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression);
String allResourceName = null;
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression);
String allResourceName = null;
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx);
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
if (allResourceName == null) {
allResourceName = resourceName;
} else {
if (!allResourceName.equals(resourceName)) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx);
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
if (allResourceName == null) {
allResourceName = resourceName;
} else {
if (!allResourceName.equals(resourceName)) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
}
}
}
}
} // if have expression
}

View File

@ -133,29 +133,33 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) {
continue;
}
TreeSet<Date> dates = new TreeSet<Date>();
String firstValue = null;
TreeSet<Date> dates = new TreeSet<>();
for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
}
}
if (dates.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
} else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string
continue;

View File

@ -26,11 +26,15 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.*;
import java.util.Map.Entry;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.util.JpaConstants;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.context.RuntimeSearchParam;
@ -42,6 +46,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamRegistryDstu3.class);
public static final int MAX_MANAGED_PARAM_COUNT = 10000;
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
@ -62,33 +67,33 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
@Override
public Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams() {
refreshCacheIfNeccesary();
refreshCacheIfNecessary();
return myActiveSearchParams;
}
@Override
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
refreshCacheIfNeccesary();
refreshCacheIfNecessary();
return myActiveSearchParams.get(theResourceName);
}
private Map<String, RuntimeSearchParam> getSearchParamMap(Map<String, Map<String, RuntimeSearchParam>> searchParams, String theResourceName) {
Map<String, RuntimeSearchParam> retVal = searchParams.get(theResourceName);
if (retVal == null) {
retVal = new HashMap<String, RuntimeSearchParam>();
retVal = new HashMap<>();
searchParams.put(theResourceName, retVal);
}
return retVal;
}
private void refreshCacheIfNeccesary() {
protected void refreshCacheIfNecessary() {
long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE;
if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
synchronized (this) {
if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
StopWatch sw = new StopWatch();
Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<>();
for (Entry<String, Map<String, RuntimeSearchParam>> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) {
for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) {
String nextResourceName = nextBuiltInEntry.getKey();
@ -97,40 +102,41 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT);
IBundleProvider allSearchParamsBp = mySpDao.search(params);
int size = allSearchParamsBp.size();
// Just in case..
if (size > 10000) {
ourLog.warn("Unable to support >10000 search params!");
size = 10000;
if (size > MAX_MANAGED_PARAM_COUNT) {
ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!");
size = MAX_MANAGED_PARAM_COUNT;
}
List<IBaseResource> allSearchParams = allSearchParamsBp.getResources(0, size);
for (IBaseResource nextResource : allSearchParams) {
SearchParameter nextSp = (SearchParameter) nextResource;
RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp);
JpaRuntimeSearchParam runtimeSp = toRuntimeSp(nextSp);
if (runtimeSp == null) {
continue;
}
int dotIdx = runtimeSp.getPath().indexOf('.');
if (dotIdx == -1) {
ourLog.warn("Can not determine resource type of {}", runtimeSp.getPath());
continue;
}
String resourceType = runtimeSp.getPath().substring(0, dotIdx);
for (org.hl7.fhir.dstu3.model.CodeType nextBaseName : nextSp.getBase()) {
String resourceType = nextBaseName.getValue();
if (isBlank(resourceType)) {
continue;
}
Map<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, resourceType);
String name = runtimeSp.getName();
if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
}
Map<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, resourceType);
String name = runtimeSp.getName();
if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
}
}
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<>();
for (Entry<String, Map<String, RuntimeSearchParam>> nextEntry : searchParams.entrySet()) {
for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) {
String nextName = nextSp.getName();
@ -155,6 +161,8 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
myActiveSearchParams = activeSearchParams;
super.populateActiveSearchParams(activeSearchParams);
myLastRefresh = System.currentTimeMillis();
ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis());
}
@ -162,7 +170,7 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
}
}
private RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
private JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode();
String description = theNextSp.getDescription();
String path = theNextSp.getExpression();
@ -215,12 +223,31 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
Set<String> targets = toStrings(theNextSp.getTarget());
if (isBlank(name) || isBlank(path) || paramType == null) {
return null;
if (paramType != RestSearchParameterTypeEnum.COMPOSITE) {
return null;
}
}
IIdType id = theNextSp.getIdElement();
String uri = "";
RuntimeSearchParam retVal = new RuntimeSearchParam(id, uri, name, description, path, paramType, null, providesMembershipInCompartments, targets, status);
boolean unique = false;
List<Extension> uniqueExts = theNextSp.getExtensionsByUrl(JpaConstants.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
}
}
}
List<JpaRuntimeSearchParam.Component> components = new ArrayList<>();
for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) {
components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition()));
}
JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
return retVal;
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
@ -24,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
@ -56,19 +58,21 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
protected void markAffectedResources(SearchParameter theResource) {
if (theResource != null) {
String expression = theResource.getExpression();
final String resourceType = expression.substring(0, expression.indexOf('.'));
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", expression);
if (isNotBlank(expression)) {
final String resourceType = expression.substring(0, expression.indexOf('.'));
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", expression);
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
});
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
}
});
ourLog.info("Marked {} resources for reindexing", updatedCount);
ourLog.info("Marked {} resources for reindexing", updatedCount);
}
}
mySearchParamRegistry.forceRefresh();
@ -125,44 +129,52 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid: " + theResource.getStatusElement().getValueAsString());
}
String expression = theResource.getExpression();
if (isBlank(expression)) {
throw new UnprocessableEntityException("SearchParameter.expression is missing");
}
if (ElementUtil.isEmpty(theResource.getBase())) {
throw new UnprocessableEntityException("SearchParameter.base is missing");
}
expression = expression.trim();
theResource.setExpression(expression);
String expression = theResource.getExpression();
if (theResource.getType() == Enumerations.SearchParamType.COMPOSITE && isBlank(expression)) {
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression);
String allResourceName = null;
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
// this is ok
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
} else if (isBlank(expression)) {
String resourceName = nextPath.substring(0, dotIdx);
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
throw new UnprocessableEntityException("SearchParameter.expression is missing");
if (allResourceName == null) {
allResourceName = resourceName;
} else {
if (!allResourceName.equals(resourceName)) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
} else {
expression = expression.trim();
theResource.setExpression(expression);
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression);
String allResourceName = null;
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx);
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
if (allResourceName == null) {
allResourceName = resourceName;
} else {
if (!allResourceName.equals(resourceName)) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
}
}
}
}
} // if have expression
}

View File

@ -133,29 +133,33 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getValue(), nextValue.getValue(), nextValue.getValueAsString());
} else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) {
continue;
}
TreeSet<Date> dates = new TreeSet<Date>();
TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
}
}
if (dates.isEmpty()) {
continue;
}
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last());
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), dates.first(), dates.last(), firstValue);
} else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string
continue;
@ -450,7 +454,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/
@Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>();
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<>();
String useSystem = null;
if (theResource instanceof CodeSystem) {
@ -477,16 +481,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
List<String> systems = new ArrayList<String>();
List<String> codes = new ArrayList<String>();
// String needContactPointSystem = null;
// if (nextPath.contains(".where(system='phone')")) {
// nextPath = nextPath.replace(".where(system='phone')", "");
// needContactPointSystem = "phone";
// }
// if (nextPath.contains(".where(system='email')")) {
// nextPath = nextPath.replace(".where(system='email')", "");
// needContactPointSystem = "email";
// }
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {

View File

@ -20,14 +20,19 @@ package ca.uhn.fhir.jpa.dao.r4;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.*;
import java.util.Map.Entry;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.util.JpaConstants;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -42,6 +47,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamRegistryR4.class);
public static final int MAX_MANAGED_PARAM_COUNT = 10000;
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
@ -62,33 +68,33 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
@Override
public Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams() {
refreshCacheIfNeccesary();
refreshCacheIfNecessary();
return myActiveSearchParams;
}
@Override
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
refreshCacheIfNeccesary();
refreshCacheIfNecessary();
return myActiveSearchParams.get(theResourceName);
}
private Map<String, RuntimeSearchParam> getSearchParamMap(Map<String, Map<String, RuntimeSearchParam>> searchParams, String theResourceName) {
Map<String, RuntimeSearchParam> retVal = searchParams.get(theResourceName);
if (retVal == null) {
retVal = new HashMap<String, RuntimeSearchParam>();
retVal = new HashMap<>();
searchParams.put(theResourceName, retVal);
}
return retVal;
}
private void refreshCacheIfNeccesary() {
protected void refreshCacheIfNecessary() {
long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE;
if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
synchronized (this) {
if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
StopWatch sw = new StopWatch();
Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<>();
for (Entry<String, Map<String, RuntimeSearchParam>> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) {
for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) {
String nextResourceName = nextBuiltInEntry.getKey();
@ -97,15 +103,15 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT);
IBundleProvider allSearchParamsBp = mySpDao.search(params);
int size = allSearchParamsBp.size();
// Just in case..
if (size > 10000) {
ourLog.warn("Unable to support >10000 search params!");
size = 10000;
if (size >= MAX_MANAGED_PARAM_COUNT) {
ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!");
size = MAX_MANAGED_PARAM_COUNT;
}
List<IBaseResource> allSearchParams = allSearchParamsBp.getResources(0, size);
@ -116,21 +122,22 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
continue;
}
int dotIdx = runtimeSp.getPath().indexOf('.');
if (dotIdx == -1) {
ourLog.warn("Can not determine resource type of {}", runtimeSp.getPath());
continue;
}
String resourceType = runtimeSp.getPath().substring(0, dotIdx);
for (CodeType nextBaseName : nextSp.getBase()) {
String resourceType = nextBaseName.getValue();
if (isBlank(resourceType)) {
continue;
}
Map<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, resourceType);
String name = runtimeSp.getName();
if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
}
Map<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, resourceType);
String name = runtimeSp.getName();
if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
}
}
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<>();
for (Entry<String, Map<String, RuntimeSearchParam>> nextEntry : searchParams.entrySet()) {
for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) {
String nextName = nextSp.getName();
@ -155,6 +162,8 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
myActiveSearchParams = activeSearchParams;
super.populateActiveSearchParams(activeSearchParams);
myLastRefresh = System.currentTimeMillis();
ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis());
}
@ -215,17 +224,36 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
Set<String> targets = toStrings(theNextSp.getTarget());
if (isBlank(name) || isBlank(path) || paramType == null) {
return null;
if (paramType != RestSearchParameterTypeEnum.COMPOSITE) {
return null;
}
}
IIdType id = theNextSp.getIdElement();
String uri = "";
RuntimeSearchParam retVal = new RuntimeSearchParam(id, uri, name, description, path, paramType, null, providesMembershipInCompartments, targets, status);
boolean unique = false;
List<Extension> uniqueExts = theNextSp.getExtensionsByUrl(JpaConstants.EXT_SP_UNIQUE);
if (uniqueExts.size() > 0) {
IPrimitiveType<?> uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive();
if (uniqueExtsValuePrimitive != null) {
if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) {
unique = true;
}
}
}
List<JpaRuntimeSearchParam.Component> components = new ArrayList<>();
for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) {
components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition()));
}
RuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
return retVal;
}
private Set<String> toStrings(List<CodeType> theTarget) {
HashSet<String> retVal = new HashSet<String>();
HashSet<String> retVal = new HashSet<>();
for (CodeType next : theTarget) {
if (isNotBlank(next.getValue())) {
retVal.add(next.getValue());

View File

@ -20,14 +20,14 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
import ca.uhn.fhir.model.api.IQueryParameterType;
import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@MappedSuperclass
public abstract class BaseResourceIndexedSearchParam implements Serializable {
@ -67,10 +67,19 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
return myParamName;
}
public void setParamName(String theName) {
myParamName = theName;
}
public ResourceTable getResource() {
return myResource;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
myResourceType = theResource.getResourceType();
}
public Long getResourcePid() {
return myResourcePid;
}
@ -83,6 +92,10 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
return myUpdated;
}
public void setUpdated(Date theUpdated) {
myUpdated = theUpdated;
}
public boolean isMissing() {
return Boolean.TRUE.equals(myMissing);
}
@ -91,17 +104,5 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
myMissing = theMissing;
}
public void setParamName(String theName) {
myParamName = theName;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
myResourceType = theResource.getResourceType();
}
public void setUpdated(Date theUpdated) {
myUpdated = theUpdated;
}
public abstract IQueryParameterType toQueryParameterType();
}

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.jpa.entity;
import org.apache.commons.lang3.builder.*;
import org.hl7.fhir.r4.model.Resource;
import javax.persistence.*;
@Entity()
@Table(name = "HFJ_IDX_CMP_STRING_UNIQ", indexes = {
@Index(name = ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING, columnList = "IDX_STRING", unique = true)
})
public class ResourceIndexedCompositeStringUnique implements Comparable<ResourceIndexedCompositeStringUnique> {
public static final int MAX_STRING_LENGTH = 800;
public static final String IDX_IDXCMPSTRUNIQ_STRING = "IDX_IDXCMPSTRUNIQ_STRING";
@SequenceGenerator(name = "SEQ_IDXCMPSTRUNIQ_ID", sequenceName = "SEQ_IDXCMPSTRUNIQ_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_IDXCMPSTRUNIQ_ID")
@Id
@Column(name = "PID")
private Long myId;
@ManyToOne
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID")
private ResourceTable myResource;
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("resourceId", myResourceId)
.append("indexString", myIndexString)
.toString();
}
@Column(name="RES_ID", insertable = false, updatable = false)
private Long myResourceId;
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
private String myIndexString;
/**
* Constructor
*/
public ResourceIndexedCompositeStringUnique() {
super();
}
/**
* Constructor
*/
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) {
setResource(theResource);
setIndexString(theIndexString);
}
@Override
public int compareTo(ResourceIndexedCompositeStringUnique theO) {
CompareToBuilder b = new CompareToBuilder();
b.append(myResource, theO.getResource());
b.append(myIndexString, theO.getIndexString());
return b.toComparison();
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
ResourceIndexedCompositeStringUnique that = (ResourceIndexedCompositeStringUnique) theO;
return new EqualsBuilder()
.append(getResource(), that.getResource())
.append(myIndexString, that.myIndexString)
.isEquals();
}
public String getIndexString() {
return myIndexString;
}
public void setIndexString(String theIndexString) {
myIndexString = theIndexString;
}
public ResourceTable getResource() {
return myResource;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getResource())
.append(myIndexString)
.toHashCode();
}
}

View File

@ -30,6 +30,8 @@ import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.model.api.IQueryParameterType;
import com.sun.prism.image.Coords;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -99,6 +101,11 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
return myId;
}
@Override
public IQueryParameterType toQueryParameterType() {
return null;
}
public double getLatitude() {
return myLatitude;
}

View File

@ -20,54 +20,48 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.param.DateParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.InstantType;
import ca.uhn.fhir.model.primitive.InstantDt;
import javax.persistence.*;
import java.util.Date;
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_DATE", indexes= {
@Table(name = "HFJ_SPIDX_DATE", indexes = {
@Index(name = "IDX_SP_DATE", columnList = "RES_TYPE,SP_NAME,SP_VALUE_LOW,SP_VALUE_HIGH"),
@Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID")
})
public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_DATE", sequenceName = "SEQ_SPIDX_DATE")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_DATE")
@Column(name = "SP_ID")
private Long myId;
@Transient
private transient String myOriginalValue;
@Column(name = "SP_VALUE_HIGH", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
@Field
public Date myValueHigh;
@Column(name = "SP_VALUE_LOW", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
@Field
public Date myValueLow;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_DATE", sequenceName = "SEQ_SPIDX_DATE")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_DATE")
@Column(name = "SP_ID")
private Long myId;
/**
* Constructor
@ -78,10 +72,11 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
/**
* Constructor
*/
public ResourceIndexedSearchParamDate(String theName, Date theLow, Date theHigh) {
public ResourceIndexedSearchParamDate(String theName, Date theLow, Date theHigh, String theOriginalValue) {
setParamName(theName);
setValueLow(theLow);
setValueHigh(theHigh);
myOriginalValue = theOriginalValue;
}
@Override
@ -113,10 +108,18 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return myValueHigh;
}
public void setValueHigh(Date theValueHigh) {
myValueHigh = theValueHigh;
}
public Date getValueLow() {
return myValueLow;
}
public void setValueLow(Date theValueLow) {
myValueLow = theValueLow;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -127,12 +130,13 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return b.toHashCode();
}
public void setValueHigh(Date theValueHigh) {
myValueHigh = theValueHigh;
}
public void setValueLow(Date theValueLow) {
myValueLow = theValueLow;
@Override
public IQueryParameterType toQueryParameterType() {
DateTimeType value = new DateTimeType(myOriginalValue);
if (value.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
value.setTimeZoneZulu(true);
}
return new DateParam(value.getValueAsString());
}
@Override

View File

@ -20,18 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -40,32 +31,31 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import javax.persistence.*;
import java.math.BigDecimal;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_NUMBER", indexes= {
@Table(name = "HFJ_SPIDX_NUMBER", indexes = {
@Index(name = "IDX_SP_NUMBER", columnList = "RES_TYPE,SP_NAME,SP_VALUE"),
@Index(name = "IDX_SP_NUMBER_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_NUMBER_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_NUMBER_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_NUMBER_RESID", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_NUMBER", sequenceName = "SEQ_SPIDX_NUMBER")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_NUMBER")
@Column(name = "SP_ID")
private Long myId;
@Column(name = "SP_VALUE", nullable = true)
@Field
@NumericField
@FieldBridge(impl = BigDecimalNumericFieldBridge.class)
public BigDecimal myValue;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_NUMBER", sequenceName = "SEQ_SPIDX_NUMBER")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_NUMBER")
@Column(name = "SP_ID")
private Long myId;
public ResourceIndexedSearchParamNumber() {
}
@ -103,6 +93,10 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return myValue;
}
public void setValue(BigDecimal theValue) {
myValue = theValue;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -112,8 +106,9 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return b.toHashCode();
}
public void setValue(BigDecimal theValue) {
myValue = theValue;
@Override
public IQueryParameterType toQueryParameterType() {
return new NumberParam(myValue.toPlainString());
}
@Override

View File

@ -20,18 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -40,15 +31,16 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import javax.persistence.*;
import java.math.BigDecimal;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_QUANTITY", indexes = {
@Index(name = "IDX_SP_QUANTITY", columnList = "RES_TYPE,SP_NAME,SP_SYSTEM,SP_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QUANTITY_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QUANTITY_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_QUANTITY_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QUANTITY_RESID", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearchParam {
@ -56,26 +48,22 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
private static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_QUANTITY", sequenceName = "SEQ_SPIDX_QUANTITY")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
@Column(name = "SP_ID")
private Long myId;
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
@Field
public String mySystem;
@Column(name = "SP_UNITS", nullable = true, length = MAX_LENGTH)
@Field
public String myUnits;
@Column(name = "SP_VALUE", nullable = true)
@Field
@NumericField
@FieldBridge(impl = BigDecimalNumericFieldBridge.class)
public BigDecimal myValue;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_QUANTITY", sequenceName = "SEQ_SPIDX_QUANTITY")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_QUANTITY")
@Column(name = "SP_ID")
private Long myId;
public ResourceIndexedSearchParamQuantity() {
// nothing
@ -118,14 +106,26 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return mySystem;
}
public void setSystem(String theSystem) {
mySystem = theSystem;
}
public String getUnits() {
return myUnits;
}
public void setUnits(String theUnits) {
myUnits = theUnits;
}
public BigDecimal getValue() {
return myValue;
}
public void setValue(BigDecimal theValue) {
myValue = theValue;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -137,16 +137,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return b.toHashCode();
}
public void setSystem(String theSystem) {
mySystem = theSystem;
}
public void setUnits(String theUnits) {
myUnits = theUnits;
}
public void setValue(BigDecimal theValue) {
myValue = theValue;
@Override
public IQueryParameterType toQueryParameterType() {
return new QuantityParam(null, getValue(), getSystem(), getUnits());
}
@Override

View File

@ -20,39 +20,25 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.*;
import javax.persistence.*;
import javax.persistence.Index;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_STRING", indexes = {
@Index(name = "IDX_SP_STRING", columnList = "RES_TYPE,SP_NAME,SP_VALUE_NORMALIZED"),
@Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
@Table(name = "HFJ_SPIDX_STRING", indexes = {
@Index(name = "IDX_SP_STRING", columnList = "RES_TYPE,SP_NAME,SP_VALUE_NORMALIZED"),
@Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
})
@Indexed()
//@AnalyzerDefs({
@ -109,13 +95,13 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name="SEQ_SPIDX_STRING", sequenceName="SEQ_SPIDX_STRING")
@SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
@Column(name = "SP_ID")
private Long myId;
@ManyToOne(optional = false)
@JoinColumn(name = "RES_ID", referencedColumnName="RES_ID", insertable=false, updatable=false, foreignKey=@ForeignKey(name="FK_SPIDXSTR_RESOURCE"))
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
@ContainedIn
private ResourceTable myResourceTable;
@ -169,10 +155,24 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return myValueExact;
}
public void setValueExact(String theValueExact) {
if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) {
throw new IllegalArgumentException("Value is too long: " + theValueExact.length());
}
myValueExact = theValueExact;
}
public String getValueNormalized() {
return myValueNormalized;
}
public void setValueNormalized(String theValueNormalized) {
if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) {
throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length());
}
myValueNormalized = theValueNormalized;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -182,18 +182,9 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return b.toHashCode();
}
public void setValueExact(String theValueExact) {
if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) {
throw new IllegalArgumentException("Value is too long: " + theValueExact.length());
}
myValueExact = theValueExact;
}
public void setValueNormalized(String theValueNormalized) {
if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) {
throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length());
}
myValueNormalized = theValueNormalized;
@Override
public IQueryParameterType toQueryParameterType() {
return new StringParam(getValueExact());
}
@Override

View File

@ -20,16 +20,8 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.TokenParam;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -37,14 +29,16 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_TOKEN", indexes = {
@Index(name = "IDX_SP_TOKEN", columnList = "RES_TYPE,SP_NAME,SP_SYSTEM,SP_VALUE"),
@Index(name = "IDX_SP_TOKEN_UNQUAL", columnList = "RES_TYPE,SP_NAME,SP_VALUE"),
@Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_TOKEN_RESID", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam {
@ -52,21 +46,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L;
@Field()
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
public String mySystem;
@Field()
@Column(name = "SP_VALUE", nullable = true, length = MAX_LENGTH)
public String myValue;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID")
private Long myId;
@Field()
@Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
public String mySystem;
@Field()
@Column(name = "SP_VALUE", nullable = true, length = MAX_LENGTH)
public String myValue;
public ResourceIndexedSearchParamToken() {
}
@ -105,10 +96,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return mySystem;
}
public void setSystem(String theSystem) {
mySystem = StringUtils.defaultIfBlank(theSystem, null);
}
public String getValue() {
return myValue;
}
public void setValue(String theValue) {
myValue = StringUtils.defaultIfBlank(theValue, null);
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -119,12 +118,9 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return b.toHashCode();
}
public void setSystem(String theSystem) {
mySystem = StringUtils.defaultIfBlank(theSystem, null);
}
public void setValue(String theValue) {
myValue = StringUtils.defaultIfBlank(theValue, null);
@Override
public IQueryParameterType toQueryParameterType() {
return new TokenParam(getSystem(), getValue());
}
@Override

View File

@ -20,30 +20,24 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.UriParam;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_URI", indexes = {
@Index(name = "IDX_SP_URI", columnList = "RES_TYPE,SP_NAME,SP_URI"),
@Index(name = "IDX_SP_URI_RESTYPE_NAME", columnList = "RES_TYPE,SP_NAME"),
@Index(name = "IDX_SP_URI_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID")
@Table(name = "HFJ_SPIDX_URI", indexes = {
@Index(name = "IDX_SP_URI", columnList = "RES_TYPE,SP_NAME,SP_URI"),
@Index(name = "IDX_SP_URI_RESTYPE_NAME", columnList = "RES_TYPE,SP_NAME"),
@Index(name = "IDX_SP_URI_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_URI_COORDS", columnList = "RES_ID")
})
//@formatter:on
public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchParam {
@ -54,16 +48,14 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
public static final int MAX_LENGTH = 255;
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name="SEQ_SPIDX_URI", sequenceName="SEQ_SPIDX_URI")
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SPIDX_URI")
@Column(name = "SP_ID")
private Long myId;
@Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
@Field()
public String myUri;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_URI", sequenceName = "SEQ_SPIDX_URI")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")
@Column(name = "SP_ID")
private Long myId;
public ResourceIndexedSearchParamUri() {
}
@ -101,6 +93,10 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return myUri;
}
public void setUri(String theUri) {
myUri = StringUtils.defaultIfBlank(theUri, null);
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
@ -110,8 +106,9 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return b.toHashCode();
}
public void setUri(String theUri) {
myUri = StringUtils.defaultIfBlank(theUri, null);
@Override
public IQueryParameterType toQueryParameterType() {
return new UriParam(getUri());
}
@Override

View File

@ -19,27 +19,11 @@ package ca.uhn.fhir.jpa.entity;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import ca.uhn.fhir.jpa.search.IndexNonDeletedInterceptor;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
@ -52,58 +36,53 @@ import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.AnalyzerDefs;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.*;
import org.hibernate.search.annotations.Parameter;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TokenFilterDef;
import org.hibernate.search.annotations.TokenizerDef;
import ca.uhn.fhir.jpa.search.IndexNonDeletedInterceptor;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import javax.persistence.*;
import javax.persistence.Index;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.defaultString;
//@formatter:off
@Indexed(interceptor=IndexNonDeletedInterceptor.class)
@Indexed(interceptor = IndexNonDeletedInterceptor.class)
@Entity
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes= {
@Index(name = "IDX_RES_DATE", columnList="RES_UPDATED"),
@Index(name = "IDX_RES_LANG", columnList="RES_TYPE,RES_LANGUAGE"),
@Index(name = "IDX_RES_PROFILE", columnList="RES_PROFILE"),
@Index(name = "IDX_RES_TYPE", columnList="RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList="SP_INDEX_STATUS")
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = {
@Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"),
@Index(name = "IDX_RES_LANG", columnList = "RES_TYPE,RES_LANGUAGE"),
@Index(name = "IDX_RES_PROFILE", columnList = "RES_PROFILE"),
@Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS")
})
@AnalyzerDefs({
@AnalyzerDef(name = "autocompleteEdgeAnalyzer",
tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= {
@Parameter(name="pattern", value="(.*)"),
@Parameter(name="group", value="1")
tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params = {
@Parameter(name = "pattern", value = "(.*)"),
@Parameter(name = "group", value = "1")
}),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = {
@Parameter(name = "minGramSize", value = "3"),
@Parameter(name = "maxGramSize", value = "50")
}),
@Parameter(name = "maxGramSize", value = "50")
}),
}),
@AnalyzerDef(name = "autocompletePhoneticAnalyzer",
tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class),
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory=StandardFilterFactory.class),
@TokenFilterDef(factory=StopFilterFactory.class),
@TokenFilterDef(factory=PhoneticFilterFactory.class, params = {
@Parameter(name="encoder", value="DoubleMetaphone")
@TokenFilterDef(factory = StandardFilterFactory.class),
@TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(factory = PhoneticFilterFactory.class, params = {
@Parameter(name = "encoder", value = "DoubleMetaphone")
}),
@TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = {
@Parameter(name="language", value="English")
@TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
@Parameter(name = "language", value = "English")
})
}),
@AnalyzerDef(name = "autocompleteNGramAnalyzer",
@ -113,7 +92,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = NGramFilterFactory.class, params = {
@Parameter(name = "minGramSize", value = "3"),
@Parameter(name = "maxGramSize", value = "20")
@Parameter(name = "maxGramSize", value = "20")
}),
}),
@AnalyzerDef(name = "standardAnalyzer",
@ -125,21 +104,18 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
})
}
}
)
//@formatter:on
public class ResourceTable extends BaseHasResource implements Serializable {
static final int RESTYPE_LEN = 30;
private static final int MAX_LANGUAGE_LENGTH = 20;
private static final int MAX_PROFILE_LENGTH = 200;
static final int RESTYPE_LEN = 30;
private static final long serialVersionUID = 1L;
/**
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
*/
//@formatter:off
@Transient()
@Fields({
@Field(name = "myContentText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
@ -147,10 +123,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Field(name = "myContentTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
@Field(name = "myContentTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
})
//@formatter:on
private String myContentText;
@Column(name = "HASH_SHA256", length=64, nullable=true)
@Column(name = "HASH_SHA256", length = 64, nullable = true)
private String myHashSha256;
@Column(name = "SP_HAS_LINKS")
@ -227,24 +202,22 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true)
private String myProfile;
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique;
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
private boolean myParamsCompositeStringUniquePresent;
@OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@IndexedEmbedded()
private Collection<ResourceLink> myResourceLinks;
@Column(name = "RES_TYPE", length = RESTYPE_LEN)
@Field
private String myResourceType;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Collection<SearchParamPresent> mySearchParamPresents;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ResourceTag> myTags;
@Transient
private transient boolean myUnchangedInCurrentOperation;
@Column(name = "RES_VER")
private long myVersion;
@ -264,11 +237,19 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myHashSha256;
}
public void setHashSha256(String theHashSha256) {
myHashSha256 = theHashSha256;
}
@Override
public Long getId() {
return myId;
}
public void setId(Long theId) {
myId = theId;
}
@Override
public IdDt getIdDt() {
if (getForcedId() == null) {
@ -283,157 +264,14 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myIndexStatus;
}
public String getLanguage() {
return myLanguage;
}
public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() {
if (myParamsCoords == null) {
myParamsCoords = new ArrayList<ResourceIndexedSearchParamCoords>();
}
return myParamsCoords;
}
public Collection<ResourceIndexedSearchParamDate> getParamsDate() {
if (myParamsDate == null) {
myParamsDate = new ArrayList<ResourceIndexedSearchParamDate>();
}
return myParamsDate;
}
public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() {
if (myParamsNumber == null) {
myParamsNumber = new ArrayList<ResourceIndexedSearchParamNumber>();
}
return myParamsNumber;
}
public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() {
if (myParamsQuantity == null) {
myParamsQuantity = new ArrayList<ResourceIndexedSearchParamQuantity>();
}
return myParamsQuantity;
}
public Collection<ResourceIndexedSearchParamString> getParamsString() {
if (myParamsString == null) {
myParamsString = new ArrayList<ResourceIndexedSearchParamString>();
}
return myParamsString;
}
public Collection<ResourceIndexedSearchParamToken> getParamsToken() {
if (myParamsToken == null) {
myParamsToken = new ArrayList<ResourceIndexedSearchParamToken>();
}
return myParamsToken;
}
public Collection<ResourceIndexedSearchParamUri> getParamsUri() {
if (myParamsUri == null) {
myParamsUri = new ArrayList<ResourceIndexedSearchParamUri>();
}
return myParamsUri;
}
public String getProfile() {
return myProfile;
}
public Collection<ResourceLink> getResourceLinks() {
if (myResourceLinks == null) {
myResourceLinks = new ArrayList<ResourceLink>();
}
return myResourceLinks;
}
@Override
public String getResourceType() {
return myResourceType;
}
@Override
public Collection<ResourceTag> getTags() {
if (myTags == null) {
myTags = new HashSet<ResourceTag>();
}
return myTags;
}
@Override
public long getVersion() {
return myVersion;
}
public boolean hasTag(System theSystem, String theTerm) {
for (ResourceTag next : getTags()) {
if (next.getTag().getSystem().equals(theSystem) && next.getTag().getCode().equals(theTerm)) {
return true;
}
}
return false;
}
public boolean isHasLinks() {
return myHasLinks;
}
public boolean isParamsCoordsPopulated() {
return myParamsCoordsPopulated;
}
public boolean isParamsDatePopulated() {
return myParamsDatePopulated;
}
public boolean isParamsNumberPopulated() {
return myParamsNumberPopulated;
}
public boolean isParamsQuantityPopulated() {
return myParamsQuantityPopulated;
}
public boolean isParamsStringPopulated() {
return myParamsStringPopulated;
}
public boolean isParamsTokenPopulated() {
return myParamsTokenPopulated;
}
public boolean isParamsUriPopulated() {
return myParamsUriPopulated;
}
/**
* Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation
* and was not re-saved in the database
*/
public boolean isUnchangedInCurrentOperation() {
return myUnchangedInCurrentOperation;
}
public void setContentTextParsedIntoWords(String theContentText) {
myContentText = theContentText;
}
public void setHashSha256(String theHashSha256) {
myHashSha256 = theHashSha256;
}
public void setHasLinks(boolean theHasLinks) {
myHasLinks = theHasLinks;
}
public void setId(Long theId) {
myId = theId;
}
public void setIndexStatus(Long theIndexStatus) {
myIndexStatus = theIndexStatus;
}
public String getLanguage() {
return myLanguage;
}
public void setLanguage(String theLanguage) {
if (defaultString(theLanguage).length() > MAX_LANGUAGE_LENGTH) {
throw new UnprocessableEntityException("Language exceeds maximum length of " + MAX_LANGUAGE_LENGTH + " chars: " + theLanguage);
@ -441,8 +279,22 @@ public class ResourceTable extends BaseHasResource implements Serializable {
myLanguage = theLanguage;
}
public void setNarrativeTextParsedIntoWords(String theNarrativeText) {
myNarrativeText = theNarrativeText;
public Collection<ResourceIndexedCompositeStringUnique> getParamsCompositeStringUnique() {
if (myParamsCompositeStringUnique == null) {
myParamsCompositeStringUnique = new ArrayList<>();
}
return myParamsCompositeStringUnique;
}
public void setParamsCompositeStringUnique(Collection<ResourceIndexedCompositeStringUnique> theParamsCompositeStringUnique) {
myParamsCompositeStringUnique = theParamsCompositeStringUnique;
}
public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() {
if (myParamsCoords == null) {
myParamsCoords = new ArrayList<>();
}
return myParamsCoords;
}
public void setParamsCoords(Collection<ResourceIndexedSearchParamCoords> theParamsCoords) {
@ -453,8 +305,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsCoords().addAll(theParamsCoords);
}
public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) {
myParamsCoordsPopulated = theParamsCoordsPopulated;
public Collection<ResourceIndexedSearchParamDate> getParamsDate() {
if (myParamsDate == null) {
myParamsDate = new ArrayList<>();
}
return myParamsDate;
}
public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) {
@ -465,8 +320,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsDate().addAll(theParamsDate);
}
public void setParamsDatePopulated(boolean theParamsDatePopulated) {
myParamsDatePopulated = theParamsDatePopulated;
public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() {
if (myParamsNumber == null) {
myParamsNumber = new ArrayList<>();
}
return myParamsNumber;
}
public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) {
@ -477,8 +335,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsNumber().addAll(theNumberParams);
}
public void setParamsNumberPopulated(boolean theParamsNumberPopulated) {
myParamsNumberPopulated = theParamsNumberPopulated;
public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() {
if (myParamsQuantity == null) {
myParamsQuantity = new ArrayList<>();
}
return myParamsQuantity;
}
public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) {
@ -489,8 +350,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsQuantity().addAll(theQuantityParams);
}
public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) {
myParamsQuantityPopulated = theParamsQuantityPopulated;
public Collection<ResourceIndexedSearchParamString> getParamsString() {
if (myParamsString == null) {
myParamsString = new ArrayList<>();
}
return myParamsString;
}
public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) {
@ -501,8 +365,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsString().addAll(theParamsString);
}
public void setParamsStringPopulated(boolean theParamsStringPopulated) {
myParamsStringPopulated = theParamsStringPopulated;
public Collection<ResourceIndexedSearchParamToken> getParamsToken() {
if (myParamsToken == null) {
myParamsToken = new ArrayList<>();
}
return myParamsToken;
}
public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) {
@ -513,8 +380,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsToken().addAll(theParamsToken);
}
public void setParamsTokenPopulated(boolean theParamsTokenPopulated) {
myParamsTokenPopulated = theParamsTokenPopulated;
public Collection<ResourceIndexedSearchParamUri> getParamsUri() {
if (myParamsUri == null) {
myParamsUri = new ArrayList<>();
}
return myParamsUri;
}
public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) {
@ -525,8 +395,8 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsUri().addAll(theParamsUri);
}
public void setParamsUriPopulated(boolean theParamsUriPopulated) {
myParamsUriPopulated = theParamsUriPopulated;
public String getProfile() {
return myProfile;
}
public void setProfile(String theProfile) {
@ -536,6 +406,13 @@ public class ResourceTable extends BaseHasResource implements Serializable {
myProfile = theProfile;
}
public Collection<ResourceLink> getResourceLinks() {
if (myResourceLinks == null) {
myResourceLinks = new ArrayList<>();
}
return myResourceLinks;
}
public void setResourceLinks(Collection<ResourceLink> theLinks) {
if (!isHasLinks() && theLinks.isEmpty()) {
return;
@ -544,10 +421,112 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getResourceLinks().addAll(theLinks);
}
@Override
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
@Override
public Collection<ResourceTag> getTags() {
if (myTags == null) {
myTags = new HashSet<>();
}
return myTags;
}
@Override
public long getVersion() {
return myVersion;
}
public void setVersion(long theVersion) {
myVersion = theVersion;
}
public boolean isHasLinks() {
return myHasLinks;
}
public void setHasLinks(boolean theHasLinks) {
myHasLinks = theHasLinks;
}
public boolean isParamsCompositeStringUniquePresent() {
return myParamsCompositeStringUniquePresent;
}
public void setParamsCompositeStringUniquePresent(boolean theParamsCompositeStringUniquePresent) {
myParamsCompositeStringUniquePresent = theParamsCompositeStringUniquePresent;
}
public boolean isParamsCoordsPopulated() {
return myParamsCoordsPopulated;
}
public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) {
myParamsCoordsPopulated = theParamsCoordsPopulated;
}
public boolean isParamsDatePopulated() {
return myParamsDatePopulated;
}
public void setParamsDatePopulated(boolean theParamsDatePopulated) {
myParamsDatePopulated = theParamsDatePopulated;
}
public boolean isParamsNumberPopulated() {
return myParamsNumberPopulated;
}
public void setParamsNumberPopulated(boolean theParamsNumberPopulated) {
myParamsNumberPopulated = theParamsNumberPopulated;
}
public boolean isParamsQuantityPopulated() {
return myParamsQuantityPopulated;
}
public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) {
myParamsQuantityPopulated = theParamsQuantityPopulated;
}
public boolean isParamsStringPopulated() {
return myParamsStringPopulated;
}
public void setParamsStringPopulated(boolean theParamsStringPopulated) {
myParamsStringPopulated = theParamsStringPopulated;
}
public boolean isParamsTokenPopulated() {
return myParamsTokenPopulated;
}
public void setParamsTokenPopulated(boolean theParamsTokenPopulated) {
myParamsTokenPopulated = theParamsTokenPopulated;
}
public boolean isParamsUriPopulated() {
return myParamsUriPopulated;
}
public void setParamsUriPopulated(boolean theParamsUriPopulated) {
myParamsUriPopulated = theParamsUriPopulated;
}
/**
* Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation
* and was not re-saved in the database
*/
public boolean isUnchangedInCurrentOperation() {
return myUnchangedInCurrentOperation;
}
/**
* Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation
* and was not re-saved in the database
@ -556,8 +535,12 @@ public class ResourceTable extends BaseHasResource implements Serializable {
myUnchangedInCurrentOperation = theUnchangedInCurrentOperation;
}
public void setVersion(long theVersion) {
myVersion = theVersion;
public void setContentTextParsedIntoWords(String theContentText) {
myContentText = theContentText;
}
public void setNarrativeTextParsedIntoWords(String theNarrativeText) {
myNarrativeText = theNarrativeText;
}
public ResourceHistoryTable toHistory(ResourceHistoryTable theResourceHistoryTable) {

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.jpa.search;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaRuntimeSearchParam extends RuntimeSearchParam {
private final boolean myUnique;
private final List<Component> myComponents;
/**
* Constructor
*/
public JpaRuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, boolean theUnique, List<Component> theComponents, Collection<? extends IPrimitiveType<String>> theBase) {
super(theId, theUri, theName, theDescription, thePath, theParamType, createCompositeList(theParamType), theProvidesMembershipInCompartments, theTargets, theStatus, toStrings(theBase));
myUnique = theUnique;
myComponents = Collections.unmodifiableList(theComponents);
}
private static Collection<String> toStrings(Collection<? extends IPrimitiveType<String>> theBase) {
HashSet<String> retVal = new HashSet<>();
for (IPrimitiveType<String> next : theBase) {
if (isNotBlank(next.getValueAsString())) {
retVal.add(next.getValueAsString());
}
}
return retVal;
}
public List<Component> getComponents() {
return myComponents;
}
public boolean isUnique() {
return myUnique;
}
private static ArrayList<RuntimeSearchParam> createCompositeList(RestSearchParameterTypeEnum theParamType) {
if (theParamType == RestSearchParameterTypeEnum.COMPOSITE) {
return new ArrayList<>();
} else {
return null;
}
}
public static class Component {
private final String myExpression;
private final IBaseReference myReference;
public Component(String theExpression, IBaseReference theReference) {
myExpression = theExpression;
myReference = theReference;
}
public String getExpression() {
return myExpression;
}
public IBaseReference getReference() {
return myReference;
}
}
}

View File

@ -0,0 +1,7 @@
package ca.uhn.fhir.jpa.util;
public class JpaConstants {
public static final String EXT_SP_UNIQUE = "http://hapifhir.io/fhir/StructureDefinition/sp-unique";
}

View File

@ -48,10 +48,10 @@ public class TestR4Config extends BaseJavaConfigR4 {
} catch (Exception e) {
ourLog.error("Exceeded maximum wait for connection", e);
logGetConnectionStackTrace();
if ("true".equals(System.getProperty("ci"))) {
fail("Exceeded maximum wait for connection: " + e.toString());
}
System.exit(1);
// if ("true".equals(System.getProperty("ci"))) {
fail("Exceeded maximum wait for connection: " + e.toString());
// }
// System.exit(1);
retVal = null;
}

View File

@ -236,6 +236,7 @@ public abstract class BaseJpaTest {
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedCompositeStringUnique.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchResult.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchInclude.class.getSimpleName() + " d").executeUpdate();

View File

@ -57,8 +57,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
// @Autowired
// protected HapiWorkerContext myHapiWorkerContext;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
@Qualifier("myAllergyIntoleranceDaoDstu3")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;

View File

@ -0,0 +1,295 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.orm.jpa.JpaSystemException;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.*;
@SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3UniqueSearchParamTest.class);
@After
public void after() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
}
@Before
public void before() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
}
private void createUniqueBirthdateAndGenderSps() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("gender");
sp.setExpression("Patient.gender");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("birthdate");
sp.setExpression("Patient.birthDate");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender-birthdate");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-gender"));
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-birthdate"));
sp.addExtension()
.setUrl(JpaConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
}
private void createUniqueNameAndManagingOrganizationSps() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-name");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("name");
sp.setExpression("Patient.name");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-organization");
sp.setType(Enumerations.SearchParamType.REFERENCE);
sp.setCode("organization");
sp.setExpression("Patient.managingOrganization");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-name-organization");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-name"));
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-organization"));
sp.addExtension()
.setUrl(JpaConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
}
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> params = mySearchParamRegsitry.getActiveUniqueSearchParams("Patient");
assertEquals(1, params.size());
assertEquals(params.get(0).isUnique(), true);
assertEquals(2, params.get(0).getCompositeOf().size());
// Should be alphabetical order
assertEquals("birthdate", params.get(0).getCompositeOf().get(0).getName());
assertEquals("gender", params.get(0).getCompositeOf().get(1).getName());
}
@Test
public void testDuplicateUniqueValuesAreRejected() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
try {
myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
fail();
} catch (JpaSystemException e) {
// good
}
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
pt2 = new Patient();
pt2.setId(id2);
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-01"));
try {
myPatientDao.update(pt2);
fail();
} catch (JpaSystemException e) {
// good
}
}
@Test
public void testUniqueValuesAreIndexed_DateAndToken() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString());
}
@Test
public void testSearchSynchronousUsingUniqueComposite() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02"));
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
SearchBuilder.resetLastHandlerMechanismForUnitTest();
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
}
@Test
public void testSearchUsingUniqueComposite() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02"));
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
SearchBuilder.resetLastHandlerMechanismForUnitTest();
SearchParameterMap params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params);
String searchId = results.getUuid();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
// Other order
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-01"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
results = myPatientDao.search(params);
assertEquals(searchId, results.getUuid());
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
// Null because we just reuse the last search
assertEquals(null, SearchBuilder.getLastHandlerMechanismForUnitTest());
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), empty());
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), empty());
assertEquals(SearchBuilder.HandlerTypeEnum.STANDARD_QUERY, SearchBuilder.getLastHandlerMechanismForUnitTest());
}
@Test
public void testUniqueValuesAreIndexed_StringAndReference() {
createUniqueNameAndManagingOrganizationSps();
Organization org = new Organization();
org.setId("Organization/ORG");
org.setName("ORG");
myOrganizationDao.update(org);
Patient pt1 = new Patient();
pt1.addName()
.setFamily("FAMILY1")
.addGiven("GIVEN1")
.addGiven("GIVEN2")
.addGiven("GIVEN2"); // GIVEN2 happens twice
pt1.setManagingOrganization(new Reference("Organization/ORG"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
Collections.sort(uniques);
assertEquals(3, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(1).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=GIVEN1&organization=Organization%2FORG", uniques.get(1).getIndexString());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(2).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=GIVEN2&organization=Organization%2FORG", uniques.get(2).getIndexString());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -3,9 +3,11 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.model.Observation;
@ -53,6 +55,16 @@ public class SearchParamExtractorDstu3Test {
return sps;
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
throw new UnsupportedOperationException();
}
@Override
public void forceRefresh() {
// nothing

View File

@ -47,17 +47,15 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
//@formatter:off
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {TestR4Config.class})
//@formatter:on
public abstract class BaseJpaR4Test extends BaseJpaTest {
private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
// @Autowired
// protected HapiWorkerContext myHapiWorkerContext;
@Autowired
protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
@Qualifier("myAllergyIntoleranceDaoR4")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;

View File

@ -0,0 +1,296 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.orm.jpa.JpaSystemException;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.*;
@SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class);
@After
public void after() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
}
@Before
public void before() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
}
private void createUniqueBirthdateAndGenderSps() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("gender");
sp.setExpression("Patient.gender");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("birthdate");
sp.setExpression("Patient.birthDate");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender-birthdate");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-gender"));
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-birthdate"));
sp.addExtension()
.setUrl(JpaConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
}
private void createUniqueNameAndManagingOrganizationSps() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-name");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("name");
sp.setExpression("Patient.name");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-organization");
sp.setType(Enumerations.SearchParamType.REFERENCE);
sp.setCode("organization");
sp.setExpression("Patient.managingOrganization");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-name-organization");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-name"));
sp.addComponent()
.setExpression("Patient")
.setDefinition(new Reference("SearchParameter/patient-organization"));
sp.addExtension()
.setUrl(JpaConstants.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegsitry.forceRefresh();
}
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> params = mySearchParamRegsitry.getActiveUniqueSearchParams("Patient");
assertEquals(1, params.size());
assertEquals(params.get(0).isUnique(), true);
assertEquals(2, params.get(0).getCompositeOf().size());
// Should be alphabetical order
assertEquals("birthdate", params.get(0).getCompositeOf().get(0).getName());
assertEquals("gender", params.get(0).getCompositeOf().get(1).getName());
}
@Test
public void testDuplicateUniqueValuesAreRejected() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
try {
myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
fail();
} catch (JpaSystemException e) {
// good
}
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
pt2 = new Patient();
pt2.setId(id2);
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-01"));
try {
myPatientDao.update(pt2);
fail();
} catch (JpaSystemException e) {
// good
}
}
@Test
public void testUniqueValuesAreIndexed_DateAndToken() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString());
}
@Test
public void testSearchSynchronousUsingUniqueComposite() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02"));
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
SearchBuilder.resetLastHandlerMechanismForUnitTest();
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
}
@Test
public void testSearchUsingUniqueComposite() {
createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02"));
IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
SearchBuilder.resetLastHandlerMechanismForUnitTest();
SearchParameterMap params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params);
String searchId = results.getUuid();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
// Other order
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-01"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
results = myPatientDao.search(params);
assertEquals(searchId, results.getUuid());
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
// Null because we just reuse the last search
assertEquals(null, SearchBuilder.getLastHandlerMechanismForUnitTest());
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), empty());
assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
SearchBuilder.resetLastHandlerMechanismForUnitTest();
params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(results), empty());
assertEquals(SearchBuilder.HandlerTypeEnum.STANDARD_QUERY, SearchBuilder.getLastHandlerMechanismForUnitTest());
}
@Test
public void testUniqueValuesAreIndexed_StringAndReference() {
createUniqueNameAndManagingOrganizationSps();
Organization org = new Organization();
org.setId("Organization/ORG");
org.setName("ORG");
myOrganizationDao.update(org);
Patient pt1 = new Patient();
pt1.addName()
.setFamily("FAMILY1")
.addGiven("GIVEN1")
.addGiven("GIVEN2")
.addGiven("GIVEN2"); // GIVEN2 happens twice
pt1.setManagingOrganization(new Reference("Organization/ORG"));
IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
List<ResourceIndexedCompositeStringUnique> uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
Collections.sort(uniques);
assertEquals(3, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=FAMILY1&organization=Organization%2FORG", uniques.get(0).getIndexString());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(1).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=GIVEN1&organization=Organization%2FORG", uniques.get(1).getIndexString());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(2).getResource().getIdDt().toUnqualifiedVersionless().getValue());
assertEquals("Patient?name=GIVEN2&organization=Organization%2FORG", uniques.get(2).getIndexString());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
import java.util.*;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.Observation;
@ -45,6 +46,16 @@ public class SearchParamExtractorR4Test {
return sps;
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}
@Override
public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
throw new UnsupportedOperationException();
}
@Override
public void forceRefresh() {
// nothing

View File

@ -49,7 +49,7 @@ public interface IServerOperationInterceptor extends IServerInterceptor {
* User code may call this method to indicate to an interceptor that
* a resource is being updated
*
* @deprecated Deprecated in HAPI FHIR 2.6 in favour of {@link #resourceUpdated(RequestDetails, IBaseResource, IBaseResource)}
* @deprecated Deprecated in HAPI FHIR 3.0.0 in favour of {@link #resourceUpdated(RequestDetails, IBaseResource, IBaseResource)}
*/
@Deprecated
void resourceUpdated(RequestDetails theRequest, IBaseResource theResource);

View File

@ -349,6 +349,12 @@
HAPI FHIR 2.5, but this was not documented. This variable has now been
documented as a part of the available features.
</action>
<action type="add">
A new experimental feature has been added to the JPA server which allows
you to define certain search parameter combinations as being resource keys,
so that a database constraint will prevent more than one resource from
having a matching pair
</action>
</release>
<release version="2.5" date="2017-06-08">
<action type="fix">