View Javadoc
1   package ca.uhn.fhir.jpa.search;
2   
3   /*
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.FhirContext;
24  import ca.uhn.fhir.jpa.dao.IDao;
25  import ca.uhn.fhir.jpa.dao.ISearchBuilder;
26  import ca.uhn.fhir.jpa.dao.data.ISearchDao;
27  import ca.uhn.fhir.jpa.entity.BaseHasResource;
28  import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
29  import ca.uhn.fhir.jpa.entity.Search;
30  import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
31  import ca.uhn.fhir.model.primitive.InstantDt;
32  import ca.uhn.fhir.rest.api.server.IBundleProvider;
33  import org.hl7.fhir.instance.model.api.IBaseResource;
34  import org.springframework.transaction.PlatformTransactionManager;
35  import org.springframework.transaction.TransactionDefinition;
36  import org.springframework.transaction.TransactionStatus;
37  import org.springframework.transaction.support.TransactionCallback;
38  import org.springframework.transaction.support.TransactionCallbackWithoutResult;
39  import org.springframework.transaction.support.TransactionTemplate;
40  
41  import javax.persistence.EntityManager;
42  import javax.persistence.NoResultException;
43  import javax.persistence.TypedQuery;
44  import javax.persistence.criteria.CriteriaBuilder;
45  import javax.persistence.criteria.CriteriaQuery;
46  import javax.persistence.criteria.Predicate;
47  import javax.persistence.criteria.Root;
48  import java.util.*;
49  
50  public class PersistedJpaBundleProvider implements IBundleProvider {
51  
52  	private FhirContext myContext;
53  	private IDao myDao;
54  	private EntityManager myEntityManager;
55  	private PlatformTransactionManager myPlatformTransactionManager;
56  	private ISearchCoordinatorSvc mySearchCoordinatorSvc;
57  	private ISearchDao mySearchDao;
58  	private Search mySearchEntity;
59  	private String myUuid;
60  	private boolean myCacheHit;
61  
62  	public PersistedJpaBundleProvider(String theSearchUuid, IDao theDao) {
63  		myUuid = theSearchUuid;
64  		myDao = theDao;
65  	}
66  
67  	/**
68  	 * When HAPI FHIR server is running "for real", a new
69  	 * instance of the bundle provider is created to serve
70  	 * every HTTP request, so it's ok for us to keep
71  	 * state in here and expect that it will go away. But
72  	 * in unit tests we keep this object around for longer
73  	 * sometimes.
74  	 */
75  	public void clearCachedDataForUnitTest() {
76  		mySearchEntity = null;
77  	}
78  
79  	private List<IBaseResource> doHistoryInTransaction(int theFromIndex, int theToIndex) {
80  		List<ResourceHistoryTable> results;
81  
82  		CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
83  		CriteriaQuery<ResourceHistoryTable> q = cb.createQuery(ResourceHistoryTable.class);
84  		Root<ResourceHistoryTable> from = q.from(ResourceHistoryTable.class);
85  		List<Predicate> predicates = new ArrayList<>();
86  
87  		if (mySearchEntity.getResourceType() == null) {
88  			// All resource types
89  		} else if (mySearchEntity.getResourceId() == null) {
90  			predicates.add(cb.equal(from.get("myResourceType"), mySearchEntity.getResourceType()));
91  		} else {
92  			predicates.add(cb.equal(from.get("myResourceId"), mySearchEntity.getResourceId()));
93  		}
94  
95  		if (mySearchEntity.getLastUpdatedLow() != null) {
96  			predicates.add(cb.greaterThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedLow()));
97  		}
98  		if (mySearchEntity.getLastUpdatedHigh() != null) {
99  			predicates.add(cb.lessThanOrEqualTo(from.get("myUpdated").as(Date.class), mySearchEntity.getLastUpdatedHigh()));
100 		}
101 
102 		if (predicates.size() > 0) {
103 			q.where(predicates.toArray(new Predicate[predicates.size()]));
104 		}
105 
106 		q.orderBy(cb.desc(from.get("myUpdated")));
107 
108 		TypedQuery<ResourceHistoryTable> query = myEntityManager.createQuery(q);
109 
110 		if (theToIndex - theFromIndex > 0) {
111 			query.setFirstResult(theFromIndex);
112 			query.setMaxResults(theToIndex - theFromIndex);
113 		}
114 
115 		results = query.getResultList();
116 
117 		ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
118 		for (ResourceHistoryTable next : results) {
119 			BaseHasResource resource;
120 			resource = next;
121 
122 			retVal.add(myDao.toResource(resource, true));
123 		}
124 
125 		return retVal;
126 	}
127 
128 	protected List<IBaseResource> doSearchOrEverything(final int theFromIndex, final int theToIndex) {
129 		final ISearchBuilder sb = myDao.newSearchBuilder();
130 
131 		String resourceName = mySearchEntity.getResourceType();
132 		Class<? extends IBaseResource> resourceType = myContext.getResourceDefinition(resourceName).getImplementingClass();
133 		sb.setType(resourceType, resourceName);
134 
135 		final List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex);
136 
137 		TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
138 		template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
139 		return template.execute(theStatus -> toResourceList(sb, pidsSubList));
140 	}
141 
142 	private void ensureDependenciesInjected() {
143 		if (myPlatformTransactionManager == null) {
144 			myDao.injectDependenciesIntoBundleProvider(this);
145 		}
146 	}
147 
148 	/**
149 	 * Returns false if the entity can't be found
150 	 */
151 	public boolean ensureSearchEntityLoaded() {
152 		if (mySearchEntity == null) {
153 			ensureDependenciesInjected();
154 
155 			TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
156 			template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
157 			return template.execute(new TransactionCallback<Boolean>() {
158 				@Override
159 				public Boolean doInTransaction(TransactionStatus theStatus) {
160 					try {
161 						setSearchEntity(mySearchDao.findByUuid(myUuid));
162 
163 						if (mySearchEntity == null) {
164 							return false;
165 						}
166 
167 						// Load the includes now so that they are available outside of this transaction
168 						mySearchEntity.getIncludes().size();
169 
170 						return true;
171 					} catch (NoResultException e) {
172 						return false;
173 					}
174 				}
175 			});
176 		}
177 		return true;
178 	}
179 
180 	@Override
181 	public InstantDt getPublished() {
182 		ensureSearchEntityLoaded();
183 		return new InstantDt(mySearchEntity.getCreated());
184 	}
185 
186 	@Override
187 	public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
188 		ensureDependenciesInjected();
189 
190 		TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
191 
192 		template.execute(new TransactionCallbackWithoutResult() {
193 			@Override
194 			protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
195 				ensureSearchEntityLoaded();
196 			}
197 		});
198 
199 		switch (mySearchEntity.getSearchType()) {
200 			case HISTORY:
201 				return template.execute(new TransactionCallback<List<IBaseResource>>() {
202 					@Override
203 					public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
204 						return doHistoryInTransaction(theFromIndex, theToIndex);
205 					}
206 				});
207 			case SEARCH:
208 			case EVERYTHING:
209 			default:
210 				return doSearchOrEverything(theFromIndex, theToIndex);
211 		}
212 	}
213 
214 	@Override
215 	public String getUuid() {
216 		return myUuid;
217 	}
218 
219 	public boolean isCacheHit() {
220 		return myCacheHit;
221 	}
222 
223 	void setCacheHit(boolean theCacheHit) {
224 		myCacheHit = theCacheHit;
225 	}
226 
227 	@Override
228 	public Integer preferredPageSize() {
229 		ensureSearchEntityLoaded();
230 		return mySearchEntity.getPreferredPageSize();
231 	}
232 
233 	public void setContext(FhirContext theContext) {
234 		myContext = theContext;
235 	}
236 
237 	public void setEntityManager(EntityManager theEntityManager) {
238 		myEntityManager = theEntityManager;
239 	}
240 
241 	public void setPlatformTransactionManager(PlatformTransactionManager thePlatformTransactionManager) {
242 		myPlatformTransactionManager = thePlatformTransactionManager;
243 	}
244 
245 	public void setSearchCoordinatorSvc(ISearchCoordinatorSvc theSearchCoordinatorSvc) {
246 		mySearchCoordinatorSvc = theSearchCoordinatorSvc;
247 	}
248 
249 	public void setSearchDao(ISearchDao theSearchDao) {
250 		mySearchDao = theSearchDao;
251 	}
252 
253 	void setSearchEntity(Search theSearchEntity) {
254 		mySearchEntity = theSearchEntity;
255 	}
256 
257 	@Override
258 	public Integer size() {
259 		ensureSearchEntityLoaded();
260 		SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearchEntity);
261 
262 		Integer size = mySearchEntity.getTotalCount();
263 		if (size == null) {
264 			return null;
265 		}
266 		return Math.max(0, size);
267 	}
268 
269 	List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
270 		Set<Long> includedPids = new HashSet<>();
271 		if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
272 			includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
273 			includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
274 		}
275 
276 		// Execute the query and make sure we return distinct results
277 		List<IBaseResource> resources = new ArrayList<>();
278 		sb.loadResourcesByPid(pidsSubList, resources, includedPids, false, myEntityManager, myContext, myDao);
279 
280 		return resources;
281 	}
282 
283 }