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; package ca.uhn.fhir.context;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim; import static org.apache.commons.lang3.StringUtils.trim;
import java.util.*; 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, 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(); super();
myId = theId; myId = theId;
myUri = theUri; myUri = theUri;
@ -71,12 +77,18 @@ public class RuntimeSearchParam {
myTargets = null; myTargets = null;
} }
HashSet<String> base = new HashSet<String>(); if (theBase == null || theBase.isEmpty()) {
int indexOf = thePath.indexOf('.'); HashSet<String> base = new HashSet<>();
if (indexOf != -1) { if (isNotBlank(thePath)) {
base.add(trim(thePath.substring(0, indexOf))); 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() { 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.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.annotation.SchedulingConfigurer;

View File

@ -19,19 +19,49 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.io.UnsupportedEncodingException; import ca.uhn.fhir.context.*;
import java.text.Normalizer; import ca.uhn.fhir.jpa.dao.data.*;
import java.util.*; import ca.uhn.fhir.jpa.entity.*;
import java.util.Map.Entry; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import javax.persistence.*; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import javax.persistence.criteria.*; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import javax.xml.stream.events.Characters; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import javax.xml.stream.events.XMLEvent; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.*;
import org.apache.commons.lang3.*; 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.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.*; 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.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import com.google.common.base.Charsets; import javax.persistence.*;
import com.google.common.collect.ArrayListMultimap; import javax.persistence.criteria.CriteriaBuilder;
import com.google.common.collect.Sets; import javax.persistence.criteria.CriteriaQuery;
import com.google.common.hash.HashFunction; import javax.persistence.criteria.Predicate;
import com.google.common.hash.Hashing; 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 static org.apache.commons.lang3.StringUtils.*;
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.*;
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao { 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_INDEXED = Long.valueOf(1L);
public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); 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 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_ERROR = "error";
public static final String OO_SEVERITY_INFO = "information"; public static final String OO_SEVERITY_INFO = "information";
public static final String OO_SEVERITY_WARN = "warning"; public static final String OO_SEVERITY_WARN = "warning";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); public static final String UCUM_NS = "http://unitsofmeasure.org";
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>(); static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
/** /**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)} * 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)} * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
*/ */
static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS; 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 { static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>(); 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); EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
} }
@Autowired(required = true)
private DaoConfig myConfig;
private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
@Autowired @Autowired
protected IForcedIdDao myForcedIdDao; protected IForcedIdDao myForcedIdDao;
@Autowired(required = false) @Autowired(required = false)
protected IFulltextSearchSvc myFulltextSearchSvc; protected IFulltextSearchSvc myFulltextSearchSvc;
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
@Autowired
private List<IFhirResourceDao<?>> myResourceDaos;
@Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao;
@Autowired() @Autowired()
protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao; protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao;
@Autowired @Autowired
protected ISearchCoordinatorSvc mySearchCoordinatorSvc; protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired
private ISearchDao mySearchDao;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired
private ISearchResultDao mySearchResultDao;
@Autowired @Autowired
protected ISearchParamRegistry mySerarchParamRegistry; protected ISearchParamRegistry mySerarchParamRegistry;
@Autowired() @Autowired()
protected IHapiTerminologySvc myTerminologySvc; 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) { protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) { if (theRequestDetails != null) {
@ -188,13 +196,84 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return InstantDt.withCurrentTime(); 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 * @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") @SuppressWarnings("unchecked")
protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) { 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.. * For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
@ -291,7 +370,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
resourceDefinition = getContext().getResourceDefinition(typeString); resourceDefinition = getContext().getResourceDefinition(typeString);
} catch (DataFormatException e) { } catch (DataFormatException e) {
throw new InvalidRequestException( 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)) { if (isNotBlank(baseUrl)) {
@ -352,7 +431,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (!typeString.equals(target.getResourceType())) { if (!typeString.equals(target.getResourceType())) {
throw new UnprocessableEntityException( 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) { if (target.getDeleted() != null) {
@ -375,11 +454,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return retVal; 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) { protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource); return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
} }
@ -509,7 +583,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type, 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) { for (Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
String nextParamName = nextEntry.getKey(); String nextParamName = nextEntry.getKey();
if (nextEntry.getValue().getParamType() == type) { if (nextEntry.getValue().getParamType() == type) {
@ -569,11 +643,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return myConfig; return myConfig;
} }
public void setConfig(DaoConfig theConfig) {
myConfig = theConfig;
}
@Override @Override
public FhirContext getContext() { public FhirContext getContext() {
return myContext; return myContext;
} }
@Autowired
public void setContext(FhirContext theContext) {
myContext = theContext;
}
public FhirContext getContext(FhirVersionEnum theVersion) { public FhirContext getContext(FhirVersionEnum theVersion) {
Validate.notNull(theVersion, "theVersion must not be null"); Validate.notNull(theVersion, "theVersion must not be null");
synchronized (ourRetrievalContexts) { synchronized (ourRetrievalContexts) {
@ -606,6 +689,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return dao; return dao;
} }
public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
return myResourceIndexedCompositeStringUniqueDao;
}
@Override @Override
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()); Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
@ -628,16 +715,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (isNotBlank(theScheme)) { if (isNotBlank(theScheme)) {
cq.where( cq.where(
builder.and( builder.and(
builder.equal(from.get("myTagType"), theTagType), builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("mySystem"), theScheme), builder.equal(from.get("mySystem"), theScheme),
builder.equal(from.get("myCode"), theTerm))); builder.equal(from.get("myCode"), theTerm)));
} else { } else {
cq.where( cq.where(
builder.and( builder.and(
builder.equal(from.get("myTagType"), theTagType), builder.equal(from.get("myTagType"), theTagType),
builder.isNull(from.get("mySystem")), builder.isNull(from.get("mySystem")),
builder.equal(from.get("myCode"), theTerm))); builder.equal(from.get("myCode"), theTerm)));
} }
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq); 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, ResourceTag.class);
findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceHistoryTag.class); findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceHistoryTag.class);
if (tagIds.isEmpty()) { if (tagIds.isEmpty()) {
@ -764,8 +851,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@Override @Override
public SearchBuilder newSearchBuilder() { public SearchBuilder newSearchBuilder() {
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao, SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
myForcedIdDao, myForcedIdDao,
myTerminologySvc, mySerarchParamRegistry); myTerminologySvc, mySerarchParamRegistry);
return builder; 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() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) { if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
throw new InternalErrorException( 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") @SuppressWarnings("rawtypes")
List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class); List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
for (@SuppressWarnings("rawtypes") for (@SuppressWarnings("rawtypes")
IPrimitiveType nextType : childElements) { IPrimitiveType nextType : childElements) {
if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) { if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
String nextValue = nextType.getValueAsString(); String nextValue = nextType.getValueAsString();
if (isNotBlank(nextValue)) { if (isNotBlank(nextValue)) {
@ -1064,10 +1151,8 @@ 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. * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
* *
* @param theEntity * @param theEntity The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * @param theResource The resource being persisted
* @param theResource
* The resource being persisted
*/ */
protected void postPersist(ResourceTable theEntity, T theResource) { protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing // nothing
@ -1076,10 +1161,8 @@ 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 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
* *
* @param theEntity * @param theEntity The resource
* The resource * @param theResource The resource being persisted
* @param theResource
* The resource being persisted
*/ */
protected void postUpdate(ResourceTable theEntity, T theResource) { protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing // nothing
@ -1111,15 +1194,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
throw new NotImplementedException(""); throw new NotImplementedException("");
} }
public void setConfig(DaoConfig theConfig) {
myConfig = theConfig;
}
@Autowired
public void setContext(FhirContext theContext) {
myContext = theContext;
}
public void setEntityManager(EntityManager theEntityManager) { public void setEntityManager(EntityManager theEntityManager) {
myEntityManager = theEntityManager; myEntityManager = theEntityManager;
} }
@ -1143,10 +1217,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* 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. * 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> * </p>
* *
* @param theEntity * @param theEntity The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * @param theTag The tag
* @param theTag
* The tag
* @return Retturns <code>true</code> if the tag should be removed * @return Retturns <code>true</code> if the tag should be removed
*/ */
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1156,6 +1228,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return false; 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) { // protected ResourceTable toEntity(IResource theResource) {
// ResourceTable retVal = new ResourceTable(); // ResourceTable retVal = new ResourceTable();
// //
@ -1164,13 +1243,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
// return retVal; // 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") @SuppressWarnings("unchecked")
@Override @Override
public <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) { 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") @SuppressWarnings("unchecked")
protected ResourceTable updateEntity(final IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, 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"); ourLog.debug("Starting entity update");
/* /*
@ -1281,7 +1353,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
String resourceType = myContext.getResourceDefinition(theResource).getName(); String resourceType = myContext.getResourceDefinition(theResource).getName();
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) { if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
throw new UnprocessableEntityException( 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); theEntity.setPublished(theUpdateTime);
} }
Collection<ResourceIndexedSearchParamString> paramsString = new ArrayList<>(); Collection<ResourceIndexedSearchParamString> existingStringParams = new ArrayList<>();
if (theEntity.isParamsStringPopulated()) { if (theEntity.isParamsStringPopulated()) {
paramsString.addAll(theEntity.getParamsString()); existingStringParams.addAll(theEntity.getParamsString());
} }
Collection<ResourceIndexedSearchParamToken> paramsToken = new ArrayList<>(); Collection<ResourceIndexedSearchParamToken> existingTokenParams = new ArrayList<>();
if (theEntity.isParamsTokenPopulated()) { if (theEntity.isParamsTokenPopulated()) {
paramsToken.addAll(theEntity.getParamsToken()); existingTokenParams.addAll(theEntity.getParamsToken());
} }
Collection<ResourceIndexedSearchParamNumber> paramsNumber = new ArrayList<>(); Collection<ResourceIndexedSearchParamNumber> existingNumberParams = new ArrayList<>();
if (theEntity.isParamsNumberPopulated()) { if (theEntity.isParamsNumberPopulated()) {
paramsNumber.addAll(theEntity.getParamsNumber()); existingNumberParams.addAll(theEntity.getParamsNumber());
} }
Collection<ResourceIndexedSearchParamQuantity> paramsQuantity = new ArrayList<>(); Collection<ResourceIndexedSearchParamQuantity> existingQuantityParams = new ArrayList<>();
if (theEntity.isParamsQuantityPopulated()) { if (theEntity.isParamsQuantityPopulated()) {
paramsQuantity.addAll(theEntity.getParamsQuantity()); existingQuantityParams.addAll(theEntity.getParamsQuantity());
} }
Collection<ResourceIndexedSearchParamDate> paramsDate = new ArrayList<>(); Collection<ResourceIndexedSearchParamDate> existingDateParams = new ArrayList<>();
if (theEntity.isParamsDatePopulated()) { if (theEntity.isParamsDatePopulated()) {
paramsDate.addAll(theEntity.getParamsDate()); existingDateParams.addAll(theEntity.getParamsDate());
} }
Collection<ResourceIndexedSearchParamUri> paramsUri = new ArrayList<>(); Collection<ResourceIndexedSearchParamUri> existingUriParams = new ArrayList<>();
if (theEntity.isParamsUriPopulated()) { if (theEntity.isParamsUriPopulated()) {
paramsUri.addAll(theEntity.getParamsUri()); existingUriParams.addAll(theEntity.getParamsUri());
} }
Collection<ResourceIndexedSearchParamCoords> paramsCoords = new ArrayList<>(); Collection<ResourceIndexedSearchParamCoords> existingCoordsParams = new ArrayList<>();
if (theEntity.isParamsCoordsPopulated()) { if (theEntity.isParamsCoordsPopulated()) {
paramsCoords.addAll(theEntity.getParamsCoords()); existingCoordsParams.addAll(theEntity.getParamsCoords());
} }
Collection<ResourceLink> existingResourceLinks = new ArrayList<>(); Collection<ResourceLink> existingResourceLinks = new ArrayList<>();
if (theEntity.isHasLinks()) { if (theEntity.isHasLinks()) {
existingResourceLinks.addAll(theEntity.getResourceLinks()); existingResourceLinks.addAll(theEntity.getResourceLinks());
} }
Collection<ResourceIndexedCompositeStringUnique> existingCompositeStringUniques = new ArrayList<>();
if (theEntity.isParamsCompositeStringUniquePresent()) {
existingCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
}
Set<ResourceIndexedSearchParamString> stringParams = null; Set<ResourceIndexedSearchParamString> stringParams = null;
Set<ResourceIndexedSearchParamToken> tokenParams = null; Set<ResourceIndexedSearchParamToken> tokenParams = null;
@ -1331,6 +1407,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
Set<ResourceIndexedSearchParamDate> dateParams = null; Set<ResourceIndexedSearchParamDate> dateParams = null;
Set<ResourceIndexedSearchParamUri> uriParams = null; Set<ResourceIndexedSearchParamUri> uriParams = null;
Set<ResourceIndexedSearchParamCoords> coordsParams = null; Set<ResourceIndexedSearchParamCoords> coordsParams = null;
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques = null;
Set<ResourceLink> links = null; Set<ResourceLink> links = null;
Set<String> populatedResourceLinkParameters = Collections.emptySet(); Set<String> populatedResourceLinkParameters = Collections.emptySet();
@ -1365,10 +1442,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
uriParams = extractSearchParamUri(theEntity, theResource); uriParams = extractSearchParamUri(theEntity, theResource);
coordsParams = extractSearchParamCoords(theEntity, theResource); coordsParams = extractSearchParamCoords(theEntity, theResource);
// ourLog.info("Indexing resource: {}", entity.getId());
ourLog.trace("Storing date indexes: {}", dateParams); ourLog.trace("Storing date indexes: {}", dateParams);
tokenParams = new HashSet<ResourceIndexedSearchParamToken>(); tokenParams = new HashSet<>();
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) { for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
if (next instanceof ResourceIndexedSearchParamToken) { if (next instanceof ResourceIndexedSearchParamToken) {
tokenParams.add((ResourceIndexedSearchParamToken) next); 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 * 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 * matching resource.
* resource.
*/ */
if (myConfig.isAllowInlineMatchUrlReferences()) { if (myConfig.isAllowInlineMatchUrlReferences()) {
FhirTerser terser = getContext().newTerser(); 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); 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 * 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(); ResourceLink nextExisting = existingLinkIter.next();
if (links.remove(nextExisting)) { if (links.remove(nextExisting)) {
existingLinkIter.remove(); 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); changed = populateResourceIntoEntity(theResource, theEntity, true);
theEntity.setUpdated(theUpdateTime); theEntity.setUpdated(theUpdateTime);
@ -1479,6 +1560,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theEntity.setParamsUriPopulated(uriParams.isEmpty() == false); theEntity.setParamsUriPopulated(uriParams.isEmpty() == false);
theEntity.setParamsCoords(coordsParams); theEntity.setParamsCoords(coordsParams);
theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false); theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false);
theEntity.setParamsCompositeStringUnique(compositeStringUniques);
theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false);
theEntity.setResourceLinks(links); theEntity.setResourceLinks(links);
theEntity.setHasLinks(links.isEmpty() == false); theEntity.setHasLinks(links.isEmpty() == false);
theEntity.setIndexStatus(INDEX_STATUS_INDEXED); theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
@ -1567,28 +1650,28 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
*/ */
if (thePerformIndexing) { if (thePerformIndexing) {
for (ResourceIndexedSearchParamString next : paramsString) { for (ResourceIndexedSearchParamString next : existingStringParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamString next : stringParams) { for (ResourceIndexedSearchParamString next : stringParams) {
myEntityManager.persist(next); myEntityManager.persist(next);
} }
for (ResourceIndexedSearchParamToken next : paramsToken) { for (ResourceIndexedSearchParamToken next : existingTokenParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamToken next : tokenParams) { for (ResourceIndexedSearchParamToken next : tokenParams) {
myEntityManager.persist(next); myEntityManager.persist(next);
} }
for (ResourceIndexedSearchParamNumber next : paramsNumber) { for (ResourceIndexedSearchParamNumber next : existingNumberParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamNumber next : numberParams) { for (ResourceIndexedSearchParamNumber next : numberParams) {
myEntityManager.persist(next); myEntityManager.persist(next);
} }
for (ResourceIndexedSearchParamQuantity next : paramsQuantity) { for (ResourceIndexedSearchParamQuantity next : existingQuantityParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamQuantity next : quantityParams) { for (ResourceIndexedSearchParamQuantity next : quantityParams) {
@ -1596,7 +1679,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
// Store date SP's // Store date SP's
for (ResourceIndexedSearchParamDate next : paramsDate) { for (ResourceIndexedSearchParamDate next : existingDateParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamDate next : dateParams) { for (ResourceIndexedSearchParamDate next : dateParams) {
@ -1604,7 +1687,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
// Store URI SP's // Store URI SP's
for (ResourceIndexedSearchParamUri next : paramsUri) { for (ResourceIndexedSearchParamUri next : existingUriParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamUri next : uriParams) { for (ResourceIndexedSearchParamUri next : uriParams) {
@ -1612,7 +1695,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
// Store Coords SP's // Store Coords SP's
for (ResourceIndexedSearchParamCoords next : paramsCoords) { for (ResourceIndexedSearchParamCoords next : existingCoordsParams) {
myEntityManager.remove(next); myEntityManager.remove(next);
} }
for (ResourceIndexedSearchParamCoords next : coordsParams) { for (ResourceIndexedSearchParamCoords next : coordsParams) {
@ -1629,6 +1712,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
// make sure links are indexed // make sure links are indexed
theEntity.setResourceLinks(links); theEntity.setResourceLinks(links);
// Store composite string uniques
for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) {
myEntityManager.remove(next);
}
for (ResourceIndexedCompositeStringUnique next : compositeStringUniques) {
myEntityManager.persist(next);
}
theEntity.toString(); theEntity.toString();
} // if thePerformIndexing } // if thePerformIndexing
@ -1693,7 +1784,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (!referencedId.getValue().contains("?")) { if (!referencedId.getValue().contains("?")) {
if (!validTypes.contains(referencedId.getResourceType())) { if (!validTypes.contains(referencedId.getResourceType())) {
throw new UnprocessableEntityException( 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");
} }
} }
} }
@ -1741,10 +1832,8 @@ 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 * 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. * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
* *
* @param theResource * @param theResource The resource that is about to be persisted
* The resource that is about to be persisted * @param theEntityToSave TODO
* @param theEntityToSave
* TODO
*/ */
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null; Object tag = null;
@ -1781,6 +1870,76 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
/**
* 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) { protected static boolean isValidPid(IIdType theId) {
if (theId == null || theId.getIdPart() == null) { if (theId == null || theId.getIdPart() == null) {
return false; return false;
@ -1990,7 +2149,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName); RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
if (paramDef == null) { if (paramDef == null) {
throw new InvalidRequestException( 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); 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) { public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
if (!theResourceName.equals(theEntity.getResourceType())) { if (!theResourceName.equals(theEntity.getResourceType())) {
throw new ResourceNotFoundException( 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.StopWatch;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; 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.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.api.QualifiedParamList;
@ -384,7 +387,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Notify JPA interceptors // Notify JPA interceptors
if (theRequestDetails != null) { if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theResource);
theRequestDetails.getRequestOperationCallback().resourceCreated(theResource); theRequestDetails.getRequestOperationCallback().resourceCreated(theResource);
} }
for (IServerInterceptor next : getConfig().getInterceptors()) { for (IServerInterceptor next : getConfig().getInterceptors()) {
@ -871,8 +873,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
//@formatter:off for (BaseTag next : new ArrayList<>(entity.getTags())) {
for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) && if (ObjectUtil.equals(next.getTag().getTagType(), theTagType) &&
ObjectUtil.equals(next.getTag().getSystem(), theScheme) && ObjectUtil.equals(next.getTag().getSystem(), theScheme) &&
ObjectUtil.equals(next.getTag().getCode(), theTerm)) { ObjectUtil.equals(next.getTag().getCode(), theTerm)) {
@ -880,7 +881,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.getTags().remove(next); entity.getTags().remove(next);
} }
} }
//@formatter:on
if (entity.getTags().isEmpty()) { if (entity.getTags().isEmpty()) {
entity.setHasTags(false); entity.setHasTags(false);

View File

@ -20,27 +20,28 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #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.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; 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 { public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams; 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 @Autowired
private FhirContext myCtx; private FhirContext myCtx;
@Autowired @Autowired
private Collection<IFhirResourceDao<?>> myDaos; private Collection<IFhirResourceDao<?>> myDaos;
@ -75,18 +76,119 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
return myBuiltInSearchParams.get(theResourceName); 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() { public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams; 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 @PostConstruct
public void 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) { for (IFhirResourceDao<?> nextDao : myDaos) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType()); RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
String nextResourceName = nextResDef.getName(); String nextResourceName = nextResDef.getName();
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>(); HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
resourceNameToSearchParams.put(nextResourceName, nameToParam); resourceNameToSearchParams.put(nextResourceName, nameToParam);
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
@ -97,4 +199,6 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams); myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams);
} }
protected abstract void refreshCacheIfNecessary();
} }

View File

@ -20,21 +20,27 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #L%
*/ */
import java.util.Map;
import ca.uhn.fhir.context.RuntimeSearchParam; 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 { public interface ISearchParamRegistry {
void forceRefresh(); void forceRefresh();
Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams();
Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName);
/** /**
* @return Returns {@literal null} if no match * @return Returns {@literal null} if no match
*/ */
RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName); 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. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.math.BigDecimal; import ca.uhn.fhir.context.*;
import java.math.MathContext; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import java.util.*; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import java.util.Map.Entry; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import javax.persistence.EntityManager; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import javax.persistence.TypedQuery; import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import javax.persistence.criteria.*; import ca.uhn.fhir.jpa.util.BaseIterator;
import javax.persistence.criteria.CriteriaBuilder.In; import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.*;
import org.apache.commons.lang3.*; 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.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -39,28 +60,20 @@ import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hl7.fhir.dstu3.model.BaseResource; 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 static org.apache.commons.lang3.StringUtils.*;
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;
/** /**
* The SearchBuilder is responsible for actually forming the SQL query that handles * 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 { public class SearchBuilder implements ISearchBuilder {
private static final List<Long> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<Long>()); 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 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 List<Long> myAlsoIncludePids;
private CriteriaBuilder myBuilder; private CriteriaBuilder myBuilder;
private BaseHapiFhirDao<?> myCallingDao; private BaseHapiFhirDao<?> myCallingDao;
@ -94,8 +108,8 @@ public class SearchBuilder implements ISearchBuilder {
* Constructor * Constructor
*/ */
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, IFulltextSearchSvc theFulltextSearchSvc, public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, IFulltextSearchSvc theFulltextSearchSvc,
BaseHapiFhirDao<?> theDao, BaseHapiFhirDao<?> theDao,
IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) { IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) {
myContext = theFhirContext; myContext = theFhirContext;
myEntityManager = theEntityManager; myEntityManager = theEntityManager;
myFulltextSearchSvc = theFulltextSearchSvc; myFulltextSearchSvc = theFulltextSearchSvc;
@ -135,7 +149,7 @@ public class SearchBuilder implements ISearchBuilder {
return; return;
} }
List<Predicate> codePredicates = new ArrayList<Predicate>(); List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) { for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr; IQueryParameterType params = nextOr;
Predicate p = createPredicateDate(params, theResourceName, theParamName, myBuilder, join); Predicate p = createPredicateDate(params, theResourceName, theParamName, myBuilder, join);
@ -358,7 +372,7 @@ public class SearchBuilder implements ISearchBuilder {
if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) { if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) {
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
resourceTypes = new ArrayList<Class<? extends IBaseResource>>(); resourceTypes = new ArrayList<>();
Set<String> targetTypes = param.getTargets(); Set<String> targetTypes = param.getTargets();
@ -457,7 +471,7 @@ public class SearchBuilder implements ISearchBuilder {
IQueryParameterType chainValue; IQueryParameterType chainValue;
if (remainingChain != null) { if (remainingChain != null) {
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { 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; continue;
} }
@ -785,7 +799,7 @@ public class SearchBuilder implements ISearchBuilder {
*/ */
ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName); ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName);
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName); Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName);
List<String> toFind = new ArrayList<String>(); List<String> toFind = new ArrayList<>();
for (String next : candidates) { for (String next : candidates) {
if (value.length() >= next.length()) { if (value.length() >= next.length()) {
if (value.substring(0, next.length()).equals(next)) { if (value.substring(0, next.length()).equals(next)) {
@ -798,12 +812,12 @@ public class SearchBuilder implements ISearchBuilder {
continue; 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) { } 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 { } 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); codePredicates.add(predicate);
} else { } else {
@ -817,7 +831,7 @@ public class SearchBuilder implements ISearchBuilder {
* just add a predicate that can never match * just add a predicate that can never match
*/ */
if (codePredicates.isEmpty()) { 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); myPredicates.add(predicate);
return; return;
} }
@ -840,32 +854,32 @@ public class SearchBuilder implements ISearchBuilder {
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) { private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) {
Predicate retVal = null; Predicate retVal = null;
switch (theParam.getParamType()) { switch (theParam.getParamType()) {
case STRING: { case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER); From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin); retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
break; break;
} }
case TOKEN: { case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER); From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin); retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin);
break; break;
} }
case DATE: { case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER); From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin); retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break; break;
} }
case QUANTITY: { case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER); From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin); retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break; break;
} }
case COMPOSITE: case COMPOSITE:
case HAS: case HAS:
case NUMBER: case NUMBER:
case REFERENCE: case REFERENCE:
case URI: case URI:
break; break;
} }
if (retVal == null) { if (retVal == null) {
@ -880,27 +894,27 @@ public class SearchBuilder implements ISearchBuilder {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null; Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) { switch (theType) {
case DATE: case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT); join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break; break;
case NUMBER: case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT); join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break; break;
case QUANTITY: case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT); join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break; break;
case REFERENCE: case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT); join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break; break;
case STRING: case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT); join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break; break;
case URI: case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT); join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break; break;
case TOKEN: case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT); join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break; break;
} }
JoinKey key = new JoinKey(theSearchParameterName, theType); JoinKey key = new JoinKey(theSearchParameterName, theType);
@ -938,8 +952,8 @@ public class SearchBuilder implements ISearchBuilder {
Predicate lb = null; Predicate lb = null;
if (lowerBound != null) { if (lowerBound != null) {
Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.<Date> get("myValueLow"), lowerBound); Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueLow"), lowerBound);
Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.<Date> get("myValueHigh"), lowerBound); Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom.<Date>get("myValueHigh"), lowerBound);
if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) { if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt; lb = gt;
} else { } else {
@ -949,8 +963,8 @@ public class SearchBuilder implements ISearchBuilder {
Predicate ub = null; Predicate ub = null;
if (upperBound != null) { if (upperBound != null) {
Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.<Date> get("myValueLow"), upperBound); Predicate gt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueLow"), upperBound);
Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.<Date> get("myValueHigh"), upperBound); Predicate lt = theBuilder.lessThanOrEqualTo(theFrom.<Date>get("myValueHigh"), upperBound);
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) { if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt; ub = lt;
} else { } else {
@ -968,45 +982,45 @@ public class SearchBuilder implements ISearchBuilder {
} }
private Predicate createPredicateNumeric(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, CriteriaBuilder builder, private Predicate createPredicateNumeric(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, CriteriaBuilder builder,
IQueryParameterType theParam, ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression<BigDecimal> thePath, IQueryParameterType theParam, ParamPrefixEnum thePrefix, BigDecimal theValue, final Expression<BigDecimal> thePath,
String invalidMessageName) { String invalidMessageName) {
Predicate num; Predicate num;
switch (thePrefix) { switch (thePrefix) {
case GREATERTHAN: case GREATERTHAN:
num = builder.gt(thePath, theValue); num = builder.gt(thePath, theValue);
break; break;
case GREATERTHAN_OR_EQUALS: case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue); num = builder.ge(thePath, theValue);
break; break;
case LESSTHAN: case LESSTHAN:
num = builder.lt(thePath, theValue); num = builder.lt(thePath, theValue);
break; break;
case LESSTHAN_OR_EQUALS: case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue); num = builder.le(thePath, theValue);
break; break;
case APPROXIMATE: case APPROXIMATE:
case EQUAL: case EQUAL:
case NOT_EQUAL: case NOT_EQUAL:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue); BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64); BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64); BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred; Predicate lowPred;
Predicate highPred; Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) { if (thePrefix != ParamPrefixEnum.NOT_EQUAL) {
lowPred = builder.ge(thePath.as(BigDecimal.class), low); lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high); highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred); num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high); ourLog.trace("Searching for {} <= val <= {}", low, high);
} else { } else {
// Prefix was "ne", so reverse it! // Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low); lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high); highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred); num = builder.or(lowPred, highPred);
} }
break; break;
default: default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext)); String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} }
if (theParamName == null) { if (theParamName == null) {
@ -1016,7 +1030,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private Predicate createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, private Predicate createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom) { From<?, ResourceIndexedSearchParamQuantity> theFrom) {
String systemValue; String systemValue;
String unitsValue; String unitsValue;
ParamPrefixEnum cmpValue; ParamPrefixEnum cmpValue;
@ -1069,7 +1083,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder, private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) { From<?, ResourceIndexedSearchParamString> theFrom) {
String rawSearchTerm; String rawSearchTerm;
if (theParameter instanceof TokenParam) { if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter; TokenParam id = (TokenParam) theParameter;
@ -1089,7 +1103,7 @@ public class SearchBuilder implements ISearchBuilder {
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" 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); 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, private Predicate createPredicateToken(IQueryParameterType theParameter, String theResourceName, String theParamName, CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom) { From<?, ResourceIndexedSearchParamToken> theFrom) {
String code; String code;
String system; String system;
TokenParamModifier modifier = null; TokenParamModifier modifier = null;
@ -1144,12 +1158,12 @@ public class SearchBuilder implements ISearchBuilder {
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException( 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) { if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException( 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(); myBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid; 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(); return new QueryIterator();
} }
@ -1370,7 +1423,7 @@ public class SearchBuilder implements ISearchBuilder {
/** /**
* @return Returns {@literal true} if any search parameter sorts were found, or false if * @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) { private boolean createSort(CriteriaBuilder theBuilder, Root<ResourceTable> theFrom, SortSpec theSort, List<Order> theOrders, List<Predicate> thePredicates) {
if (theSort == null || isBlank(theSort.getParamName())) { if (theSort == null || isBlank(theSort.getParamName())) {
@ -1411,43 +1464,43 @@ public class SearchBuilder implements ISearchBuilder {
JoinEnum joinType; JoinEnum joinType;
switch (param.getParamType()) { switch (param.getParamType()) {
case STRING: case STRING:
joinAttrName = "myParamsString"; joinAttrName = "myParamsString";
sortAttrName = new String[] { "myValueExact" }; sortAttrName = new String[]{"myValueExact"};
joinType = JoinEnum.STRING; joinType = JoinEnum.STRING;
break; break;
case DATE: case DATE:
joinAttrName = "myParamsDate"; joinAttrName = "myParamsDate";
sortAttrName = new String[] { "myValueLow" }; sortAttrName = new String[]{"myValueLow"};
joinType = JoinEnum.DATE; joinType = JoinEnum.DATE;
break; break;
case REFERENCE: case REFERENCE:
joinAttrName = "myResourceLinks"; joinAttrName = "myResourceLinks";
sortAttrName = new String[] { "myTargetResourcePid" }; sortAttrName = new String[]{"myTargetResourcePid"};
joinType = JoinEnum.REFERENCE; joinType = JoinEnum.REFERENCE;
break; break;
case TOKEN: case TOKEN:
joinAttrName = "myParamsToken"; joinAttrName = "myParamsToken";
sortAttrName = new String[] { "mySystem", "myValue" }; sortAttrName = new String[]{"mySystem", "myValue"};
joinType = JoinEnum.TOKEN; joinType = JoinEnum.TOKEN;
break; break;
case NUMBER: case NUMBER:
joinAttrName = "myParamsNumber"; joinAttrName = "myParamsNumber";
sortAttrName = new String[] { "myValue" }; sortAttrName = new String[]{"myValue"};
joinType = JoinEnum.NUMBER; joinType = JoinEnum.NUMBER;
break; break;
case URI: case URI:
joinAttrName = "myParamsUri"; joinAttrName = "myParamsUri";
sortAttrName = new String[] { "myUri" }; sortAttrName = new String[]{"myUri"};
joinType = JoinEnum.URI; joinType = JoinEnum.URI;
break; break;
case QUANTITY: case QUANTITY:
joinAttrName = "myParamsQuantity"; joinAttrName = "myParamsQuantity";
sortAttrName = new String[] { "myValue" }; sortAttrName = new String[]{"myValue"};
joinType = JoinEnum.QUANTITY; joinType = JoinEnum.QUANTITY;
break; break;
default: default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName()); 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, 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(); CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class); CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class); Root<ResourceTable> from = cq.from(ResourceTable.class);
@ -1549,7 +1602,7 @@ public class SearchBuilder implements ISearchBuilder {
@Override @Override
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, 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()) { if (theIncludePids.isEmpty()) {
ourLog.info("The include pids are empty"); ourLog.info("The include pids are empty");
// return; // 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 // Dupes will cause a crash later anyhow, but this is expensive so only do it
// when running asserts // 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>(); Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) { for (Long next : theIncludePids) {
@ -1572,7 +1625,7 @@ public class SearchBuilder implements ISearchBuilder {
* but this should work too. Sigh. * but this should work too. Sigh.
*/ */
int maxLoad = 800; 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) { for (int i = 0; i < pids.size(); i += maxLoad) {
int to = i + maxLoad; int to = i + maxLoad;
to = Math.min(to, pids.size()); to = Math.min(to, pids.size());
@ -1589,7 +1642,7 @@ public class SearchBuilder implements ISearchBuilder {
*/ */
@Override @Override
public HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, 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) { if (theMatches.size() == 0) {
return new HashSet<Long>(); return new HashSet<Long>();
} }
@ -1599,9 +1652,9 @@ public class SearchBuilder implements ISearchBuilder {
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid"; String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
Collection<Long> nextRoundMatches = theMatches; Collection<Long> nextRoundMatches = theMatches;
HashSet<Long> allAdded = new HashSet<Long>(); HashSet<Long> allAdded = new HashSet<>();
HashSet<Long> original = new HashSet<Long>(theMatches); HashSet<Long> original = new HashSet<>(theMatches);
ArrayList<Include> includes = new ArrayList<Include>(theRevIncludes); ArrayList<Include> includes = new ArrayList<>(theRevIncludes);
int roundCounts = 0; int roundCounts = 0;
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
@ -1610,10 +1663,10 @@ public class SearchBuilder implements ISearchBuilder {
do { do {
roundCounts++; roundCounts++;
HashSet<Long> pidsToInclude = new HashSet<Long>(); HashSet<Long> pidsToInclude = new HashSet<>();
Set<Long> nextRoundOmit = new HashSet<Long>(); 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(); Include nextInclude = iter.next();
if (nextInclude.isRecurse() == false) { if (nextInclude.isRecurse() == false) {
iter.remove(); iter.remove();
@ -1712,7 +1765,7 @@ public class SearchBuilder implements ISearchBuilder {
nextRoundMatches = pidsToInclude; nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound); } 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; return allAdded;
} }
@ -1788,49 +1841,49 @@ public class SearchBuilder implements ISearchBuilder {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
if (nextParamDef != null) { if (nextParamDef != null) {
switch (nextParamDef.getParamType()) { switch (nextParamDef.getParamType()) {
case DATE: case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateDate(theResourceName, theParamName, nextAnd); addPredicateDate(theResourceName, theParamName, nextAnd);
} }
break; break;
case QUANTITY: case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateQuantity(theResourceName, theParamName, nextAnd); addPredicateQuantity(theResourceName, theParamName, nextAnd);
} }
break; break;
case REFERENCE: case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateReference(theResourceName, theParamName, nextAnd); addPredicateReference(theResourceName, theParamName, nextAnd);
} }
break; break;
case STRING: case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateString(theResourceName, theParamName, nextAnd); addPredicateString(theResourceName, theParamName, nextAnd);
} }
break; break;
case TOKEN: case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateToken(theResourceName, theParamName, nextAnd); addPredicateToken(theResourceName, theParamName, nextAnd);
} }
break; break;
case NUMBER: case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateNumber(theResourceName, theParamName, nextAnd); addPredicateNumber(theResourceName, theParamName, nextAnd);
} }
break; break;
case COMPOSITE: case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd); addPredicateComposite(theResourceName, nextParamDef, nextAnd);
} }
break; break;
case URI: case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) { for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateUri(theResourceName, theParamName, nextAnd); addPredicateUri(theResourceName, theParamName, nextAnd);
} }
break; break;
case HAS: case HAS:
// should not happen // should not happen
break; break;
} }
} else { } else {
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) { 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) { private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp; IQueryParameterType qp;
switch (theParam.getParamType()) { switch (theParam.getParamType()) {
case DATE: case DATE:
qp = new DateParam(); qp = new DateParam();
break; break;
case NUMBER: case NUMBER:
qp = new NumberParam(); qp = new NumberParam();
break; break;
case QUANTITY: case QUANTITY:
qp = new QuantityParam(); qp = new QuantityParam();
break; break;
case STRING: case STRING:
qp = new StringParam(); qp = new StringParam();
break; break;
case TOKEN: case TOKEN:
qp = new TokenParam(); qp = new TokenParam();
break; break;
case COMPOSITE: case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf(); List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) { if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this."); throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
} }
IQueryParameterType leftParam = toParameterType(compositeOf.get(0)); IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1)); IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam); qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break; break;
case REFERENCE: case REFERENCE:
qp = new ReferenceParam(); qp = new ReferenceParam();
break; break;
default: default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType()); throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
} }
return qp; return qp;
} }
@ -1918,11 +1971,11 @@ public class SearchBuilder implements ISearchBuilder {
if (theLastUpdated != null) { if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) { if (theLastUpdated.getLowerBoundAsInstant() != null) {
ourLog.info("LastUpdated lower bound: {}", new InstantDt(theLastUpdated.getLowerBoundAsInstant())); 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); lastUpdatedPredicates.add(predicateLower);
} }
if (theLastUpdated.getUpperBoundAsInstant() != null) { 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); 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, private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From<?, ? extends ResourceLink> theFrom,
String theResourceType) { String theResourceType) {
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType); RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(theResourceType);
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName); RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName);
List<String> path = param.getPathsSplit(); List<String> path = param.getPathsSplit();
@ -1958,10 +2011,37 @@ public class SearchBuilder implements ISearchBuilder {
return resultList; 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) { static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[thePredicates.size()]); 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> { public class IncludesIterator extends BaseIterator<Long> implements Iterator<Long> {
private Iterator<Long> myCurrentIterator; 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 class QueryIterator extends BaseIterator<Long> implements Iterator<Long> {
private final Set<Long> myPidSet = new HashSet<Long>();
private boolean myFirst = true; private boolean myFirst = true;
private IncludesIterator myIncludesIterator; private IncludesIterator myIncludesIterator;
private Long myNext; private Long myNext;
private final Set<Long> myPidSet = new HashSet<Long>();
private Iterator<Long> myPreResultsIterator; private Iterator<Long> myPreResultsIterator;
private Iterator<Long> myResultsIterator; private Iterator<Long> myResultsIterator;
private SortSpec mySort; 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()) { if (nextValue.isEmpty()) {
continue; 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) { } else if (nextObject instanceof PeriodDt) {
PeriodDt nextValue = (PeriodDt) nextObject; PeriodDt nextValue = (PeriodDt) nextObject;
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
continue; continue;
} }
nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd()); nextEntity = new ResourceIndexedSearchParamDate(nextSpDef.getName(), nextValue.getStart(), nextValue.getEnd(), nextValue.getStartElement().getValueAsString());
} else { } else {
if (!multiType) { if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); 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 { 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() { public Set<Include> getRevIncludes() {
if (myRevIncludes == null) { if (myRevIncludes == null) {
myRevIncludes = new HashSet<Include>(); myRevIncludes = new HashSet<>();
} }
return myRevIncludes; return myRevIncludes;
} }
@ -210,6 +210,22 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
return mySort; 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 * If set, tells the server to load these results synchronously, and not to load
* more than X results * 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; 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.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -22,27 +42,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* #L% * #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> { public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class); 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) { protected void markAffectedResources(SearchParameter theResource) {
if (theResource != null) { if (theResource != null) {
String expression = theResource.getExpression(); String expression = theResource.getExpression();
final String resourceType = expression.substring(0, expression.indexOf('.')); if (isNotBlank(expression)) {
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", 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); TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() { int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override @Override
public Integer doInTransaction(TransactionStatus theStatus) { public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType); return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
} }
}); });
ourLog.info("Marked {} resources for reindexing", updatedCount); ourLog.info("Marked {} resources for reindexing", updatedCount);
}
} }
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
@ -126,43 +127,47 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
} }
String expression = theResource.getExpression(); 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"); throw new UnprocessableEntityException("SearchParameter.expression is missing");
}
if (ElementUtil.isEmpty(theResource.getBase())) { } else {
throw new UnprocessableEntityException("SearchParameter.base is missing");
}
expression = expression.trim(); expression = expression.trim();
theResource.setExpression(expression); theResource.setExpression(expression);
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression); String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression);
String allResourceName = null; String allResourceName = null;
for (String nextPath : expressionSplit) { for (String nextPath : expressionSplit) {
nextPath = nextPath.trim(); nextPath = nextPath.trim();
int dotIdx = nextPath.indexOf('.'); int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) { if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name"); 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");
} }
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()) { if (nextValue.isEmpty()) {
continue; 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) { } else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject; Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
continue; 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) { } else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject; Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
continue; continue;
} }
TreeSet<Date> dates = new TreeSet<Date>(); String firstValue = null;
TreeSet<Date> dates = new TreeSet<>();
for (DateTimeType nextEvent : nextValue.getEvent()) { for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) { if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue()); dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
} }
} }
if (dates.isEmpty()) { if (dates.isEmpty()) {
continue; 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) { } else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string // CarePlan.activitydate can be a string
continue; continue;

View File

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

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -24,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.r4.model.Bundle; 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.Meta;
import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -56,19 +58,21 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
protected void markAffectedResources(SearchParameter theResource) { protected void markAffectedResources(SearchParameter theResource) {
if (theResource != null) { if (theResource != null) {
String expression = theResource.getExpression(); String expression = theResource.getExpression();
final String resourceType = expression.substring(0, expression.indexOf('.')); if (isNotBlank(expression)) {
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", 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); TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() { int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
@Override @Override
public Integer doInTransaction(TransactionStatus theStatus) { public Integer doInTransaction(TransactionStatus theStatus) {
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType); return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
} }
}); });
ourLog.info("Marked {} resources for reindexing", updatedCount); ourLog.info("Marked {} resources for reindexing", updatedCount);
}
} }
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
@ -125,44 +129,52 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid: " + theResource.getStatusElement().getValueAsString()); 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())) { if (ElementUtil.isEmpty(theResource.getBase())) {
throw new UnprocessableEntityException("SearchParameter.base is missing"); throw new UnprocessableEntityException("SearchParameter.base is missing");
} }
expression = expression.trim(); String expression = theResource.getExpression();
theResource.setExpression(expression); if (theResource.getType() == Enumerations.SearchParamType.COMPOSITE && isBlank(expression)) {
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(expression); // this is ok
String allResourceName = null;
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
int dotIdx = nextPath.indexOf('.'); } else if (isBlank(expression)) {
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx); throw new UnprocessableEntityException("SearchParameter.expression is missing");
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
if (allResourceName == null) { } else {
allResourceName = resourceName;
} else { expression = expression.trim();
if (!allResourceName.equals(resourceName)) { theResource.setExpression(expression);
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
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()) { if (nextValue.isEmpty()) {
continue; 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) { } else if (nextObject instanceof Period) {
Period nextValue = (Period) nextObject; Period nextValue = (Period) nextObject;
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
continue; 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) { } else if (nextObject instanceof Timing) {
Timing nextValue = (Timing) nextObject; Timing nextValue = (Timing) nextObject;
if (nextValue.isEmpty()) { if (nextValue.isEmpty()) {
continue; continue;
} }
TreeSet<Date> dates = new TreeSet<Date>(); TreeSet<Date> dates = new TreeSet<>();
String firstValue = null;
for (DateTimeType nextEvent : nextValue.getEvent()) { for (DateTimeType nextEvent : nextValue.getEvent()) {
if (nextEvent.getValue() != null) { if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue()); dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
} }
} }
if (dates.isEmpty()) { if (dates.isEmpty()) {
continue; 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) { } else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string // CarePlan.activitydate can be a string
continue; continue;
@ -450,7 +454,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/ */
@Override @Override
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) { public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>(); HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<>();
String useSystem = null; String useSystem = null;
if (theResource instanceof CodeSystem) { if (theResource instanceof CodeSystem) {
@ -477,16 +481,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
List<String> systems = new ArrayList<String>(); List<String> systems = new ArrayList<String>();
List<String> codes = 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)) { for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) { if (nextObject == null) {

View File

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

View File

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

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.SequenceGenerator;
import javax.persistence.Table; 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.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@ -99,6 +101,11 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
return myId; return myId;
} }
@Override
public IQueryParameterType toQueryParameterType() {
return null;
}
public double getLatitude() { public double getLatitude() {
return myLatitude; return myLatitude;
} }

View File

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

View File

@ -20,18 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import java.math.BigDecimal; import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.persistence.Column; import ca.uhn.fhir.rest.param.NumberParam;
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 org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@ -40,12 +31,13 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge; import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField; import org.hibernate.search.annotations.NumericField;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge; import javax.persistence.*;
import java.math.BigDecimal;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@Entity @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", columnList = "RES_TYPE,SP_NAME,SP_VALUE"),
@Index(name = "IDX_SP_NUMBER_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_NUMBER_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_NUMBER_RESID", columnList = "RES_ID") @Index(name = "IDX_SP_NUMBER_RESID", columnList = "RES_ID")
@ -54,18 +46,16 @@ import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam { public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam {
private static final long serialVersionUID = 1L; 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) @Column(name = "SP_VALUE", nullable = true)
@Field @Field
@NumericField @NumericField
@FieldBridge(impl = BigDecimalNumericFieldBridge.class) @FieldBridge(impl = BigDecimalNumericFieldBridge.class)
public BigDecimal myValue; 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() { public ResourceIndexedSearchParamNumber() {
} }
@ -103,6 +93,10 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return myValue; return myValue;
} }
public void setValue(BigDecimal theValue) {
myValue = theValue;
}
@Override @Override
public int hashCode() { public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
@ -112,8 +106,9 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
return b.toHashCode(); return b.toHashCode();
} }
public void setValue(BigDecimal theValue) { @Override
myValue = theValue; public IQueryParameterType toQueryParameterType() {
return new NumberParam(myValue.toPlainString());
} }
@Override @Override

