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 78a7e4a2ab9..8f33d302d21 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 @@ -1,5 +1,86 @@ package ca.uhn.fhir.jpa.dao; +import static org.apache.commons.lang3.StringUtils.compare; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; + +import java.io.CharArrayWriter; +import java.io.UnsupportedEncodingException; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Arrays; +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.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.XMLEvent; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.hibernate.Session; +import org.hibernate.internal.SessionImpl; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IDomainResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.BaseResource; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.Reference; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Sets; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + /* * #%L * HAPI FHIR JPA Server @@ -19,10 +100,70 @@ package ca.uhn.fhir.jpa.dao; * limitations under the License. * #L% */ - -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +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.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamNumberDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; +import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +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.IBaseResourceEntity; +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.ResourceIndexedCompositeStringUnique; +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.ResourceSearchView; +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.SearchInclude; +import ca.uhn.fhir.jpa.entity.SearchParam; +import ca.uhn.fhir.jpa.entity.SearchParamPresent; +import ca.uhn.fhir.jpa.entity.SearchResult; +import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; +import ca.uhn.fhir.jpa.entity.TagDefinition; +import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptDesignation; +import ca.uhn.fhir.jpa.entity.TermConceptMap; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.entity.TermConceptProperty; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; @@ -32,7 +173,12 @@ import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.JpaConstants; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.primitive.IdDt; @@ -48,61 +194,31 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ParameterUtil; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriAndListParam; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.*; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import org.apache.commons.lang3.NotImplementedException; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.hibernate.Session; -import org.hibernate.internal.SessionImpl; -import org.hl7.fhir.instance.model.api.*; -import org.hl7.fhir.r4.model.BaseResource; -import org.hl7.fhir.r4.model.Bundle.HTTPVerb; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.Reference; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.support.TransactionTemplate; - -import javax.annotation.PostConstruct; -import javax.persistence.*; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import javax.xml.stream.events.Characters; -import javax.xml.stream.events.XMLEvent; -import java.io.CharArrayWriter; -import java.io.UnsupportedEncodingException; -import java.text.Normalizer; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import static org.apache.commons.lang3.StringUtils.*; +import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.util.FhirTerser; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.UrlUtil; +import ca.uhn.fhir.util.XmlUtil; @SuppressWarnings("WeakerAccess") @Repository @@ -186,6 +302,8 @@ public abstract class BaseHapiFhirDao implements IDao, protected IResourceTableDao myResourceTableDao; @Autowired protected IResourceTagDao myResourceTagDao; + @Autowired + protected IResourceSearchViewDao myResourceViewDao; @Autowired(required = true) private DaoConfig myConfig; private FhirContext myContext; @@ -199,8 +317,8 @@ public abstract class BaseHapiFhirDao implements IDao, private ISearchParamPresenceSvc mySearchParamPresenceSvc; @Autowired private ISearchParamRegistry mySearchParamRegistry; - @Autowired - private ISearchResultDao mySearchResultDao; + //@Autowired + //private ISearchResultDao mySearchResultDao; @Autowired private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; private ApplicationContext myApplicationContext; @@ -1174,7 +1292,7 @@ public abstract class BaseHapiFhirDao implements IDao, public SearchBuilder newSearchBuilder() { SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, - myTerminologySvc, mySerarchParamRegistry, myResourceHistoryTableDao, myResourceTagDao); + myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao); return builder; } @@ -1223,7 +1341,7 @@ public abstract class BaseHapiFhirDao implements IDao, } } - private void populateResourceIdFromEntity(BaseHasResource theEntity, final IBaseResource theResource) { + private void populateResourceIdFromEntity(IBaseResourceEntity theEntity, final IBaseResource theResource) { IIdType id = theEntity.getIdDt(); if (getContext().getVersion().getVersion().isRi()) { id = getContext().getVersion().newIdType().setValue(id.getValue()); @@ -1355,7 +1473,7 @@ public abstract class BaseHapiFhirDao implements IDao, } @SuppressWarnings("unchecked") - private R populateResourceMetadataHapi(Class theResourceType, BaseHasResource theEntity, Collection theTagList, boolean theForHistoryOperation, IResource res) { + private R populateResourceMetadataHapi(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation, IResource res) { R retVal = (R) res; if (theEntity.getDeleted() != null) { res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance(); @@ -1421,7 +1539,7 @@ public abstract class BaseHapiFhirDao implements IDao, } @SuppressWarnings("unchecked") - private R populateResourceMetadataRi(Class theResourceType, BaseHasResource theEntity, Collection theTagList, boolean theForHistoryOperation, IAnyResource res) { + private R populateResourceMetadataRi(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation, IAnyResource res) { R retVal = (R) res; if (theEntity.getDeleted() != null) { res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance(); @@ -1601,37 +1719,50 @@ public abstract class BaseHapiFhirDao implements IDao, public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) { RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType()); Class resourceType = type.getImplementingClass(); - return toResource(resourceType, theEntity, null, null, theForHistoryOperation); + return toResource(resourceType, theEntity, null, theForHistoryOperation); } @SuppressWarnings("unchecked") @Override - public R toResource(Class theResourceType, BaseHasResource theEntity, ResourceHistoryTable theHistory, Collection theTagList, - boolean theForHistoryOperation) { - - // May 28, 2018 - #936 - // Could set historyList to null, if it's not called in the loop for the backward compatibility - ResourceHistoryTable history; + public R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation) { + + // 1. get resource, it's encoding and the tags if any + byte[] resourceBytes = null; + ResourceEncodingEnum resourceEncoding = null; + Collection myTagList = null; + if (theEntity instanceof ResourceHistoryTable) { - history = (ResourceHistoryTable) theEntity; - } else { - if (theHistory == null) { - history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion()); - } else { - history = theHistory; + ResourceHistoryTable history = (ResourceHistoryTable) theEntity; + resourceBytes = history.getResource(); + resourceEncoding = history.getEncoding(); + myTagList = history.getTags(); + } else if (theEntity instanceof ResourceTable) { + ResourceTable resource = (ResourceTable)theEntity; + ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion()); + if (history == null) { + return null; } - } - - if (history == null) { + resourceBytes = history.getResource(); + resourceEncoding = history.getEncoding(); + myTagList = resource.getTags(); + } else if (theEntity instanceof ResourceSearchView) { + // This is the search View + ResourceSearchView myView = (ResourceSearchView)theEntity; + resourceBytes = myView.getResource(); + resourceEncoding = myView.getEncoding(); + if (theTagList == null) + myTagList = new HashSet<>(); + else + myTagList = theTagList; + } else { + // something wrong return null; } - byte[] resourceBytes = history.getResource(); - ResourceEncodingEnum resourceEncoding = history.getEncoding(); - + // 2. get The text String resourceText = null; switch (resourceEncoding) { - case JSON: + case JSON: try { resourceText = new String(resourceBytes, "UTF-8"); } catch (UnsupportedEncodingException e) { @@ -1644,19 +1775,8 @@ public abstract class BaseHapiFhirDao implements IDao, case DEL: break; } - - // get preload the tagList - Collection myTagList; - - if (theTagList == null) - myTagList = theEntity.getTags(); - else - myTagList = theTagList; - - - /* - * Use the appropriate custom type if one is specified in the context - */ + + // 3. Use the appropriate custom type if one is specified in the context Class resourceType = theResourceType; if (myContext.hasDefaultTypeForProfile()) { for (BaseTag nextTag : myTagList) { @@ -1674,6 +1794,7 @@ public abstract class BaseHapiFhirDao implements IDao, } } + // 4. parse the text to FHIR R retVal; if (resourceEncoding != ResourceEncodingEnum.DEL) { IParser parser = resourceEncoding.newParser(getContext(theEntity.getFhirVersion())); @@ -1704,6 +1825,7 @@ public abstract class BaseHapiFhirDao implements IDao, } + // 5. fill MetaData if (retVal instanceof IResource) { IResource res = (IResource) retVal; retVal = populateResourceMetadataHapi(resourceType, theEntity, myTagList, theForHistoryOperation, res); @@ -1712,7 +1834,6 @@ public abstract class BaseHapiFhirDao implements IDao, retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res); } - return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 2a8882f14fd..fe381d44ab1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -207,7 +207,7 @@ public abstract class BaseHapiFhirResourceDao extends B StopWatch w = new StopWatch(); - T resourceToDelete = toResource(myResourceType, entity, null, null, false); + T resourceToDelete = toResource(myResourceType, entity, null, false); // Notify IServerOperationInterceptors about pre-action call if (theReques != null) { @@ -289,7 +289,7 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); deletedResources.add(entity); - T resourceToDelete = toResource(myResourceType, entity, null, null, false); + T resourceToDelete = toResource(myResourceType, entity, null, false); // Notify IServerOperationInterceptors about pre-action call if (theRequest != null) { @@ -854,7 +854,7 @@ public abstract class BaseHapiFhirResourceDao extends B BaseHasResource entity = readEntity(theId); validateResourceType(entity); - T retVal = toResource(myResourceType, entity, null, null, false); + T retVal = toResource(myResourceType, entity, null, false); IPrimitiveType deleted; if (retVal instanceof IResource) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java index 1a389835ef1..fd47335fb25 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java @@ -9,7 +9,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.entity.BaseHasResource; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.entity.IBaseResourceEntity; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; @@ -59,6 +59,6 @@ public interface IDao { IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation); - R toResource(Class theResourceType, BaseHasResource theEntity, ResourceHistoryTable theHistory, Collection theTagList, boolean theForHistoryOperation); + R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation); } 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 978826d5bf3..6dbe735cd96 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 @@ -85,6 +85,7 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.ForcedId; @@ -98,6 +99,7 @@ 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.ResourceSearchView; import ca.uhn.fhir.jpa.entity.SearchParam; import ca.uhn.fhir.jpa.entity.SearchParamPresent; import ca.uhn.fhir.jpa.entity.TagDefinition; @@ -174,8 +176,8 @@ public class SearchBuilder implements ISearchBuilder { private IHapiTerminologySvc myTerminologySvc; private int myFetchSize; - protected IResourceHistoryTableDao myResourceHistoryTableDao; protected IResourceTagDao myResourceTagDao; + protected IResourceSearchViewDao myResourceSearchViewDao; /** * Constructor @@ -184,7 +186,7 @@ public class SearchBuilder implements ISearchBuilder { IFulltextSearchSvc theFulltextSearchSvc, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry, - IResourceHistoryTableDao theResourceHistoryTableDao, IResourceTagDao theResourceTagDao) { + IResourceTagDao theResourceTagDao, IResourceSearchViewDao theResourceViewDao) { myContext = theFhirContext; myEntityManager = theEntityManager; myFulltextSearchSvc = theFulltextSearchSvc; @@ -193,8 +195,8 @@ public class SearchBuilder implements ISearchBuilder { myForcedIdDao = theForcedIdDao; myTerminologySvc = theTerminologySvc; mySearchParamRegistry = theSearchParamRegistry; - myResourceHistoryTableDao = theResourceHistoryTableDao; myResourceTagDao = theResourceTagDao; + myResourceSearchViewDao = theResourceViewDao; } private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List theNextAnd) { @@ -1675,52 +1677,39 @@ public class SearchBuilder implements ISearchBuilder { private void doLoadPids(List theResourceListToPopulate, Set theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao, Map position, Collection pids) { - CriteriaBuilder builder = entityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(ResourceTable.class); - Root from = cq.from(ResourceTable.class); - cq.where(from.get("myId").in(pids)); - TypedQuery q = entityManager.createQuery(cq); - List resultList = q.getResultList(); - - //-- Issue #963: Load resource histories based on pids once to improve the performance - Map historyMap = getResourceHistoryMap(pids); + // -- get the resource from the searchView + Collection resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(pids); //-- preload all tags with tag definition if any - Map> tagMap = getResourceTagMap(resultList); + Map> tagMap = getResourceTagMap(resourceSearchViewList); - //-- pre-load all forcedId - Map forcedIdMap = getForcedIdMap(pids); - - ForcedId forcedId = null; Long resourceId = null; - for (ResourceTable next : resultList) { + for (ResourceSearchView next : resourceSearchViewList) { + Class resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass(); resourceId = next.getId(); - forcedId = forcedIdMap.get(resourceId); - if (forcedId != null) - next.setForcedId(forcedId); - IBaseResource resource = theDao.toResource(resourceType, next, historyMap.get(next.getId()), tagMap.get(next.getId()), theForHistoryOperation); + IBaseResource resource = theDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation); if (resource == null) { ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion()); continue; } - Integer index = position.get(next.getId()); + Integer index = position.get(resourceId); if (index == null) { - ourLog.warn("Got back unexpected resource PID {}", next.getId()); + ourLog.warn("Got back unexpected resource PID {}", resourceId); continue; } if (resource instanceof IResource) { - if (theRevIncludedPids.contains(next.getId())) { + if (theRevIncludedPids.contains(resourceId)) { ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put((IResource) resource, BundleEntrySearchModeEnum.INCLUDE); } else { ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put((IResource) resource, BundleEntrySearchModeEnum.MATCH); } } else { - if (theRevIncludedPids.contains(next.getId())) { + if (theRevIncludedPids.contains(resourceId)) { ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put((IAnyResource) resource, BundleEntrySearchModeEnum.INCLUDE.getCode()); } else { ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put((IAnyResource) resource, BundleEntrySearchModeEnum.MATCH.getCode()); @@ -1731,30 +1720,12 @@ public class SearchBuilder implements ISearchBuilder { } } - //-- load all history in to the map - private Map getResourceHistoryMap(Collection pids) { - - Map historyMap = new HashMap(); - - if (pids.size() == 0) - return historyMap; - - Collection historyList = myResourceHistoryTableDao.findByResourceIds(pids); - - for (ResourceHistoryTable history : historyList) { - - historyMap.put(history.getResourceId(), history); - } - - return historyMap; - } - - private Map> getResourceTagMap(List resourceList) { + private Map> getResourceTagMap(Collection theResourceSearchViewList) { - List idList = new ArrayList(resourceList.size()); + List idList = new ArrayList(theResourceSearchViewList.size()); //-- find all resource has tags - for (ResourceTable resource: resourceList) { + for (ResourceSearchView resource: theResourceSearchViewList) { if (resource.isHasTags()) idList.add(resource.getId()); } @@ -1787,23 +1758,6 @@ public class SearchBuilder implements ISearchBuilder { return tagMap; } - //-- load all forcedId in to the map - private Map getForcedIdMap(Collection pids) { - - Map forceIdMap = new HashMap(); - - if (pids.size() == 0) - return forceIdMap; - - Collection forceIdList = myForcedIdDao.findByResourcePids(pids); - - for (ForcedId forcedId : forceIdList) { - - forceIdMap.put(forcedId.getResourcePid(), forcedId); - } - - return forceIdMap; - } @Override public void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, Set theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchViewDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchViewDao.java new file mode 100644 index 00000000000..cf13d923fc6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchViewDao.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.dao.data; + +import java.util.Collection; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 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.ResourceSearchView; + +public interface IResourceSearchViewDao extends JpaRepository { + + @Query("SELECT v FROM ResourceSearchView v WHERE v.myResourceId in (:pids)") + Collection findByResourceIds(@Param("pids") Collection pids); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java index 177220e1b37..770544375c2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java @@ -30,7 +30,7 @@ import java.util.Collection; import java.util.Date; @MappedSuperclass -public abstract class BaseHasResource { +public abstract class BaseHasResource implements IBaseResourceEntity { @Column(name = "RES_DELETED_AT", nullable = true) @Temporal(TemporalType.TIMESTAMP) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/IBaseResourceEntity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/IBaseResourceEntity.java new file mode 100644 index 00000000000..b1ca8d47c96 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/IBaseResourceEntity.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 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 java.util.Date; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; + +public interface IBaseResourceEntity { + + Date getDeleted(); + FhirVersionEnum getFhirVersion(); + Long getId(); + IdDt getIdDt(); + InstantDt getPublished(); + Long getResourceId(); + String getResourceType(); + InstantDt getUpdated(); + Date getUpdatedDate(); + long getVersion(); + boolean isHasTags(); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java new file mode 100644 index 00000000000..e62712ebbe7 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java @@ -0,0 +1,200 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 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 java.io.Serializable; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Subselect; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.rest.api.Constants; + +//@formatter:off +@Entity +@Immutable +@Subselect("SELECT h.pid as pid " + + ", h.res_id as res_id " + + ", h.res_type as res_type " + + ", h.res_version as res_version " + // FHIR version + ", h.res_ver as res_ver " + // resource version + ", h.has_tags as has_tags " + + ", h.res_deleted_at as res_deleted_at " + + ", h.res_published as res_published " + + ", h.res_updated as res_updated " + + ", h.res_text as res_text " + + ", h.res_encoding as res_encoding " + + ", f.forced_id as forced_pid " + + "FROM HFJ_RES_VER h " + + " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id " + + " INNER JOIN HFJ_RESOURCE r ON r.res_id = h.res_id and r.res_ver = h.res_ver") +// @formatter:on +public class ResourceSearchView implements IBaseResourceEntity, Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "PID") + private Long myId; + + @Column(name = "RES_ID") + private Long myResourceId; + + @Column(name = "RES_TYPE") + private String myResourceType; + + @Column(name = "RES_VERSION") + @Enumerated(EnumType.STRING) + private FhirVersionEnum myFhirVersion; + + @Column(name = "RES_VER") + private Long myResourceVersion; + + @Column(name = "HAS_TAGS") + private boolean myHasTags; + + @Column(name = "RES_DELETED_AT") + @Temporal(TemporalType.TIMESTAMP) + private Date myDeleted; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "RES_PUBLISHED") + private Date myPublished; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "RES_UPDATED") + private Date myUpdated; + + @Column(name = "RES_TEXT") + @Lob() + private byte[] myResource; + + @Column(name = "RES_ENCODING") + @Enumerated(EnumType.STRING) + private ResourceEncodingEnum myEncoding; + + @Column(name = "forced_pid") + private String myForcedPid; + + public ResourceSearchView() { + } + + @Override + public Date getDeleted() { + return myDeleted; + } + + public void setDeleted(Date theDate) { + myDeleted = theDate; + } + + @Override + public FhirVersionEnum getFhirVersion() { + return myFhirVersion; + } + + public void setFhirVersion(FhirVersionEnum theFhirVersion) { + myFhirVersion = theFhirVersion; + } + + public String getForcedId() { + return myForcedPid; + } + + @Override + public Long getId() { + return myResourceId; + } + + @Override + public IdDt getIdDt() { + if (myForcedPid == null) { + Long id = myResourceId; + return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); + } else { + return new IdDt( + getResourceType() + '/' + getForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); + } + } + + @Override + public InstantDt getPublished() { + if (myPublished != null) { + return new InstantDt(myPublished); + } else { + return null; + } + } + + public void setPublished(Date thePublished) { + myPublished = thePublished; + } + + @Override + public Long getResourceId() { + return myResourceId; + } + + @Override + public String getResourceType() { + return myResourceType; + } + + @Override + public InstantDt getUpdated() { + return new InstantDt(myUpdated); + } + + @Override + public Date getUpdatedDate() { + return myUpdated; + } + + @Override + public long getVersion() { + return myResourceVersion; + } + + @Override + public boolean isHasTags() { + return myHasTags; + } + + public byte[] getResource() { + return myResource; + } + + public ResourceEncodingEnum getEncoding() { + return myEncoding; + } + +}