View Javadoc
1   package ca.uhn.fhir.jpa.dao;
2   
3   import ca.uhn.fhir.context.*;
4   import ca.uhn.fhir.jpa.dao.data.*;
5   import ca.uhn.fhir.jpa.entity.*;
6   import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
7   import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
8   import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
9   import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
10  import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
11  import ca.uhn.fhir.jpa.util.DeleteConflict;
12  import ca.uhn.fhir.jpa.util.ExpungeOptions;
13  import ca.uhn.fhir.jpa.util.ExpungeOutcome;
14  import ca.uhn.fhir.jpa.util.JpaConstants;
15  import ca.uhn.fhir.model.api.*;
16  import ca.uhn.fhir.model.base.composite.BaseCodingDt;
17  import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
18  import ca.uhn.fhir.model.primitive.IdDt;
19  import ca.uhn.fhir.model.primitive.InstantDt;
20  import ca.uhn.fhir.model.primitive.StringDt;
21  import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
22  import ca.uhn.fhir.parser.DataFormatException;
23  import ca.uhn.fhir.parser.IParser;
24  import ca.uhn.fhir.parser.LenientErrorHandler;
25  import ca.uhn.fhir.rest.api.Constants;
26  import ca.uhn.fhir.rest.api.QualifiedParamList;
27  import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
28  import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
29  import ca.uhn.fhir.rest.api.server.IBundleProvider;
30  import ca.uhn.fhir.rest.api.server.RequestDetails;
31  import ca.uhn.fhir.rest.param.*;
32  import ca.uhn.fhir.rest.server.exceptions.*;
33  import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
34  import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
35  import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
36  import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
37  import ca.uhn.fhir.util.*;
38  import com.google.common.annotations.VisibleForTesting;
39  import com.google.common.base.Charsets;
40  import com.google.common.collect.ArrayListMultimap;
41  import com.google.common.collect.Sets;
42  import com.google.common.hash.HashFunction;
43  import com.google.common.hash.Hashing;
44  import org.apache.commons.lang3.NotImplementedException;
45  import org.apache.commons.lang3.StringUtils;
46  import org.apache.commons.lang3.Validate;
47  import org.apache.http.NameValuePair;
48  import org.apache.http.client.utils.URLEncodedUtils;
49  import org.hibernate.Session;
50  import org.hibernate.internal.SessionImpl;
51  import org.hl7.fhir.instance.model.api.*;
52  import org.hl7.fhir.r4.model.BaseResource;
53  import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
54  import org.hl7.fhir.r4.model.CanonicalType;
55  import org.hl7.fhir.r4.model.Reference;
56  import org.springframework.beans.BeansException;
57  import org.springframework.beans.factory.annotation.Autowired;
58  import org.springframework.context.ApplicationContext;
59  import org.springframework.context.ApplicationContextAware;
60  import org.springframework.data.domain.PageRequest;
61  import org.springframework.data.domain.Pageable;
62  import org.springframework.data.domain.Slice;
63  import org.springframework.data.domain.SliceImpl;
64  import org.springframework.stereotype.Repository;
65  import org.springframework.transaction.PlatformTransactionManager;
66  import org.springframework.transaction.support.TransactionTemplate;
67  
68  import javax.annotation.PostConstruct;
69  import javax.persistence.*;
70  import javax.persistence.criteria.CriteriaBuilder;
71  import javax.persistence.criteria.CriteriaQuery;
72  import javax.persistence.criteria.Predicate;
73  import javax.persistence.criteria.Root;
74  import javax.xml.stream.events.Characters;
75  import javax.xml.stream.events.XMLEvent;
76  import java.io.CharArrayWriter;
77  import java.text.Normalizer;
78  import java.util.*;
79  import java.util.Map.Entry;
80  import java.util.concurrent.atomic.AtomicInteger;
81  import java.util.stream.Collectors;
82  
83  import static org.apache.commons.lang3.StringUtils.*;
84  
85  /*
86   * #%L
87   * HAPI FHIR JPA Server
88   * %%
89   * Copyright (C) 2014 - 2018 University Health Network
90   * %%
91   * Licensed under the Apache License, Version 2.0 (the "License");
92   * you may not use this file except in compliance with the License.
93   * You may obtain a copy of the License at
94   * 
95   * http://www.apache.org/licenses/LICENSE-2.0
96   * 
97   * Unless required by applicable law or agreed to in writing, software
98   * distributed under the License is distributed on an "AS IS" BASIS,
99   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
100  * See the License for the specific language governing permissions and
101  * limitations under the License.
102  * #L%
103  */
104 
105 @SuppressWarnings("WeakerAccess")
106 @Repository
107 public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware {
108 
109 	public static final long INDEX_STATUS_INDEXED = 1L;
110 	public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
111 	public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
112 	public static final String OO_SEVERITY_ERROR = "error";
113 	public static final String OO_SEVERITY_INFO = "information";
114 	public static final String OO_SEVERITY_WARN = "warning";
115 	public static final String UCUM_NS = "http://unitsofmeasure.org";
116 	static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
117 	/**
118 	 * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
119 	 */
120 	static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
121 	/**
122 	 * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
123 	 */
124 	static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
125 	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
126 	private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
127 	private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
128 	private static boolean ourValidationDisabledForUnitTest;
129 	private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
130 
131 	static {
132 		Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
133 		Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
134 		resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
135 		resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
136 		resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
137 		resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
138 		resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
139 		resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
140 		resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
141 		resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
142 		resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
143 		resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
144 		RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
145 		RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
146 
147 		HashSet<String> excludeElementsInEncoded = new HashSet<String>();
148 		excludeElementsInEncoded.add("id");
149 		excludeElementsInEncoded.add("*.meta");
150 		EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
151 	}
152 
153 	@PersistenceContext(type = PersistenceContextType.TRANSACTION)
154 	protected EntityManager myEntityManager;
155 	@Autowired
156 	protected IForcedIdDao myForcedIdDao;
157 	@Autowired(required = false)
158 	protected IFulltextSearchSvc myFulltextSearchSvc;
159 	@Autowired()
160 	protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
161 	@Autowired()
162 	protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao;
163 	@Autowired()
164 	protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
165 	@Autowired()
166 	protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
167 	@Autowired()
168 	protected IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao;
169 	@Autowired()
170 	protected IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao;
171 	@Autowired()
172 	protected IResourceIndexedSearchParamNumberDao myResourceIndexedSearchParamNumberDao;
173 	@Autowired
174 	protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
175 	@Autowired
176 	protected ISearchParamRegistry mySerarchParamRegistry;
177 	@Autowired()
178 	protected IHapiTerminologySvc myTerminologySvc;
179 	@Autowired
180 	protected IResourceHistoryTableDao myResourceHistoryTableDao;
181 	@Autowired
182 	protected IResourceHistoryTagDao myResourceHistoryTagDao;
183 	@Autowired
184 	protected IResourceTableDao myResourceTableDao;
185 	@Autowired
186 	protected IResourceTagDao myResourceTagDao;
187 	@Autowired
188 	protected IResourceSearchViewDao myResourceViewDao;
189 	@Autowired(required = true)
190 	private DaoConfig myConfig;
191 	private FhirContext myContext;
192 	@Autowired
193 	private PlatformTransactionManager myPlatformTransactionManager;
194 	@Autowired
195 	private ISearchDao mySearchDao;
196 	@Autowired
197 	private ISearchParamExtractor mySearchParamExtractor;
198 	@Autowired
199 	private ISearchParamPresenceSvc mySearchParamPresenceSvc;
200 	@Autowired
201 	private ISearchParamRegistry mySearchParamRegistry;
202 	//@Autowired
203 	//private ISearchResultDao mySearchResultDao;
204 	@Autowired
205 	private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
206 	private ApplicationContext myApplicationContext;
207 	private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao;
208 
209 	public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
210 		if (theRequestDetails != null) {
211 			theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
212 		}
213 	}
214 
215 	protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) {
216 		if (theId.isEmpty() == false && theId.hasIdPart()) {
217 			if (isValidPid(theId)) {
218 				return;
219 			}
220 
221 			ForcedId fid = new ForcedId();
222 			fid.setResourceType(theEntity.getResourceType());
223 			fid.setForcedId(theId.getIdPart());
224 			fid.setResource(theEntity);
225 			theEntity.setForcedId(fid);
226 		}
227 	}
228 
229 	protected ExpungeOutcome doExpunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions) {
230 		TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
231 
232 		if (!getConfig().isExpungeEnabled()) {
233 			throw new MethodNotAllowedException("$expunge is not enabled on this server");
234 		}
235 
236 		AtomicInteger remainingCount = new AtomicInteger(theExpungeOptions.getLimit());
237 
238 		if (theResourceName == null && theResourceId == null && theVersion == null) {
239 			if (theExpungeOptions.isExpungeEverything()) {
240 				doExpungeEverything();
241 			}
242 		}
243 
244 		if (theExpungeOptions.isExpungeDeletedResources() && theVersion == null) {
245 
246 			/*
247 			 * Delete historical versions of deleted resources
248 			 */
249 			Pageable page = PageRequest.of(0, remainingCount.get());
250 			Slice<Long> resourceIds = txTemplate.execute(t -> {
251 				if (theResourceId != null) {
252 					return myResourceTableDao.findIdsOfDeletedResourcesOfType(page, theResourceId, theResourceName);
253 				} else {
254 					if (theResourceName != null) {
255 						return myResourceTableDao.findIdsOfDeletedResourcesOfType(page, theResourceName);
256 					} else {
257 						return myResourceTableDao.findIdsOfDeletedResources(page);
258 					}
259 				}
260 			});
261 			for (Long next : resourceIds) {
262 				txTemplate.execute(t -> {
263 					expungeHistoricalVersionsOfId(next, remainingCount);
264 					if (remainingCount.get() <= 0) {
265 						return toExpungeOutcome(theExpungeOptions, remainingCount);
266 					}
267 					return null;
268 				});
269 			}
270 
271 			/*
272 			 * Delete current versions of deleted resources
273 			 */
274 			for (Long next : resourceIds) {
275 				txTemplate.execute(t -> {
276 					expungeCurrentVersionOfResource(next);
277 					if (remainingCount.get() <= 0) {
278 						return toExpungeOutcome(theExpungeOptions, remainingCount);
279 					}
280 					return null;
281 				});
282 			}
283 
284 		}
285 
286 		if (theExpungeOptions.isExpungeOldVersions()) {
287 
288 			/*
289 			 * Delete historical versions of non-deleted resources
290 			 */
291 			Pageable page = PageRequest.of(0, remainingCount.get());
292 			Slice<Long> historicalIds = txTemplate.execute(t -> {
293 				if (theResourceId != null && theVersion != null) {
294 					return toSlice(myResourceHistoryTableDao.findForIdAndVersion(theResourceId, theVersion));
295 				} else {
296 					if (theResourceName != null) {
297 						return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResources(page, theResourceName);
298 					} else {
299 						return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResources(page);
300 					}
301 				}
302 			});
303 			for (Long next : historicalIds) {
304 				txTemplate.execute(t -> {
305 					expungeHistoricalVersion(next);
306 					if (remainingCount.decrementAndGet() <= 0) {
307 						return toExpungeOutcome(theExpungeOptions, remainingCount);
308 					}
309 					return null;
310 				});
311 			}
312 
313 		}
314 		return toExpungeOutcome(theExpungeOptions, remainingCount);
315 	}
316 
317 	private void doExpungeEverything() {
318 
319 		ourLog.info("** BEGINNING GLOBAL $expunge **");
320 		TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
321 		txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
322 		txTemplate.execute(t -> {
323 			doExpungeEverythingQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null");
324 			doExpungeEverythingQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null");
325 			doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null");
326 			return null;
327 		});
328 		txTemplate.execute(t -> {
329 			doExpungeEverythingQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d");
330 			doExpungeEverythingQuery("DELETE from " + ForcedId.class.getSimpleName() + " d");
331 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d");
332 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d");
333 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamQuantity.class.getSimpleName() + " d");
334 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamString.class.getSimpleName() + " d");
335 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d");
336 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d");
337 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d");
338 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedCompositeStringUnique.class.getSimpleName() + " d");
339 			doExpungeEverythingQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d");
340 			doExpungeEverythingQuery("DELETE from " + SearchResult.class.getSimpleName() + " d");
341 			doExpungeEverythingQuery("DELETE from " + SearchInclude.class.getSimpleName() + " d");
342 			doExpungeEverythingQuery("DELETE from " + TermConceptParentChildLink.class.getSimpleName() + " d");
343 			return null;
344 		});
345 		txTemplate.execute(t -> {
346 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroupElementTarget.class.getSimpleName() + " d");
347 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroupElement.class.getSimpleName() + " d");
348 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroup.class.getSimpleName() + " d");
349 			doExpungeEverythingQuery("DELETE from " + TermConceptMap.class.getSimpleName() + " d");
350 			return null;
351 		});
352 		txTemplate.execute(t -> {
353 			doExpungeEverythingQuery("DELETE from " + TermConceptProperty.class.getSimpleName() + " d");
354 			doExpungeEverythingQuery("DELETE from " + TermConceptDesignation.class.getSimpleName() + " d");
355 			doExpungeEverythingQuery("DELETE from " + TermConcept.class.getSimpleName() + " d");
356 			for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
357 				next.setCurrentVersion(null);
358 				myEntityManager.merge(next);
359 			}
360 			return null;
361 		});
362 		txTemplate.execute(t -> {
363 			doExpungeEverythingQuery("DELETE from " + TermCodeSystemVersion.class.getSimpleName() + " d");
364 			doExpungeEverythingQuery("DELETE from " + TermCodeSystem.class.getSimpleName() + " d");
365 			return null;
366 		});
367 		txTemplate.execute(t -> {
368 			doExpungeEverythingQuery("DELETE from " + SubscriptionTable.class.getSimpleName() + " d");
369 			doExpungeEverythingQuery("DELETE from " + ResourceHistoryTag.class.getSimpleName() + " d");
370 			doExpungeEverythingQuery("DELETE from " + ResourceTag.class.getSimpleName() + " d");
371 			doExpungeEverythingQuery("DELETE from " + TagDefinition.class.getSimpleName() + " d");
372 			doExpungeEverythingQuery("DELETE from " + ResourceHistoryTable.class.getSimpleName() + " d");
373 			doExpungeEverythingQuery("DELETE from " + ResourceTable.class.getSimpleName() + " d");
374 			doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d");
375 			return null;
376 		});
377 
378 		ourLog.info("** COMPLETED GLOBAL $expunge **");
379 	}
380 
381 	private void doExpungeEverythingQuery(String theQuery) {
382 		StopWatch sw = new StopWatch();
383 		int outcome = myEntityManager.createQuery(theQuery).executeUpdate();
384 		ourLog.info("Query affected {} rows in {}: {}", outcome, sw.toString(), theQuery);
385 	}
386 
387 	private void expungeCurrentVersionOfResource(Long theResourceId) {
388 		ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalStateException::new);
389 
390 		ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersion(resource.getId(), resource.getVersion());
391 		expungeHistoricalVersion(currentVersion.getId());
392 
393 		ourLog.info("Deleting current version of resource {}", resource.getIdDt().getValue());
394 
395 		myResourceIndexedSearchParamUriDao.deleteAll(resource.getParamsUri());
396 		myResourceIndexedSearchParamCoordsDao.deleteAll(resource.getParamsCoords());
397 		myResourceIndexedSearchParamDateDao.deleteAll(resource.getParamsDate());
398 		myResourceIndexedSearchParamNumberDao.deleteAll(resource.getParamsNumber());
399 		myResourceIndexedSearchParamQuantityDao.deleteAll(resource.getParamsQuantity());
400 		myResourceIndexedSearchParamStringDao.deleteAll(resource.getParamsString());
401 		myResourceIndexedSearchParamTokenDao.deleteAll(resource.getParamsToken());
402 
403 		myResourceTagDao.deleteAll(resource.getTags());
404 		resource.getTags().clear();
405 
406 		if (resource.getForcedId() != null) {
407 			ForcedId forcedId = resource.getForcedId();
408 			resource.setForcedId(null);
409 			myResourceTableDao.saveAndFlush(resource);
410 			myForcedIdDao.delete(forcedId);
411 		}
412 
413 		myResourceTableDao.delete(resource);
414 
415 	}
416 
417 	protected void expungeHistoricalVersion(Long theNextVersionId) {
418 		ResourceHistoryTable version = myResourceHistoryTableDao.findById(theNextVersionId).orElseThrow(IllegalArgumentException::new);
419 		ourLog.info("Deleting resource version {}", version.getIdDt().getValue());
420 
421 		myResourceHistoryTagDao.deleteAll(version.getTags());
422 		myResourceHistoryTableDao.delete(version);
423 	}
424 
425 	protected void expungeHistoricalVersionsOfId(Long theResourceId, AtomicInteger theRemainingCount) {
426 		ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalArgumentException::new);
427 
428 		Pageable page = new PageRequest(0, theRemainingCount.get());
429 
430 		Slice<Long> versionIds = myResourceHistoryTableDao.findForResourceId(page, resource.getId(), resource.getVersion());
431 		for (Long nextVersionId : versionIds) {
432 			expungeHistoricalVersion(nextVersionId);
433 			if (theRemainingCount.decrementAndGet() <= 0) {
434 				return;
435 			}
436 		}
437 	}
438 
439 	private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> theStringParams, Set<ResourceIndexedSearchParamToken> theTokenParams, Set<ResourceIndexedSearchParamNumber> theNumberParams, Set<ResourceIndexedSearchParamQuantity> theQuantityParams, Set<ResourceIndexedSearchParamDate> theDateParams, Set<ResourceIndexedSearchParamUri> theUriParams, Set<ResourceLink> theLinks) {
440 		Set<ResourceIndexedCompositeStringUnique> compositeStringUniques;
441 		compositeStringUniques = new HashSet<>();
442 		List<JpaRuntimeSearchParam> uniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(theEntity.getResourceType());
443 		for (JpaRuntimeSearchParam next : uniqueSearchParams) {
444 
445 			List<List<String>> partsChoices = new ArrayList<>();
446 
447 			for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
448 				Set<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
449 				Set<ResourceLink> linksForCompositePart = null;
450 				Set<String> linksForCompositePartWantPaths = null;
451 				switch (nextCompositeOf.getParamType()) {
452 					case NUMBER:
453 						paramsListForCompositePart = theNumberParams;
454 						break;
455 					case DATE:
456 						paramsListForCompositePart = theDateParams;
457 						break;
458 					case STRING:
459 						paramsListForCompositePart = theStringParams;
460 						break;
461 					case TOKEN:
462 						paramsListForCompositePart = theTokenParams;
463 						break;
464 					case REFERENCE:
465 						linksForCompositePart = theLinks;
466 						linksForCompositePartWantPaths = new HashSet<>();
467 						linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
468 						break;
469 					case QUANTITY:
470 						paramsListForCompositePart = theQuantityParams;
471 						break;
472 					case URI:
473 						paramsListForCompositePart = theUriParams;
474 						break;
475 					case COMPOSITE:
476 					case HAS:
477 						break;
478 				}
479 
480 				ArrayList<String> nextChoicesList = new ArrayList<>();
481 				partsChoices.add(nextChoicesList);
482 
483 				String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName());
484 				if (paramsListForCompositePart != null) {
485 					for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
486 						if (nextParam.getParamName().equals(nextCompositeOf.getName())) {
487 							IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
488 							String value = nextParamAsClientParam.getValueAsQueryToken(getContext());
489 							if (isNotBlank(value)) {
490 								value = UrlUtil.escapeUrlParam(value);
491 								nextChoicesList.add(key + "=" + value);
492 							}
493 						}
494 					}
495 				}
496 				if (linksForCompositePart != null) {
497 					for (ResourceLink nextLink : linksForCompositePart) {
498 						if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
499 							String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
500 							if (isNotBlank(value)) {
501 								value = UrlUtil.escapeUrlParam(value);
502 								nextChoicesList.add(key + "=" + value);
503 							}
504 						}
505 					}
506 				}
507 			}
508 
509 			Set<String> queryStringsToPopulate = extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices);
510 
511 			for (String nextQueryString : queryStringsToPopulate) {
512 				if (isNotBlank(nextQueryString)) {
513 					compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString));
514 				}
515 			}
516 		}
517 
518 		return compositeStringUniques;
519 	}
520 
521 	/**
522 	 * @return Returns a set containing all of the parameter names that
523 	 * were found to have a value
524 	 */
525 	@SuppressWarnings("unchecked")
526 	protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) {
527 		HashSet<String> retVal = new HashSet<>();
528 		String resourceType = theEntity.getResourceType();
529 
530 		/*
531 		 * For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
532 		 */
533 		if (theResource instanceof IBaseBundle) {
534 			return Collections.emptySet();
535 		}
536 
537 		Map<String, RuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveSearchParams(toResourceName(theResource.getClass()));
538 		for (RuntimeSearchParam nextSpDef : searchParams.values()) {
539 
540 			if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
541 				continue;
542 			}
543 
544 			String nextPathsUnsplit = nextSpDef.getPath();
545 			if (isBlank(nextPathsUnsplit)) {
546 				continue;
547 			}
548 
549 			boolean multiType = false;
550 			if (nextPathsUnsplit.endsWith("[x]")) {
551 				multiType = true;
552 			}
553 
554 			List<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource, nextSpDef);
555 			for (PathAndRef nextPathAndRef : refs) {
556 				Object nextObject = nextPathAndRef.getRef();
557 
558 				/*
559 				 * A search parameter on an extension field that contains
560 				 * references should index those references
561 				 */
562 				if (nextObject instanceof IBaseExtension<?, ?>) {
563 					nextObject = ((IBaseExtension<?, ?>) nextObject).getValue();
564 				}
565 
566 				if (nextObject instanceof CanonicalType) {
567 					nextObject = new Reference(((CanonicalType) nextObject).getValueAsString());
568 				}
569 
570 				IIdType nextId;
571 				if (nextObject instanceof IBaseReference) {
572 					IBaseReference nextValue = (IBaseReference) nextObject;
573 					if (nextValue.isEmpty()) {
574 						continue;
575 					}
576 					nextId = nextValue.getReferenceElement();
577 
578 					/*
579 					 * This can only really happen if the DAO is being called
580 					 * programatically with a Bundle (not through the FHIR REST API)
581 					 * but Smile does this
582 					 */
583 					if (nextId.isEmpty() && nextValue.getResource() != null) {
584 						nextId = nextValue.getResource().getIdElement();
585 					}
586 
587 					if (nextId.isEmpty() || nextId.getValue().startsWith("#")) {
588 						// This is a blank or contained resource reference
589 						continue;
590 					}
591 				} else if (nextObject instanceof IBaseResource) {
592 					nextId = ((IBaseResource) nextObject).getIdElement();
593 					if (nextId == null || nextId.hasIdPart() == false) {
594 						continue;
595 					}
596 				} else if (myContext.getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
597 					continue;
598 				} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
599 					// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
600 					continue;
601 				} else {
602 					if (!multiType) {
603 						if (nextSpDef.getName().equals("sourceuri")) {
604 							continue; // TODO: disable this eventually - ConceptMap:sourceuri is of type reference but points to a URI
605 						}
606 						throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
607 					} else {
608 						continue;
609 					}
610 				}
611 
612 				retVal.add(nextSpDef.getName());
613 
614 				if (isLogicalReference(nextId)) {
615 					ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
616 					if (theLinks.add(resourceLink)) {
617 						ourLog.debug("Indexing remote resource reference URL: {}", nextId);
618 					}
619 					continue;
620 				}
621 
622 				String baseUrl = nextId.getBaseUrl();
623 				String typeString = nextId.getResourceType();
624 				if (isBlank(typeString)) {
625 					throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextId.getValue());
626 				}
627 				RuntimeResourceDefinition resourceDefinition;
628 				try {
629 					resourceDefinition = getContext().getResourceDefinition(typeString);
630 				} catch (DataFormatException e) {
631 					throw new InvalidRequestException(
632 						"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextId.getValue());
633 				}
634 
635 				if (isNotBlank(baseUrl)) {
636 					if (!getConfig().getTreatBaseUrlsAsLocal().contains(baseUrl) && !getConfig().isAllowExternalReferences()) {
637 						String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "externalReferenceNotAllowed", nextId.getValue());
638 						throw new InvalidRequestException(msg);
639 					} else {
640 						ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
641 						if (theLinks.add(resourceLink)) {
642 							ourLog.debug("Indexing remote resource reference URL: {}", nextId);
643 						}
644 						continue;
645 					}
646 				}
647 
648 				Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
649 				String id = nextId.getIdPart();
650 				if (StringUtils.isBlank(id)) {
651 					throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextId.getValue());
652 				}
653 
654 				IFhirResourceDao<?> dao = getDao(type);
655 				if (dao == null) {
656 					StringBuilder b = new StringBuilder();
657 					b.append("This server (version ");
658 					b.append(myContext.getVersion().getVersion());
659 					b.append(") is not able to handle resources of type[");
660 					b.append(nextId.getResourceType());
661 					b.append("] - Valid resource types for this server: ");
662 					b.append(myResourceTypeToDao.keySet().toString());
663 
664 					throw new InvalidRequestException(b.toString());
665 				}
666 				Long valueOf;
667 				try {
668 					valueOf = translateForcedIdToPid(typeString, id);
669 				} catch (ResourceNotFoundException e) {
670 					if (myConfig.isEnforceReferentialIntegrityOnWrite() == false) {
671 						continue;
672 					}
673 					RuntimeResourceDefinition missingResourceDef = getContext().getResourceDefinition(type);
674 					String resName = missingResourceDef.getName();
675 
676 					if (getConfig().isAutoCreatePlaceholderReferenceTargets()) {
677 						IBaseResource newResource = missingResourceDef.newInstance();
678 						newResource.setId(resName + "/" + id);
679 						IFhirResourceDao<IBaseResource> placeholderResourceDao = (IFhirResourceDao<IBaseResource>) getDao(newResource.getClass());
680 						ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue());
681 						valueOf = placeholderResourceDao.update(newResource).getEntity().getId();
682 					} else {
683 						throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
684 					}
685 				}
686 				ResourceTable target = myEntityManager.find(ResourceTable.class, valueOf);
687 				RuntimeResourceDefinition targetResourceDef = getContext().getResourceDefinition(type);
688 				if (target == null) {
689 					String resName = targetResourceDef.getName();
690 					throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
691 				}
692 
693 				if (!typeString.equals(target.getResourceType())) {
694 					throw new UnprocessableEntityException(
695 						"Resource contains reference to " + nextId.getValue() + " but resource with ID " + nextId.getIdPart() + " is actually of type " + target.getResourceType());
696 				}
697 
698 				if (target.getDeleted() != null) {
699 					String resName = targetResourceDef.getName();
700 					throw new InvalidRequestException("Resource " + resName + "/" + id + " is deleted, specified in path: " + nextPathsUnsplit);
701 				}
702 
703 				if (nextSpDef.getTargets() != null && !nextSpDef.getTargets().contains(typeString)) {
704 					continue;
705 				}
706 
707 				ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, target, theUpdateTime);
708 				theLinks.add(resourceLink);
709 			}
710 
711 		}
712 
713 		theEntity.setHasLinks(theLinks.size() > 0);
714 
715 		return retVal;
716 	}
717 
718 	protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
719 		return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
720 	}
721 
722 	protected Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
723 		return mySearchParamExtractor.extractSearchParamDates(theEntity, theResource);
724 	}
725 
726 	protected Set<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
727 		return mySearchParamExtractor.extractSearchParamNumber(theEntity, theResource);
728 	}
729 
730 	protected Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
731 		return mySearchParamExtractor.extractSearchParamQuantity(theEntity, theResource);
732 	}
733 
734 	protected Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
735 		return mySearchParamExtractor.extractSearchParamStrings(theEntity, theResource);
736 	}
737 
738 	protected Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
739 		return mySearchParamExtractor.extractSearchParamTokens(theEntity, theResource);
740 	}
741 
742 	protected Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
743 		return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource);
744 	}
745 
746 	private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) {
747 		TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
748 		if (tagList != null) {
749 			for (Tag next : tagList) {
750 				TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
751 				if (def != null) {
752 					ResourceTag tag = theEntity.addTag(def);
753 					allDefs.add(tag);
754 					theEntity.setHasTags(true);
755 				}
756 			}
757 		}
758 
759 		List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
760 		if (securityLabels != null) {
761 			for (BaseCodingDt next : securityLabels) {
762 				TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
763 				if (def != null) {
764 					ResourceTag tag = theEntity.addTag(def);
765 					allDefs.add(tag);
766 					theEntity.setHasTags(true);
767 				}
768 			}
769 		}
770 
771 		List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
772 		if (profiles != null) {
773 			for (IIdType next : profiles) {
774 				TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
775 				if (def != null) {
776 					ResourceTag tag = theEntity.addTag(def);
777 					allDefs.add(tag);
778 					theEntity.setHasTags(true);
779 				}
780 			}
781 		}
782 	}
783 
784 	private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set<ResourceTag> theAllTags) {
785 		List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
786 		if (tagList != null) {
787 			for (IBaseCoding next : tagList) {
788 				TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
789 				if (def != null) {
790 					ResourceTag tag = theEntity.addTag(def);
791 					theAllTags.add(tag);
792 					theEntity.setHasTags(true);
793 				}
794 			}
795 		}
796 
797 		List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
798 		if (securityLabels != null) {
799 			for (IBaseCoding next : securityLabels) {
800 				TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
801 				if (def != null) {
802 					ResourceTag tag = theEntity.addTag(def);
803 					theAllTags.add(tag);
804 					theEntity.setHasTags(true);
805 				}
806 			}
807 		}
808 
809 		List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
810 		if (profiles != null) {
811 			for (IPrimitiveType<String> next : profiles) {
812 				TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
813 				if (def != null) {
814 					ResourceTag tag = theEntity.addTag(def);
815 					theAllTags.add(tag);
816 					theEntity.setHasTags(true);
817 				}
818 			}
819 		}
820 	}
821 
822 	private void findMatchingTagIds(String theResourceName, IIdType theResourceId, Set<Long> tagIds, Class<? extends BaseTag> entityClass) {
823 		{
824 			CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
825 			CriteriaQuery<Tuple> cq = builder.createTupleQuery();
826 			Root<? extends BaseTag> from = cq.from(entityClass);
827 			cq.multiselect(from.get("myTagId").as(Long.class)).distinct(true);
828 
829 			if (theResourceName != null) {
830 				Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
831 				if (theResourceId != null) {
832 					cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceName, theResourceId.getIdPart())));
833 				} else {
834 					cq.where(typePredicate);
835 				}
836 			}
837 
838 			TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
839 			for (Tuple next : query.getResultList()) {
840 				tagIds.add(next.get(0, Long.class));
841 			}
842 		}
843 	}
844 
845 	@SuppressWarnings("unchecked")
846 	private <RT extends BaseResourceIndexedSearchParam> void findMissingSearchParams(ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> activeSearchParams, RestSearchParameterTypeEnum type,
847 																												Set<RT> paramCollection) {
848 		for (Entry<String, RuntimeSearchParam> nextEntry : activeSearchParams) {
849 			String nextParamName = nextEntry.getKey();
850 			if (nextEntry.getValue().getParamType() == type) {
851 				boolean haveParam = false;
852 				for (BaseResourceIndexedSearchParam nextParam : paramCollection) {
853 					if (nextParam.getParamName().equals(nextParamName)) {
854 						haveParam = true;
855 						break;
856 					}
857 				}
858 
859 				if (!haveParam) {
860 					BaseResourceIndexedSearchParam param;
861 					switch (type) {
862 						case DATE:
863 							param = new ResourceIndexedSearchParamDate();
864 							break;
865 						case NUMBER:
866 							param = new ResourceIndexedSearchParamNumber();
867 							break;
868 						case QUANTITY:
869 							param = new ResourceIndexedSearchParamQuantity();
870 							break;
871 						case STRING:
872 							param = new ResourceIndexedSearchParamString()
873 								.setDaoConfig(myConfig);
874 							break;
875 						case TOKEN:
876 							param = new ResourceIndexedSearchParamToken();
877 							break;
878 						case URI:
879 							param = new ResourceIndexedSearchParamUri();
880 							break;
881 						case COMPOSITE:
882 						case HAS:
883 						case REFERENCE:
884 						default:
885 							continue;
886 					}
887 					param.setResource(theEntity);
888 					param.setMissing(true);
889 					param.setParamName(nextParamName);
890 					paramCollection.add((RT) param);
891 				}
892 			}
893 		}
894 	}
895 
896 	protected void flushJpaSession() {
897 		SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class);
898 		int insertionCount = session.getActionQueue().numberOfInsertions();
899 		int updateCount = session.getActionQueue().numberOfUpdates();
900 
901 		StopWatch sw = new StopWatch();
902 		myEntityManager.flush();
903 		ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount);
904 	}
905 
906 	private Set<ResourceTag> getAllTagDefinitions(ResourceTable theEntity) {
907 		HashSet<ResourceTag> retVal = Sets.newHashSet();
908 		if (theEntity.isHasTags()) {
909 			for (ResourceTag next : theEntity.getTags()) {
910 				retVal.add(next);
911 			}
912 		}
913 		return retVal;
914 	}
915 
916 	protected DaoConfig getConfig() {
917 		return myConfig;
918 	}
919 
920 	public void setConfig(DaoConfig theConfig) {
921 		myConfig = theConfig;
922 	}
923 
924 	@Override
925 	public FhirContext getContext() {
926 		return myContext;
927 	}
928 
929 	@Autowired
930 	public void setContext(FhirContext theContext) {
931 		myContext = theContext;
932 	}
933 
934 	public FhirContext getContext(FhirVersionEnum theVersion) {
935 		Validate.notNull(theVersion, "theVersion must not be null");
936 		synchronized (ourRetrievalContexts) {
937 			FhirContext retVal = ourRetrievalContexts.get(theVersion);
938 			if (retVal == null) {
939 				retVal = new FhirContext(theVersion);
940 				ourRetrievalContexts.put(theVersion, retVal);
941 			}
942 			return retVal;
943 		}
944 	}
945 
946 	@SuppressWarnings("unchecked")
947 	public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
948 		Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = getDaos();
949 		IFhirResourceDao<R> dao = (IFhirResourceDao<R>) resourceTypeToDao.get(theType);
950 		return dao;
951 	}
952 
953 	protected IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
954 		IFhirResourceDao<? extends IBaseResource> retVal = getDao(theClass);
955 		if (retVal == null) {
956 			List<String> supportedResourceTypes = getDaos()
957 				.keySet()
958 				.stream()
959 				.map(t -> myContext.getResourceDefinition(t).getName())
960 				.sorted()
961 				.collect(Collectors.toList());
962 			throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName() + " - Can handle: " + supportedResourceTypes);
963 		}
964 		return retVal;
965 	}
966 
967 	private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getDaos() {
968 		if (myResourceTypeToDao == null) {
969 			Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = new HashMap<>();
970 
971 			Map<String, IFhirResourceDao> daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false);
972 
973 			String[] beanNames = myApplicationContext.getBeanNamesForType(IFhirResourceDao.class);
974 
975 			for (IFhirResourceDao<?> next : daos.values()) {
976 				resourceTypeToDao.put(next.getResourceType(), next);
977 			}
978 
979 			if (this instanceof IFhirResourceDao<?>) {
980 				IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
981 				resourceTypeToDao.put(thiz.getResourceType(), thiz);
982 			}
983 
984 			myResourceTypeToDao = resourceTypeToDao;
985 		}
986 
987 		return Collections.unmodifiableMap(myResourceTypeToDao);
988 	}
989 
990 	public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
991 		return myResourceIndexedCompositeStringUniqueDao;
992 	}
993 
994 	@Override
995 	public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
996 		Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
997 		return params.get(theParamName);
998 	}
999 
1000 	@Override
1001 	public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
1002 		return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
1003 	}
1004 
1005 	protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
1006 		if (isBlank(theScheme) && isBlank(theTerm) && isBlank(theLabel)) {
1007 			return null;
1008 		}
1009 
1010 		CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
1011 		CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
1012 		Root<TagDefinition> from = cq.from(TagDefinition.class);
1013 
1014 		if (isNotBlank(theScheme)) {
1015 			cq.where(
1016 				builder.and(
1017 					builder.equal(from.get("myTagType"), theTagType),
1018 					builder.equal(from.get("mySystem"), theScheme),
1019 					builder.equal(from.get("myCode"), theTerm)));
1020 		} else {
1021 			cq.where(
1022 				builder.and(
1023 					builder.equal(from.get("myTagType"), theTagType),
1024 					builder.isNull(from.get("mySystem")),
1025 					builder.equal(from.get("myCode"), theTerm)));
1026 		}
1027 
1028 		TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
1029 		try {
1030 			return q.getSingleResult();
1031 		} catch (NoResultException e) {
1032 			TagDefinition retVal = new TagDefinition(theTagType, theScheme, theTerm, theLabel);
1033 			myEntityManager.persist(retVal);
1034 			return retVal;
1035 		}
1036 	}
1037 
1038 	protected TagList getTags(Class<? extends IBaseResource> theResourceType, IIdType theResourceId) {
1039 		String resourceName = null;
1040 		if (theResourceType != null) {
1041 			resourceName = toResourceName(theResourceType);
1042 			if (theResourceId != null && theResourceId.hasVersionIdPart()) {
1043 				IFhirResourceDao<? extends IBaseResource> dao = getDao(theResourceType);
1044 				BaseHasResource entity = dao.readEntity(theResourceId);
1045 				TagList retVal = new TagList();
1046 				for (BaseTag next : entity.getTags()) {
1047 					retVal.add(next.getTag().toTag());
1048 				}
1049 				return retVal;
1050 			}
1051 		}
1052 
1053 		Set<Long> tagIds = new HashSet<>();
1054 		findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceTag.class);
1055 		findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceHistoryTag.class);
1056 		if (tagIds.isEmpty()) {
1057 			return new TagList();
1058 		}
1059 		{
1060 			CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
1061 			CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
1062 			Root<TagDefinition> from = cq.from(TagDefinition.class);
1063 			cq.where(from.get("myId").in(tagIds));
1064 			cq.orderBy(builder.asc(from.get("mySystem")), builder.asc(from.get("myCode")));
1065 			TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
1066 			q.setMaxResults(getConfig().getHardTagListLimit());
1067 
1068 			TagList retVal = new TagList();
1069 			for (TagDefinition next : q.getResultList()) {
1070 				retVal.add(next.toTag());
1071 			}
1072 
1073 			return retVal;
1074 		}
1075 	}
1076 
1077 	protected IBundleProvider history(String theResourceName, Long theId, Date theSince, Date theUntil) {
1078 
1079 		String resourceName = defaultIfBlank(theResourceName, null);
1080 
1081 		Search search = new Search();
1082 		search.setCreated(new Date());
1083 		search.setSearchLastReturned(new Date());
1084 		search.setLastUpdated(theSince, theUntil);
1085 		search.setUuid(UUID.randomUUID().toString());
1086 		search.setResourceType(resourceName);
1087 		search.setResourceId(theId);
1088 		search.setSearchType(SearchTypeEnum.HISTORY);
1089 		search.setStatus(SearchStatusEnum.FINISHED);
1090 
1091 		if (theSince != null) {
1092 			if (resourceName == null) {
1093 				search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes(theSince));
1094 			} else if (theId == null) {
1095 				search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName, theSince));
1096 			} else {
1097 				search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId, theSince));
1098 			}
1099 		} else {
1100 			if (resourceName == null) {
1101 				search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes());
1102 			} else if (theId == null) {
1103 				search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName));
1104 			} else {
1105 				search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId));
1106 			}
1107 		}
1108 
1109 		search = mySearchDao.save(search);
1110 
1111 		return new PersistedJpaBundleProvider(search.getUuid(), this);
1112 	}
1113 
1114 	void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
1115 		String newVersion;
1116 		long newVersionLong;
1117 		if (theResourceId == null || theResourceId.getVersionIdPart() == null) {
1118 			newVersion = "1";
1119 			newVersionLong = 1;
1120 		} else {
1121 			newVersionLong = theResourceId.getVersionIdPartAsLong() + 1;
1122 			newVersion = Long.toString(newVersionLong);
1123 		}
1124 
1125 		IIdType newId = theResourceId.withVersion(newVersion);
1126 		theResource.getIdElement().setValue(newId.getValue());
1127 		theSavedEntity.setVersion(newVersionLong);
1128 	}
1129 
1130 	@Override
1131 	public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) {
1132 		theProvider.setContext(getContext());
1133 		theProvider.setEntityManager(myEntityManager);
1134 		theProvider.setPlatformTransactionManager(myPlatformTransactionManager);
1135 		theProvider.setSearchDao(mySearchDao);
1136 		theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
1137 	}
1138 
1139 	protected boolean isLogicalReference(IIdType theId) {
1140 		Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
1141 		if (treatReferencesAsLogical != null) {
1142 			for (String nextLogicalRef : treatReferencesAsLogical) {
1143 				nextLogicalRef = trim(nextLogicalRef);
1144 				if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
1145 					if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
1146 						return true;
1147 					}
1148 				} else {
1149 					if (theId.getValue().equals(nextLogicalRef)) {
1150 						return true;
1151 					}
1152 				}
1153 			}
1154 
1155 		}
1156 		return false;
1157 	}
1158 
1159 	public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
1160 		if (theRequestDetails != null) {
1161 			theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
1162 		}
1163 	}
1164 
1165 	@Override
1166 	public SearchBuilder newSearchBuilder() {
1167 		SearchBuilder builder = new SearchBuilder(
1168 			getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
1169 			myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao);
1170 		return builder;
1171 	}
1172 
1173 	public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
1174 		if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
1175 			if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
1176 				throw new InternalErrorException(
1177 					"Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
1178 			}
1179 		}
1180 
1181 		if (theRequestDetails.getUserData().get(PROCESSING_SUB_REQUEST) == Boolean.TRUE) {
1182 			theRequestDetails.notifyIncomingRequestPreHandled(theOperationType);
1183 		}
1184 		List<IServerInterceptor> interceptors = getConfig().getInterceptors();
1185 		for (IServerInterceptor next : interceptors) {
1186 			next.incomingRequestPreHandled(theOperationType, theRequestDetails);
1187 		}
1188 	}
1189 
1190 	public String parseContentTextIntoWords(IBaseResource theResource) {
1191 		StringBuilder retVal = new StringBuilder();
1192 		@SuppressWarnings("rawtypes")
1193 		List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
1194 		for (@SuppressWarnings("rawtypes")
1195 			IPrimitiveType nextType : childElements) {
1196 			if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
1197 				String nextValue = nextType.getValueAsString();
1198 				if (isNotBlank(nextValue)) {
1199 					retVal.append(nextValue.replace("\n", " ").replace("\r", " "));
1200 					retVal.append("\n");
1201 				}
1202 			}
1203 		}
1204 		return retVal.toString();
1205 	}
1206 
1207 	@Override
1208 	public void populateFullTextFields(final IBaseResource theResource, ResourceTable theEntity) {
1209 		if (theEntity.getDeleted() != null) {
1210 			theEntity.setNarrativeTextParsedIntoWords(null);
1211 			theEntity.setContentTextParsedIntoWords(null);
1212 		} else {
1213 			theEntity.setNarrativeTextParsedIntoWords(parseNarrativeTextIntoWords(theResource));
1214 			theEntity.setContentTextParsedIntoWords(parseContentTextIntoWords(theResource));
1215 		}
1216 	}
1217 
1218 	private void populateResourceIdFromEntity(IBaseResourceEntity theEntity, final IBaseResource theResource) {
1219 		IIdType id = theEntity.getIdDt();
1220 		if (getContext().getVersion().getVersion().isRi()) {
1221 			id = getContext().getVersion().newIdType().setValue(id.getValue());
1222 		}
1223 		theResource.setId(id);
1224 	}
1225 
1226 	/**
1227 	 * Returns true if the resource has changed (either the contents or the tags)
1228 	 */
1229 	protected EncodedResource populateResourceIntoEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, boolean theUpdateHash) {
1230 		if (theEntity.getResourceType() == null) {
1231 			theEntity.setResourceType(toResourceName(theResource));
1232 		}
1233 
1234 		if (theResource != null) {
1235 			List<BaseResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class);
1236 			for (BaseResourceReferenceDt nextRef : refs) {
1237 				if (nextRef.getReference().isEmpty() == false) {
1238 					if (nextRef.getReference().hasVersionIdPart()) {
1239 						nextRef.setReference(nextRef.getReference().toUnqualifiedVersionless());
1240 					}
1241 				}
1242 			}
1243 		}
1244 
1245 		byte[] bytes;
1246 		ResourceEncodingEnum encoding;
1247 		boolean changed = false;
1248 
1249 		if (theEntity.getDeleted() == null) {
1250 
1251 			encoding = myConfig.getResourceEncoding();
1252 			Set<String> excludeElements = EXCLUDE_ELEMENTS_IN_ENCODED;
1253 			theEntity.setFhirVersion(myContext.getVersion().getVersion());
1254 
1255 			bytes = encodeResource(theResource, encoding, excludeElements, myContext);
1256 
1257 			if (theUpdateHash) {
1258 				HashFunction sha256 = Hashing.sha256();
1259 				String hashSha256 = sha256.hashBytes(bytes).toString();
1260 				if (hashSha256.equals(theEntity.getHashSha256()) == false) {
1261 					changed = true;
1262 				}
1263 				theEntity.setHashSha256(hashSha256);
1264 			}
1265 
1266 			Set<ResourceTag> allDefs = new HashSet<>();
1267 			Set<ResourceTag> allTagsOld = getAllTagDefinitions(theEntity);
1268 
1269 			if (theResource instanceof IResource) {
1270 				extractTagsHapi((IResource) theResource, theEntity, allDefs);
1271 			} else {
1272 				extractTagsRi((IAnyResource) theResource, theEntity, allDefs);
1273 			}
1274 
1275 			RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
1276 			if (def.isStandardType() == false) {
1277 				String profile = def.getResourceProfile("");
1278 				if (isNotBlank(profile)) {
1279 					TagDefinition profileDef = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
1280 					if (def != null) {
1281 						ResourceTag tag = theEntity.addTag(profileDef);
1282 						allDefs.add(tag);
1283 						theEntity.setHasTags(true);
1284 					}
1285 				}
1286 			}
1287 
1288 			Set<ResourceTag> allTagsNew = getAllTagDefinitions(theEntity);
1289 			Set<TagDefinition> allDefsPresent = new HashSet<>();
1290 			allTagsNew.forEach(tag -> {
1291 
1292 				// Don't keep duplicate tags
1293 				if (!allDefsPresent.add(tag.getTag())) {
1294 					theEntity.getTags().remove(tag);
1295 				}
1296 
1297 				// Drop any tags that have been removed
1298 				if (!allDefs.contains(tag)) {
1299 					if (shouldDroppedTagBeRemovedOnUpdate(theRequest, tag)) {
1300 						theEntity.getTags().remove(tag);
1301 					}
1302 				}
1303 
1304 			});
1305 
1306 			if (!allTagsOld.equals(allTagsNew)) {
1307 				changed = true;
1308 			}
1309 			theEntity.setHasTags(!allTagsNew.isEmpty());
1310 
1311 		} else {
1312 			theEntity.setHashSha256(null);
1313 			bytes = null;
1314 			encoding = ResourceEncodingEnum.DEL;
1315 		}
1316 
1317 		if (changed == false) {
1318 			if (theEntity.getId() == null) {
1319 				changed = true;
1320 			} else {
1321 				ResourceHistoryTable currentHistoryVersion = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
1322 				if (currentHistoryVersion == null || currentHistoryVersion.getResource() == null) {
1323 					changed = true;
1324 				} else {
1325 					changed = !Arrays.equals(currentHistoryVersion.getResource(), bytes);
1326 				}
1327 			}
1328 		}
1329 
1330 		EncodedResource retVal = new EncodedResource();
1331 		retVal.setEncoding(encoding);
1332 		retVal.setResource(bytes);
1333 		retVal.setChanged(changed);
1334 
1335 		return retVal;
1336 	}
1337 
1338 	@SuppressWarnings("unchecked")
1339 	private <R extends IBaseResource> R populateResourceMetadataHapi(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IResource res) {
1340 		R retVal = (R) res;
1341 		if (theEntity.getDeleted() != null) {
1342 			res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance();
1343 			retVal = (R) res;
1344 			ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
1345 			if (theForHistoryOperation) {
1346 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
1347 			}
1348 		} else if (theForHistoryOperation) {
1349 			/*
1350 			 * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
1351 			 */
1352 			Date published = theEntity.getPublished().getValue();
1353 			Date updated = theEntity.getUpdated().getValue();
1354 			if (published.equals(updated)) {
1355 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
1356 			} else {
1357 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
1358 			}
1359 		}
1360 
1361 		res.setId(theEntity.getIdDt());
1362 
1363 		ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
1364 		ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
1365 		ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
1366 		IDao.RESOURCE_PID.put(res, theEntity.getId());
1367 
1368 		Collection<? extends BaseTag> tags = theTagList;
1369 		if (theEntity.isHasTags()) {
1370 			TagList tagList = new TagList();
1371 			List<IBaseCoding> securityLabels = new ArrayList<>();
1372 			List<IdDt> profiles = new ArrayList<>();
1373 			for (BaseTag next : tags) {
1374 				switch (next.getTag().getTagType()) {
1375 					case PROFILE:
1376 						profiles.add(new IdDt(next.getTag().getCode()));
1377 						break;
1378 					case SECURITY_LABEL:
1379 						IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt();
1380 						secLabel.setSystem(next.getTag().getSystem());
1381 						secLabel.setCode(next.getTag().getCode());
1382 						secLabel.setDisplay(next.getTag().getDisplay());
1383 						securityLabels.add(secLabel);
1384 						break;
1385 					case TAG:
1386 						tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay()));
1387 						break;
1388 				}
1389 			}
1390 			if (tagList.size() > 0) {
1391 				ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList);
1392 			}
1393 			if (securityLabels.size() > 0) {
1394 				ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels));
1395 			}
1396 			if (profiles.size() > 0) {
1397 				ResourceMetadataKeyEnum.PROFILES.put(res, profiles);
1398 			}
1399 		}
1400 
1401 		return retVal;
1402 	}
1403 
1404 	@SuppressWarnings("unchecked")
1405 	private <R extends IBaseResource> R populateResourceMetadataRi(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IAnyResource res) {
1406 		R retVal = (R) res;
1407 		if (theEntity.getDeleted() != null) {
1408 			res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance();
1409 			retVal = (R) res;
1410 			ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
1411 			if (theForHistoryOperation) {
1412 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode());
1413 			}
1414 		} else if (theForHistoryOperation) {
1415 			/*
1416 			 * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
1417 			 */
1418 			Date published = theEntity.getPublished().getValue();
1419 			Date updated = theEntity.getUpdated().getValue();
1420 			if (published.equals(updated)) {
1421 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode());
1422 			} else {
1423 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode());
1424 			}
1425 		}
1426 
1427 		res.getMeta().getTag().clear();
1428 		res.getMeta().getProfile().clear();
1429 		res.getMeta().getSecurity().clear();
1430 		res.getMeta().setLastUpdated(null);
1431 		res.getMeta().setVersionId(null);
1432 
1433 		populateResourceIdFromEntity(theEntity, res);
1434 
1435 		res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
1436 		IDao.RESOURCE_PID.put(res, theEntity.getId());
1437 
1438 		Collection<? extends BaseTag> tags = theTagList;
1439 
1440 		if (theEntity.isHasTags()) {
1441 			for (BaseTag next : tags) {
1442 				switch (next.getTag().getTagType()) {
1443 					case PROFILE:
1444 						res.getMeta().addProfile(next.getTag().getCode());
1445 						break;
1446 					case SECURITY_LABEL:
1447 						IBaseCoding sec = res.getMeta().addSecurity();
1448 						sec.setSystem(next.getTag().getSystem());
1449 						sec.setCode(next.getTag().getCode());
1450 						sec.setDisplay(next.getTag().getDisplay());
1451 						break;
1452 					case TAG:
1453 						IBaseCoding tag = res.getMeta().addTag();
1454 						tag.setSystem(next.getTag().getSystem());
1455 						tag.setCode(next.getTag().getCode());
1456 						tag.setDisplay(next.getTag().getDisplay());
1457 						break;
1458 				}
1459 			}
1460 		}
1461 		return retVal;
1462 	}
1463 
1464 	/**
1465 	 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
1466 	 *
1467 	 * @param theEntity The resource
1468 	 */
1469 	protected void postDelete(ResourceTable theEntity) {
1470 		// nothing
1471 	}
1472 
1473 	/**
1474 	 * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
1475 	 *
1476 	 * @param theEntity   The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
1477 	 * @param theResource The resource being persisted
1478 	 */
1479 	protected void postPersist(ResourceTable theEntity, T theResource) {
1480 		// nothing
1481 	}
1482 
1483 	/**
1484 	 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
1485 	 *
1486 	 * @param theEntity   The resource
1487 	 * @param theResource The resource being persisted
1488 	 */
1489 	protected void postUpdate(ResourceTable theEntity, T theResource) {
1490 		// nothing
1491 	}
1492 
1493 	@Override
1494 	public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
1495 		RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
1496 
1497 		SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef);
1498 		paramMap.setLoadSynchronous(true);
1499 
1500 		if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
1501 			throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
1502 		}
1503 
1504 		IFhirResourceDao<R> dao = getDao(theResourceType);
1505 		if (dao == null) {
1506 			throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
1507 		}
1508 
1509 		return dao.searchForIds(paramMap);
1510 	}
1511 
1512 	@CoverageIgnore
1513 	public BaseHasResource readEntity(IIdType theValueId) {
1514 		throw new NotImplementedException("");
1515 	}
1516 
1517 	private <T> Collection<T> removeCommon(Collection<T> theInput, Collection<T> theToRemove) {
1518 		assert theInput != theToRemove;
1519 
1520 		if (theInput.isEmpty()) {
1521 			return theInput;
1522 		}
1523 
1524 		ArrayList<T> retVal = new ArrayList<>(theInput);
1525 		retVal.removeAll(theToRemove);
1526 		return retVal;
1527 	}
1528 
1529 	@Override
1530 	public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
1531 		/*
1532 		 * We do a null check here because Smile's module system tries to
1533 		 * initialize the application context twice if two modules depend on
1534 		 * the persistence module. The second time sets the dependency's appctx.
1535 		 */
1536 		if (myApplicationContext == null) {
1537 			myApplicationContext = theApplicationContext;
1538 		}
1539 	}
1540 
1541 	private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
1542 		for (BaseResourceIndexedSearchParam nextSearchParam : theParams) {
1543 			nextSearchParam.setUpdated(theUpdateTime);
1544 		}
1545 	}
1546 
1547 	/**
1548 	 * This method is called when an update to an existing resource detects that the resource supplied for update is missing a tag/profile/security label that the currently persisted resource holds.
1549 	 * <p>
1550 	 * The default implementation removes any profile declarations, but leaves tags and security labels in place. Subclasses may choose to override and change this behaviour.
1551 	 * </p>
1552 	 * <p>
1553 	 * See <a href="http://hl7.org/fhir/resource.html#tag-updates">Updates to Tags, Profiles, and Security Labels</a> for a description of the logic that the default behaviour folows.
1554 	 * </p>
1555 	 *
1556 	 * @param theTag The tag
1557 	 * @return Returns <code>true</code> if the tag should be removed
1558 	 */
1559 	protected boolean shouldDroppedTagBeRemovedOnUpdate(RequestDetails theRequest, ResourceTag theTag) {
1560 
1561 		Set<TagTypeEnum> metaSnapshotModeTokens = null;
1562 
1563 		if (theRequest != null) {
1564 			List<String> metaSnapshotMode = theRequest.getHeaders(JpaConstants.HEADER_META_SNAPSHOT_MODE);
1565 			if (metaSnapshotMode != null && !metaSnapshotMode.isEmpty()) {
1566 				metaSnapshotModeTokens = new HashSet<>();
1567 				for (String nextHeaderValue : metaSnapshotMode) {
1568 					StringTokenizer tok = new StringTokenizer(nextHeaderValue, ",");
1569 					while (tok.hasMoreTokens()) {
1570 						switch (trim(tok.nextToken())) {
1571 							case "TAG":
1572 								metaSnapshotModeTokens.add(TagTypeEnum.TAG);
1573 								break;
1574 							case "PROFILE":
1575 								metaSnapshotModeTokens.add(TagTypeEnum.PROFILE);
1576 								break;
1577 							case "SECURITY_LABEL":
1578 								metaSnapshotModeTokens.add(TagTypeEnum.SECURITY_LABEL);
1579 								break;
1580 						}
1581 					}
1582 				}
1583 			}
1584 		}
1585 
1586 		if (metaSnapshotModeTokens == null) {
1587 			metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
1588 		}
1589 
1590 		if (metaSnapshotModeTokens.contains(theTag.getTag().getTagType())) {
1591 			return true;
1592 		}
1593 
1594 		return false;
1595 	}
1596 
1597 	@PostConstruct
1598 	public void startClearCaches() {
1599 		myResourceTypeToDao = null;
1600 	}
1601 
1602 	private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) {
1603 		return new ExpungeOutcome()
1604 			.setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get());
1605 	}
1606 
1607 	@Override
1608 	public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
1609 		RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
1610 		Class<? extends IBaseResource> resourceType = type.getImplementingClass();
1611 		return toResource(resourceType, theEntity, null, theForHistoryOperation);
1612 	}
1613 
1614 	@SuppressWarnings("unchecked")
1615 	@Override
1616 	public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
1617 
1618 		// 1. get resource, it's encoding and the tags if any
1619 		byte[] resourceBytes = null;
1620 		ResourceEncodingEnum resourceEncoding = null;
1621 		Collection<? extends BaseTag> myTagList = null;
1622 
1623 		if (theEntity instanceof ResourceHistoryTable) {
1624 			ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
1625 			resourceBytes = history.getResource();
1626 			resourceEncoding = history.getEncoding();
1627 			myTagList = history.getTags();
1628 		} else if (theEntity instanceof ResourceTable) {
1629 			ResourceTable resource = (ResourceTable) theEntity;
1630 			ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
1631 			if (history == null) {
1632 				return null;
1633 			}
1634 			resourceBytes = history.getResource();
1635 			resourceEncoding = history.getEncoding();
1636 			myTagList = resource.getTags();
1637 		} else if (theEntity instanceof ResourceSearchView) {
1638 			// This is the search View
1639 			ResourceSearchView myView = (ResourceSearchView) theEntity;
1640 			resourceBytes = myView.getResource();
1641 			resourceEncoding = myView.getEncoding();
1642 			if (theTagList == null)
1643 				myTagList = new HashSet<>();
1644 			else
1645 				myTagList = theTagList;
1646 		} else {
1647 			// something wrong
1648 			return null;
1649 		}
1650 
1651 		// 2. get The text
1652 		String resourceText = decodeResource(resourceBytes, resourceEncoding);
1653 
1654 		// 3. Use the appropriate custom type if one is specified in the context
1655 		Class<R> resourceType = theResourceType;
1656 		if (myContext.hasDefaultTypeForProfile()) {
1657 			for (BaseTag nextTag : myTagList) {
1658 				if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
1659 					String profile = nextTag.getTag().getCode();
1660 					if (isNotBlank(profile)) {
1661 						Class<? extends IBaseResource> newType = myContext.getDefaultTypeForProfile(profile);
1662 						if (newType != null && theResourceType.isAssignableFrom(newType)) {
1663 							ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile);
1664 							resourceType = (Class<R>) newType;
1665 							break;
1666 						}
1667 					}
1668 				}
1669 			}
1670 		}
1671 
1672 		// 4. parse the text to FHIR
1673 		R retVal;
1674 		if (resourceEncoding != ResourceEncodingEnum.DEL) {
1675 			IParser parser = resourceEncoding.newParser(getContext(theEntity.getFhirVersion()));
1676 			parser.setParserErrorHandler(new LenientErrorHandler(false).setErrorOnInvalidValue(false));
1677 
1678 			try {
1679 				retVal = parser.parseResource(resourceType, resourceText);
1680 			} catch (Exception e) {
1681 				StringBuilder b = new StringBuilder();
1682 				b.append("Failed to parse database resource[");
1683 				b.append(resourceType);
1684 				b.append("/");
1685 				b.append(theEntity.getIdDt().getIdPart());
1686 				b.append(" (pid ");
1687 				b.append(theEntity.getId());
1688 				b.append(", version ");
1689 				b.append(theEntity.getFhirVersion().name());
1690 				b.append("): ");
1691 				b.append(e.getMessage());
1692 				String msg = b.toString();
1693 				ourLog.error(msg, e);
1694 				throw new DataFormatException(msg, e);
1695 			}
1696 
1697 		} else {
1698 
1699 			retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance();
1700 
1701 		}
1702 
1703 		// 5. fill MetaData
1704 		if (retVal instanceof IResource) {
1705 			IResource res = (IResource) retVal;
1706 			retVal = populateResourceMetadataHapi(resourceType, theEntity, myTagList, theForHistoryOperation, res);
1707 		} else {
1708 			IAnyResource res = (IAnyResource) retVal;
1709 			retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res);
1710 		}
1711 
1712 		return retVal;
1713 	}
1714 
1715 	protected String toResourceName(Class<? extends IBaseResource> theResourceType) {
1716 		return myContext.getResourceDefinition(theResourceType).getName();
1717 	}
1718 
1719 	String toResourceName(IBaseResource theResource) {
1720 		return myContext.getResourceDefinition(theResource).getName();
1721 	}
1722 
1723 	private Slice<Long> toSlice(ResourceHistoryTable theVersion) {
1724 		Validate.notNull(theVersion);
1725 		return new SliceImpl<>(Collections.singletonList(theVersion.getId()));
1726 	}
1727 
1728 	Long translateForcedIdToPid(String theResourceName, String theResourceId) {
1729 		return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
1730 	}
1731 
1732 	protected List<Long> translateForcedIdToPids(IIdType theId) {
1733 		return translateForcedIdToPids(theId, myForcedIdDao);
1734 	}
1735 
1736 	private String translatePidIdToForcedId(String theResourceType, Long theId) {
1737 		ForcedId forcedId = myForcedIdDao.findByResourcePid(theId);
1738 		if (forcedId != null) {
1739 			return forcedId.getResourceType() + '/' + forcedId.getForcedId();
1740 		} else {
1741 			return theResourceType + '/' + theId.toString();
1742 		}
1743 	}
1744 
1745 	@SuppressWarnings("unchecked")
1746 	protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
1747 		theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
1748 													 boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
1749 		Validate.notNull(theEntity);
1750 		Validate.isTrue(theDeletedTimestampOrNull != null || theResource != null, "Must have either a resource[{}] or a deleted timestamp[{}] for resource PID[{}]", theDeletedTimestampOrNull != null, theResource != null, theEntity.getId());
1751 
1752 		ourLog.debug("Starting entity update");
1753 
1754 
1755 		/*
1756 		 * This should be the very first thing..
1757 		 */
1758 		if (theResource != null) {
1759 			if (thePerformIndexing) {
1760 				if (!ourValidationDisabledForUnitTest) {
1761 					validateResourceForStorage((T) theResource, theEntity);
1762 				}
1763 			}
1764 			String resourceType = myContext.getResourceDefinition(theResource).getName();
1765 			if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
1766 				throw new UnprocessableEntityException(
1767 					"Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
1768 			}
1769 		}
1770 
1771 		if (theEntity.getPublished() == null) {
1772 			ourLog.debug("Entity has published time: {}", new InstantDt(theUpdateTime));
1773 
1774 			theEntity.setPublished(theUpdateTime);
1775 		}
1776 
1777 		Collection<ResourceIndexedSearchParamString> existingStringParams = new ArrayList<>();
1778 		if (theEntity.isParamsStringPopulated()) {
1779 			existingStringParams.addAll(theEntity.getParamsString());
1780 		}
1781 		Collection<ResourceIndexedSearchParamToken> existingTokenParams = new ArrayList<>();
1782 		if (theEntity.isParamsTokenPopulated()) {
1783 			existingTokenParams.addAll(theEntity.getParamsToken());
1784 		}
1785 		Collection<ResourceIndexedSearchParamNumber> existingNumberParams = new ArrayList<>();
1786 		if (theEntity.isParamsNumberPopulated()) {
1787 			existingNumberParams.addAll(theEntity.getParamsNumber());
1788 		}
1789 		Collection<ResourceIndexedSearchParamQuantity> existingQuantityParams = new ArrayList<>();
1790 		if (theEntity.isParamsQuantityPopulated()) {
1791 			existingQuantityParams.addAll(theEntity.getParamsQuantity());
1792 		}
1793 		Collection<ResourceIndexedSearchParamDate> existingDateParams = new ArrayList<>();
1794 		if (theEntity.isParamsDatePopulated()) {
1795 			existingDateParams.addAll(theEntity.getParamsDate());
1796 		}
1797 		Collection<ResourceIndexedSearchParamUri> existingUriParams = new ArrayList<>();
1798 		if (theEntity.isParamsUriPopulated()) {
1799 			existingUriParams.addAll(theEntity.getParamsUri());
1800 		}
1801 		Collection<ResourceIndexedSearchParamCoords> existingCoordsParams = new ArrayList<>();
1802 		if (theEntity.isParamsCoordsPopulated()) {
1803 			existingCoordsParams.addAll(theEntity.getParamsCoords());
1804 		}
1805 		Collection<ResourceLink> existingResourceLinks = new ArrayList<>();
1806 		if (theEntity.isHasLinks()) {
1807 			existingResourceLinks.addAll(theEntity.getResourceLinks());
1808 		}
1809 
1810 		Collection<ResourceIndexedCompositeStringUnique> existingCompositeStringUniques = new ArrayList<>();
1811 		if (theEntity.isParamsCompositeStringUniquePresent()) {
1812 			existingCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
1813 		}
1814 
1815 		Set<ResourceIndexedSearchParamString> stringParams = null;
1816 		Set<ResourceIndexedSearchParamToken> tokenParams = null;
1817 		Set<ResourceIndexedSearchParamNumber> numberParams = null;
1818 		Set<ResourceIndexedSearchParamQuantity> quantityParams = null;
1819 		Set<ResourceIndexedSearchParamDate> dateParams = null;
1820 		Set<ResourceIndexedSearchParamUri> uriParams = null;
1821 		Set<ResourceIndexedSearchParamCoords> coordsParams = null;
1822 		Set<ResourceIndexedCompositeStringUnique> compositeStringUniques = null;
1823 		Set<ResourceLink> links = null;
1824 
1825 		Set<String> populatedResourceLinkParameters = Collections.emptySet();
1826 		EncodedResource changed;
1827 		if (theDeletedTimestampOrNull != null) {
1828 
1829 			stringParams = Collections.emptySet();
1830 			tokenParams = Collections.emptySet();
1831 			numberParams = Collections.emptySet();
1832 			quantityParams = Collections.emptySet();
1833 			dateParams = Collections.emptySet();
1834 			uriParams = Collections.emptySet();
1835 			coordsParams = Collections.emptySet();
1836 			links = Collections.emptySet();
1837 			compositeStringUniques = Collections.emptySet();
1838 
1839 			theEntity.setDeleted(theDeletedTimestampOrNull);
1840 			theEntity.setUpdated(theDeletedTimestampOrNull);
1841 			theEntity.setNarrativeTextParsedIntoWords(null);
1842 			theEntity.setContentTextParsedIntoWords(null);
1843 			theEntity.setHashSha256(null);
1844 			theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
1845 			changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
1846 
1847 		} else {
1848 
1849 			theEntity.setDeleted(null);
1850 
1851 			if (thePerformIndexing) {
1852 
1853 				stringParams = extractSearchParamStrings(theEntity, theResource);
1854 				numberParams = extractSearchParamNumber(theEntity, theResource);
1855 				quantityParams = extractSearchParamQuantity(theEntity, theResource);
1856 				dateParams = extractSearchParamDates(theEntity, theResource);
1857 				uriParams = extractSearchParamUri(theEntity, theResource);
1858 				coordsParams = extractSearchParamCoords(theEntity, theResource);
1859 
1860 				ourLog.trace("Storing date indexes: {}", dateParams);
1861 
1862 				tokenParams = new HashSet<>();
1863 				for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
1864 					if (next instanceof ResourceIndexedSearchParamToken) {
1865 						tokenParams.add((ResourceIndexedSearchParamToken) next);
1866 					} else {
1867 						stringParams.add((ResourceIndexedSearchParamString) next);
1868 					}
1869 				}
1870 
1871 				Set<Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
1872 				if (myConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
1873 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.STRING, stringParams);
1874 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams);
1875 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams);
1876 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.DATE, dateParams);
1877 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.URI, uriParams);
1878 					findMissingSearchParams(theEntity, activeSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams);
1879 				}
1880 
1881 				setUpdatedTime(stringParams, theUpdateTime);
1882 				setUpdatedTime(numberParams, theUpdateTime);
1883 				setUpdatedTime(quantityParams, theUpdateTime);
1884 				setUpdatedTime(dateParams, theUpdateTime);
1885 				setUpdatedTime(uriParams, theUpdateTime);
1886 				setUpdatedTime(coordsParams, theUpdateTime);
1887 				setUpdatedTime(tokenParams, theUpdateTime);
1888 
1889 				/*
1890 				 * Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
1891 				 * matching resource.
1892 				 */
1893 				if (myConfig.isAllowInlineMatchUrlReferences()) {
1894 					FhirTerser terser = getContext().newTerser();
1895 					List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
1896 					for (IBaseReference nextRef : allRefs) {
1897 						IIdType nextId = nextRef.getReferenceElement();
1898 						String nextIdText = nextId.getValue();
1899 						if (nextIdText == null) {
1900 							continue;
1901 						}
1902 						int qmIndex = nextIdText.indexOf('?');
1903 						if (qmIndex != -1) {
1904 							for (int i = qmIndex - 1; i >= 0; i--) {
1905 								if (nextIdText.charAt(i) == '/') {
1906 									if (i < nextIdText.length() - 1 && nextIdText.charAt(i + 1) == '?') {
1907 										// Just in case the URL is in the form Patient/?foo=bar
1908 										continue;
1909 									}
1910 									nextIdText = nextIdText.substring(i + 1);
1911 									break;
1912 								}
1913 							}
1914 							String resourceTypeString = nextIdText.substring(0, nextIdText.indexOf('?')).replace("/", "");
1915 							RuntimeResourceDefinition matchResourceDef = getContext().getResourceDefinition(resourceTypeString);
1916 							if (matchResourceDef == null) {
1917 								String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlInvalidResourceType", nextId.getValue(), resourceTypeString);
1918 								throw new InvalidRequestException(msg);
1919 							}
1920 							Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
1921 							Set<Long> matches = processMatchUrl(nextIdText, matchResourceType);
1922 							if (matches.isEmpty()) {
1923 								String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
1924 								throw new ResourceNotFoundException(msg);
1925 							}
1926 							if (matches.size() > 1) {
1927 								String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
1928 								throw new PreconditionFailedException(msg);
1929 							}
1930 							Long next = matches.iterator().next();
1931 							String newId = translatePidIdToForcedId(resourceTypeString, next);
1932 							ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
1933 							nextRef.setReference(newId);
1934 						}
1935 					}
1936 				}
1937 
1938 				links = new HashSet<>();
1939 				populatedResourceLinkParameters = extractResourceLinks(theEntity, theResource, links, theUpdateTime);
1940 
1941 				/*
1942 				 * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
1943 				 */
1944 				for (Iterator<ResourceLink> existingLinkIter = existingResourceLinks.iterator(); existingLinkIter.hasNext(); ) {
1945 					ResourceLink nextExisting = existingLinkIter.next();
1946 					if (links.remove(nextExisting)) {
1947 						existingLinkIter.remove();
1948 						links.add(nextExisting);
1949 					}
1950 				}
1951 
1952 				/*
1953 				 * Handle composites
1954 				 */
1955 				compositeStringUniques = extractCompositeStringUniques(theEntity, stringParams, tokenParams, numberParams, quantityParams, dateParams, uriParams, links);
1956 
1957 				changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
1958 
1959 				theEntity.setUpdated(theUpdateTime);
1960 				if (theResource instanceof IResource) {
1961 					theEntity.setLanguage(((IResource) theResource).getLanguage().getValue());
1962 				} else {
1963 					theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
1964 				}
1965 				theEntity.setParamsString(stringParams);
1966 				theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
1967 				theEntity.setParamsToken(tokenParams);
1968 				theEntity.setParamsTokenPopulated(tokenParams.isEmpty() == false);
1969 				theEntity.setParamsNumber(numberParams);
1970 				theEntity.setParamsNumberPopulated(numberParams.isEmpty() == false);
1971 				theEntity.setParamsQuantity(quantityParams);
1972 				theEntity.setParamsQuantityPopulated(quantityParams.isEmpty() == false);
1973 				theEntity.setParamsDate(dateParams);
1974 				theEntity.setParamsDatePopulated(dateParams.isEmpty() == false);
1975 				theEntity.setParamsUri(uriParams);
1976 				theEntity.setParamsUriPopulated(uriParams.isEmpty() == false);
1977 				theEntity.setParamsCoords(coordsParams);
1978 				theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false);
1979 				theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false);
1980 				theEntity.setResourceLinks(links);
1981 				theEntity.setHasLinks(links.isEmpty() == false);
1982 				theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
1983 				populateFullTextFields(theResource, theEntity);
1984 
1985 			} else {
1986 
1987 				changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
1988 
1989 				theEntity.setUpdated(theUpdateTime);
1990 				// theEntity.setLanguage(theResource.getLanguage().getValue());
1991 				theEntity.setIndexStatus(null);
1992 
1993 			}
1994 
1995 		}
1996 
1997 		if (!changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange()) {
1998 			ourLog.debug("Resource {} has not changed", theEntity.getIdDt().toUnqualified().getValue());
1999 			if (theResource != null) {
2000 				populateResourceIdFromEntity(theEntity, theResource);
2001 			}
2002 			theEntity.setUnchangedInCurrentOperation(true);
2003 			return theEntity;
2004 		}
2005 
2006 		if (theUpdateVersion) {
2007 			theEntity.setVersion(theEntity.getVersion() + 1);
2008 		}
2009 
2010 		/*
2011 		 * Save the resource itself
2012 		 */
2013 		if (theEntity.getId() == null) {
2014 			myEntityManager.persist(theEntity);
2015 
2016 			if (theEntity.getForcedId() != null) {
2017 				myEntityManager.persist(theEntity.getForcedId());
2018 			}
2019 
2020 			postPersist(theEntity, (T) theResource);
2021 
2022 		} else if (theEntity.getDeleted() != null) {
2023 			theEntity = myEntityManager.merge(theEntity);
2024 
2025 			postDelete(theEntity);
2026 
2027 		} else {
2028 			theEntity = myEntityManager.merge(theEntity);
2029 
2030 			postUpdate(theEntity, (T) theResource);
2031 		}
2032 
2033 		/*
2034 		 * Create history entry
2035 		 */
2036 		if (theCreateNewHistoryEntry) {
2037 			final ResourceHistoryTable historyEntry = theEntity.toHistory();
2038 			historyEntry.setEncoding(changed.getEncoding());
2039 			historyEntry.setResource(changed.getResource());
2040 
2041 			ourLog.debug("Saving history entry {}", historyEntry.getIdDt());
2042 			myResourceHistoryTableDao.save(historyEntry);
2043 		}
2044 
2045 		/*
2046 		 * Update the "search param present" table which is used for the
2047 		 * ?foo:missing=true queries
2048 		 *
2049 		 * Note that we're only populating this for reference params
2050 		 * because the index tables for all other types have a MISSING column
2051 		 * right on them for handling the :missing queries. We can't use the
2052 		 * index table for resource links (reference indexes) because we index
2053 		 * those by path and not by parameter name.
2054 		 */
2055 		if (thePerformIndexing) {
2056 			Map<String, Boolean> presentSearchParams = new HashMap<>();
2057 			for (String nextKey : populatedResourceLinkParameters) {
2058 				presentSearchParams.put(nextKey, Boolean.TRUE);
2059 			}
2060 			Set<Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
2061 			for (Entry<String, RuntimeSearchParam> nextSpEntry : activeSearchParams) {
2062 				if (nextSpEntry.getValue().getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
2063 					if (!presentSearchParams.containsKey(nextSpEntry.getKey())) {
2064 						presentSearchParams.put(nextSpEntry.getKey(), Boolean.FALSE);
2065 					}
2066 				}
2067 			}
2068 			mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams);
2069 		}
2070 
2071 		/*
2072 		 * Indexing
2073 		 */
2074 		if (thePerformIndexing) {
2075 
2076 			for (ResourceIndexedSearchParamString next : removeCommon(existingStringParams, stringParams)) {
2077 				next.setDaoConfig(myConfig);
2078 				myEntityManager.remove(next);
2079 				theEntity.getParamsString().remove(next);
2080 			}
2081 			for (ResourceIndexedSearchParamString next : removeCommon(stringParams, existingStringParams)) {
2082 				myEntityManager.persist(next);
2083 			}
2084 
2085 			for (ResourceIndexedSearchParamToken next : removeCommon(existingTokenParams, tokenParams)) {
2086 				myEntityManager.remove(next);
2087 				theEntity.getParamsToken().remove(next);
2088 			}
2089 			for (ResourceIndexedSearchParamToken next : removeCommon(tokenParams, existingTokenParams)) {
2090 				myEntityManager.persist(next);
2091 			}
2092 
2093 			for (ResourceIndexedSearchParamNumber next : removeCommon(existingNumberParams, numberParams)) {
2094 				myEntityManager.remove(next);
2095 				theEntity.getParamsNumber().remove(next);
2096 			}
2097 			for (ResourceIndexedSearchParamNumber next : removeCommon(numberParams, existingNumberParams)) {
2098 				myEntityManager.persist(next);
2099 			}
2100 
2101 			for (ResourceIndexedSearchParamQuantity next : removeCommon(existingQuantityParams, quantityParams)) {
2102 				myEntityManager.remove(next);
2103 				theEntity.getParamsQuantity().remove(next);
2104 			}
2105 			for (ResourceIndexedSearchParamQuantity next : removeCommon(quantityParams, existingQuantityParams)) {
2106 				myEntityManager.persist(next);
2107 			}
2108 
2109 			// Store date SP's
2110 			for (ResourceIndexedSearchParamDate next : removeCommon(existingDateParams, dateParams)) {
2111 				myEntityManager.remove(next);
2112 				theEntity.getParamsDate().remove(next);
2113 			}
2114 			for (ResourceIndexedSearchParamDate next : removeCommon(dateParams, existingDateParams)) {
2115 				myEntityManager.persist(next);
2116 			}
2117 
2118 			// Store URI SP's
2119 			for (ResourceIndexedSearchParamUri next : removeCommon(existingUriParams, uriParams)) {
2120 				myEntityManager.remove(next);
2121 				theEntity.getParamsUri().remove(next);
2122 			}
2123 			for (ResourceIndexedSearchParamUri next : removeCommon(uriParams, existingUriParams)) {
2124 				myEntityManager.persist(next);
2125 			}
2126 
2127 			// Store Coords SP's
2128 			for (ResourceIndexedSearchParamCoords next : removeCommon(existingCoordsParams, coordsParams)) {
2129 				myEntityManager.remove(next);
2130 				theEntity.getParamsCoords().remove(next);
2131 			}
2132 			for (ResourceIndexedSearchParamCoords next : removeCommon(coordsParams, existingCoordsParams)) {
2133 				myEntityManager.persist(next);
2134 			}
2135 
2136 			// Store resource links
2137 			for (ResourceLink next : removeCommon(existingResourceLinks, links)) {
2138 				myEntityManager.remove(next);
2139 				theEntity.getResourceLinks().remove(next);
2140 			}
2141 			for (ResourceLink next : removeCommon(links, existingResourceLinks)) {
2142 				myEntityManager.persist(next);
2143 			}
2144 			// make sure links are indexed
2145 			theEntity.setResourceLinks(links);
2146 
2147 			// Store composite string uniques
2148 			if (getConfig().isUniqueIndexesEnabled()) {
2149 				for (ResourceIndexedCompositeStringUnique next : removeCommon(existingCompositeStringUniques, compositeStringUniques)) {
2150 					ourLog.debug("Removing unique index: {}", next);
2151 					myEntityManager.remove(next);
2152 					theEntity.getParamsCompositeStringUnique().remove(next);
2153 				}
2154 				for (ResourceIndexedCompositeStringUnique next : removeCommon(compositeStringUniques, existingCompositeStringUniques)) {
2155 					if (myConfig.isUniqueIndexesCheckedBeforeSave()) {
2156 						ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
2157 						if (existing != null) {
2158 							String msg = getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
2159 							throw new PreconditionFailedException(msg);
2160 						}
2161 					}
2162 					ourLog.debug("Persisting unique index: {}", next);
2163 					myEntityManager.persist(next);
2164 				}
2165 			}
2166 
2167 		} // if thePerformIndexing
2168 
2169 		if (theResource != null) {
2170 			populateResourceIdFromEntity(theEntity, theResource);
2171 		}
2172 
2173 
2174 		return theEntity;
2175 	}
2176 
2177 	protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable
2178 		entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
2179 		return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
2180 	}
2181 
2182 	public ResourceTable updateInternal(RequestDetails theRequest, T theResource, boolean thePerformIndexing,
2183 													boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType
2184 														theResourceId, IBaseResource theOldResource) {
2185 		// Notify interceptors
2186 		ActionRequestDetails requestDetails = null;
2187 		if (theRequestDetails != null) {
2188 			requestDetails = new ActionRequestDetails(theRequestDetails, theResource, theResourceId.getResourceType(), theResourceId);
2189 			notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
2190 		}
2191 
2192 		// Notify IServerOperationInterceptors about pre-action call
2193 		if (theRequestDetails != null) {
2194 			theRequestDetails.getRequestOperationCallback().resourcePreUpdate(theOldResource, theResource);
2195 		}
2196 		for (IServerInterceptor next : getConfig().getInterceptors()) {
2197 			if (next instanceof IServerOperationInterceptor) {
2198 				((IServerOperationInterceptor) next).resourcePreUpdate(theRequestDetails, theOldResource, theResource);
2199 			}
2200 		}
2201 
2202 		// Perform update
2203 		ResourceTable savedEntity = updateEntity(theRequest, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
2204 
2205 		/*
2206 		 * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
2207 		 * we'll manually increase the version. This is important because we want the updated version number
2208 		 * to be reflected in the resource shared with interceptors
2209 		 */
2210 		if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation() && !ourDisableIncrementOnUpdateForUnitTest) {
2211 			if (theResourceId.hasVersionIdPart() == false) {
2212 				theResourceId = theResourceId.withVersion(Long.toString(savedEntity.getVersion()));
2213 			}
2214 			incrementId(theResource, savedEntity, theResourceId);
2215 		}
2216 
2217 		// Notify interceptors
2218 		if (!savedEntity.isUnchangedInCurrentOperation()) {
2219 			if (theRequestDetails != null) {
2220 				theRequestDetails.getRequestOperationCallback().resourceUpdated(theResource);
2221 				theRequestDetails.getRequestOperationCallback().resourceUpdated(theOldResource, theResource);
2222 			}
2223 			for (IServerInterceptor next : getConfig().getInterceptors()) {
2224 				if (next instanceof IServerOperationInterceptor) {
2225 					((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theResource);
2226 					((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theOldResource, theResource);
2227 				}
2228 			}
2229 		}
2230 		return savedEntity;
2231 	}
2232 
2233 	private void validateChildReferences(IBase theElement, String thePath) {
2234 		if (theElement == null) {
2235 			return;
2236 		}
2237 		BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
2238 		if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
2239 			return;
2240 		}
2241 
2242 		BaseRuntimeElementCompositeDefinition<?> cdef = (BaseRuntimeElementCompositeDefinition<?>) def;
2243 		for (BaseRuntimeChildDefinition nextChildDef : cdef.getChildren()) {
2244 
2245 			List<IBase> values = nextChildDef.getAccessor().getValues(theElement);
2246 			if (values == null || values.isEmpty()) {
2247 				continue;
2248 			}
2249 
2250 			String newPath = thePath + "." + nextChildDef.getElementName();
2251 
2252 			for (IBase nextChild : values) {
2253 				validateChildReferences(nextChild, newPath);
2254 			}
2255 
2256 			if (nextChildDef instanceof RuntimeChildResourceDefinition) {
2257 				RuntimeChildResourceDefinition nextChildDefRes = (RuntimeChildResourceDefinition) nextChildDef;
2258 				Set<String> validTypes = new HashSet<>();
2259 				boolean allowAny = false;
2260 				for (Class<? extends IBaseResource> nextValidType : nextChildDefRes.getResourceTypes()) {
2261 					if (nextValidType.isInterface()) {
2262 						allowAny = true;
2263 						break;
2264 					}
2265 					validTypes.add(getContext().getResourceDefinition(nextValidType).getName());
2266 				}
2267 
2268 				if (allowAny) {
2269 					continue;
2270 				}
2271 
2272 				for (IBase nextChild : values) {
2273 					IBaseReference nextRef = (IBaseReference) nextChild;
2274 					IIdType referencedId = nextRef.getReferenceElement();
2275 					if (!isBlank(referencedId.getResourceType())) {
2276 						if (!isLogicalReference(referencedId)) {
2277 							if (!referencedId.getValue().contains("?")) {
2278 								if (!validTypes.contains(referencedId.getResourceType())) {
2279 									throw new UnprocessableEntityException(
2280 										"Invalid reference found at path '" + newPath + "'. Resource type '" + referencedId.getResourceType() + "' is not valid for this path");
2281 								}
2282 							}
2283 						}
2284 					}
2285 				}
2286 
2287 			}
2288 		}
2289 	}
2290 
2291 	public void validateDeleteConflictsEmptyOrThrowException(List<DeleteConflict> theDeleteConflicts) {
2292 		if (theDeleteConflicts.isEmpty()) {
2293 			return;
2294 		}
2295 
2296 		IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
2297 		String firstMsg = null;
2298 		for (DeleteConflict next : theDeleteConflicts) {
2299 			StringBuilder b = new StringBuilder();
2300 			b.append("Unable to delete ");
2301 			b.append(next.getTargetId().toUnqualifiedVersionless().getValue());
2302 			b.append(" because at least one resource has a reference to this resource. First reference found was resource ");
2303 			b.append(next.getTargetId().toUnqualifiedVersionless().getValue());
2304 			b.append(" in path ");
2305 			b.append(next.getSourcePath());
2306 			String msg = b.toString();
2307 			if (firstMsg == null) {
2308 				firstMsg = msg;
2309 			}
2310 			OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing");
2311 		}
2312 
2313 		throw new ResourceVersionConflictException(firstMsg, oo);
2314 	}
2315 
2316 	protected void validateMetaCount(int theMetaCount) {
2317 		if (myConfig.getResourceMetaCountHardLimit() != null) {
2318 			if (theMetaCount > myConfig.getResourceMetaCountHardLimit()) {
2319 				throw new UnprocessableEntityException("Resource contains " + theMetaCount + " meta entries (tag/profile/security label), maximum is " + myConfig.getResourceMetaCountHardLimit());
2320 			}
2321 		}
2322 	}
2323 
2324 	/**
2325 	 * This method is invoked immediately before storing a new resource, or an update to an existing resource to allow the DAO to ensure that it is valid for persistence. By default, checks for the
2326 	 * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
2327 	 *
2328 	 * @param theResource     The resource that is about to be persisted
2329 	 * @param theEntityToSave TODO
2330 	 */
2331 	protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
2332 		Object tag = null;
2333 
2334 		int totalMetaCount = 0;
2335 
2336 		if (theResource instanceof IResource) {
2337 			IResource res = (IResource) theResource;
2338 			TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
2339 			if (tagList != null) {
2340 				tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
2341 				totalMetaCount += tagList.size();
2342 			}
2343 			List<IdDt> profileList = ResourceMetadataKeyEnum.PROFILES.get(res);
2344 			if (profileList != null) {
2345 				totalMetaCount += profileList.size();
2346 			}
2347 		} else {
2348 			IAnyResource res = (IAnyResource) theResource;
2349 			tag = res.getMeta().getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
2350 			totalMetaCount += res.getMeta().getTag().size();
2351 			totalMetaCount += res.getMeta().getProfile().size();
2352 			totalMetaCount += res.getMeta().getSecurity().size();
2353 		}
2354 
2355 		if (tag != null) {
2356 			throw new UnprocessableEntityException("Resource contains the 'subsetted' tag, and must not be stored as it may contain a subset of available data");
2357 		}
2358 
2359 		String resName = getContext().getResourceDefinition(theResource).getName();
2360 		validateChildReferences(theResource, resName);
2361 
2362 		validateMetaCount(totalMetaCount);
2363 
2364 	}
2365 
2366 	public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) {
2367 		String resourceText = null;
2368 		switch (theResourceEncoding) {
2369 			case JSON:
2370 				resourceText = new String(theResourceBytes, Charsets.UTF_8);
2371 				break;
2372 			case JSONC:
2373 				resourceText = GZipUtil.decompress(theResourceBytes);
2374 				break;
2375 			case DEL:
2376 				break;
2377 		}
2378 		return resourceText;
2379 	}
2380 
2381 	public static byte[] encodeResource(IBaseResource theResource, ResourceEncodingEnum theEncoding, Set<String> theExcludeElements, FhirContext theContext) {
2382 		byte[] bytes;
2383 		IParser parser = theEncoding.newParser(theContext);
2384 		parser.setDontEncodeElements(theExcludeElements);
2385 		String encoded = parser.encodeResourceToString(theResource);
2386 
2387 
2388 		switch (theEncoding) {
2389 			case JSON:
2390 				bytes = encoded.getBytes(Charsets.UTF_8);
2391 				break;
2392 			case JSONC:
2393 				bytes = GZipUtil.compress(encoded);
2394 				break;
2395 			default:
2396 			case DEL:
2397 				bytes = new byte[0];
2398 				break;
2399 		}
2400 
2401 		ourLog.debug("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
2402 		return bytes;
2403 	}
2404 
2405 	/**
2406 	 * This method is used to create a set of all possible combinations of
2407 	 * parameters across a set of search parameters. An example of why
2408 	 * this is needed:
2409 	 * <p>
2410 	 * Let's say we have a unique index on (Patient:gender AND Patient:name).
2411 	 * Then we pass in <code>SMITH, John</code> with a gender of <code>male</code>.
2412 	 * </p>
2413 	 * <p>
2414 	 * In this case, because the name parameter matches both first and last name,
2415 	 * we now need two unique indexes:
2416 	 * <ul>
2417 	 * <li>Patient?gender=male&amp;name=SMITH</li>
2418 	 * <li>Patient?gender=male&amp;name=JOHN</li>
2419 	 * </ul>
2420 	 * </p>
2421 	 * <p>
2422 	 * So this recursive algorithm calculates those
2423 	 * </p>
2424 	 *
2425 	 * @param theResourceType E.g. <code>Patient
2426 	 * @param thePartsChoices E.g. <code>[[gender=male], [name=SMITH, name=JOHN]]</code>
2427 	 */
2428 	public static Set<String> extractCompositeStringUniquesValueChains(String
2429 																								 theResourceType, List<List<String>> thePartsChoices) {
2430 
2431 		for (List<String> next : thePartsChoices) {
2432 			next.removeIf(StringUtils::isBlank);
2433 			if (next.isEmpty()) {
2434 				return Collections.emptySet();
2435 			}
2436 		}
2437 
2438 		if (thePartsChoices.isEmpty()) {
2439 			return Collections.emptySet();
2440 		}
2441 
2442 		thePartsChoices.sort((o1, o2) -> {
2443 			String str1 = null;
2444 			String str2 = null;
2445 			if (o1.size() > 0) {
2446 				str1 = o1.get(0);
2447 			}
2448 			if (o2.size() > 0) {
2449 				str2 = o2.get(0);
2450 			}
2451 			return compare(str1, str2);
2452 		});
2453 
2454 		List<String> values = new ArrayList<>();
2455 		Set<String> queryStringsToPopulate = new HashSet<>();
2456 		extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices, values, queryStringsToPopulate);
2457 		return queryStringsToPopulate;
2458 	}
2459 
2460 	private static void extractCompositeStringUniquesValueChains(String
2461 																						 theResourceType, List<List<String>> thePartsChoices, List<String> theValues, Set<String> theQueryStringsToPopulate) {
2462 		if (thePartsChoices.size() > 0) {
2463 			List<String> nextList = thePartsChoices.get(0);
2464 			Collections.sort(nextList);
2465 			for (String nextChoice : nextList) {
2466 				theValues.add(nextChoice);
2467 				extractCompositeStringUniquesValueChains(theResourceType, thePartsChoices.subList(1, thePartsChoices.size()), theValues, theQueryStringsToPopulate);
2468 				theValues.remove(theValues.size() - 1);
2469 			}
2470 		} else {
2471 			if (theValues.size() > 0) {
2472 				StringBuilder uniqueString = new StringBuilder();
2473 				uniqueString.append(theResourceType);
2474 
2475 				for (int i = 0; i < theValues.size(); i++) {
2476 					uniqueString.append(i == 0 ? "?" : "&");
2477 					uniqueString.append(theValues.get(i));
2478 				}
2479 
2480 				theQueryStringsToPopulate.add(uniqueString.toString());
2481 			}
2482 		}
2483 	}
2484 
2485 	protected static boolean isValidPid(IIdType theId) {
2486 		if (theId == null || theId.getIdPart() == null) {
2487 			return false;
2488 		}
2489 		String idPart = theId.getIdPart();
2490 		for (int i = 0; i < idPart.length(); i++) {
2491 			char nextChar = idPart.charAt(i);
2492 			if (nextChar < '0' || nextChar > '9') {
2493 				return false;
2494 			}
2495 		}
2496 		return true;
2497 	}
2498 
2499 	@CoverageIgnore
2500 	protected static IQueryParameterAnd<?> newInstanceAnd(String chain) {
2501 		IQueryParameterAnd<?> type;
2502 		Class<? extends IQueryParameterAnd<?>> clazz = RESOURCE_META_AND_PARAMS.get(chain);
2503 		try {
2504 			type = clazz.newInstance();
2505 		} catch (Exception e) {
2506 			throw new InternalErrorException("Failure creating instance of " + clazz, e);
2507 		}
2508 		return type;
2509 	}
2510 
2511 	@CoverageIgnore
2512 	protected static IQueryParameterType newInstanceType(String chain) {
2513 		IQueryParameterType type;
2514 		Class<? extends IQueryParameterType> clazz = RESOURCE_META_PARAMS.get(chain);
2515 		try {
2516 			type = clazz.newInstance();
2517 		} catch (Exception e) {
2518 			throw new InternalErrorException("Failure creating instance of " + clazz, e);
2519 		}
2520 		return type;
2521 	}
2522 
2523 	public static String normalizeString(String theString) {
2524 		CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
2525 
2526 		/*
2527 		 * The following block of code is used to strip out diacritical marks from latin script
2528 		 * and also convert to upper case. E.g. "j?mes" becomes "JAMES".
2529 		 *
2530 		 * See http://www.unicode.org/charts/PDF/U0300.pdf for the logic
2531 		 * behind stripping 0300-036F
2532 		 *
2533 		 * See #454 for an issue where we were completely stripping non latin characters
2534 		 * See #832 for an issue where we normalize korean characters, which are decomposed
2535 		 */
2536 		String string = Normalizer.normalize(theString, Normalizer.Form.NFD);
2537 		for (int i = 0, n = string.length(); i < n; ++i) {
2538 			char c = string.charAt(i);
2539 			if (c >= '\u0300' && c <= '\u036F') {
2540 				continue;
2541 			} else {
2542 				outBuffer.append(c);
2543 			}
2544 		}
2545 
2546 		return new String(outBuffer.toCharArray()).toUpperCase();
2547 	}
2548 
2549 	private static String parseNarrativeTextIntoWords(IBaseResource theResource) {
2550 
2551 		StringBuilder b = new StringBuilder();
2552 		if (theResource instanceof IResource) {
2553 			IResource resource = (IResource) theResource;
2554 			List<XMLEvent> xmlEvents = XmlUtil.parse(resource.getText().getDiv().getValue());
2555 			if (xmlEvents != null) {
2556 				for (XMLEvent next : xmlEvents) {
2557 					if (next.isCharacters()) {
2558 						Characters characters = next.asCharacters();
2559 						b.append(characters.getData()).append(" ");
2560 					}
2561 				}
2562 			}
2563 		} else if (theResource instanceof IDomainResource) {
2564 			IDomainResource resource = (IDomainResource) theResource;
2565 			try {
2566 				String divAsString = resource.getText().getDivAsString();
2567 				List<XMLEvent> xmlEvents = XmlUtil.parse(divAsString);
2568 				if (xmlEvents != null) {
2569 					for (XMLEvent next : xmlEvents) {
2570 						if (next.isCharacters()) {
2571 							Characters characters = next.asCharacters();
2572 							b.append(characters.getData()).append(" ");
2573 						}
2574 					}
2575 				}
2576 			} catch (Exception e) {
2577 				throw new DataFormatException("Unable to convert DIV to string", e);
2578 			}
2579 
2580 		}
2581 		return b.toString();
2582 	}
2583 
2584 	@VisibleForTesting
2585 	public static void setDisableIncrementOnUpdateForUnitTest(boolean theDisableIncrementOnUpdateForUnitTest) {
2586 		ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
2587 	}
2588 
2589 	/**
2590 	 * Do not call this method outside of unit tests
2591 	 */
2592 	@VisibleForTesting
2593 	public static void setValidationDisabledForUnitTest(boolean theValidationDisabledForUnitTest) {
2594 		ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
2595 	}
2596 
2597 	private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
2598 		ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
2599 		for (IBaseCoding next : theSecurityLabels) {
2600 			retVal.add((BaseCodingDt) next);
2601 		}
2602 		return retVal;
2603 	}
2604 
2605 	protected static Long translateForcedIdToPid(String theResourceName, String theResourceId, IForcedIdDao
2606 		theForcedIdDao) {
2607 		return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
2608 	}
2609 
2610 	static List<Long> translateForcedIdToPids(IIdType theId, IForcedIdDao theForcedIdDao) {
2611 		Validate.isTrue(theId.hasIdPart());
2612 
2613 		if (isValidPid(theId)) {
2614 			return Collections.singletonList(theId.getIdPartAsLong());
2615 		} else {
2616 			List<ForcedId> forcedId;
2617 			if (theId.hasResourceType()) {
2618 				forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
2619 			} else {
2620 				forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
2621 			}
2622 
2623 			if (forcedId.isEmpty() == false) {
2624 				List<Long> retVal = new ArrayList<>(forcedId.size());
2625 				for (ForcedId next : forcedId) {
2626 					retVal.add(next.getResourcePid());
2627 				}
2628 				return retVal;
2629 			} else {
2630 				throw new ResourceNotFoundException(theId);
2631 			}
2632 		}
2633 	}
2634 
2635 	public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String
2636 		theMatchUrl, RuntimeResourceDefinition resourceDef) {
2637 		SearchParameterMap paramMap = new SearchParameterMap();
2638 		List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
2639 
2640 		ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
2641 		for (NameValuePair next : parameters) {
2642 			if (isBlank(next.getValue())) {
2643 				continue;
2644 			}
2645 
2646 			String paramName = next.getName();
2647 			String qualifier = null;
2648 			for (int i = 0; i < paramName.length(); i++) {
2649 				switch (paramName.charAt(i)) {
2650 					case '.':
2651 					case ':':
2652 						qualifier = paramName.substring(i);
2653 						paramName = paramName.substring(0, i);
2654 						i = Integer.MAX_VALUE - 1;
2655 						break;
2656 				}
2657 			}
2658 
2659 			QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue());
2660 			nameToParamLists.put(paramName, paramList);
2661 		}
2662 
2663 		for (String nextParamName : nameToParamLists.keySet()) {
2664 			List<QualifiedParamList> paramList = nameToParamLists.get(nextParamName);
2665 			if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) {
2666 				if (paramList != null && paramList.size() > 0) {
2667 					if (paramList.size() > 2) {
2668 						throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions");
2669 					} else {
2670 						DateRangeParam p1 = new DateRangeParam();
2671 						p1.setValuesAsQueryTokens(theContext, nextParamName, paramList);
2672 						paramMap.setLastUpdated(p1);
2673 					}
2674 				}
2675 				continue;
2676 			}
2677 
2678 			if (Constants.PARAM_HAS.equals(nextParamName)) {
2679 				IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
2680 				paramMap.add(nextParamName, param);
2681 				continue;
2682 			}
2683 
2684 			if (Constants.PARAM_COUNT.equals(nextParamName)) {
2685 				if (paramList.size() > 0 && paramList.get(0).size() > 0) {
2686 					String intString = paramList.get(0).get(0);
2687 					try {
2688 						paramMap.setCount(Integer.parseInt(intString));
2689 					} catch (NumberFormatException e) {
2690 						throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString);
2691 					}
2692 				}
2693 				continue;
2694 			}
2695 
2696 			if (RESOURCE_META_PARAMS.containsKey(nextParamName)) {
2697 				if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) {
2698 					throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier());
2699 				}
2700 				IQueryParameterAnd<?> type = newInstanceAnd(nextParamName);
2701 				type.setValuesAsQueryTokens(theContext, nextParamName, (paramList));
2702 				paramMap.add(nextParamName, type);
2703 			} else if (nextParamName.startsWith("_")) {
2704 				// ignore these since they aren't search params (e.g. _sort)
2705 			} else {
2706 				RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
2707 				if (paramDef == null) {
2708 					throw new InvalidRequestException(
2709 						"Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
2710 				}
2711 
2712 				IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);
2713 				paramMap.add(nextParamName, param);
2714 			}
2715 		}
2716 		return paramMap;
2717 	}
2718 
2719 	public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
2720 		List<NameValuePair> parameters;
2721 		String matchUrl = theMatchUrl;
2722 		int questionMarkIndex = matchUrl.indexOf('?');
2723 		if (questionMarkIndex != -1) {
2724 			matchUrl = matchUrl.substring(questionMarkIndex + 1);
2725 		}
2726 		matchUrl = matchUrl.replace("|", "%7C");
2727 		matchUrl = matchUrl.replace("=>=", "=%3E%3D");
2728 		matchUrl = matchUrl.replace("=<=", "=%3C%3D");
2729 		matchUrl = matchUrl.replace("=>", "=%3E");
2730 		matchUrl = matchUrl.replace("=<", "=%3C");
2731 		if (matchUrl.contains(" ")) {
2732 			throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
2733 		}
2734 
2735 		parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
2736 		return parameters;
2737 	}
2738 
2739 	public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
2740 		if (!theResourceName.equals(theEntity.getResourceType())) {
2741 			throw new ResourceNotFoundException(
2742 				"Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
2743 		}
2744 	}
2745 
2746 }