View File

@ -20,18 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import java.math.BigDecimal; import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge;
import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.persistence.Column; import ca.uhn.fhir.rest.param.QuantityParam;
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 org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@ -40,7 +31,8 @@ import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge; import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.NumericField; import org.hibernate.search.annotations.NumericField;
import ca.uhn.fhir.jpa.util.BigDecimalNumericFieldBridge; import javax.persistence.*;
import java.math.BigDecimal;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@ -56,26 +48,22 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
private static final int MAX_LENGTH = 200; private static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L; 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) @Column(name = "SP_SYSTEM", nullable = true, length = MAX_LENGTH)
@Field @Field
public String mySystem; public String mySystem;
@Column(name = "SP_UNITS", nullable = true, length = MAX_LENGTH) @Column(name = "SP_UNITS", nullable = true, length = MAX_LENGTH)
@Field @Field
public String myUnits; public String myUnits;
@Column(name = "SP_VALUE", nullable = true) @Column(name = "SP_VALUE", nullable = true)
@Field @Field
@NumericField @NumericField
@FieldBridge(impl = BigDecimalNumericFieldBridge.class) @FieldBridge(impl = BigDecimalNumericFieldBridge.class)
public BigDecimal myValue; 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() { public ResourceIndexedSearchParamQuantity() {
// nothing // nothing
@ -118,14 +106,26 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return mySystem; return mySystem;
} }
public void setSystem(String theSystem) {
mySystem = theSystem;
}
public String getUnits() { public String getUnits() {
return myUnits; return myUnits;
} }
public void setUnits(String theUnits) {
myUnits = theUnits;
}
public BigDecimal getValue() { public BigDecimal getValue() {
return myValue; return myValue;
} }
public void setValue(BigDecimal theValue) {
myValue = theValue;
}
@Override @Override
public int hashCode() { public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
@ -137,16 +137,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
return b.toHashCode(); return b.toHashCode();
} }
public void setSystem(String theSystem) { @Override
mySystem = theSystem; public IQueryParameterType toQueryParameterType() {
} return new QuantityParam(null, getValue(), getSystem(), getUnits());
public void setUnits(String theUnits) {
myUnits = theUnits;
}
public void setValue(BigDecimal theValue) {
myValue = theValue;
} }
@Override @Override

