Refactor search capability in JPA out of resource specific module

This commit is contained in:
jamesagnew 2015-10-28 08:44:52 -04:00
parent 65598c200e
commit 30ab0203a3
14 changed files with 799 additions and 714 deletions

View File

@ -48,6 +48,7 @@ import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
@ -134,11 +135,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/
protected static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/
protected static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
@ -1535,4 +1536,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return b.toString();
}
public BaseHasResource readEntity(IIdType theValueId) {
throw new NotImplementedException("");
}
}

View File

@ -22,20 +22,15 @@ package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
@ -43,94 +38,45 @@ import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TemporalType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
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.ResourceHistoryTable;
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.SearchResult;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
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.dstu.resource.BaseResource;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
@ -170,7 +116,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Autowired()
private ISearchResultDao mySearchResultDao;
@Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
StopWatch w = new StopWatch();
@ -227,8 +172,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return doCreate(theResource, theIfNoneExist, thePerformIndexing, new Date());
}
protected IBaseOperationOutcome createErrorOperationOutcome(String theMessage) {
return createOperationOutcome(OO_SEVERITY_ERROR, theMessage);
}
@ -239,7 +182,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
protected abstract IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage);
@Override
public DaoMethodOutcome delete(IIdType theId) {
if (theId == null || !theId.hasIdPart()) {
@ -343,8 +285,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (isNotBlank(theResource.getId().getIdPart())) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
throw new UnprocessableEntityException("This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
}
createForcedIdIfNeeded(entity, theResource.getId());
@ -550,42 +491,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return retVal;
}
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation) {
if (theIncludePids.isEmpty()) {
return;
}
Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) {
position.put(next, theResourceListToPopulate.size());
theResourceListToPopulate.add(null);
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.where(from.get("myId").in(theIncludePids));
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
for (ResourceTable next : q.getResultList()) {
Class<? extends IBaseResource> resourceType = getContext().getResourceDefinition(next.getResourceType()).getImplementingClass();
IResource resource = (IResource) toResource(resourceType, next, theForHistoryOperation);
Integer index = position.get(next.getId());
if (index == null) {
ourLog.warn("Got back unexpected resource PID {}", next.getId());
continue;
}
if (theRevIncludedPids.contains(next.getId())) {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, BundleEntrySearchModeEnum.INCLUDE);
} else {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, BundleEntrySearchModeEnum.MATCH);
}
theResourceListToPopulate.set(index, resource);
}
}
// @Override
// public IBundleProvider everything(IIdType theId) {
// Search search = new Search();
@ -668,123 +573,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
// };
// }
/**
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
* @param theLastUpdated
*/
private HashSet<Long> loadReverseIncludes(Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, EverythingModeEnum theEverythingModeEnum, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<Long>();
}
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
return new HashSet<Long>();
}
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
Collection<Long> nextRoundMatches = theMatches;
HashSet<Long> allAdded = new HashSet<Long>();
HashSet<Long> original = new HashSet<Long>(theMatches);
ArrayList<Include> includes = new ArrayList<Include>(theRevIncludes);
int roundCounts = 0;
StopWatch w = new StopWatch();
boolean addedSomeThisRound;
do {
roundCounts++;
HashSet<Long> pidsToInclude = new HashSet<Long>();
Set<Long> nextRoundOmit = new HashSet<Long>();
for (Iterator<Include> iter = includes.iterator(); iter.hasNext();) {
Include nextInclude = iter.next();
if (nextInclude.isRecurse() == false) {
iter.remove();
}
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql;
sql = "SELECT r FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
// if (theEverythingModeEnum.isEncounter()) {
// if (resourceLink.getSourcePath().equals("Encounter.subject") || resourceLink.getSourcePath().equals("Encounter.patient")) {
// nextRoundOmit.add(resourceLink.getSourceResourcePid());
// }
// }
pidsToInclude.add(resourceLink.getSourceResourcePid());
} else {
pidsToInclude.add(resourceLink.getTargetResourcePid());
}
}
} else {
List<String> paths;
if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
paths = Collections.singletonList(nextInclude.getValue());
} else {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = getContext().getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx + 1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
continue;
}
paths = param.getPathsSplit();
}
for (String nextPath : paths) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
} else {
pidsToInclude.add(resourceLink.getTargetResourcePid());
}
}
}
}
}
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<Long>(filterResourceIdsByLastUpdated(pidsToInclude, theLastUpdated));
}
for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) {
theMatches.add(next);
}
}
pidsToInclude.removeAll(nextRoundOmit);
addedSomeThisRound = allAdded.addAll(pidsToInclude);
nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound);
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[] { allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart() });
return allAdded;
}
@Override
public MetaDt metaAddOperation(IIdType theResourceId, MetaDt theMetaAdd) {
// Notify interceptors
@ -993,8 +781,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (entity == null) {
if (theId.hasVersionIdPart()) {
TypedQuery<ResourceHistoryTable> q = myEntityManager
.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
q.setParameter("RID", pid);
q.setParameter("RTYP", myResourceName);
q.setParameter("RVER", theId.getVersionIdPartAsLong());
@ -1070,202 +857,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
ActionRequestDetails requestDetails = new ActionRequestDetails(null, getResourceName());
notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails);
StopWatch w = new StopWatch();
final InstantDt now = InstantDt.withCurrentTime();
DateRangeParam lu = theParams.getLastUpdated();
if (lu != null && lu.isEmpty()) {
lu = null;
}
Collection<Long> loadPids;
if (theParams.getEverythingMode() != null) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (theParams.get(BaseResource.SP_RES_ID) != null) {
StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0);
predicates.add(builder.equal(from.get("myId"), idParm.getValue()));
}
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
cq.where(builder.and(SearchBuilder.toArray(predicates)));
Join<Object, Object> join = from.join("myIncomingResourceLinks", JoinType.LEFT);
cq.multiselect(from.get("myId").as(Long.class), join.get("mySourceResourcePid").as(Long.class));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
loadPids = new HashSet<Long>();
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
Long nextLong = next.get(1, Long.class);
if(nextLong != null) {
loadPids.add(nextLong);
}
}
} else if (theParams.isEmpty()) {
loadPids = new HashSet<Long>();
TypedQuery<Tuple> query = createSearchAllByTypeQuery(lu);
lu = null;
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
} else {
List<Long> searchResultPids;
if (mySearchDao == null) {
if (theParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (theParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
searchResultPids = null;
} else {
searchResultPids = mySearchDao.search(getResourceName(), theParams);
}
if (theParams.isEmpty()) {
loadPids = searchResultPids;
} else {
loadPids = searchForIdsWithAndOr(theParams, searchResultPids, lu);
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
}
// // Load _include and _revinclude before filter and sort in everything mode
// if (theParams.getEverythingMode() != null) {
// if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) {
// loadPids.addAll(loadReverseIncludes(loadPids, theParams.getRevIncludes(), true, theParams.getEverythingMode()));
// loadPids.addAll(loadReverseIncludes(loadPids, theParams.getIncludes(), false, theParams.getEverythingMode()));
// }
// }
// Handle _lastUpdated
if (lu != null) {
List<Long> resultList = filterResourceIdsByLastUpdated(loadPids, lu);
loadPids.clear();
for (Long next : resultList) {
loadPids.add(next);
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
}
// Handle sorting if any was provided
final List<Long> pids = processSort(theParams, loadPids);
// Load _revinclude resources
final Set<Long> revIncludedPids;
if (theParams.getEverythingMode() == null) {
if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) {
revIncludedPids = loadReverseIncludes(pids, theParams.getRevIncludes(), true, null, lu);
} else {
revIncludedPids = new HashSet<Long>();
}
} else {
revIncludedPids = new HashSet<Long>();
}
ourLog.debug("Search returned PIDs: {}", pids);
final int totalCount = pids.size();
IBundleProvider retVal = new IBundleProvider() {
@Override
public InstantDt getPublished() {
return now;
}
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null, theParams.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, resources, revIncludedPids, false);
return resources;
}
});
}
@Override
public Integer preferredPageSize() {
return theParams.getCount();
}
@Override
public int size() {
return totalCount;
}
};
ourLog.info(" {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() });
return retVal;
}
private List<Long> filterResourceIdsByLastUpdated(Collection<Long> thePids, final DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
lastUpdatedPredicates.add(0, from.get("myId").in(thePids));
cq.where(toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
List<Long> resultList = query.getResultList();
return resultList;
}
private List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From<?, ResourceTable> from) {
List<Predicate> lastUpdatedPredicates = new ArrayList<Predicate>();
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
Predicate predicateLower = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getLowerBoundAsInstant());
lastUpdatedPredicates.add(predicateLower);
}
if (theLastUpdated.getUpperBoundAsInstant() != null) {
Predicate predicateUpper = builder.lessThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getUpperBoundAsInstant());
lastUpdatedPredicates.add(predicateUpper);
}
}
return lastUpdatedPredicates;
}
private List<Long> toList(Collection<Long> theLoadPids) {
final List<Long> pids;
if (theLoadPids instanceof List) {
pids = (List<Long>) theLoadPids;
} else {
pids = new ArrayList<Long>(theLoadPids);
}
return pids;
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this);
builder.setType(getResourceType(), getResourceName());
return builder.search(theParams);
}
@Override
@ -1289,145 +883,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Override
public Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids, DateRangeParam theLastUpdated) {
SearchParameterMap params = theParams;
if (params == null) {
params = new SearchParameterMap();
}
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType);
Set<Long> pids = new HashSet<Long>();
if (theInitialPids != null) {
pids.addAll(theInitialPids);
}
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
String nextParamName = nextParamEntry.getKey();
if (nextParamName.equals(BaseResource.SP_RES_ID)) {
if (nextParamEntry.getValue().isEmpty()) {
continue;
} else {
for (List<? extends IQueryParameterType> nextValue : nextParamEntry.getValue()) {
Set<Long> joinPids = new HashSet<Long>();
if (nextValue == null || nextValue.size() == 0) {
continue;
} else {
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken();
IIdType valueId = new IdDt(value);
try {
BaseHasResource entity = readEntity(valueId);
if (entity.getDeleted() != null) {
continue;
}
joinPids.add(entity.getId());
} catch (ResourceNotFoundException e) {
// This isn't an error, just means no result found
}
}
if (joinPids.isEmpty()) {
return new HashSet<Long>();
}
}
pids = addPredicateId(pids, joinPids, theLastUpdated);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
if (pids.isEmpty()) {
pids.addAll(joinPids);
} else {
pids.retainAll(joinPids);
}
}
}
} else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
pids = addPredicateLanguage(pids, nextParamEntry.getValue(), theLastUpdated);
} else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) {
pids = addPredicateTag(pids, nextParamEntry.getValue(), nextParamName, theLastUpdated);
} else {
RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateDate(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateQuantity(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateReference(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateString(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateToken(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateNumber(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateComposite(nextParamDef, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateUri(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
}
}
}
}
return pids;
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this);
builder.setType(getResourceType(), getResourceName());
return builder.searchForIdsWithAndOr(theParams, theInitialPids, theLastUpdated);
}
@SuppressWarnings("unchecked")
@ -1437,7 +895,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
/**
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value.
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to
* share the same value.
*/
public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) {
mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName;
@ -1460,48 +919,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return retVal;
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
}
return qp;
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
IQueryParameterType qp = toParameterType(theParam);
qp.setValueAsQueryToken(theQualifier, theValueAsQueryToken); // aaaa
return qp;
}
private ArrayList<TagDefinition> toTagList(MetaDt theMeta) {
ArrayList<TagDefinition> retVal = new ArrayList<TagDefinition>();
@ -1570,8 +989,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) {
throw new UnprocessableEntityException(
"Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]");
throw new UnprocessableEntityException("Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]");
}
// Notify interceptors
@ -1623,14 +1041,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
String sourceId = link.getSourceResource().getIdDt().toUnqualifiedVersionless().getValue();
String sourcePath = link.getSourcePath();
throw new ResourceVersionConflictException(
"Unable to delete " + targetId + " because at least one resource has a reference to this resource. First reference found was resource " + sourceId + " in path " + sourcePath);
throw new ResourceVersionConflictException("Unable to delete " + targetId + " because at least one resource has a reference to this resource. First reference found was resource " + sourceId + " in path " + sourcePath);
}
private void validateResourceType(BaseHasResource entity) {
if (!myResourceName.equals(entity.getResourceType())) {
throw new ResourceNotFoundException(
"Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType());
throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType());
}
}

