package ca.uhn.fhir.jpa.search; /* * #%L * HAPI FHIR JPA Server * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.util.*; import javax.persistence.EntityManager; import javax.persistence.NoResultException; 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 org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; 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.FhirContext; import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.server.IBundleProvider; public class PersistedJpaBundleProvider implements IBundleProvider { private FhirContext myContext; private IDao myDao; private EntityManager myEntityManager; private PlatformTransactionManager myPlatformTransactionManager; private ISearchCoordinatorSvc mySearchCoordinatorSvc; private ISearchDao mySearchDao; private Search mySearchEntity; private String myUuid; public PersistedJpaBundleProvider(String theSearchUuid, IDao theDao) { myUuid = theSearchUuid; myDao = theDao; } protected List<IBaseResource> doHistoryInTransaction(int theFromIndex, int theToIndex) { List<ResourceHistoryTable> results; CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); CriteriaQuery<ResourceHistoryTable> q = cb.createQuery(ResourceHistoryTable.class); Root<ResourceHistoryTable> from = q.from(ResourceHistoryTable.class); List<Predicate> predicates = new ArrayList<Predicate>(); if (mySearchEntity.getResourceType() == null) { // All resource types } else if (mySearchEntity.getResourceId() == null) { predicates.add(cb.equal(from.get("myResourceType"), mySearchEntity.getResourceType())); } else { predicates.add(cb.equal(from.get("myResourceId"), mySearchEntity.getResourceId())); } if (mySearchEntity.getLastUpdatedLow() != null) { predicates.add(cb.greaterThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedLow())); } if (mySearchEntity.getLastUpdatedHigh() != null) { predicates.add(cb.lessThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedHigh())); } if (predicates.size() > 0) { q.where(predicates.toArray(new Predicate[predicates.size()])); } q.orderBy(cb.desc(from.get("myUpdated"))); TypedQuery<ResourceHistoryTable> query = myEntityManager.createQuery(q); if (theToIndex - theFromIndex > 0) { query.setFirstResult(theFromIndex); query.setMaxResults(theToIndex - theFromIndex); } results = query.getResultList(); ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>(); for (ResourceHistoryTable next : results) { BaseHasResource resource; resource = next; retVal.add(myDao.toResource(resource, true)); } return retVal; } protected List<IBaseResource> doSearchOrEverythingInTransaction(final int theFromIndex, final int theToIndex) { ISearchBuilder sb = myDao.newSearchBuilder(); String resourceName = mySearchEntity.getResourceType(); Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(resourceName).getImplementingClass(); sb.setType(resourceType, resourceName); List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex); return toResourceList(sb, pidsSubList); } private void ensureDependenciesInjected() { if (myPlatformTransactionManager == null) { myDao.injectDependenciesIntoBundleProvider(this); } } /** * Returns false if the entity can't be found */ public boolean ensureSearchEntityLoaded() { if (mySearchEntity == null) { ensureDependenciesInjected(); TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); return template.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus theStatus) { try { setSearchEntity(mySearchDao.findByUuid(myUuid)); if (mySearchEntity == null) { return false; } // Load the includes now so that they are available outside of this transaction mySearchEntity.getIncludes().size(); return true; } catch (NoResultException e) { return false; } } }); } return true; } @Override public InstantDt getPublished() { ensureSearchEntityLoaded(); return new InstantDt(mySearchEntity.getCreated()); } @Override public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) { ensureDependenciesInjected(); TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); return template.execute(new TransactionCallback<List<IBaseResource>>() { @Override public List<IBaseResource> doInTransaction(TransactionStatus theStatus) { ensureSearchEntityLoaded(); switch (mySearchEntity.getSearchType()) { case HISTORY: return doHistoryInTransaction(theFromIndex, theToIndex); case SEARCH: case EVERYTHING: default: return doSearchOrEverythingInTransaction(theFromIndex, theToIndex); } } }); } public String getUuid() { return myUuid; } @Override public Integer preferredPageSize() { ensureSearchEntityLoaded(); return mySearchEntity.getPreferredPageSize(); } public void setContext(FhirContext theContext) { myContext = theContext; } public void setEntityManager(EntityManager theEntityManager) { myEntityManager = theEntityManager; } public void setPlatformTransactionManager(PlatformTransactionManager thePlatformTransactionManager) { myPlatformTransactionManager = thePlatformTransactionManager; } public void setSearchCoordinatorSvc(ISearchCoordinatorSvc theSearchCoordinatorSvc) { mySearchCoordinatorSvc = theSearchCoordinatorSvc; } public void setSearchDao(ISearchDao theSearchDao) { mySearchDao = theSearchDao; } protected void setSearchEntity(Search theSearchEntity) { mySearchEntity = theSearchEntity; } @Override public Integer size() { ensureSearchEntityLoaded(); Integer size = mySearchEntity.getTotalCount(); if (size == null) { return null; } return Math.max(0, size); } protected List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) { Set<Long> includedPids = new HashSet<Long>(); if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated())); } includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated())); // Execute the query and make sure we return distinct results List<IBaseResource> resources = new ArrayList<IBaseResource>(); sb.loadResourcesByPid(pidsSubList, resources, includedPids, false, myEntityManager, myContext, myDao); return resources; } }