View File

@ -20,31 +20,17 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import javax.persistence.Column; import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.persistence.Embeddable; import ca.uhn.fhir.rest.param.StringParam;
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 org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.*;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.ContainedIn; import javax.persistence.*;
import org.hibernate.search.annotations.Field; import javax.persistence.Index;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@ -109,13 +95,13 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Id @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") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; private Long myId;
@ManyToOne(optional = false) @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 @ContainedIn
private ResourceTable myResourceTable; private ResourceTable myResourceTable;
@ -169,10 +155,24 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return myValueExact; 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() { public String getValueNormalized() {
return myValueNormalized; 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 @Override
public int hashCode() { public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
@ -182,18 +182,9 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
return b.toHashCode(); return b.toHashCode();
} }
public void setValueExact(String theValueExact) { @Override
if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) { public IQueryParameterType toQueryParameterType() {
throw new IllegalArgumentException("Value is too long: " + theValueExact.length()); return new StringParam(getValueExact());
}
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 @Override

View File

@ -20,16 +20,8 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import javax.persistence.Column; import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.persistence.Embeddable; import ca.uhn.fhir.rest.param.TokenParam;
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 org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -37,6 +29,8 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@ -52,21 +46,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public static final int MAX_LENGTH = 200; public static final int MAX_LENGTH = 200;
private static final long serialVersionUID = 1L; 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 @Id
@SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN") @SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@Column(name = "SP_ID") @Column(name = "SP_ID")
private Long myId; 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() { public ResourceIndexedSearchParamToken() {
} }
@ -105,10 +96,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return mySystem; return mySystem;
} }
public void setSystem(String theSystem) {
mySystem = StringUtils.defaultIfBlank(theSystem, null);
}
public String getValue() { public String getValue() {
return myValue; return myValue;
} }
public void setValue(String theValue) {
myValue = StringUtils.defaultIfBlank(theValue, null);
}
@Override @Override
public int hashCode() { public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
@ -119,12 +118,9 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return b.toHashCode(); return b.toHashCode();
} }
public void setSystem(String theSystem) { @Override
mySystem = StringUtils.defaultIfBlank(theSystem, null); public IQueryParameterType toQueryParameterType() {
} return new TokenParam(getSystem(), getValue());
public void setValue(String theValue) {
myValue = StringUtils.defaultIfBlank(theValue, null);
} }
@Override @Override

