diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index 03771b9ebfd..90540c05f0c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -40,6 +40,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; @Configuration @EnableScheduling @@ -63,18 +65,23 @@ public class BaseConfig implements SchedulingConfigurer { DatabaseBackedPagingProvider retVal = new DatabaseBackedPagingProvider(); return retVal; } - - @Bean(autowire=Autowire.BY_TYPE) - public IStaleSearchDeletingSvc staleSearchDeletingSvc() { - return new StaleSearchDeletingSvcImpl(); - } - + @Bean() public ScheduledExecutorFactoryBean scheduledExecutorService() { ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); b.setPoolSize(5); return b; } + + @Bean + public ISearchParamPresenceSvc searchParamPresenceSvc() { + return new SearchParamPresenceSvcImpl(); + } + + @Bean(autowire=Autowire.BY_TYPE) + public IStaleSearchDeletingSvc staleSearchDeletingSvc() { + return new StaleSearchDeletingSvcImpl(); + } @Bean public TaskScheduler taskScheduler() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index d3ad152b342..0b9f21725aa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -85,28 +85,9 @@ import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.entity.BaseHasResource; -import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.entity.BaseTag; -import ca.uhn.fhir.jpa.entity.ForcedId; -import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTag; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; -import ca.uhn.fhir.jpa.entity.ResourceLink; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.ResourceTag; -import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchTypeEnum; -import ca.uhn.fhir.jpa.entity.TagDefinition; -import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.entity.*; 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.IQueryParameterAnd; @@ -197,7 +178,6 @@ public abstract class BaseHapiFhirDao implements IDao { protected EntityManager myEntityManager; @Autowired protected IForcedIdDao myForcedIdDao; - @Autowired(required = false) protected IFulltextSearchSvc myFulltextSearchSvc; @Autowired @@ -220,6 +200,9 @@ public abstract class BaseHapiFhirDao implements IDao { @Autowired private ISearchParamExtractor mySearchParamExtractor; + @Autowired + private ISearchParamPresenceSvc mySearchParamPresenceSvc; + @Autowired private ISearchParamRegistry mySearchParamRegistry; @@ -258,14 +241,19 @@ public abstract class BaseHapiFhirDao implements IDao { return InstantDt.withCurrentTime(); } + /** + * @return Returns a set containing all of the parameter names that + * were found to have a value + */ @SuppressWarnings("unchecked") - protected void extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set theLinks, Date theUpdateTime) { + protected Set extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set theLinks, Date theUpdateTime) { + HashSet retVal = new HashSet(); /* * For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing.. */ if (theResource instanceof IBaseBundle) { - return; + return Collections.emptySet(); } RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); @@ -318,6 +306,8 @@ public abstract class BaseHapiFhirDao implements IDao { } } + retVal.add(nextSpDef.getName()); + if (isLogicalReference(nextId)) { ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); if (theLinks.add(resourceLink)) { @@ -401,6 +391,7 @@ public abstract class BaseHapiFhirDao implements IDao { theEntity.setHasLinks(theLinks.size() > 0); + return retVal; } protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) { @@ -978,9 +969,9 @@ public abstract class BaseHapiFhirDao implements IDao { * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time. * * @param theEntity - * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) + * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * @param theTag - * The tag + * The tag */ protected void postPersist(ResourceTable theEntity, T theResource) { // nothing @@ -990,9 +981,9 @@ public abstract class BaseHapiFhirDao implements IDao { * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database * * @param theEntity - * The resource + * The resource * @param theResource - * The resource being persisted + * The resource being persisted */ protected void postUpdate(ResourceTable theEntity, T theResource) { // nothing @@ -1053,9 +1044,9 @@ public abstract class BaseHapiFhirDao implements IDao { *

* * @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 + * The tag * @return Retturns true if the tag should be removed */ protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { @@ -1343,7 +1334,7 @@ public abstract class BaseHapiFhirDao implements IDao { } links = new HashSet(); - extractResourceLinks(theEntity, theResource, links, theUpdateTime); + Set 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 @@ -1383,6 +1374,29 @@ public abstract class BaseHapiFhirDao implements IDao { theEntity.setIndexStatus(INDEX_STATUS_INDEXED); populateFullTextFields(theResource, theEntity); + /* + * Update the "search param present" table which is used for the + * ?foo:missing=true queries + */ + Map presentSearchParams = new HashMap(); + for (String nextKey : populatedResourceLinkParameters) { + presentSearchParams.put(nextKey, Boolean.TRUE); + } + updateSearchParamPresent(presentSearchParams, stringParams); + updateSearchParamPresent(presentSearchParams, tokenParams); + updateSearchParamPresent(presentSearchParams, numberParams); + updateSearchParamPresent(presentSearchParams, quantityParams); + updateSearchParamPresent(presentSearchParams, dateParams); + updateSearchParamPresent(presentSearchParams, uriParams); + updateSearchParamPresent(presentSearchParams, coordsParams); + Set activeSearchParamNames = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).keySet(); + for (String nextSpName : activeSearchParamNames) { + if (!presentSearchParams.containsKey(nextSpName)) { + presentSearchParams.put(nextSpName, Boolean.FALSE); + } + } + mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams); + } else { populateResourceIntoEntity(theResource, theEntity); @@ -1502,6 +1516,12 @@ public abstract class BaseHapiFhirDao implements IDao { return theEntity; } + private void updateSearchParamPresent(Map presentSearchParams, Set params) { + for (BaseResourceIndexedSearchParam nextSearchParam : params) { + presentSearchParams.put(nextSearchParam.getParamName(), Boolean.TRUE); + } + } + protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable entity, Date theDeletedTimestampOrNull, Date theUpdateTime) { return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime); } @@ -1594,9 +1614,9 @@ public abstract class BaseHapiFhirDao implements IDao { * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check. * * @param theResource - * The resource that is about to be persisted + * The resource that is about to be persisted * @param theEntityToSave - * TODO + * TODO */ protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { Object tag = null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index a80311275a8..c7ab8d61158 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -106,6 +106,8 @@ public class SearchBuilder { private CriteriaBuilder myBuilder; + private CriteriaQuery myResourceTableQuery; + public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theFulltextSearchSvc, ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) { @@ -317,7 +319,7 @@ public class SearchBuilder { return missingFalse; } - private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, Root from, List codePredicates, + private boolean addPredicateMissingFalseIfPresentForResourceLink(CriteriaBuilder theBuilder, String theParamName, From from, List codePredicates, IQueryParameterType nextOr) { boolean missingFalse = false; if (nextOr.getMissing() != null) { @@ -456,17 +458,14 @@ public class SearchBuilder { return; } - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceLink.class); - cq.select(from.get("mySourceResourcePid").as(Long.class)); + Join join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT); List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; - if (addPredicateMissingFalseIfPresentForResourceLink(builder, theParamName, from, codePredicates, nextOr)) { + if (addPredicateMissingFalseIfPresentForResourceLink(myBuilder, theParamName, join, codePredicates, nextOr)) { continue; } @@ -481,7 +480,7 @@ public class SearchBuilder { dt = dt.toUnqualified(); } else { ourLog.debug("Searching for resource link with target URL: {}", dt.getValue()); - Predicate eq = builder.equal(from.get("myTargetResourceUrl"), dt.getValue()); + Predicate eq = myBuilder.equal(join.get("myTargetResourceUrl"), dt.getValue()); codePredicates.add(eq); continue; } @@ -491,12 +490,12 @@ public class SearchBuilder { try { targetPid = myCallingDao.translateForcedIdToPids(dt); } catch (ResourceNotFoundException e) { - doSetPids(new ArrayList()); - return; + // Use a PID that will never exist + targetPid = Collections.singletonList(-1L); } for (Long next : targetPid) { ourLog.debug("Searching for resource link with target PID: {}", next); - Predicate eq = builder.equal(from.get("myTargetResourcePid"), next); + Predicate eq = myBuilder.equal(join.get("myTargetResourcePid"), next); codePredicates.add(eq); } } else { @@ -586,13 +585,35 @@ public class SearchBuilder { } foundChainMatch = true; +// Set pids = dao.searchForIds(chain, chainValue); - Set pids = dao.searchForIds(chain, chainValue); - if (pids.isEmpty()) { - continue; - } + Subquery subQ = myResourceTableQuery.subquery(Long.class); + Root subQfrom = subQ.from(ResourceTable.class); + subQ.select(subQfrom.get("myId").as(Long.class)); + + List> andOrParams = new ArrayList>(); + andOrParams.add(Collections.singletonList(chainValue)); - Predicate eq = from.get("myTargetResourcePid").in(pids); + /* + * We're doing a chain call, so push the current query root + * and predicate list down and put new ones at the top of the + * stack and run a subuery + */ + Root stackRoot = myResourceTableRoot; + ArrayList stackPredicates = myPredicates; + myResourceTableRoot = subQfrom; + myPredicates = new ArrayList(); + + searchForIdsWithAndOr(chain, andOrParams); + subQ.where(toArray(myPredicates)); + + /* + * Pop the old query root and predicate list back + */ + myResourceTableRoot = stackRoot; + myPredicates = stackPredicates; + + Predicate eq = join.get("myTargetResourcePid").in(subQ); codePredicates.add(eq); } @@ -608,16 +629,7 @@ public class SearchBuilder { } - List predicates = new ArrayList(); - predicates.add(createResourceLinkPathPredicate(theParamName, from)); - predicates.add(builder.or(toArray(codePredicates))); - createPredicateResourceId(builder, cq, predicates, from.get("mySourceResourcePid").as(Long.class)); - createPredicateLastUpdatedForResourceLink(builder, from, predicates); - - cq.where(builder.and(toArray(predicates))); - - TypedQuery q = myEntityManager.createQuery(cq); - doSetPids(new HashSet(q.getResultList())); + myPredicates.add(myBuilder.or(toArray(codePredicates))); } private void addPredicateString(String theParamName, List theList) { @@ -713,8 +725,6 @@ public class SearchBuilder { continue; } - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - boolean paramInverted = false; List> tokens = Lists.newArrayList(); for (IQueryParameterType nextOrParams : nextAndParams) { @@ -745,19 +755,11 @@ public class SearchBuilder { if (paramInverted) { ourLog.debug("Searching for _tag:not"); - CriteriaQuery cq = builder.createQuery(Long.class); - Root newFrom = cq.from(ResourceTable.class); - - Subquery subQ = cq.subquery(Long.class); + Subquery subQ = myResourceTableQuery.subquery(Long.class); Root subQfrom = subQ.from(ResourceTag.class); subQ.select(subQfrom.get("myResourceId").as(Long.class)); - cq.select(newFrom.get("myId").as(Long.class)); - - List andPredicates = new ArrayList(); - andPredicates = new ArrayList(); - andPredicates.add(builder.equal(newFrom.get("myResourceType"), myResourceName)); - andPredicates.add(builder.not(builder.in(newFrom.get("myId")).value(subQ))); + myPredicates.add(myBuilder.not(myBuilder.in(myResourceTableRoot.get("myId")).value(subQ))); Subquery defJoin = subQ.subquery(Long.class); Root defJoinFrom = defJoin.from(TagDefinition.class); @@ -765,44 +767,18 @@ public class SearchBuilder { subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin)); - List orPredicates = createPredicateTagList(defJoinFrom, builder, tagType, tokens); + List orPredicates = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens); defJoin.where(toArray(orPredicates)); - cq.where(toArray(andPredicates)); - - TypedQuery q = myEntityManager.createQuery(cq); - Set pids = new HashSet(q.getResultList()); - doSetPids(pids); continue; } - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceTag.class); - List andPredicates = new ArrayList(); - andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName)); - From defJoin = from.join("myTag"); + Join tagJoin = myResourceTableRoot.join("myTags", JoinType.LEFT); + From defJoin = tagJoin.join("myTag"); + + List orPredicates = createPredicateTagList(defJoin, myBuilder, tagType, tokens); + myPredicates.add(myBuilder.or(toArray(orPredicates))); - Join defJoin2 = from.join("myResource"); - - Predicate notDeletedPredicatePrediate = builder.isNull(defJoin2.get("myDeleted")); - andPredicates.add(notDeletedPredicatePrediate); - - List orPredicates = createPredicateTagList(defJoin, builder, tagType, tokens); - andPredicates.add(builder.or(toArray(orPredicates))); - - if (theLastUpdated != null) { - andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin2)); - } - - createPredicateResourceId(builder, cq, andPredicates, from.get("myResourceId").as(Long.class)); - Predicate masterCodePredicate = builder.and(toArray(andPredicates)); - - cq.select(from.get("myResourceId").as(Long.class)); - cq.where(masterCodePredicate); - - TypedQuery q = myEntityManager.createQuery(cq); - Set pids = new HashSet(q.getResultList()); - doSetPids(pids); } } @@ -851,16 +827,13 @@ public class SearchBuilder { return; } - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceIndexedSearchParamUri.class); - cq.select(from.get("myResourcePid").as(Long.class)); + Join join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT); List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; - if (addPredicateMissingFalseIfPresent(builder, theParamName, from, codePredicates, nextOr)) { + if (addPredicateMissingFalseIfPresent(myBuilder, theParamName, join, codePredicates, nextOr)) { continue; } @@ -872,7 +845,7 @@ public class SearchBuilder { continue; } - Path fromObj = from.get("myUri"); + Path fromObj = join.get("myUri"); Predicate predicate; if (param.getQualifier() == UriParamQualifierEnum.ABOVE) { @@ -905,9 +878,9 @@ public class SearchBuilder { predicate = fromObj.as(String.class).in(toFind); } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) { - predicate = builder.like(fromObj.as(String.class), createLeftMatchLikeExpression(value)); + predicate = myBuilder.like(fromObj.as(String.class), createLeftMatchLikeExpression(value)); } else { - predicate = builder.equal(fromObj.as(String.class), value); + predicate = myBuilder.equal(fromObj.as(String.class), value); } codePredicates.add(predicate); } else { @@ -921,16 +894,8 @@ public class SearchBuilder { return; } - List predicates = new ArrayList(); - predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); - predicates.add(builder.equal(from.get("myParamName"), theParamName)); - predicates.add(builder.or(toArray(codePredicates))); - createPredicateResourceId(builder, cq, predicates, from.get("myResourcePid").as(Long.class)); - - cq.where(builder.and(toArray(predicates))); - - TypedQuery q = myEntityManager.createQuery(cq); - doSetPids(new HashSet(q.getResultList())); + Predicate orPredicate = myBuilder.or(toArray(codePredicates)); + myPredicates.add(orPredicate); } private Predicate createCompositeParamPart(CriteriaBuilder builder, Root from, RuntimeSearchParam left, IQueryParameterType leftValue) { @@ -1300,7 +1265,7 @@ public class SearchBuilder { return singleCode; } - private Predicate createResourceLinkPathPredicate(String theParamName, Root from) { + private Predicate createResourceLinkPathPredicate(String theParamName, From from) { return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, myResourceType); } @@ -1642,9 +1607,9 @@ public class SearchBuilder { } myBuilder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = myBuilder.createTupleQuery(); - cq.distinct(true); - myResourceTableRoot = cq.from(ResourceTable.class); + myResourceTableQuery = myBuilder.createTupleQuery(); + myResourceTableQuery.distinct(true); + myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); myPredicates = new ArrayList(); myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName)); myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted"))); @@ -1655,10 +1620,10 @@ public class SearchBuilder { searchForIdsWithAndOr(theParams); - cq.where(myBuilder.and(SearchBuilder.toArray(myPredicates))); + myResourceTableQuery.where(myBuilder.and(SearchBuilder.toArray(myPredicates))); - cq.multiselect(myResourceTableRoot.get("myId").as(Long.class)); - TypedQuery query = myEntityManager.createQuery(cq); + myResourceTableQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class)); + TypedQuery query = myEntityManager.createQuery(myResourceTableQuery); query.setFirstResult(theFromIndex); query.setMaxResults(theToIndex - theFromIndex); @@ -1774,80 +1739,85 @@ public class SearchBuilder { for (Entry>> nextParamEntry : params.entrySet()) { String nextParamName = nextParamEntry.getKey(); - if (nextParamName.equals(BaseResource.SP_RES_ID)) { - - addPredicateResourceId(nextParamEntry.getValue()); - - } else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) { - - addPredicateLanguage(nextParamEntry.getValue()); - - } else if (nextParamName.equals(Constants.PARAM_HAS)) { - - // FIXME - addPredicateHas(nextParamEntry.getValue(), null); - - } else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) { - - // FIXME - addPredicateTag(nextParamEntry.getValue(), nextParamName, null); - - } else { - - RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); - if (nextParamDef != null) { - switch (nextParamDef.getParamType()) { - case DATE: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateDate(nextParamName, nextAnd); - } - break; - case QUANTITY: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateQuantity(nextParamName, nextAnd); - } - break; - case REFERENCE: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateReference(nextParamName, nextAnd); - } - break; - case STRING: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateString(nextParamName, nextAnd); - } - break; - case TOKEN: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateToken(nextParamName, nextAnd); - } - break; - case NUMBER: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateNumber(nextParamName, nextAnd); - } - break; - case COMPOSITE: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateComposite(nextParamDef, nextAnd); - } - break; - case URI: - for (List nextAnd : nextParamEntry.getValue()) { - addPredicateUri(nextParamName, nextAnd); - } - break; - case HAS: - // should not happen - break; - } - } - } + List> andOrParams = nextParamEntry.getValue(); + searchForIdsWithAndOr(nextParamName, andOrParams); } } + private void searchForIdsWithAndOr(String nextParamName, List> andOrParams) { + if (nextParamName.equals(BaseResource.SP_RES_ID)) { + + addPredicateResourceId(andOrParams); + + } else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) { + + addPredicateLanguage(andOrParams); + + } else if (nextParamName.equals(Constants.PARAM_HAS)) { + + // FIXME + addPredicateHas(andOrParams, null); + + } else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) { + + // FIXME + addPredicateTag(andOrParams, nextParamName, null); + + } else { + + RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); + if (nextParamDef != null) { + switch (nextParamDef.getParamType()) { + case DATE: + for (List nextAnd : andOrParams) { + addPredicateDate(nextParamName, nextAnd); + } + break; + case QUANTITY: + for (List nextAnd : andOrParams) { + addPredicateQuantity(nextParamName, nextAnd); + } + break; + case REFERENCE: + for (List nextAnd : andOrParams) { + addPredicateReference(nextParamName, nextAnd); + } + break; + case STRING: + for (List nextAnd : andOrParams) { + addPredicateString(nextParamName, nextAnd); + } + break; + case TOKEN: + for (List nextAnd : andOrParams) { + addPredicateToken(nextParamName, nextAnd); + } + break; + case NUMBER: + for (List nextAnd : andOrParams) { + addPredicateNumber(nextParamName, nextAnd); + } + break; + case COMPOSITE: + for (List nextAnd : andOrParams) { + addPredicateComposite(nextParamDef, nextAnd); + } + break; + case URI: + for (List nextAnd : andOrParams) { + addPredicateUri(nextParamName, nextAnd); + } + break; + case HAS: + // should not happen + break; + } + } + } + } + private void addPredicateResourceId(List> theValues) { for (List nextValue : theValues) { Set orPids = new HashSet(); @@ -1972,7 +1942,7 @@ public class SearchBuilder { return likeExpression.replace("%", "[%]") + "%"; } - private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, Root from, + private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, From from, Class resourceType) { RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(resourceType); RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamDao.java new file mode 100644 index 00000000000..b11a4e10cd0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamDao.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.SearchParam; + +public interface ISearchParamDao extends JpaRepository { + + @Query("SELECT s FROM SearchParam s WHERE s.myResourceName = :resname AND s.myParamName = :parmname") + public SearchParam findForResource(@Param("resname") String theResourceType, @Param("parmname") String theParamName); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java new file mode 100644 index 00000000000..1290e524da1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchParamPresentDao.java @@ -0,0 +1,38 @@ +package ca.uhn.fhir.jpa.dao.data; + +import java.util.Collection; +import java.util.Date; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2017 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.SearchParamPresent; + +public interface ISearchParamPresentDao extends JpaRepository { + + @Query("SELECT s FROM SearchParamPresent s WHERE s.myResourceTable = :res") + public Collection findAllForResource(@Param("res") ResourceTable theResource); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java index d3afcf6804c..481fa2794d4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java @@ -42,7 +42,6 @@ import org.hibernate.search.annotations.Field; import ca.uhn.fhir.model.primitive.InstantDt; -//@formatter:off @Embeddable @Entity @Table(name = "HFJ_SPIDX_DATE", indexes= { @@ -50,7 +49,6 @@ import ca.uhn.fhir.model.primitive.InstantDt; @Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"), @Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID") }) -//@formatter:on public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParam.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParam.java new file mode 100644 index 00000000000..548e83ebee2 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParam.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jpa.entity; + +import javax.persistence.*; + +@Entity +@Table(name = "HFJ_SEARCH_PARM", uniqueConstraints= { + @UniqueConstraint(name="IDX_SEARCHPARM_RESTYPE_SPNAME", columnNames= {"RES_TYPE", "PARAM_NAME"}) +}) +public class SearchParam { + + @Id + @SequenceGenerator(name = "SEQ_SEARCHPARM_ID", sequenceName = "SEQ_SEARCHPARM_ID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCHPARM_ID") + @Column(name = "RES_ID") + private Long myId; + + @Column(name="PARAM_NAME", length=BaseResourceIndexedSearchParam.MAX_SP_NAME, nullable=false, updatable=false) + private String myParamName; + + @Column(name="RES_TYPE", length=ResourceTable.RESTYPE_LEN, nullable=false, updatable=false) + private String myResourceName; + + public String getParamName() { + return myParamName; + } + + public void setParamName(String theParamName) { + myParamName = theParamName; + } + + public void setResourceName(String theResourceName) { + myResourceName = theResourceName; + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParamPresent.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParamPresent.java new file mode 100644 index 00000000000..1b7b283f6f6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchParamPresent.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.jpa.entity; + +import java.io.Serializable; + +import javax.persistence.*; + +@Entity +@Table(name = "HFJ_RES_PARAM_PRESENT", indexes = { + @Index(name = "IDX_RESPARMPRESENT_RESID", columnList = "RES_ID") +}, uniqueConstraints = { + @UniqueConstraint(name = "IDX_RESPARMPRESENT_SPID_RESID", columnNames = { "SP_ID", "RES_ID" }) +}) +public class SearchParamPresent implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @SequenceGenerator(name = "SEQ_RESPARMPRESENT_ID", sequenceName = "SEQ_RESPARMPRESENT_ID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESPARMPRESENT_ID") + @Column(name = "PID") + private Long myId; + + @Column(name = "SP_PRESENT", nullable = false) + private boolean myPresent; + + @ManyToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_RESPARMPRES_RESID")) + private ResourceTable myResourceTable; + + @ManyToOne() + @JoinColumn(name = "SP_ID", referencedColumnName = "PID", nullable = false, foreignKey = @ForeignKey(name = "FK_RESPARMPRES_SPID")) + private SearchParam mySearchParam; + + public ResourceTable getResourceTable() { + return myResourceTable; + } + + public SearchParam getSearchParam() { + return mySearchParam; + } + + public boolean isPresent() { + return myPresent; + } + + public void setPresent(boolean thePresent) { + myPresent = thePresent; + } + + public void setResourceTable(ResourceTable theResourceTable) { + myResourceTable = theResourceTable; + } + + public void setSearchParam(SearchParam theSearchParam) { + mySearchParam = theSearchParam; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java new file mode 100644 index 00000000000..6864ade89e8 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/ISearchParamPresenceSvc.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.jpa.sp; + +import java.util.Map; + +import ca.uhn.fhir.jpa.entity.ResourceTable; + +public interface ISearchParamPresenceSvc { + + void updatePresence(ResourceTable theResource, Map theParamNameToPresence); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java new file mode 100644 index 00000000000..ad5ffe15030 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sp/SearchParamPresenceSvcImpl.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.jpa.sp; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.jpa.dao.data.ISearchParamDao; +import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.SearchParam; +import ca.uhn.fhir.jpa.entity.SearchParamPresent; + +public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamPresenceSvcImpl.class); + + private Map, SearchParam> myResourceTypeToSearchParamToEntity = new ConcurrentHashMap, SearchParam>(); + + @Autowired + private ISearchParamDao mySearchParamDao; + + @Autowired + private ISearchParamPresentDao mySearchParamPresentDao; + + @Override + public void updatePresence(ResourceTable theResource, Map theParamNameToPresence) { + + Map presenceMap = new HashMap(theParamNameToPresence); + List entitiesToSave = new ArrayList(); + List entitiesToDelete = new ArrayList(); + + Collection existing = mySearchParamPresentDao.findAllForResource(theResource); + for (SearchParamPresent nextExistingEntity : existing) { + String nextSearchParamName = nextExistingEntity.getSearchParam().getParamName(); + Boolean existingValue = presenceMap.remove(nextSearchParamName); + if (existingValue == null) { + entitiesToDelete.add(nextExistingEntity); + } else if (existingValue.booleanValue() == nextExistingEntity.isPresent()) { + ourLog.trace("No change for search param {}", nextSearchParamName); + } else { + nextExistingEntity.setPresent(existingValue); + entitiesToSave.add(nextExistingEntity); + } + } + + for (Entry next : presenceMap.entrySet()) { + String resourceType = theResource.getResourceType(); + String paramName = next.getKey(); + Pair key = Pair.of(resourceType, paramName); + + SearchParam searchParam = myResourceTypeToSearchParamToEntity.get(key); + if (searchParam == null) { + searchParam = mySearchParamDao.findForResource(resourceType, paramName); + if (searchParam != null) { + myResourceTypeToSearchParamToEntity.put(key, searchParam); + } else { + searchParam = new SearchParam(); + searchParam.setResourceName(resourceType); + searchParam.setParamName(paramName); + mySearchParamDao.save(searchParam); + // Don't add the newly saved entity to the map in case the save fails + } + + SearchParamPresent present = new SearchParamPresent(); + present.setResourceTable(theResource); + present.setSearchParam(searchParam); + present.setPresent(next.getValue()); + entitiesToSave.add(present); + } + + mySearchParamPresentDao.deleteInBatch(entitiesToDelete); + mySearchParamPresentDao.save(entitiesToSave); + + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index 02f227cfa71..7c2c8e25845 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -2620,6 +2620,10 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/").setQualifier(UriParamQualifierEnum.BELOW)); assertThat(toUnqualifiedVersionlessIds(result), contains(id1)); + + result = myValueSetDao.search(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/FOOOOOO")); + assertThat(toUnqualifiedVersionlessIds(result), empty()); + } @Test