View File

@ -40,4 +40,5 @@ public interface IDao {
}
};
}

View File

@ -45,8 +45,6 @@ public interface IFhirSystemDao<T> extends IDao {
IBundleProvider history(Date theDate);
int performReindexingPass(Integer theCount);
/**
* Marks all indexes as needing fresh indexing
*
@ -59,6 +57,8 @@ public interface IFhirSystemDao<T> extends IDao {
*/
MetaDt metaGetOperation();
int performReindexingPass(Integer theCount);
T transaction(RequestDetails theRequestDetails, T theResources);
}

View File

@ -8,9 +8,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.EntityManager;
@ -20,6 +24,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
@ -30,13 +35,21 @@ import javax.persistence.criteria.Subquery;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
@ -49,14 +62,20 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
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.dstu.resource.BaseResource;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
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.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
@ -70,18 +89,31 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public class SearchBuilder {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class);
private EntityManager myEntityManager;
private FhirContext myContext;
private Class<? extends IBaseResource> myResourceType;
private String myResourceName;
private PlatformTransactionManager myPlatformTransactionManager;
private ISearchDao mySearchDao;
private ISearchResultDao mySearchResultDao;
private BaseHapiFhirDao<?> myCallingDao;
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager) {
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, ISearchDao theSearchDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao) {
myContext = theFhirContext;
myEntityManager = theEntityManager;
myPlatformTransactionManager = thePlatformTransactionManager;
mySearchDao = theSearchDao;
mySearchResultDao = theSearchResultDao;
myCallingDao = theDao;
}
private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) {
@ -206,6 +238,149 @@ public class SearchBuilder {
return singleCode;
}
public Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams, Collection<Long> theInitialPids, DateRangeParam theLastUpdated) {
SearchParameterMap params = theParams;
if (params == null) {
params = new SearchParameterMap();
}
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
Set<Long> pids = new HashSet<Long>();
if (theInitialPids != null) {
pids.addAll(theInitialPids);
}
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
String nextParamName = nextParamEntry.getKey();
if (nextParamName.equals(BaseResource.SP_RES_ID)) {
if (nextParamEntry.getValue().isEmpty()) {
continue;
} else {
for (List<? extends IQueryParameterType> nextValue : nextParamEntry.getValue()) {
Set<Long> joinPids = new HashSet<Long>();
if (nextValue == null || nextValue.size() == 0) {
continue;
} else {
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken();
IIdType valueId = new IdDt(value);
try {
BaseHasResource entity = myCallingDao.readEntity(valueId);
if (entity.getDeleted() != null) {
continue;
}
joinPids.add(entity.getId());
} catch (ResourceNotFoundException e) {
// This isn't an error, just means no result found
}
}
if (joinPids.isEmpty()) {
return new HashSet<Long>();
}
}
pids = addPredicateId(pids, joinPids, theLastUpdated);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
if (pids.isEmpty()) {
pids.addAll(joinPids);
} else {
pids.retainAll(joinPids);
}
}
}
} else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
pids = addPredicateLanguage(pids, nextParamEntry.getValue(), theLastUpdated);
} else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) {
pids = addPredicateTag(pids, nextParamEntry.getValue(), nextParamName, theLastUpdated);
} else {
RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateDate(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateQuantity(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateReference(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateString(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateToken(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateNumber(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateComposite(nextParamDef, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateUri(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
break;
}
}
}
}
return pids;
}
private TypedQuery<Tuple> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
@ -225,6 +400,200 @@ public class SearchBuilder {
return query;
}
private List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder, From<?, ResourceTable> from) {
List<Predicate> lastUpdatedPredicates = new ArrayList<Predicate>();
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
Predicate predicateLower = builder.greaterThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getLowerBoundAsInstant());
lastUpdatedPredicates.add(predicateLower);
}
if (theLastUpdated.getUpperBoundAsInstant() != null) {
Predicate predicateUpper = builder.lessThanOrEqualTo(from.<Date> get("myUpdated"), theLastUpdated.getUpperBoundAsInstant());
lastUpdatedPredicates.add(predicateUpper);
}
}
return lastUpdatedPredicates;
}
private List<Long> filterResourceIdsByLastUpdated(Collection<Long> thePids, final DateRangeParam theLastUpdated) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from);
lastUpdatedPredicates.add(0, from.get("myId").in(thePids));
cq.where(SearchBuilder.toArray(lastUpdatedPredicates));
TypedQuery<Long> query = myEntityManager.createQuery(cq);
List<Long> resultList = query.getResultList();
return resultList;
}
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation) {
if (theIncludePids.isEmpty()) {
return;
}
Map<Long, Integer> position = new HashMap<Long, Integer>();
for (Long next : theIncludePids) {
position.put(next, theResourceListToPopulate.size());
theResourceListToPopulate.add(null);
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.where(from.get("myId").in(theIncludePids));
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
for (ResourceTable next : q.getResultList()) {
Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass();
IResource resource = (IResource) myCallingDao.toResource(resourceType, next, theForHistoryOperation);
Integer index = position.get(next.getId());
if (index == null) {
ourLog.warn("Got back unexpected resource PID {}", next.getId());
continue;
}
if (theRevIncludedPids.contains(next.getId())) {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, BundleEntrySearchModeEnum.INCLUDE);
} else {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(resource, BundleEntrySearchModeEnum.MATCH);
}
theResourceListToPopulate.set(index, resource);
}
}
/**
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
* @param theLastUpdated
*/
private HashSet<Long> loadReverseIncludes(Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, EverythingModeEnum theEverythingModeEnum, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<Long>();
}
if (theRevIncludes == null || theRevIncludes.isEmpty()) {
return new HashSet<Long>();
}
String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid";
Collection<Long> nextRoundMatches = theMatches;
HashSet<Long> allAdded = new HashSet<Long>();
HashSet<Long> original = new HashSet<Long>(theMatches);
ArrayList<Include> includes = new ArrayList<Include>(theRevIncludes);
int roundCounts = 0;
StopWatch w = new StopWatch();
boolean addedSomeThisRound;
do {
roundCounts++;
HashSet<Long> pidsToInclude = new HashSet<Long>();
Set<Long> nextRoundOmit = new HashSet<Long>();
for (Iterator<Include> iter = includes.iterator(); iter.hasNext();) {
Include nextInclude = iter.next();
if (nextInclude.isRecurse() == false) {
iter.remove();
}
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql;
sql = "SELECT r FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
// if (theEverythingModeEnum.isEncounter()) {
// if (resourceLink.getSourcePath().equals("Encounter.subject") || resourceLink.getSourcePath().equals("Encounter.patient")) {
// nextRoundOmit.add(resourceLink.getSourceResourcePid());
// }
// }
pidsToInclude.add(resourceLink.getSourceResourcePid());
} else {
pidsToInclude.add(resourceLink.getTargetResourcePid());
}
}
} else {
List<String> paths;
if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
paths = Collections.singletonList(nextInclude.getValue());
} else {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = myContext.getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx + 1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
continue;
}
paths = param.getPathsSplit();
}
for (String nextPath : paths) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", nextRoundMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
if (theReverseMode) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
} else {
pidsToInclude.add(resourceLink.getTargetResourcePid());
}
}
}
}
}
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<Long>(filterResourceIdsByLastUpdated(pidsToInclude, theLastUpdated));
}
for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) {
theMatches.add(next);
}
}
pidsToInclude.removeAll(nextRoundOmit);
addedSomeThisRound = allAdded.addAll(pidsToInclude);
nextRoundMatches = pidsToInclude;
} while (includes.size() > 0 && nextRoundMatches.size() > 0 && addedSomeThisRound);
ourLog.info("Loaded {} {} in {} rounds and {} ms", new Object[] { allAdded.size(), theReverseMode ? "_revincludes" : "_includes", roundCounts, w.getMillisAndRestart() });
return allAdded;
}
private List<Long> toList(Collection<Long> theLoadPids) {
final List<Long> pids;
if (theLoadPids instanceof List) {
pids = (List<Long>) theLoadPids;
} else {
pids = new ArrayList<Long>(theLoadPids);
}
return pids;
}
private List<Long> processSort(final SearchParameterMap theParams, Collection<Long> theLoadPids) {
final List<Long> pids;
// Set<Long> loadPids = theLoadPids;
@ -316,7 +685,7 @@ public class SearchBuilder {
}
private Predicate createResourceLinkPathPredicate(String theParamName, CriteriaBuilder builder, Root<? extends ResourceLink> from) {
RuntimeSearchParam param = myContext.getResourceDefinition(getResourceType()).getSearchParam(theParamName);
RuntimeSearchParam param = myContext.getResourceDefinition(myResourceType).getSearchParam(theParamName);
List<String> path = param.getPathsSplit();
Predicate type = from.get("mySourcePath").in(path);
return type;
@ -682,8 +1051,6 @@ public class SearchBuilder {
}
private Set<Long> addPredicateParamMissing(Set<Long> thePids, String joinName, String theParamName, Class<? extends BaseResourceIndexedSearchParam> theParamTable) {
String resourceType = myContext.getResourceDefinition(getResourceType()).getName();
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
@ -693,11 +1060,11 @@ public class SearchBuilder {
Root<? extends BaseResourceIndexedSearchParam> subQfrom = subQ.from(theParamTable);
subQ.select(subQfrom.get("myResourcePid").as(Long.class));
Predicate subQname = builder.equal(subQfrom.get("myParamName"), theParamName);
Predicate subQtype = builder.equal(subQfrom.get("myResourceType"), resourceType);
Predicate subQtype = builder.equal(subQfrom.get("myResourceType"), myResourceName);
subQ.where(builder.and(subQtype, subQname));
Predicate joinPredicate = builder.not(builder.in(from.get("myId")).value(subQ));
Predicate typePredicate = builder.equal(from.get("myResourceType"), resourceType);
Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName);
Predicate notDeletedPredicate = builder.isNull(from.get("myDeleted"));
if (thePids.size() > 0) {
@ -895,7 +1262,7 @@ public class SearchBuilder {
IIdType dt = new IdDt(resourceId);
resourceId = dt.getIdPart();
}
Long targetPid = translateForcedIdToPid(new IdDt(resourceId));
Long targetPid = myCallingDao.translateForcedIdToPid(new IdDt(resourceId));
ourLog.debug("Searching for resource link with target PID: {}", targetPid);
Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid);
@ -935,7 +1302,7 @@ public class SearchBuilder {
for (Class<? extends IBaseResource> nextType : resourceTypes) {
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
IFhirResourceDao<?> dao = getDao(nextType);
IFhirResourceDao<?> dao = myCallingDao.getDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName());
continue;
@ -948,7 +1315,7 @@ public class SearchBuilder {
chain = chain.substring(0, qualifierIndex);
}
boolean isMeta = RESOURCE_META_PARAMS.containsKey(chain);
boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null;
if (!isMeta) {
param = typeDef.getSearchParam(chain);
@ -969,7 +1336,7 @@ public class SearchBuilder {
chainValue.setValueAsQueryToken(qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) {
IQueryParameterType type = newInstanceType(chain);
IQueryParameterType type = BaseHapiFhirDao.newInstanceType(chain);
type.setValueAsQueryToken(qualifier, resourceId);
chainValue = type;
} else {
@ -1013,6 +1380,51 @@ public class SearchBuilder {
return new HashSet<Long>(q.getResultList());
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
IQueryParameterType qp = toParameterType(theParam);
qp.setValueAsQueryToken(theQualifier, theValueAsQueryToken); // aaaa
return qp;
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
}
return qp;
}
private Set<Long> addPredicateString(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) {
if (theList == null || theList.isEmpty()) {
return thePids;
@ -1256,5 +1668,166 @@ public class SearchBuilder {
return new HashSet<Long>(q.getResultList());
}
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
myResourceType = theResourceType;
myResourceName = theResourceName;
}
public IBundleProvider search(final SearchParameterMap theParams) {
StopWatch w = new StopWatch();
final InstantDt now = InstantDt.withCurrentTime();
DateRangeParam lu = theParams.getLastUpdated();
if (lu != null && lu.isEmpty()) {
lu = null;
}
Collection<Long> loadPids;
if (theParams.getEverythingMode() != null) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<ResourceTable> from = cq.from(ResourceTable.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (theParams.get(BaseResource.SP_RES_ID) != null) {
StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0);
predicates.add(builder.equal(from.get("myId"), idParm.getValue()));
}
predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
predicates.add(builder.isNull(from.get("myDeleted")));
cq.where(builder.and(SearchBuilder.toArray(predicates)));
Join<Object, Object> join = from.join("myIncomingResourceLinks", JoinType.LEFT);
cq.multiselect(from.get("myId").as(Long.class), join.get("mySourceResourcePid").as(Long.class));
TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
loadPids = new HashSet<Long>();
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
Long nextLong = next.get(1, Long.class);
if(nextLong != null) {
loadPids.add(nextLong);
}
}
} else if (theParams.isEmpty()) {
loadPids = new HashSet<Long>();
TypedQuery<Tuple> query = createSearchAllByTypeQuery(lu);
lu = null;
for (Tuple next : query.getResultList()) {
loadPids.add(next.get(0, Long.class));
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
} else {
List<Long> searchResultPids;
if (mySearchDao == null) {
if (theParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (theParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
searchResultPids = null;
} else {
searchResultPids = mySearchDao.search(myResourceName, theParams);
}
if (theParams.isEmpty()) {
loadPids = searchResultPids;
} else {
loadPids = searchForIdsWithAndOr(theParams, searchResultPids, lu);
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
}
// // Load _include and _revinclude before filter and sort in everything mode
// if (theParams.getEverythingMode() != null) {
// if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) {
// loadPids.addAll(loadReverseIncludes(loadPids, theParams.getRevIncludes(), true, theParams.getEverythingMode()));
// loadPids.addAll(loadReverseIncludes(loadPids, theParams.getIncludes(), false, theParams.getEverythingMode()));
// }
// }
// Handle _lastUpdated
if (lu != null) {
List<Long> resultList = filterResourceIdsByLastUpdated(loadPids, lu);
loadPids.clear();
for (Long next : resultList) {
loadPids.add(next);
}
if (loadPids.isEmpty()) {
return new SimpleBundleProvider();
}
}
// Handle sorting if any was provided
final List<Long> pids = processSort(theParams, loadPids);
// Load _revinclude resources
final Set<Long> revIncludedPids;
if (theParams.getEverythingMode() == null) {
if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) {
revIncludedPids = loadReverseIncludes(pids, theParams.getRevIncludes(), true, null, lu);
} else {
revIncludedPids = new HashSet<Long>();
}
} else {
revIncludedPids = new HashSet<Long>();
}
ourLog.debug("Search returned PIDs: {}", pids);
final int totalCount = pids.size();
IBundleProvider retVal = new IBundleProvider() {
@Override
public InstantDt getPublished() {
return now;
}
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
// Load includes
pidsSubList = new ArrayList<Long>(pidsSubList);
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null, theParams.getLastUpdated()));
// Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, resources, revIncludedPids, false);
return resources;
}
});
}
@Override
public Integer preferredPageSize() {
return theParams.getCount();
}
@Override
public int size() {
return totalCount;
}
};
ourLog.info(" {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() });
return retVal;
}
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.jpa.provider;
import org.springframework.beans.factory.annotation.Required;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
public class BaseJpaPlainProvider extends BaseJpaProvider {
private IFhirSystemDao<?> myDao;
public BaseJpaPlainProvider() {
// nothing
}
@Required
public void setDao(IFhirSystemDao<?> theDao) {
myDao = theDao;
}
public IFhirSystemDao<?> getDao() {
return myDao;
}
}

View File

@ -29,15 +29,17 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.method.RequestDetails;
public class BaseJpaProvider {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaProvider.class);
public static final String REMOTE_ADDR = "req.remoteAddr";
public static final String REMOTE_UA = "req.userAgent";
private FhirContext myContext;
public void endRequest(HttpServletRequest theRequest) {
MDC.remove(REMOTE_ADDR);
MDC.remove(REMOTE_UA);
@ -47,6 +49,14 @@ public class BaseJpaProvider {
endRequest(theRequest.getServletRequest());
}
public FhirContext getContext() {
return myContext;
}
public void setContext(FhirContext theContext) {
myContext = theContext;
}
public void startRequest(HttpServletRequest theRequest) {
if (theRequest == null) {
return;

View File

@ -43,7 +43,6 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
public abstract class BaseJpaResourceProvider<T extends IResource> extends BaseJpaProvider implements IResourceProvider {
private FhirContext myContext;
private IFhirResourceDao<T> myDao;
public BaseJpaResourceProvider() {
@ -55,10 +54,6 @@ public abstract class BaseJpaResourceProvider<T extends IResource> extends BaseJ
myDao = theDao;
}
public FhirContext getContext() {
return myContext;
}
public IFhirResourceDao<T> getDao() {
return myDao;
}
@ -118,10 +113,6 @@ public abstract class BaseJpaResourceProvider<T extends IResource> extends BaseJ
}
}
public void setContext(FhirContext theContext) {
myContext = theContext;
}
@Required
public void setDao(IFhirResourceDao<T> theDao) {
myDao = theDao;

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.jpa.dao;
public class FhirSystemDaoDstu2SearchTest {
/*//@formatter:off
* [ERROR] Search parameter action has conflicting types token and reference
* [ERROR] Search parameter source has conflicting types token and reference
* [ERROR] Search parameter plan has conflicting types reference and token
* [ERROR] Search parameter version has conflicting types token and string
* [ERROR] Search parameter source has conflicting types reference and uri
* [ERROR] Search parameter location has conflicting types reference and uri
* [ERROR] Search parameter title has conflicting types string and token
* [ERROR] Search parameter manufacturer has conflicting types string and reference
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter source has conflicting types reference and string
* [ERROR] Search parameter destination has conflicting types reference and string
* [ERROR] Search parameter responsible has conflicting types reference and string
* [ERROR] Search parameter value has conflicting types token and string
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter action has conflicting types reference and token
* [ERROR] Search parameter version has conflicting types token and string
* [ERROR] Search parameter address has conflicting types token and string
* [ERROR] Search parameter base has conflicting types reference and token
* [ERROR] Search parameter target has conflicting types reference and token
* [ERROR] Search parameter base has conflicting types reference and uri
* [ERROR] Search parameter contact has conflicting types string and token
* [ERROR] Search parameter substance has conflicting types token and reference
* [ERROR] Search parameter provider has conflicting types reference and token
* [ERROR] Search parameter system has conflicting types token and uri
* [ERROR] Search parameter reference has conflicting types reference and uri
* //@formatter:off
*/
}

View File

@ -55,6 +55,26 @@ public class ServerSearchDstu2Test {
assertEquals("param1value", ourLastRef.getValue());
}
@Test
public void testSearchParam2() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/?param2=param2value&foo=bar");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals("searchParam2", ourLastMethod);
assertEquals("param2value", ourLastRef.getValue());
}
@Test
public void testUnknownSearchParam() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/?foo=bar");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(null, ourLastMethod);
}
@AfterClass
public static void afterClass() throws Exception {
@ -86,9 +106,8 @@ public class ServerSearchDstu2Test {
public static class DummyPatientResourceProvider {
//@formatter:off
@Search()
@Search(allowUnknownParams=true)
public List<IBaseResource> searchParam1(
@RequiredParam(name = "param1") StringParam theParam) {
ourLastMethod = "searchParam1";
@ -104,7 +123,7 @@ public class ServerSearchDstu2Test {
//@formatter:on
//@formatter:off
@Search()
@Search(allowUnknownParams=true)
public List<IBaseResource> searchParam2(
@RequiredParam(name = "param2") StringParam theParam) {
ourLastMethod = "searchParam2";

View File

@ -210,9 +210,14 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
// Conformance conformance = new FhirContext(Conformance.class).newXmlParser().parseResource(Conformance.class, metadataString);
TinderJpaRestServerMojo mojo = new TinderJpaRestServerMojo();
mojo.myProject = new MavenProject();
mojo.version = "dstu2";
mojo.packageBase = "ca.uhn.test";
mojo.baseResourceNames = java.util.Collections.singletonList("observation");
mojo.configPackageBase = "ca.uhn.test";
// mojo.baseResourceNames = new ArrayList<String>(Collections.singletonList("observation"));
mojo.targetDirectory = new File("target/generated/valuesets");
mojo.targetResourceDirectory = new File("target/generated/valuesets");
mojo.targetResourceSpringBeansFile = "tmp_beans.xml";
mojo.execute();
}

View File

@ -60,7 +60,7 @@ public abstract class BaseStructureParser {
private TreeMap<String, String> myNameToDatatypeClass = new TreeMap<String, String>();
private TreeMap<String, String> myNameToResourceClass = new TreeMap<String, String>();
private String myPackageBase;
private List<BaseRootType> myResources = new ArrayList<BaseRootType>();
protected List<BaseRootType> myResources = new ArrayList<BaseRootType>();
private String myVersion;
public BaseStructureParser(String theVersion, String theBaseDir) {
@ -555,7 +555,7 @@ public abstract class BaseStructureParser {
}
}
private FhirVersionEnum determineVersionEnum() throws MojoFailureException {
protected FhirVersionEnum determineVersionEnum() throws MojoFailureException {
FhirVersionEnum versionEnum = null;
if ("dstu".equals(myVersion)) {
versionEnum = FhirVersionEnum.DSTU1;

View File

@ -16,7 +16,7 @@ public class ResourceGeneratorUsingSpreadsheet extends BaseStructureSpreadsheetP
private List<String> myInputStreamNames;
private ArrayList<InputStream> myInputStreams;
private String myTemplate = null;
private String myVersion;
protected String myVersion;
public ResourceGeneratorUsingSpreadsheet(String theVersion, String theBaseDir) {
super(theVersion, theBaseDir);

View File

@ -217,6 +217,11 @@
which are part of a bundle when the resource has a UUID/OID
ID.
</action>
<action type="add">
Add ability for a server REST resource provider @Search method
to declare that it should allow even parameters it doesn't
understand.
<action>
</release>
<release version="1.2" date="2015-09-18">
<action type="add">