View File

@ -20,22 +20,16 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import javax.persistence.Column; import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.persistence.Embeddable; import ca.uhn.fhir.rest.param.UriParam;
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 org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
import javax.persistence.*;
//@formatter:off //@formatter:off
@Embeddable @Embeddable
@Entity @Entity
@ -54,16 +48,14 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
public static final int MAX_LENGTH = 255; public static final int MAX_LENGTH = 255;
private static final long serialVersionUID = 1L; 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) @Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
@Field() @Field()
public String myUri; 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() { public ResourceIndexedSearchParamUri() {
} }
@ -101,6 +93,10 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return myUri; return myUri;
} }
public void setUri(String theUri) {
myUri = StringUtils.defaultIfBlank(theUri, null);
}
@Override @Override
public int hashCode() { public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
@ -110,8 +106,9 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
return b.toHashCode(); return b.toHashCode();
} }
public void setUri(String theUri) { @Override
myUri = StringUtils.defaultIfBlank(theUri, null); public IQueryParameterType toQueryParameterType() {
return new UriParam(getUri());
} }
@Override @Override

View File

@ -19,27 +19,11 @@ package ca.uhn.fhir.jpa.entity;
* limitations under the License. * limitations under the License.
* #L% * #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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.lucene.analysis.core.LowerCaseFilterFactory; import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
@ -52,39 +36,34 @@ import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory;
import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory; import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
import org.apache.lucene.analysis.standard.StandardFilterFactory; import org.apache.lucene.analysis.standard.StandardFilterFactory;
import org.apache.lucene.analysis.standard.StandardTokenizerFactory; import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.*;
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.Parameter; 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 javax.persistence.*;
import ca.uhn.fhir.model.primitive.IdDt; import javax.persistence.Index;
import ca.uhn.fhir.rest.api.Constants; import java.io.Serializable;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 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 //@formatter:off
@Indexed(interceptor=IndexNonDeletedInterceptor.class) @Indexed(interceptor = IndexNonDeletedInterceptor.class)
@Entity @Entity
@Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes= { @Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes = {
@Index(name = "IDX_RES_DATE", columnList="RES_UPDATED"), @Index(name = "IDX_RES_DATE", columnList = "RES_UPDATED"),
@Index(name = "IDX_RES_LANG", columnList="RES_TYPE,RES_LANGUAGE"), @Index(name = "IDX_RES_LANG", columnList = "RES_TYPE,RES_LANGUAGE"),
@Index(name = "IDX_RES_PROFILE", columnList="RES_PROFILE"), @Index(name = "IDX_RES_PROFILE", columnList = "RES_PROFILE"),
@Index(name = "IDX_RES_TYPE", columnList="RES_TYPE"), @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList="SP_INDEX_STATUS") @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS")
}) })
@AnalyzerDefs({ @AnalyzerDefs({
@AnalyzerDef(name = "autocompleteEdgeAnalyzer", @AnalyzerDef(name = "autocompleteEdgeAnalyzer",
tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= { tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params = {
@Parameter(name="pattern", value="(.*)"), @Parameter(name = "pattern", value = "(.*)"),
@Parameter(name="group", value="1") @Parameter(name = "group", value = "1")
}), }),
filters = { filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class),
@ -95,15 +74,15 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
}), }),
}), }),
@AnalyzerDef(name = "autocompletePhoneticAnalyzer", @AnalyzerDef(name = "autocompletePhoneticAnalyzer",
tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class), tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = { filters = {
@TokenFilterDef(factory=StandardFilterFactory.class), @TokenFilterDef(factory = StandardFilterFactory.class),
@TokenFilterDef(factory=StopFilterFactory.class), @TokenFilterDef(factory = StopFilterFactory.class),
@TokenFilterDef(factory=PhoneticFilterFactory.class, params = { @TokenFilterDef(factory = PhoneticFilterFactory.class, params = {
@Parameter(name="encoder", value="DoubleMetaphone") @Parameter(name = "encoder", value = "DoubleMetaphone")
}), }),
@TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = { @TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
@Parameter(name="language", value="English") @Parameter(name = "language", value = "English")
}) })
}), }),
@AnalyzerDef(name = "autocompleteNGramAnalyzer", @AnalyzerDef(name = "autocompleteNGramAnalyzer",
@ -125,21 +104,18 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = { filters = {
}) })
} }
) )
//@formatter:on //@formatter:on
public class ResourceTable extends BaseHasResource implements Serializable { 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_LANGUAGE_LENGTH = 20;
private static final int MAX_PROFILE_LENGTH = 200; private static final int MAX_PROFILE_LENGTH = 200;
static final int RESTYPE_LEN = 30;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
*/ */
//@formatter:off
@Transient() @Transient()
@Fields({ @Fields({
@Field(name = "myContentText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @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 = "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")) @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; private String myContentText;
@Column(name = "HASH_SHA256", length=64, nullable=true) @Column(name = "HASH_SHA256", length = 64, nullable = true)
private String myHashSha256; private String myHashSha256;
@Column(name = "SP_HAS_LINKS") @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) @Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true)
private String myProfile; 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) @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
@IndexedEmbedded() @IndexedEmbedded()
private Collection<ResourceLink> myResourceLinks; private Collection<ResourceLink> myResourceLinks;
@Column(name = "RES_TYPE", length = RESTYPE_LEN) @Column(name = "RES_TYPE", length = RESTYPE_LEN)
@Field @Field
private String myResourceType; private String myResourceType;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Collection<SearchParamPresent> mySearchParamPresents; private Collection<SearchParamPresent> mySearchParamPresents;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ResourceTag> myTags; private Set<ResourceTag> myTags;
@Transient @Transient
private transient boolean myUnchangedInCurrentOperation; private transient boolean myUnchangedInCurrentOperation;
@Column(name = "RES_VER") @Column(name = "RES_VER")
private long myVersion; private long myVersion;
@ -264,11 +237,19 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myHashSha256; return myHashSha256;
} }
public void setHashSha256(String theHashSha256) {
myHashSha256 = theHashSha256;
}
@Override @Override
public Long getId() { public Long getId() {
return myId; return myId;
} }
public void setId(Long theId) {
myId = theId;
}
@Override @Override
public IdDt getIdDt() { public IdDt getIdDt() {
if (getForcedId() == null) { if (getForcedId() == null) {
@ -283,157 +264,14 @@ public class ResourceTable extends BaseHasResource implements Serializable {
return myIndexStatus; 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) { public void setIndexStatus(Long theIndexStatus) {
myIndexStatus = theIndexStatus; myIndexStatus = theIndexStatus;
} }
public String getLanguage() {
return myLanguage;
}
public void setLanguage(String theLanguage) { public void setLanguage(String theLanguage) {
if (defaultString(theLanguage).length() > MAX_LANGUAGE_LENGTH) { if (defaultString(theLanguage).length() > MAX_LANGUAGE_LENGTH) {
throw new UnprocessableEntityException("Language exceeds maximum length of " + MAX_LANGUAGE_LENGTH + " chars: " + theLanguage); 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; myLanguage = theLanguage;
} }
public void setNarrativeTextParsedIntoWords(String theNarrativeText) { public Collection<ResourceIndexedCompositeStringUnique> getParamsCompositeStringUnique() {
myNarrativeText = theNarrativeText; 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) { public void setParamsCoords(Collection<ResourceIndexedSearchParamCoords> theParamsCoords) {
@ -453,8 +305,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsCoords().addAll(theParamsCoords); getParamsCoords().addAll(theParamsCoords);
} }
public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) { public Collection<ResourceIndexedSearchParamDate> getParamsDate() {
myParamsCoordsPopulated = theParamsCoordsPopulated; if (myParamsDate == null) {
myParamsDate = new ArrayList<>();
}
return myParamsDate;
} }
public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) { public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) {
@ -465,8 +320,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsDate().addAll(theParamsDate); getParamsDate().addAll(theParamsDate);
} }
public void setParamsDatePopulated(boolean theParamsDatePopulated) { public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() {
myParamsDatePopulated = theParamsDatePopulated; if (myParamsNumber == null) {
myParamsNumber = new ArrayList<>();
}
return myParamsNumber;
} }
public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) { public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) {
@ -477,8 +335,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsNumber().addAll(theNumberParams); getParamsNumber().addAll(theNumberParams);
} }
public void setParamsNumberPopulated(boolean theParamsNumberPopulated) { public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() {
myParamsNumberPopulated = theParamsNumberPopulated; if (myParamsQuantity == null) {
myParamsQuantity = new ArrayList<>();
}
return myParamsQuantity;
} }
public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) { public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) {
@ -489,8 +350,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsQuantity().addAll(theQuantityParams); getParamsQuantity().addAll(theQuantityParams);
} }
public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) { public Collection<ResourceIndexedSearchParamString> getParamsString() {
myParamsQuantityPopulated = theParamsQuantityPopulated; if (myParamsString == null) {
myParamsString = new ArrayList<>();
}
return myParamsString;
} }
public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) { public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) {
@ -501,8 +365,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsString().addAll(theParamsString); getParamsString().addAll(theParamsString);
} }
public void setParamsStringPopulated(boolean theParamsStringPopulated) { public Collection<ResourceIndexedSearchParamToken> getParamsToken() {
myParamsStringPopulated = theParamsStringPopulated; if (myParamsToken == null) {
myParamsToken = new ArrayList<>();
}
return myParamsToken;
} }
public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) { public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) {
@ -513,8 +380,11 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsToken().addAll(theParamsToken); getParamsToken().addAll(theParamsToken);
} }
public void setParamsTokenPopulated(boolean theParamsTokenPopulated) { public Collection<ResourceIndexedSearchParamUri> getParamsUri() {
myParamsTokenPopulated = theParamsTokenPopulated; if (myParamsUri == null) {
myParamsUri = new ArrayList<>();
}
return myParamsUri;
} }
public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) { public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) {
@ -525,8 +395,8 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getParamsUri().addAll(theParamsUri); getParamsUri().addAll(theParamsUri);
} }
public void setParamsUriPopulated(boolean theParamsUriPopulated) { public String getProfile() {
myParamsUriPopulated = theParamsUriPopulated; return myProfile;
} }
public void setProfile(String theProfile) { public void setProfile(String theProfile) {
@ -536,6 +406,13 @@ public class ResourceTable extends BaseHasResource implements Serializable {
myProfile = theProfile; myProfile = theProfile;
} }
public Collection<ResourceLink> getResourceLinks() {
if (myResourceLinks == null) {
myResourceLinks = new ArrayList<>();
}
return myResourceLinks;
}
public void setResourceLinks(Collection<ResourceLink> theLinks) { public void setResourceLinks(Collection<ResourceLink> theLinks) {
if (!isHasLinks() && theLinks.isEmpty()) { if (!isHasLinks() && theLinks.isEmpty()) {
return; return;
@ -544,10 +421,112 @@ public class ResourceTable extends BaseHasResource implements Serializable {
getResourceLinks().addAll(theLinks); getResourceLinks().addAll(theLinks);
} }
@Override
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) { public void setResourceType(String theResourceType) {
myResourceType = 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 * 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 * and was not re-saved in the database
@ -556,8 +535,12 @@ public class ResourceTable extends BaseHasResource implements Serializable {
myUnchangedInCurrentOperation = theUnchangedInCurrentOperation; myUnchangedInCurrentOperation = theUnchangedInCurrentOperation;
} }
public void setVersion(long theVersion) { public void setContentTextParsedIntoWords(String theContentText) {
myVersion = theVersion; myContentText = theContentText;
}
public void setNarrativeTextParsedIntoWords(String theNarrativeText) {
myNarrativeText = theNarrativeText;
} }
public ResourceHistoryTable toHistory(ResourceHistoryTable theResourceHistoryTable) { 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) { } catch (Exception e) {
ourLog.error("Exceeded maximum wait for connection", e); ourLog.error("Exceeded maximum wait for connection", e);
logGetConnectionStackTrace(); logGetConnectionStackTrace();
if ("true".equals(System.getProperty("ci"))) { // if ("true".equals(System.getProperty("ci"))) {
fail("Exceeded maximum wait for connection: " + e.toString()); fail("Exceeded maximum wait for connection: " + e.toString());
} // }
System.exit(1); // System.exit(1);
retVal = null; 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 " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamUri.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 " + 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 " + ResourceLink.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchResult.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + SearchResult.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchInclude.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 JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao; private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
// @Autowired @Autowired
// protected HapiWorkerContext myHapiWorkerContext; protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired @Autowired
@Qualifier("myAllergyIntoleranceDaoDstu3") @Qualifier("myAllergyIntoleranceDaoDstu3")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao; 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 static org.junit.Assert.assertEquals;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation;
@ -53,6 +55,16 @@ public class SearchParamExtractorDstu3Test {
return sps; 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 @Override
public void forceRefresh() { public void forceRefresh() {
// nothing // 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.TestUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
//@formatter:off
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {TestR4Config.class}) @ContextConfiguration(classes= {TestR4Config.class})
//@formatter:on
public abstract class BaseJpaR4Test extends BaseJpaTest { public abstract class BaseJpaR4Test extends BaseJpaTest {
private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4; private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao; private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
// @Autowired @Autowired
// protected HapiWorkerContext myHapiWorkerContext; protected IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired @Autowired
@Qualifier("myAllergyIntoleranceDaoR4") @Qualifier("myAllergyIntoleranceDaoR4")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao; 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 java.util.*;
import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation;
@ -45,6 +46,16 @@ public class SearchParamExtractorR4Test {
return sps; 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 @Override
public void forceRefresh() { public void forceRefresh() {
// nothing // nothing

View File

@ -49,7 +49,7 @@ public interface IServerOperationInterceptor extends IServerInterceptor {
* User code may call this method to indicate to an interceptor that * User code may call this method to indicate to an interceptor that
* a resource is being updated * 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 @Deprecated
void resourceUpdated(RequestDetails theRequest, IBaseResource theResource); 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 HAPI FHIR 2.5, but this was not documented. This variable has now been
documented as a part of the available features. documented as a part of the available features.
</action> </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>
<release version="2.5" date="2017-06-08"> <release version="2.5" date="2017-06-08">
<action type="fix"> <action type="fix">