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