View Javadoc
1   package ca.uhn.fhir.jpa.dao;
2   
3   import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
4   import static org.apache.commons.lang3.StringUtils.isBlank;
5   import static org.apache.commons.lang3.StringUtils.isNotBlank;
6   import static org.apache.commons.lang3.StringUtils.trim;
7   
8   import java.io.CharArrayWriter;
9   import java.text.Normalizer;
10  import java.util.ArrayList;
11  import java.util.Arrays;
12  import java.util.Collection;
13  import java.util.Collections;
14  import java.util.Date;
15  import java.util.HashMap;
16  import java.util.HashSet;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.Map.Entry;
20  import java.util.Set;
21  import java.util.StringTokenizer;
22  import java.util.UUID;
23  import java.util.concurrent.atomic.AtomicInteger;
24  
25  import javax.annotation.PostConstruct;
26  import javax.persistence.EntityManager;
27  import javax.persistence.NoResultException;
28  import javax.persistence.PersistenceContext;
29  import javax.persistence.PersistenceContextType;
30  import javax.persistence.Tuple;
31  import javax.persistence.TypedQuery;
32  import javax.persistence.criteria.CriteriaBuilder;
33  import javax.persistence.criteria.CriteriaQuery;
34  import javax.persistence.criteria.Predicate;
35  import javax.persistence.criteria.Root;
36  import javax.xml.stream.events.Characters;
37  import javax.xml.stream.events.XMLEvent;
38  
39  import ca.uhn.fhir.jpa.dao.data.*;
40  import org.apache.commons.lang3.NotImplementedException;
41  import org.apache.commons.lang3.Validate;
42  import org.apache.http.NameValuePair;
43  import org.apache.http.client.utils.URLEncodedUtils;
44  import org.hibernate.Session;
45  import org.hibernate.internal.SessionImpl;
46  import org.hl7.fhir.instance.model.api.IAnyResource;
47  import org.hl7.fhir.instance.model.api.IBase;
48  import org.hl7.fhir.instance.model.api.IBaseCoding;
49  import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
50  import org.hl7.fhir.instance.model.api.IBaseReference;
51  import org.hl7.fhir.instance.model.api.IBaseResource;
52  import org.hl7.fhir.instance.model.api.IDomainResource;
53  import org.hl7.fhir.instance.model.api.IIdType;
54  import org.hl7.fhir.instance.model.api.IPrimitiveType;
55  import org.hl7.fhir.r4.model.BaseResource;
56  import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
57  import org.springframework.beans.BeansException;
58  import org.springframework.beans.factory.annotation.Autowired;
59  import org.springframework.context.ApplicationContext;
60  import org.springframework.context.ApplicationContextAware;
61  import org.springframework.data.domain.PageRequest;
62  import org.springframework.data.domain.Pageable;
63  import org.springframework.data.domain.Slice;
64  import org.springframework.data.domain.SliceImpl;
65  import org.springframework.stereotype.Repository;
66  import org.springframework.transaction.PlatformTransactionManager;
67  import org.springframework.transaction.support.TransactionTemplate;
68  
69  import com.google.common.annotations.VisibleForTesting;
70  import com.google.common.base.Charsets;
71  import com.google.common.collect.ArrayListMultimap;
72  import com.google.common.collect.Sets;
73  import com.google.common.hash.HashFunction;
74  import com.google.common.hash.Hashing;
75  
76  import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
77  import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
78  import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
79  import ca.uhn.fhir.context.FhirContext;
80  import ca.uhn.fhir.context.FhirVersionEnum;
81  import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
82  import ca.uhn.fhir.context.RuntimeResourceDefinition;
83  import ca.uhn.fhir.context.RuntimeSearchParam;
84  import ca.uhn.fhir.jpa.dao.index.IndexingSupport;
85  import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
86  import ca.uhn.fhir.jpa.entity.BaseHasResource;
87  import ca.uhn.fhir.jpa.entity.BaseTag;
88  import ca.uhn.fhir.jpa.entity.ForcedId;
89  import ca.uhn.fhir.jpa.entity.IBaseResourceEntity;
90  import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
91  import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
92  import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
93  import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
94  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
95  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
96  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
97  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
98  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
99  import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
100 import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
101 import ca.uhn.fhir.jpa.entity.ResourceLink;
102 import ca.uhn.fhir.jpa.entity.ResourceSearchView;
103 import ca.uhn.fhir.jpa.entity.ResourceTable;
104 import ca.uhn.fhir.jpa.entity.ResourceTag;
105 import ca.uhn.fhir.jpa.entity.Search;
106 import ca.uhn.fhir.jpa.entity.SearchInclude;
107 import ca.uhn.fhir.jpa.entity.SearchParamPresent;
108 import ca.uhn.fhir.jpa.entity.SearchResult;
109 import ca.uhn.fhir.jpa.entity.SearchStatusEnum;
110 import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
111 import ca.uhn.fhir.jpa.entity.SubscriptionTable;
112 import ca.uhn.fhir.jpa.entity.TagDefinition;
113 import ca.uhn.fhir.jpa.entity.TagTypeEnum;
114 import ca.uhn.fhir.jpa.entity.TermCodeSystem;
115 import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
116 import ca.uhn.fhir.jpa.entity.TermConcept;
117 import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
118 import ca.uhn.fhir.jpa.entity.TermConceptMap;
119 import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
120 import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
121 import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
122 import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
123 import ca.uhn.fhir.jpa.entity.TermConceptProperty;
124 import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
125 import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
126 import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
127 import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
128 import ca.uhn.fhir.jpa.util.DeleteConflict;
129 import ca.uhn.fhir.jpa.util.ExpungeOptions;
130 import ca.uhn.fhir.jpa.util.ExpungeOutcome;
131 import ca.uhn.fhir.jpa.util.JpaConstants;
132 import ca.uhn.fhir.model.api.IQueryParameterAnd;
133 import ca.uhn.fhir.model.api.IQueryParameterType;
134 import ca.uhn.fhir.model.api.IResource;
135 import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
136 import ca.uhn.fhir.model.api.Tag;
137 import ca.uhn.fhir.model.api.TagList;
138 import ca.uhn.fhir.model.base.composite.BaseCodingDt;
139 import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
140 import ca.uhn.fhir.model.primitive.IdDt;
141 import ca.uhn.fhir.model.primitive.InstantDt;
142 import ca.uhn.fhir.model.primitive.StringDt;
143 import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
144 import ca.uhn.fhir.parser.DataFormatException;
145 import ca.uhn.fhir.parser.IParser;
146 import ca.uhn.fhir.parser.LenientErrorHandler;
147 import ca.uhn.fhir.rest.api.Constants;
148 import ca.uhn.fhir.rest.api.QualifiedParamList;
149 import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
150 import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
151 import ca.uhn.fhir.rest.api.server.IBundleProvider;
152 import ca.uhn.fhir.rest.api.server.RequestDetails;
153 import ca.uhn.fhir.rest.param.DateRangeParam;
154 import ca.uhn.fhir.rest.param.ParameterUtil;
155 import ca.uhn.fhir.rest.param.StringAndListParam;
156 import ca.uhn.fhir.rest.param.StringParam;
157 import ca.uhn.fhir.rest.param.TokenAndListParam;
158 import ca.uhn.fhir.rest.param.TokenParam;
159 import ca.uhn.fhir.rest.param.UriAndListParam;
160 import ca.uhn.fhir.rest.param.UriParam;
161 import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
162 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
163 import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
164 import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
165 import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
166 import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
167 import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
168 import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
169 import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
170 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
171 import ca.uhn.fhir.util.CoverageIgnore;
172 import ca.uhn.fhir.util.OperationOutcomeUtil;
173 import ca.uhn.fhir.util.StopWatch;
174 import ca.uhn.fhir.util.XmlUtil;
175 
176 /*
177  * #%L
178  * HAPI FHIR JPA Server
179  * %%
180  * Copyright (C) 2014 - 2018 University Health Network
181  * %%
182  * Licensed under the Apache License, Version 2.0 (the "License");
183  * you may not use this file except in compliance with the License.
184  * You may obtain a copy of the License at
185  * 
186  * http://www.apache.org/licenses/LICENSE-2.0
187  * 
188  * Unless required by applicable law or agreed to in writing, software
189  * distributed under the License is distributed on an "AS IS" BASIS,
190  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
191  * See the License for the specific language governing permissions and
192  * limitations under the License.
193  * #L%
194  */
195 
196 @SuppressWarnings("WeakerAccess")
197 @Repository
198 public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao, ApplicationContextAware, IndexingSupport {
199 
200 	public static final long INDEX_STATUS_INDEXED = 1L;
201 	public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
202 	public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
203 	public static final String OO_SEVERITY_ERROR = "error";
204 	public static final String OO_SEVERITY_INFO = "information";
205 	public static final String OO_SEVERITY_WARN = "warning";
206 	public static final String UCUM_NS = "http://unitsofmeasure.org";
207 	static final Set<String> EXCLUDE_ELEMENTS_IN_ENCODED;
208 	/**
209 	 * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
210 	 */
211 	static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
212 	/**
213 	 * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(SearchParameterMap)}
214 	 */
215 	static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
216 	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
217 	private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
218 	private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
219 	private static boolean ourValidationDisabledForUnitTest;
220 	private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
221 
222 	static {
223 		Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
224 		Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
225 		resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
226 		resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
227 		resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
228 		resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
229 		resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
230 		resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
231 		resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
232 		resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
233 		resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
234 		resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
235 		RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
236 		RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
237 
238 		HashSet<String> excludeElementsInEncoded = new HashSet<String>();
239 		excludeElementsInEncoded.add("id");
240 		excludeElementsInEncoded.add("*.meta");
241 		EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
242 	}
243 
244 	@PersistenceContext(type = PersistenceContextType.TRANSACTION)
245 	protected EntityManager myEntityManager;
246 	@Autowired
247 	protected IForcedIdDao myForcedIdDao;
248 	@Autowired
249 	protected ISearchResultDao mySearchResultDao;
250 	@Autowired(required = false)
251 	protected IFulltextSearchSvc myFulltextSearchSvc;
252 	@Autowired()
253 	protected IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
254 	@Autowired()
255 	protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao;
256 	@Autowired()
257 	protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
258 	@Autowired()
259 	protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
260 	@Autowired()
261 	protected IResourceIndexedSearchParamQuantityDao myResourceIndexedSearchParamQuantityDao;
262 	@Autowired()
263 	protected IResourceIndexedSearchParamCoordsDao myResourceIndexedSearchParamCoordsDao;
264 	@Autowired()
265 	protected IResourceIndexedSearchParamNumberDao myResourceIndexedSearchParamNumberDao;
266 	@Autowired
267 	protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
268 	@Autowired
269 	protected ISearchParamRegistry mySerarchParamRegistry;
270 	@Autowired()
271 	protected IHapiTerminologySvc myTerminologySvc;
272 	@Autowired
273 	protected IResourceHistoryTableDao myResourceHistoryTableDao;
274 	@Autowired
275 	protected IResourceHistoryTagDao myResourceHistoryTagDao;
276 	@Autowired
277 	protected IResourceTableDao myResourceTableDao;
278 	@Autowired
279 	protected IResourceTagDao myResourceTagDao;
280 	@Autowired
281 	protected IResourceSearchViewDao myResourceViewDao;
282 	@Autowired(required = true)
283 	private DaoConfig myConfig;
284 	private FhirContext myContext;
285 	@Autowired
286 	private PlatformTransactionManager myPlatformTransactionManager;
287 	@Autowired
288 	private ISearchDao mySearchDao;
289 	@Autowired
290 	private ISearchParamExtractor mySearchParamExtractor;
291 	@Autowired
292 	private ISearchParamPresenceSvc mySearchParamPresenceSvc;
293 	@Autowired
294 	private ISearchParamRegistry mySearchParamRegistry;
295 	//@Autowired
296 	//private ISearchResultDao mySearchResultDao;
297 	@Autowired
298 	private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
299 	private ApplicationContext myApplicationContext;
300 	private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> myResourceTypeToDao;
301 
302 	public static void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
303 		if (theRequestDetails != null) {
304 			theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
305 		}
306 	}
307 
308 	/**
309 	 * Returns the newly created forced ID. If the entity already had a forced ID, or if
310 	 * none was created, returns null.
311 	 */
312 	protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
313 		if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
314 			if (!theCreateForPureNumericIds && isValidPid(theId)) {
315 				return null;
316 			}
317 
318 			ForcedId fid = new ForcedId();
319 			fid.setResourceType(theEntity.getResourceType());
320 			fid.setForcedId(theId.getIdPart());
321 			fid.setResource(theEntity);
322 			theEntity.setForcedId(fid);
323 			return fid;
324 		}
325 
326 		return null;
327 	}
328 
329 	protected ExpungeOutcome doExpunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions) {
330 		TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
331 		ourLog.info("Expunge: ResourceName[{}] Id[{}] Version[{}] Options[{}]", theResourceName, theResourceId, theVersion, theExpungeOptions);
332 
333 		if (!getConfig().isExpungeEnabled()) {
334 			throw new MethodNotAllowedException("$expunge is not enabled on this server");
335 		}
336 
337 		AtomicInteger remainingCount = new AtomicInteger(theExpungeOptions.getLimit());
338 
339 		if (theResourceName == null && theResourceId == null && theVersion == null) {
340 			if (theExpungeOptions.isExpungeEverything()) {
341 				doExpungeEverything();
342 			}
343 		}
344 
345 		if (theExpungeOptions.isExpungeDeletedResources() && theVersion == null) {
346 
347 			/*
348 			 * Delete historical versions of deleted resources
349 			 */
350 			Pageable page = PageRequest.of(0, remainingCount.get());
351 			Slice<Long> resourceIds = txTemplate.execute(t -> {
352 				if (theResourceId != null) {
353 					Slice<Long> ids = myResourceTableDao.findIdsOfDeletedResourcesOfType(page, theResourceId, theResourceName);
354 					ourLog.info("Expunging {} deleted resources of type[{}] and ID[{}]", ids.getNumberOfElements(), theResourceName, theResourceId);
355 					return ids;
356 				} else {
357 					if (theResourceName != null) {
358 						Slice<Long> ids = myResourceTableDao.findIdsOfDeletedResourcesOfType(page, theResourceName);
359 						ourLog.info("Expunging {} deleted resources of type[{}]", ids.getNumberOfElements(), theResourceName);
360 						return ids;
361 					} else {
362 						Slice<Long> ids = myResourceTableDao.findIdsOfDeletedResources(page);
363 						ourLog.info("Expunging {} deleted resources (all types)", ids.getNumberOfElements(), theResourceName);
364 						return ids;
365 					}
366 				}
367 			});
368 
369 			/*
370 			 * Delete any search result cache entries pointing to the given resource
371 			 */
372 			if (resourceIds.getContent().size() > 0) {
373 				txTemplate.execute(t -> {
374 					mySearchResultDao.deleteByResourceIds(resourceIds.getContent());
375 					return null;
376 				});
377 			}
378 
379 			/*
380 			 * Delete historical versions
381 			 */
382 			for (Long next : resourceIds) {
383 				txTemplate.execute(t -> {
384 					expungeHistoricalVersionsOfId(next, remainingCount);
385 					if (remainingCount.get() <= 0) {
386 						ourLog.debug("Expunge limit has been hit - Stopping operation");
387 						return toExpungeOutcome(theExpungeOptions, remainingCount);
388 					}
389 					return null;
390 				});
391 			}
392 
393 			/*
394 			 * Delete current versions of deleted resources
395 			 */
396 			for (Long next : resourceIds) {
397 				txTemplate.execute(t -> {
398 					expungeCurrentVersionOfResource(next, remainingCount);
399 					return null;
400 				});
401 			}
402 
403 		}
404 
405 		if (theExpungeOptions.isExpungeOldVersions()) {
406 
407 			/*
408 			 * Delete historical versions of non-deleted resources
409 			 */
410 			Pageable page = PageRequest.of(0, remainingCount.get());
411 			Slice<Long> historicalIds = txTemplate.execute(t -> {
412 				if (theResourceId != null && theVersion != null) {
413 					return toSlice(myResourceHistoryTableDao.findForIdAndVersion(theResourceId, theVersion));
414 				} else {
415 					if (theResourceName != null) {
416 						return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResources(page, theResourceName);
417 					} else {
418 						return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResources(page);
419 					}
420 				}
421 			});
422 
423 			for (Long next : historicalIds) {
424 				txTemplate.execute(t -> {
425 					expungeHistoricalVersion(next);
426 					if (remainingCount.decrementAndGet() <= 0) {
427 						return toExpungeOutcome(theExpungeOptions, remainingCount);
428 					}
429 					return null;
430 				});
431 			}
432 
433 		}
434 		return toExpungeOutcome(theExpungeOptions, remainingCount);
435 	}
436 
437 	private void doExpungeEverything() {
438 
439 		ourLog.info("** BEGINNING GLOBAL $expunge **");
440 		TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
441 		txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
442 		txTemplate.execute(t -> {
443 			doExpungeEverythingQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null");
444 			doExpungeEverythingQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null");
445 			doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null");
446 			return null;
447 		});
448 		txTemplate.execute(t -> {
449 			doExpungeEverythingQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d");
450 			doExpungeEverythingQuery("DELETE from " + ForcedId.class.getSimpleName() + " d");
451 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d");
452 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d");
453 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamQuantity.class.getSimpleName() + " d");
454 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamString.class.getSimpleName() + " d");
455 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamToken.class.getSimpleName() + " d");
456 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamUri.class.getSimpleName() + " d");
457 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedSearchParamCoords.class.getSimpleName() + " d");
458 			doExpungeEverythingQuery("DELETE from " + ResourceIndexedCompositeStringUnique.class.getSimpleName() + " d");
459 			doExpungeEverythingQuery("DELETE from " + ResourceLink.class.getSimpleName() + " d");
460 			doExpungeEverythingQuery("DELETE from " + SearchResult.class.getSimpleName() + " d");
461 			doExpungeEverythingQuery("DELETE from " + SearchInclude.class.getSimpleName() + " d");
462 			doExpungeEverythingQuery("DELETE from " + TermConceptParentChildLink.class.getSimpleName() + " d");
463 			return null;
464 		});
465 		txTemplate.execute(t -> {
466 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroupElementTarget.class.getSimpleName() + " d");
467 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroupElement.class.getSimpleName() + " d");
468 			doExpungeEverythingQuery("DELETE from " + TermConceptMapGroup.class.getSimpleName() + " d");
469 			doExpungeEverythingQuery("DELETE from " + TermConceptMap.class.getSimpleName() + " d");
470 			return null;
471 		});
472 		txTemplate.execute(t -> {
473 			doExpungeEverythingQuery("DELETE from " + TermConceptProperty.class.getSimpleName() + " d");
474 			doExpungeEverythingQuery("DELETE from " + TermConceptDesignation.class.getSimpleName() + " d");
475 			doExpungeEverythingQuery("DELETE from " + TermConcept.class.getSimpleName() + " d");
476 			for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
477 				next.setCurrentVersion(null);
478 				myEntityManager.merge(next);
479 			}
480 			return null;
481 		});
482 		txTemplate.execute(t -> {
483 			doExpungeEverythingQuery("DELETE from " + TermCodeSystemVersion.class.getSimpleName() + " d");
484 			doExpungeEverythingQuery("DELETE from " + TermCodeSystem.class.getSimpleName() + " d");
485 			return null;
486 		});
487 		txTemplate.execute(t -> {
488 			doExpungeEverythingQuery("DELETE from " + SubscriptionTable.class.getSimpleName() + " d");
489 			doExpungeEverythingQuery("DELETE from " + ResourceHistoryTag.class.getSimpleName() + " d");
490 			doExpungeEverythingQuery("DELETE from " + ResourceTag.class.getSimpleName() + " d");
491 			doExpungeEverythingQuery("DELETE from " + TagDefinition.class.getSimpleName() + " d");
492 			doExpungeEverythingQuery("DELETE from " + ResourceHistoryTable.class.getSimpleName() + " d");
493 			doExpungeEverythingQuery("DELETE from " + ResourceTable.class.getSimpleName() + " d");
494 			doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d");
495 			return null;
496 		});
497 
498 		ourLog.info("** COMPLETED GLOBAL $expunge **");
499 	}
500 
501 	private void doExpungeEverythingQuery(String theQuery) {
502 		StopWatch sw = new StopWatch();
503 		int outcome = myEntityManager.createQuery(theQuery).executeUpdate();
504 		ourLog.info("Query affected {} rows in {}: {}", outcome, sw.toString(), theQuery);
505 	}
506 
507 	private void expungeCurrentVersionOfResource(Long theResourceId, AtomicInteger theRemainingCount) {
508 		ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalStateException::new);
509 
510 		ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersion(resource.getId(), resource.getVersion());
511 		if (currentVersion != null) {
512 			expungeHistoricalVersion(currentVersion.getId());
513 		}
514 
515 		ourLog.info("Expunging current version of resource {}", resource.getIdDt().getValue());
516 
517 		myResourceIndexedSearchParamUriDao.deleteAll(resource.getParamsUri());
518 		myResourceIndexedSearchParamCoordsDao.deleteAll(resource.getParamsCoords());
519 		myResourceIndexedSearchParamDateDao.deleteAll(resource.getParamsDate());
520 		myResourceIndexedSearchParamNumberDao.deleteAll(resource.getParamsNumber());
521 		myResourceIndexedSearchParamQuantityDao.deleteAll(resource.getParamsQuantity());
522 		myResourceIndexedSearchParamStringDao.deleteAll(resource.getParamsString());
523 		myResourceIndexedSearchParamTokenDao.deleteAll(resource.getParamsToken());
524 
525 		myResourceTagDao.deleteAll(resource.getTags());
526 		resource.getTags().clear();
527 
528 		if (resource.getForcedId() != null) {
529 			ForcedId forcedId = resource.getForcedId();
530 			resource.setForcedId(null);
531 			myResourceTableDao.saveAndFlush(resource);
532 			myForcedIdDao.delete(forcedId);
533 		}
534 
535 		myResourceTableDao.delete(resource);
536 
537 		theRemainingCount.decrementAndGet();
538 	}
539 
540 	protected void expungeHistoricalVersion(Long theNextVersionId) {
541 		ResourceHistoryTable version = myResourceHistoryTableDao.findById(theNextVersionId).orElseThrow(IllegalArgumentException::new);
542 		ourLog.info("Deleting resource version {}", version.getIdDt().getValue());
543 
544 		myResourceHistoryTagDao.deleteAll(version.getTags());
545 		myResourceHistoryTableDao.delete(version);
546 	}
547 
548 	protected void expungeHistoricalVersionsOfId(Long theResourceId, AtomicInteger theRemainingCount) {
549 		ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalArgumentException::new);
550 
551 		Pageable page = PageRequest.of(0, theRemainingCount.get());
552 
553 		Slice<Long> versionIds = myResourceHistoryTableDao.findForResourceId(page, resource.getId(), resource.getVersion());
554 		ourLog.debug("Found {} versions of resource {} to expunge", versionIds.getNumberOfElements(), resource.getIdDt().getValue());
555 		for (Long nextVersionId : versionIds) {
556 			expungeHistoricalVersion(nextVersionId);
557 			if (theRemainingCount.decrementAndGet() <= 0) {
558 				return;
559 			}
560 		}
561 	}
562 
563 
564 	private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set<ResourceTag> allDefs) {
565 		TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
566 		if (tagList != null) {
567 			for (Tag next : tagList) {
568 				TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
569 				if (def != null) {
570 					ResourceTag tag = theEntity.addTag(def);
571 					allDefs.add(tag);
572 					theEntity.setHasTags(true);
573 				}
574 			}
575 		}
576 
577 		List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
578 		if (securityLabels != null) {
579 			for (BaseCodingDt next : securityLabels) {
580 				TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
581 				if (def != null) {
582 					ResourceTag tag = theEntity.addTag(def);
583 					allDefs.add(tag);
584 					theEntity.setHasTags(true);
585 				}
586 			}
587 		}
588 
589 		List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
590 		if (profiles != null) {
591 			for (IIdType next : profiles) {
592 				TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
593 				if (def != null) {
594 					ResourceTag tag = theEntity.addTag(def);
595 					allDefs.add(tag);
596 					theEntity.setHasTags(true);
597 				}
598 			}
599 		}
600 	}
601 
602 	private void extractTagsRi(IAnyResource theResource, ResourceTable theEntity, Set<ResourceTag> theAllTags) {
603 		List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
604 		if (tagList != null) {
605 			for (IBaseCoding next : tagList) {
606 				TagDefinition def = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
607 				if (def != null) {
608 					ResourceTag tag = theEntity.addTag(def);
609 					theAllTags.add(tag);
610 					theEntity.setHasTags(true);
611 				}
612 			}
613 		}
614 
615 		List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
616 		if (securityLabels != null) {
617 			for (IBaseCoding next : securityLabels) {
618 				TagDefinition def = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
619 				if (def != null) {
620 					ResourceTag tag = theEntity.addTag(def);
621 					theAllTags.add(tag);
622 					theEntity.setHasTags(true);
623 				}
624 			}
625 		}
626 
627 		List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
628 		if (profiles != null) {
629 			for (IPrimitiveType<String> next : profiles) {
630 				TagDefinition def = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
631 				if (def != null) {
632 					ResourceTag tag = theEntity.addTag(def);
633 					theAllTags.add(tag);
634 					theEntity.setHasTags(true);
635 				}
636 			}
637 		}
638 	}
639 
640 	private void findMatchingTagIds(String theResourceName, IIdType theResourceId, Set<Long> tagIds, Class<? extends BaseTag> entityClass) {
641 		{
642 			CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
643 			CriteriaQuery<Tuple> cq = builder.createTupleQuery();
644 			Root<? extends BaseTag> from = cq.from(entityClass);
645 			cq.multiselect(from.get("myTagId").as(Long.class)).distinct(true);
646 
647 			if (theResourceName != null) {
648 				Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
649 				if (theResourceId != null) {
650 					cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceName, theResourceId.getIdPart())));
651 				} else {
652 					cq.where(typePredicate);
653 				}
654 			}
655 
656 			TypedQuery<Tuple> query = myEntityManager.createQuery(cq);
657 			for (Tuple next : query.getResultList()) {
658 				tagIds.add(next.get(0, Long.class));
659 			}
660 		}
661 	}
662 	protected void flushJpaSession() {
663 		SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class);
664 		int insertionCount = session.getActionQueue().numberOfInsertions();
665 		int updateCount = session.getActionQueue().numberOfUpdates();
666 
667 		StopWatch sw = new StopWatch();
668 		myEntityManager.flush();
669 		ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount);
670 	}
671 
672 	private Set<ResourceTag> getAllTagDefinitions(ResourceTable theEntity) {
673 		HashSet<ResourceTag> retVal = Sets.newHashSet();
674 		if (theEntity.isHasTags()) {
675 			for (ResourceTag next : theEntity.getTags()) {
676 				retVal.add(next);
677 			}
678 		}
679 		return retVal;
680 	}
681 
682 	@Override
683 	public DaoConfig getConfig() {
684 		return myConfig;
685 	}
686 
687 	public void setConfig(DaoConfig theConfig) {
688 		myConfig = theConfig;
689 	}
690 
691 	@Override
692 	public FhirContext getContext() {
693 		return myContext;
694 	}
695 
696 	@Autowired
697 	public void setContext(FhirContext theContext) {
698 		myContext = theContext;
699 	}
700 
701 	public FhirContext getContext(FhirVersionEnum theVersion) {
702 		Validate.notNull(theVersion, "theVersion must not be null");
703 		synchronized (ourRetrievalContexts) {
704 			FhirContext retVal = ourRetrievalContexts.get(theVersion);
705 			if (retVal == null) {
706 				retVal = new FhirContext(theVersion);
707 				ourRetrievalContexts.put(theVersion, retVal);
708 			}
709 			return retVal;
710 		}
711 	}
712 
713 	@Override
714 	@SuppressWarnings("unchecked")
715 	public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
716 		Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = getDaos();
717 		IFhirResourceDao<R> dao = (IFhirResourceDao<R>) resourceTypeToDao.get(theType);
718 		return dao;
719 	}
720 
721 	private Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getDaos() {
722 		if (myResourceTypeToDao == null) {
723 			Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = new HashMap<>();
724 
725 			Map<String, IFhirResourceDao> daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false);
726 
727 			String[] beanNames = myApplicationContext.getBeanNamesForType(IFhirResourceDao.class);
728 
729 			for (IFhirResourceDao<?> next : daos.values()) {
730 				resourceTypeToDao.put(next.getResourceType(), next);
731 			}
732 
733 			if (this instanceof IFhirResourceDao<?>) {
734 				IFhirResourceDao<?> thiz = (IFhirResourceDao<?>) this;
735 				resourceTypeToDao.put(thiz.getResourceType(), thiz);
736 			}
737 
738 			myResourceTypeToDao = resourceTypeToDao;
739 		}
740 
741 		return Collections.unmodifiableMap(myResourceTypeToDao);
742 	}
743 
744 	@Override
745 	public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() {
746 		return myResourceIndexedCompositeStringUniqueDao;
747 	}
748 
749 	@Override
750 	public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
751 		Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
752 		return params.get(theParamName);
753 	}
754 
755 	@Override
756 	public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
757 		return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
758 	}
759 
760 	protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
761 		if (isBlank(theScheme) && isBlank(theTerm) && isBlank(theLabel)) {
762 			return null;
763 		}
764 
765 		CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
766 		CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
767 		Root<TagDefinition> from = cq.from(TagDefinition.class);
768 
769 		if (isNotBlank(theScheme)) {
770 			cq.where(
771 				builder.and(
772 					builder.equal(from.get("myTagType"), theTagType),
773 					builder.equal(from.get("mySystem"), theScheme),
774 					builder.equal(from.get("myCode"), theTerm)));
775 		} else {
776 			cq.where(
777 				builder.and(
778 					builder.equal(from.get("myTagType"), theTagType),
779 					builder.isNull(from.get("mySystem")),
780 					builder.equal(from.get("myCode"), theTerm)));
781 		}
782 
783 		TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
784 		try {
785 			return q.getSingleResult();
786 		} catch (NoResultException e) {
787 			TagDefinition retVal = new TagDefinition(theTagType, theScheme, theTerm, theLabel);
788 			myEntityManager.persist(retVal);
789 			return retVal;
790 		}
791 	}
792 
793 	protected TagList getTags(Class<? extends IBaseResource> theResourceType, IIdType theResourceId) {
794 		String resourceName = null;
795 		if (theResourceType != null) {
796 			resourceName = toResourceName(theResourceType);
797 			if (theResourceId != null && theResourceId.hasVersionIdPart()) {
798 				IFhirResourceDao<? extends IBaseResource> dao = getDao(theResourceType);
799 				BaseHasResource entity = dao.readEntity(theResourceId);
800 				TagList retVal = new TagList();
801 				for (BaseTag next : entity.getTags()) {
802 					retVal.add(next.getTag().toTag());
803 				}
804 				return retVal;
805 			}
806 		}
807 
808 		Set<Long> tagIds = new HashSet<>();
809 		findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceTag.class);
810 		findMatchingTagIds(resourceName, theResourceId, tagIds, ResourceHistoryTag.class);
811 		if (tagIds.isEmpty()) {
812 			return new TagList();
813 		}
814 		{
815 			CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
816 			CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
817 			Root<TagDefinition> from = cq.from(TagDefinition.class);
818 			cq.where(from.get("myId").in(tagIds));
819 			cq.orderBy(builder.asc(from.get("mySystem")), builder.asc(from.get("myCode")));
820 			TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
821 			q.setMaxResults(getConfig().getHardTagListLimit());
822 
823 			TagList retVal = new TagList();
824 			for (TagDefinition next : q.getResultList()) {
825 				retVal.add(next.toTag());
826 			}
827 
828 			return retVal;
829 		}
830 	}
831 
832 	protected IBundleProvider history(String theResourceName, Long theId, Date theSince, Date theUntil) {
833 
834 		String resourceName = defaultIfBlank(theResourceName, null);
835 
836 		Search search = new Search();
837 		search.setDeleted(false);
838 		search.setCreated(new Date());
839 		search.setSearchLastReturned(new Date());
840 		search.setLastUpdated(theSince, theUntil);
841 		search.setUuid(UUID.randomUUID().toString());
842 		search.setResourceType(resourceName);
843 		search.setResourceId(theId);
844 		search.setSearchType(SearchTypeEnum.HISTORY);
845 		search.setStatus(SearchStatusEnum.FINISHED);
846 
847 		if (theSince != null) {
848 			if (resourceName == null) {
849 				search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes(theSince));
850 			} else if (theId == null) {
851 				search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName, theSince));
852 			} else {
853 				search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId, theSince));
854 			}
855 		} else {
856 			if (resourceName == null) {
857 				search.setTotalCount(myResourceHistoryTableDao.countForAllResourceTypes());
858 			} else if (theId == null) {
859 				search.setTotalCount(myResourceHistoryTableDao.countForResourceType(resourceName));
860 			} else {
861 				search.setTotalCount(myResourceHistoryTableDao.countForResourceInstance(theId));
862 			}
863 		}
864 
865 		search = mySearchDao.save(search);
866 
867 		return new PersistedJpaBundleProvider(search.getUuid(), this);
868 	}
869 
870 	void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
871 		String newVersion;
872 		long newVersionLong;
873 		if (theResourceId == null || theResourceId.getVersionIdPart() == null) {
874 			newVersion = "1";
875 			newVersionLong = 1;
876 		} else {
877 			newVersionLong = theResourceId.getVersionIdPartAsLong() + 1;
878 			newVersion = Long.toString(newVersionLong);
879 		}
880 
881 		IIdType newId = theResourceId.withVersion(newVersion);
882 		theResource.getIdElement().setValue(newId.getValue());
883 		theSavedEntity.setVersion(newVersionLong);
884 	}
885 
886 	@Override
887 	public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) {
888 		theProvider.setContext(getContext());
889 		theProvider.setEntityManager(myEntityManager);
890 		theProvider.setPlatformTransactionManager(myPlatformTransactionManager);
891 		theProvider.setSearchDao(mySearchDao);
892 		theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
893 	}
894 
895 	@Override
896 	public boolean isLogicalReference(IIdType theId) {
897 		Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
898 		if (treatReferencesAsLogical != null) {
899 			for (String nextLogicalRef : treatReferencesAsLogical) {
900 				nextLogicalRef = trim(nextLogicalRef);
901 				if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
902 					if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
903 						return true;
904 					}
905 				} else {
906 					if (theId.getValue().equals(nextLogicalRef)) {
907 						return true;
908 					}
909 				}
910 			}
911 
912 		}
913 		return false;
914 	}
915 
916 	public static void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
917 		if (theRequestDetails != null) {
918 			theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
919 		}
920 	}
921 
922 	@Override
923 	public SearchBuilder newSearchBuilder() {
924 		SearchBuilder builder = new SearchBuilder(
925 			getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao,
926 			myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao);
927 		return builder;
928 	}
929 
930 	public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
931 		if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
932 			if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
933 				throw new InternalErrorException(
934 					"Inconsistent server state - Resource types don't match: " + theRequestDetails.getId().getResourceType() + " / " + theRequestDetails.getResourceType());
935 			}
936 		}
937 
938 		if (theRequestDetails.getUserData().get(PROCESSING_SUB_REQUEST) == Boolean.TRUE) {
939 			theRequestDetails.notifyIncomingRequestPreHandled(theOperationType);
940 		}
941 		List<IServerInterceptor> interceptors = getConfig().getInterceptors();
942 		for (IServerInterceptor next : interceptors) {
943 			next.incomingRequestPreHandled(theOperationType, theRequestDetails);
944 		}
945 	}
946 
947 	public String parseContentTextIntoWords(IBaseResource theResource) {
948 		StringBuilder retVal = new StringBuilder();
949 		@SuppressWarnings("rawtypes")
950 		List<IPrimitiveType> childElements = getContext().newTerser().getAllPopulatedChildElementsOfType(theResource, IPrimitiveType.class);
951 		for (@SuppressWarnings("rawtypes")
952 			IPrimitiveType nextType : childElements) {
953 			if (nextType instanceof StringDt || nextType.getClass().getSimpleName().equals("StringType")) {
954 				String nextValue = nextType.getValueAsString();
955 				if (isNotBlank(nextValue)) {
956 					retVal.append(nextValue.replace("\n", " ").replace("\r", " "));
957 					retVal.append("\n");
958 				}
959 			}
960 		}
961 		return retVal.toString();
962 	}
963 
964 	@Override
965 	public void populateFullTextFields(final IBaseResource theResource, ResourceTable theEntity) {
966 		if (theEntity.getDeleted() != null) {
967 			theEntity.setNarrativeTextParsedIntoWords(null);
968 			theEntity.setContentTextParsedIntoWords(null);
969 		} else {
970 			theEntity.setNarrativeTextParsedIntoWords(parseNarrativeTextIntoWords(theResource));
971 			theEntity.setContentTextParsedIntoWords(parseContentTextIntoWords(theResource));
972 		}
973 	}
974 
975 	private void populateResourceIdFromEntity(IBaseResourceEntity theEntity, final IBaseResource theResource) {
976 		IIdType id = theEntity.getIdDt();
977 		if (getContext().getVersion().getVersion().isRi()) {
978 			id = getContext().getVersion().newIdType().setValue(id.getValue());
979 		}
980 		theResource.setId(id);
981 	}
982 
983 	/**
984 	 * Returns true if the resource has changed (either the contents or the tags)
985 	 */
986 	protected EncodedResource populateResourceIntoEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, boolean theUpdateHash) {
987 		if (theEntity.getResourceType() == null) {
988 			theEntity.setResourceType(toResourceName(theResource));
989 		}
990 
991 		if (theResource != null) {
992 			List<BaseResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class);
993 			for (BaseResourceReferenceDt nextRef : refs) {
994 				if (nextRef.getReference().isEmpty() == false) {
995 					if (nextRef.getReference().hasVersionIdPart()) {
996 						nextRef.setReference(nextRef.getReference().toUnqualifiedVersionless());
997 					}
998 				}
999 			}
1000 		}
1001 
1002 		byte[] bytes;
1003 		ResourceEncodingEnum encoding;
1004 		boolean changed = false;
1005 
1006 		if (theEntity.getDeleted() == null) {
1007 
1008 			encoding = myConfig.getResourceEncoding();
1009 			Set<String> excludeElements = EXCLUDE_ELEMENTS_IN_ENCODED;
1010 			theEntity.setFhirVersion(myContext.getVersion().getVersion());
1011 
1012 			bytes = encodeResource(theResource, encoding, excludeElements, myContext);
1013 
1014 			if (theUpdateHash) {
1015 				HashFunction sha256 = Hashing.sha256();
1016 				String hashSha256 = sha256.hashBytes(bytes).toString();
1017 				if (hashSha256.equals(theEntity.getHashSha256()) == false) {
1018 					changed = true;
1019 				}
1020 				theEntity.setHashSha256(hashSha256);
1021 			}
1022 
1023 			Set<ResourceTag> allDefs = new HashSet<>();
1024 			Set<ResourceTag> allTagsOld = getAllTagDefinitions(theEntity);
1025 
1026 			if (theResource instanceof IResource) {
1027 				extractTagsHapi((IResource) theResource, theEntity, allDefs);
1028 			} else {
1029 				extractTagsRi((IAnyResource) theResource, theEntity, allDefs);
1030 			}
1031 
1032 			RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
1033 			if (def.isStandardType() == false) {
1034 				String profile = def.getResourceProfile("");
1035 				if (isNotBlank(profile)) {
1036 					TagDefinition profileDef = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
1037 					if (def != null) {
1038 						ResourceTag tag = theEntity.addTag(profileDef);
1039 						allDefs.add(tag);
1040 						theEntity.setHasTags(true);
1041 					}
1042 				}
1043 			}
1044 
1045 			Set<ResourceTag> allTagsNew = getAllTagDefinitions(theEntity);
1046 			Set<TagDefinition> allDefsPresent = new HashSet<>();
1047 			allTagsNew.forEach(tag -> {
1048 
1049 				// Don't keep duplicate tags
1050 				if (!allDefsPresent.add(tag.getTag())) {
1051 					theEntity.getTags().remove(tag);
1052 				}
1053 
1054 				// Drop any tags that have been removed
1055 				if (!allDefs.contains(tag)) {
1056 					if (shouldDroppedTagBeRemovedOnUpdate(theRequest, tag)) {
1057 						theEntity.getTags().remove(tag);
1058 					}
1059 				}
1060 
1061 			});
1062 
1063 			if (!allTagsOld.equals(allTagsNew)) {
1064 				changed = true;
1065 			}
1066 			theEntity.setHasTags(!allTagsNew.isEmpty());
1067 
1068 		} else {
1069 			theEntity.setHashSha256(null);
1070 			bytes = null;
1071 			encoding = ResourceEncodingEnum.DEL;
1072 		}
1073 
1074 		if (changed == false) {
1075 			if (theEntity.getId() == null) {
1076 				changed = true;
1077 			} else {
1078 				ResourceHistoryTable currentHistoryVersion = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
1079 				if (currentHistoryVersion == null || currentHistoryVersion.getResource() == null) {
1080 					changed = true;
1081 				} else {
1082 					changed = !Arrays.equals(currentHistoryVersion.getResource(), bytes);
1083 				}
1084 			}
1085 		}
1086 
1087 		EncodedResource retVal = new EncodedResource();
1088 		retVal.setEncoding(encoding);
1089 		retVal.setResource(bytes);
1090 		retVal.setChanged(changed);
1091 
1092 		return retVal;
1093 	}
1094 
1095 	@SuppressWarnings("unchecked")
1096 	private <R extends IBaseResource> R populateResourceMetadataHapi(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IResource res) {
1097 		R retVal = (R) res;
1098 		if (theEntity.getDeleted() != null) {
1099 			res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance();
1100 			retVal = (R) res;
1101 			ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
1102 			if (theForHistoryOperation) {
1103 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
1104 			}
1105 		} else if (theForHistoryOperation) {
1106 			/*
1107 			 * 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.
1108 			 */
1109 			Date published = theEntity.getPublished().getValue();
1110 			Date updated = theEntity.getUpdated().getValue();
1111 			if (published.equals(updated)) {
1112 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
1113 			} else {
1114 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
1115 			}
1116 		}
1117 
1118 		res.setId(theEntity.getIdDt());
1119 
1120 		ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
1121 		ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
1122 		ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
1123 		IDao.RESOURCE_PID.put(res, theEntity.getId());
1124 
1125 		Collection<? extends BaseTag> tags = theTagList;
1126 		if (theEntity.isHasTags()) {
1127 			TagList tagList = new TagList();
1128 			List<IBaseCoding> securityLabels = new ArrayList<>();
1129 			List<IdDt> profiles = new ArrayList<>();
1130 			for (BaseTag next : tags) {
1131 				switch (next.getTag().getTagType()) {
1132 					case PROFILE:
1133 						profiles.add(new IdDt(next.getTag().getCode()));
1134 						break;
1135 					case SECURITY_LABEL:
1136 						IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt();
1137 						secLabel.setSystem(next.getTag().getSystem());
1138 						secLabel.setCode(next.getTag().getCode());
1139 						secLabel.setDisplay(next.getTag().getDisplay());
1140 						securityLabels.add(secLabel);
1141 						break;
1142 					case TAG:
1143 						tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay()));
1144 						break;
1145 				}
1146 			}
1147 			if (tagList.size() > 0) {
1148 				ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList);
1149 			}
1150 			if (securityLabels.size() > 0) {
1151 				ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels));
1152 			}
1153 			if (profiles.size() > 0) {
1154 				ResourceMetadataKeyEnum.PROFILES.put(res, profiles);
1155 			}
1156 		}
1157 
1158 		return retVal;
1159 	}
1160 
1161 	@SuppressWarnings("unchecked")
1162 	private <R extends IBaseResource> R populateResourceMetadataRi(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IAnyResource res) {
1163 		R retVal = (R) res;
1164 		if (theEntity.getDeleted() != null) {
1165 			res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance();
1166 			retVal = (R) res;
1167 			ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
1168 			if (theForHistoryOperation) {
1169 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode());
1170 			}
1171 		} else if (theForHistoryOperation) {
1172 			/*
1173 			 * 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.
1174 			 */
1175 			Date published = theEntity.getPublished().getValue();
1176 			Date updated = theEntity.getUpdated().getValue();
1177 			if (published.equals(updated)) {
1178 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode());
1179 			} else {
1180 				ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode());
1181 			}
1182 		}
1183 
1184 		res.getMeta().getTag().clear();
1185 		res.getMeta().getProfile().clear();
1186 		res.getMeta().getSecurity().clear();
1187 		res.getMeta().setLastUpdated(null);
1188 		res.getMeta().setVersionId(null);
1189 
1190 		populateResourceIdFromEntity(theEntity, res);
1191 
1192 		res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
1193 		IDao.RESOURCE_PID.put(res, theEntity.getId());
1194 
1195 		Collection<? extends BaseTag> tags = theTagList;
1196 
1197 		if (theEntity.isHasTags()) {
1198 			for (BaseTag next : tags) {
1199 				switch (next.getTag().getTagType()) {
1200 					case PROFILE:
1201 						res.getMeta().addProfile(next.getTag().getCode());
1202 						break;
1203 					case SECURITY_LABEL:
1204 						IBaseCoding sec = res.getMeta().addSecurity();
1205 						sec.setSystem(next.getTag().getSystem());
1206 						sec.setCode(next.getTag().getCode());
1207 						sec.setDisplay(next.getTag().getDisplay());
1208 						break;
1209 					case TAG:
1210 						IBaseCoding tag = res.getMeta().addTag();
1211 						tag.setSystem(next.getTag().getSystem());
1212 						tag.setCode(next.getTag().getCode());
1213 						tag.setDisplay(next.getTag().getDisplay());
1214 						break;
1215 				}
1216 			}
1217 		}
1218 		return retVal;
1219 	}
1220 
1221 	/**
1222 	 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
1223 	 *
1224 	 * @param theEntity The resource
1225 	 */
1226 	protected void postDelete(ResourceTable theEntity) {
1227 		// nothing
1228 	}
1229 
1230 	/**
1231 	 * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
1232 	 *
1233 	 * @param theEntity   The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
1234 	 * @param theResource The resource being persisted
1235 	 */
1236 	protected void postPersist(ResourceTable theEntity, T theResource) {
1237 		// nothing
1238 	}
1239 
1240 	/**
1241 	 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
1242 	 *
1243 	 * @param theEntity   The resource
1244 	 * @param theResource The resource being persisted
1245 	 */
1246 	protected void postUpdate(ResourceTable theEntity, T theResource) {
1247 		// nothing
1248 	}
1249 
1250 	@Override
1251 	public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
1252 		RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
1253 
1254 		SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef);
1255 		paramMap.setLoadSynchronous(true);
1256 
1257 		if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
1258 			throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
1259 		}
1260 
1261 		IFhirResourceDao<R> dao = getDao(theResourceType);
1262 		if (dao == null) {
1263 			throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName());
1264 		}
1265 
1266 		return dao.searchForIds(paramMap);
1267 	}
1268 
1269 	@CoverageIgnore
1270 	public BaseHasResource readEntity(IIdType theValueId) {
1271 		throw new NotImplementedException("");
1272 	}
1273 
1274 	@Override
1275 	public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
1276 		/*
1277 		 * We do a null check here because Smile's module system tries to
1278 		 * initialize the application context twice if two modules depend on
1279 		 * the persistence module. The second time sets the dependency's appctx.
1280 		 */
1281 		if (myApplicationContext == null) {
1282 			myApplicationContext = theApplicationContext;
1283 		}
1284 	}
1285 
1286 
1287 	/**
1288 	 * 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.
1289 	 * <p>
1290 	 * The default implementation removes any profile declarations, but leaves tags and security labels in place. Subclasses may choose to override and change this behaviour.
1291 	 * </p>
1292 	 * <p>
1293 	 * 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.
1294 	 * </p>
1295 	 *
1296 	 * @param theTag The tag
1297 	 * @return Returns <code>true</code> if the tag should be removed
1298 	 */
1299 	protected boolean shouldDroppedTagBeRemovedOnUpdate(RequestDetails theRequest, ResourceTag theTag) {
1300 
1301 		Set<TagTypeEnum> metaSnapshotModeTokens = null;
1302 
1303 		if (theRequest != null) {
1304 			List<String> metaSnapshotMode = theRequest.getHeaders(JpaConstants.HEADER_META_SNAPSHOT_MODE);
1305 			if (metaSnapshotMode != null && !metaSnapshotMode.isEmpty()) {
1306 				metaSnapshotModeTokens = new HashSet<>();
1307 				for (String nextHeaderValue : metaSnapshotMode) {
1308 					StringTokenizer tok = new StringTokenizer(nextHeaderValue, ",");
1309 					while (tok.hasMoreTokens()) {
1310 						switch (trim(tok.nextToken())) {
1311 							case "TAG":
1312 								metaSnapshotModeTokens.add(TagTypeEnum.TAG);
1313 								break;
1314 							case "PROFILE":
1315 								metaSnapshotModeTokens.add(TagTypeEnum.PROFILE);
1316 								break;
1317 							case "SECURITY_LABEL":
1318 								metaSnapshotModeTokens.add(TagTypeEnum.SECURITY_LABEL);
1319 								break;
1320 						}
1321 					}
1322 				}
1323 			}
1324 		}
1325 
1326 		if (metaSnapshotModeTokens == null) {
1327 			metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
1328 		}
1329 
1330 		if (metaSnapshotModeTokens.contains(theTag.getTag().getTagType())) {
1331 			return true;
1332 		}
1333 
1334 		return false;
1335 	}
1336 
1337 	@PostConstruct
1338 	public void startClearCaches() {
1339 		myResourceTypeToDao = null;
1340 	}
1341 
1342 	private ExpungeOutcome toExpungeOutcome(ExpungeOptions theExpungeOptions, AtomicInteger theRemainingCount) {
1343 		return new ExpungeOutcome()
1344 			.setDeletedCount(theExpungeOptions.getLimit() - theRemainingCount.get());
1345 	}
1346 
1347 	@Override
1348 	public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
1349 		RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
1350 		Class<? extends IBaseResource> resourceType = type.getImplementingClass();
1351 		return toResource(resourceType, theEntity, null, theForHistoryOperation);
1352 	}
1353 
1354 	@SuppressWarnings("unchecked")
1355 	@Override
1356 	public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
1357 
1358 		// 1. get resource, it's encoding and the tags if any
1359 		byte[] resourceBytes = null;
1360 		ResourceEncodingEnum resourceEncoding = null;
1361 		Collection<? extends BaseTag> myTagList = null;
1362 
1363 		if (theEntity instanceof ResourceHistoryTable) {
1364 			ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
1365 			resourceBytes = history.getResource();
1366 			resourceEncoding = history.getEncoding();
1367 			myTagList = history.getTags();
1368 		} else if (theEntity instanceof ResourceTable) {
1369 			ResourceTable resource = (ResourceTable) theEntity;
1370 			ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
1371 			if (history == null) {
1372 				return null;
1373 			}
1374 			resourceBytes = history.getResource();
1375 			resourceEncoding = history.getEncoding();
1376 			myTagList = resource.getTags();
1377 		} else if (theEntity instanceof ResourceSearchView) {
1378 			// This is the search View
1379 			ResourceSearchView myView = (ResourceSearchView) theEntity;
1380 			resourceBytes = myView.getResource();
1381 			resourceEncoding = myView.getEncoding();
1382 			if (theTagList == null)
1383 				myTagList = new HashSet<>();
1384 			else
1385 				myTagList = theTagList;
1386 		} else {
1387 			// something wrong
1388 			return null;
1389 		}
1390 
1391 		// 2. get The text
1392 		String resourceText = decodeResource(resourceBytes, resourceEncoding);
1393 
1394 		// 3. Use the appropriate custom type if one is specified in the context
1395 		Class<R> resourceType = theResourceType;
1396 		if (myContext.hasDefaultTypeForProfile()) {
1397 			for (BaseTag nextTag : myTagList) {
1398 				if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
1399 					String profile = nextTag.getTag().getCode();
1400 					if (isNotBlank(profile)) {
1401 						Class<? extends IBaseResource> newType = myContext.getDefaultTypeForProfile(profile);
1402 						if (newType != null && theResourceType.isAssignableFrom(newType)) {
1403 							ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile);
1404 							resourceType = (Class<R>) newType;
1405 							break;
1406 						}
1407 					}
1408 				}
1409 			}
1410 		}
1411 
1412 		// 4. parse the text to FHIR
1413 		R retVal;
1414 		if (resourceEncoding != ResourceEncodingEnum.DEL) {
1415 			IParser parser = resourceEncoding.newParser(getContext(theEntity.getFhirVersion()));
1416 			parser.setParserErrorHandler(new LenientErrorHandler(false).setErrorOnInvalidValue(false));
1417 
1418 			try {
1419 				retVal = parser.parseResource(resourceType, resourceText);
1420 			} catch (Exception e) {
1421 				StringBuilder b = new StringBuilder();
1422 				b.append("Failed to parse database resource[");
1423 				b.append(resourceType);
1424 				b.append("/");
1425 				b.append(theEntity.getIdDt().getIdPart());
1426 				b.append(" (pid ");
1427 				b.append(theEntity.getId());
1428 				b.append(", version ");
1429 				b.append(theEntity.getFhirVersion().name());
1430 				b.append("): ");
1431 				b.append(e.getMessage());
1432 				String msg = b.toString();
1433 				ourLog.error(msg, e);
1434 				throw new DataFormatException(msg, e);
1435 			}
1436 
1437 		} else {
1438 
1439 			retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance();
1440 
1441 		}
1442 
1443 		// 5. fill MetaData
1444 		if (retVal instanceof IResource) {
1445 			IResource res = (IResource) retVal;
1446 			retVal = populateResourceMetadataHapi(resourceType, theEntity, myTagList, theForHistoryOperation, res);
1447 		} else {
1448 			IAnyResource res = (IAnyResource) retVal;
1449 			retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res);
1450 		}
1451 
1452 		return retVal;
1453 	}
1454 
1455 	@Override
1456 	public String toResourceName(Class<? extends IBaseResource> theResourceType) {
1457 		return myContext.getResourceDefinition(theResourceType).getName();
1458 	}
1459 
1460 	String toResourceName(IBaseResource theResource) {
1461 		return myContext.getResourceDefinition(theResource).getName();
1462 	}
1463 
1464 	private Slice<Long> toSlice(ResourceHistoryTable theVersion) {
1465 		Validate.notNull(theVersion);
1466 		return new SliceImpl<>(Collections.singletonList(theVersion.getId()));
1467 	}
1468 
1469 	@Override
1470 	public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
1471 		return translateForcedIdToPids(getConfig(), new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
1472 	}
1473 
1474 	protected List<Long> translateForcedIdToPids(IIdType theId) {
1475 		return translateForcedIdToPids(getConfig(), theId, myForcedIdDao);
1476 	}
1477 
1478 
1479 	@SuppressWarnings("unchecked")
1480 	protected ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable
1481 		theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
1482 													 boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
1483 		Validate.notNull(theEntity);
1484 		Validate.isTrue(theDeletedTimestampOrNull != null || theResource != null, "Must have either a resource[%s] or a deleted timestamp[%s] for resource PID[%s]", theDeletedTimestampOrNull != null, theResource != null, theEntity.getId());
1485 
1486 		ourLog.debug("Starting entity update");
1487 
1488 		/*
1489 		 * This should be the very first thing..
1490 		 */
1491 		if (theResource != null) {
1492 			if (thePerformIndexing) {
1493 				if (!ourValidationDisabledForUnitTest) {
1494 					validateResourceForStorage((T) theResource, theEntity);
1495 				}
1496 			}
1497 			String resourceType = myContext.getResourceDefinition(theResource).getName();
1498 			if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
1499 				throw new UnprocessableEntityException(
1500 					"Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
1501 			}
1502 		}
1503 
1504 		if (theEntity.getPublished() == null) {
1505 			ourLog.debug("Entity has published time: {}", new InstantDt(theUpdateTime));
1506 
1507 			theEntity.setPublished(theUpdateTime);
1508 		}
1509 
1510 		ResourceIndexedSearchParams existingParams = new ResourceIndexedSearchParams(this, theEntity);
1511 
1512 		ResourceIndexedSearchParams newParams = null;
1513 
1514 		EncodedResource changed;
1515 		if (theDeletedTimestampOrNull != null) {
1516 			
1517 			newParams = new ResourceIndexedSearchParams(this);
1518 
1519 			theEntity.setDeleted(theDeletedTimestampOrNull);
1520 			theEntity.setUpdated(theDeletedTimestampOrNull);
1521 			theEntity.setNarrativeTextParsedIntoWords(null);
1522 			theEntity.setContentTextParsedIntoWords(null);
1523 			theEntity.setHashSha256(null);
1524 			theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
1525 			changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
1526 
1527 		} else {
1528 
1529 			theEntity.setDeleted(null);
1530 
1531 			if (thePerformIndexing) {
1532 
1533 				newParams = new ResourceIndexedSearchParams(this, theUpdateTime, theEntity, theResource, existingParams);
1534 
1535 				changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
1536 
1537 				theEntity.setUpdated(theUpdateTime);
1538 				if (theResource instanceof IResource) {
1539 					theEntity.setLanguage(((IResource) theResource).getLanguage().getValue());
1540 				} else {
1541 					theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue());
1542 				}
1543 				
1544 				newParams.setParams(theEntity);
1545 				theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
1546 				populateFullTextFields(theResource, theEntity);
1547 			} else {
1548 
1549 				changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
1550 
1551 				theEntity.setUpdated(theUpdateTime);
1552 				// theEntity.setLanguage(theResource.getLanguage().getValue());
1553 				theEntity.setIndexStatus(null);
1554 
1555 			}
1556 
1557 		}
1558 
1559 		if (!changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange()) {
1560 			ourLog.debug("Resource {} has not changed", theEntity.getIdDt().toUnqualified().getValue());
1561 			if (theResource != null) {
1562 				populateResourceIdFromEntity(theEntity, theResource);
1563 			}
1564 			theEntity.setUnchangedInCurrentOperation(true);
1565 			return theEntity;
1566 		}
1567 
1568 		if (theUpdateVersion) {
1569 			theEntity.setVersion(theEntity.getVersion() + 1);
1570 		}
1571 
1572 		/*
1573 		 * Save the resource itself
1574 		 */
1575 		if (theEntity.getId() == null) {
1576 			myEntityManager.persist(theEntity);
1577 
1578 			if (theEntity.getForcedId() != null) {
1579 				myEntityManager.persist(theEntity.getForcedId());
1580 			}
1581 
1582 			postPersist(theEntity, (T) theResource);
1583 
1584 		} else if (theEntity.getDeleted() != null) {
1585 			theEntity = myEntityManager.merge(theEntity);
1586 
1587 			postDelete(theEntity);
1588 
1589 		} else {
1590 			theEntity = myEntityManager.merge(theEntity);
1591 
1592 			postUpdate(theEntity, (T) theResource);
1593 		}
1594 
1595 		/*
1596 		 * Create history entry
1597 		 */
1598 		if (theCreateNewHistoryEntry) {
1599 			final ResourceHistoryTable historyEntry = theEntity.toHistory();
1600 			historyEntry.setEncoding(changed.getEncoding());
1601 			historyEntry.setResource(changed.getResource());
1602 
1603 			ourLog.debug("Saving history entry {}", historyEntry.getIdDt());
1604 			myResourceHistoryTableDao.save(historyEntry);
1605 		}
1606 
1607 		/*
1608 		 * Update the "search param present" table which is used for the
1609 		 * ?foo:missing=true queries
1610 		 *
1611 		 * Note that we're only populating this for reference params
1612 		 * because the index tables for all other types have a MISSING column
1613 		 * right on them for handling the :missing queries. We can't use the
1614 		 * index table for resource links (reference indexes) because we index
1615 		 * those by path and not by parameter name.
1616 		 */
1617 		if (thePerformIndexing) {
1618 			Map<String, Boolean> presentSearchParams = new HashMap<>();
1619 			// TODO KHS null check?
1620 			for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
1621 				presentSearchParams.put(nextKey, Boolean.TRUE);
1622 			}
1623 			Set<Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
1624 			for (Entry<String, RuntimeSearchParam> nextSpEntry : activeSearchParams) {
1625 				if (nextSpEntry.getValue().getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
1626 					if (!presentSearchParams.containsKey(nextSpEntry.getKey())) {
1627 						presentSearchParams.put(nextSpEntry.getKey(), Boolean.FALSE);
1628 					}
1629 				}
1630 			}
1631 			mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams);
1632 		}
1633 
1634 		/*
1635 		 * Indexing
1636 		 */
1637 		if (thePerformIndexing) {
1638 			newParams.removeCommon(theEntity, existingParams);
1639 
1640 		} // if thePerformIndexing
1641 
1642 		if (theResource != null) {
1643 			populateResourceIdFromEntity(theEntity, theResource);
1644 		}
1645 
1646 
1647 		return theEntity;
1648 	}
1649 
1650 	protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable
1651 		entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
1652 		return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
1653 	}
1654 
1655 	public ResourceTable updateInternal(RequestDetails theRequest, T theResource, boolean thePerformIndexing,
1656 													boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType
1657 														theResourceId, IBaseResource theOldResource) {
1658 		// Notify interceptors
1659 		ActionRequestDetails requestDetails;
1660 		if (theRequestDetails != null) {
1661 			requestDetails = new ActionRequestDetails(theRequestDetails, theResource, theResourceId.getResourceType(), theResourceId);
1662 			notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
1663 		}
1664 
1665 		// Notify IServerOperationInterceptors about pre-action call
1666 		if (theRequestDetails != null) {
1667 			theRequestDetails.getRequestOperationCallback().resourcePreUpdate(theOldResource, theResource);
1668 		}
1669 		for (IServerInterceptor next : getConfig().getInterceptors()) {
1670 			if (next instanceof IServerOperationInterceptor) {
1671 				((IServerOperationInterceptor) next).resourcePreUpdate(theRequestDetails, theOldResource, theResource);
1672 			}
1673 		}
1674 
1675 		// Perform update
1676 		ResourceTable savedEntity = updateEntity(theRequest, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
1677 
1678 		/*
1679 		 * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
1680 		 * we'll manually increase the version. This is important because we want the updated version number
1681 		 * to be reflected in the resource shared with interceptors
1682 		 */
1683 		if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation() && !ourDisableIncrementOnUpdateForUnitTest) {
1684 			if (theResourceId.hasVersionIdPart() == false) {
1685 				theResourceId = theResourceId.withVersion(Long.toString(savedEntity.getVersion()));
1686 			}
1687 			incrementId(theResource, savedEntity, theResourceId);
1688 		}
1689 
1690 		// Notify interceptors
1691 		if (!savedEntity.isUnchangedInCurrentOperation()) {
1692 			if (theRequestDetails != null) {
1693 				theRequestDetails.getRequestOperationCallback().resourceUpdated(theResource);
1694 				theRequestDetails.getRequestOperationCallback().resourceUpdated(theOldResource, theResource);
1695 			}
1696 			for (IServerInterceptor next : getConfig().getInterceptors()) {
1697 				if (next instanceof IServerOperationInterceptor) {
1698 					((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theResource);
1699 					((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theOldResource, theResource);
1700 				}
1701 			}
1702 		}
1703 		return savedEntity;
1704 	}
1705 
1706 	private void validateChildReferences(IBase theElement, String thePath) {
1707 		if (theElement == null) {
1708 			return;
1709 		}
1710 		BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
1711 		if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
1712 			return;
1713 		}
1714 
1715 		BaseRuntimeElementCompositeDefinition<?> cdef = (BaseRuntimeElementCompositeDefinition<?>) def;
1716 		for (BaseRuntimeChildDefinition nextChildDef : cdef.getChildren()) {
1717 
1718 			List<IBase> values = nextChildDef.getAccessor().getValues(theElement);
1719 			if (values == null || values.isEmpty()) {
1720 				continue;
1721 			}
1722 
1723 			String newPath = thePath + "." + nextChildDef.getElementName();
1724 
1725 			for (IBase nextChild : values) {
1726 				validateChildReferences(nextChild, newPath);
1727 			}
1728 
1729 			if (nextChildDef instanceof RuntimeChildResourceDefinition) {
1730 				RuntimeChildResourceDefinition nextChildDefRes = (RuntimeChildResourceDefinition) nextChildDef;
1731 				Set<String> validTypes = new HashSet<>();
1732 				boolean allowAny = false;
1733 				for (Class<? extends IBaseResource> nextValidType : nextChildDefRes.getResourceTypes()) {
1734 					if (nextValidType.isInterface()) {
1735 						allowAny = true;
1736 						break;
1737 					}
1738 					validTypes.add(getContext().getResourceDefinition(nextValidType).getName());
1739 				}
1740 
1741 				if (allowAny) {
1742 					continue;
1743 				}
1744 
1745 				for (IBase nextChild : values) {
1746 					IBaseReference nextRef = (IBaseReference) nextChild;
1747 					IIdType referencedId = nextRef.getReferenceElement();
1748 					if (!isBlank(referencedId.getResourceType())) {
1749 						if (!isLogicalReference(referencedId)) {
1750 							if (!referencedId.getValue().contains("?")) {
1751 								if (!validTypes.contains(referencedId.getResourceType())) {
1752 									throw new UnprocessableEntityException(
1753 										"Invalid reference found at path '" + newPath + "'. Resource type '" + referencedId.getResourceType() + "' is not valid for this path");
1754 								}
1755 							}
1756 						}
1757 					}
1758 				}
1759 
1760 			}
1761 		}
1762 	}
1763 
1764 	public void validateDeleteConflictsEmptyOrThrowException(List<DeleteConflict> theDeleteConflicts) {
1765 		if (theDeleteConflicts.isEmpty()) {
1766 			return;
1767 		}
1768 
1769 		IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
1770 		String firstMsg = null;
1771 		for (DeleteConflict next : theDeleteConflicts) {
1772 			StringBuilder b = new StringBuilder();
1773 			b.append("Unable to delete ");
1774 			b.append(next.getTargetId().toUnqualifiedVersionless().getValue());
1775 			b.append(" because at least one resource has a reference to this resource. First reference found was resource ");
1776 			b.append(next.getTargetId().toUnqualifiedVersionless().getValue());
1777 			b.append(" in path ");
1778 			b.append(next.getSourcePath());
1779 			String msg = b.toString();
1780 			if (firstMsg == null) {
1781 				firstMsg = msg;
1782 			}
1783 			OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing");
1784 		}
1785 
1786 		throw new ResourceVersionConflictException(firstMsg, oo);
1787 	}
1788 
1789 	protected void validateMetaCount(int theMetaCount) {
1790 		if (myConfig.getResourceMetaCountHardLimit() != null) {
1791 			if (theMetaCount > myConfig.getResourceMetaCountHardLimit()) {
1792 				throw new UnprocessableEntityException("Resource contains " + theMetaCount + " meta entries (tag/profile/security label), maximum is " + myConfig.getResourceMetaCountHardLimit());
1793 			}
1794 		}
1795 	}
1796 
1797 	/**
1798 	 * 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
1799 	 * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
1800 	 *
1801 	 * @param theResource     The resource that is about to be persisted
1802 	 * @param theEntityToSave TODO
1803 	 */
1804 	protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
1805 		Object tag = null;
1806 
1807 		int totalMetaCount = 0;
1808 
1809 		if (theResource instanceof IResource) {
1810 			IResource res = (IResource) theResource;
1811 			TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
1812 			if (tagList != null) {
1813 				tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM_DSTU3, Constants.TAG_SUBSETTED_CODE);
1814 				totalMetaCount += tagList.size();
1815 			}
1816 			List<IdDt> profileList = ResourceMetadataKeyEnum.PROFILES.get(res);
1817 			if (profileList != null) {
1818 				totalMetaCount += profileList.size();
1819 			}
1820 		} else {
1821 			IAnyResource res = (IAnyResource) theResource;
1822 			tag = res.getMeta().getTag(Constants.TAG_SUBSETTED_SYSTEM_DSTU3, Constants.TAG_SUBSETTED_CODE);
1823 			totalMetaCount += res.getMeta().getTag().size();
1824 			totalMetaCount += res.getMeta().getProfile().size();
1825 			totalMetaCount += res.getMeta().getSecurity().size();
1826 		}
1827 
1828 		if (tag != null) {
1829 			throw new UnprocessableEntityException("Resource contains the 'subsetted' tag, and must not be stored as it may contain a subset of available data");
1830 		}
1831 
1832 		String resName = getContext().getResourceDefinition(theResource).getName();
1833 		validateChildReferences(theResource, resName);
1834 
1835 		validateMetaCount(totalMetaCount);
1836 
1837 	}
1838 
1839 	public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) {
1840 		String resourceText = null;
1841 		switch (theResourceEncoding) {
1842 			case JSON:
1843 				resourceText = new String(theResourceBytes, Charsets.UTF_8);
1844 				break;
1845 			case JSONC:
1846 				resourceText = GZipUtil.decompress(theResourceBytes);
1847 				break;
1848 			case DEL:
1849 				break;
1850 		}
1851 		return resourceText;
1852 	}
1853 
1854 	public static byte[] encodeResource(IBaseResource theResource, ResourceEncodingEnum theEncoding, Set<String> theExcludeElements, FhirContext theContext) {
1855 		byte[] bytes;
1856 		IParser parser = theEncoding.newParser(theContext);
1857 		parser.setDontEncodeElements(theExcludeElements);
1858 		String encoded = parser.encodeResourceToString(theResource);
1859 
1860 
1861 		switch (theEncoding) {
1862 			case JSON:
1863 				bytes = encoded.getBytes(Charsets.UTF_8);
1864 				break;
1865 			case JSONC:
1866 				bytes = GZipUtil.compress(encoded);
1867 				break;
1868 			default:
1869 			case DEL:
1870 				bytes = new byte[0];
1871 				break;
1872 		}
1873 
1874 		ourLog.debug("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
1875 		return bytes;
1876 	}
1877 
1878 	protected static boolean isValidPid(IIdType theId) {
1879 		if (theId == null || theId.getIdPart() == null) {
1880 			return false;
1881 		}
1882 		String idPart = theId.getIdPart();
1883 		for (int i = 0; i < idPart.length(); i++) {
1884 			char nextChar = idPart.charAt(i);
1885 			if (nextChar < '0' || nextChar > '9') {
1886 				return false;
1887 			}
1888 		}
1889 		return true;
1890 	}
1891 
1892 	@CoverageIgnore
1893 	protected static IQueryParameterAnd<?> newInstanceAnd(String chain) {
1894 		IQueryParameterAnd<?> type;
1895 		Class<? extends IQueryParameterAnd<?>> clazz = RESOURCE_META_AND_PARAMS.get(chain);
1896 		try {
1897 			type = clazz.newInstance();
1898 		} catch (Exception e) {
1899 			throw new InternalErrorException("Failure creating instance of " + clazz, e);
1900 		}
1901 		return type;
1902 	}
1903 
1904 	@CoverageIgnore
1905 	protected static IQueryParameterType newInstanceType(String chain) {
1906 		IQueryParameterType type;
1907 		Class<? extends IQueryParameterType> clazz = RESOURCE_META_PARAMS.get(chain);
1908 		try {
1909 			type = clazz.newInstance();
1910 		} catch (Exception e) {
1911 			throw new InternalErrorException("Failure creating instance of " + clazz, e);
1912 		}
1913 		return type;
1914 	}
1915 
1916 	public static String normalizeString(String theString) {
1917 		CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
1918 
1919 		/*
1920 		 * The following block of code is used to strip out diacritical marks from latin script
1921 		 * and also convert to upper case. E.g. "j?mes" becomes "JAMES".
1922 		 *
1923 		 * See http://www.unicode.org/charts/PDF/U0300.pdf for the logic
1924 		 * behind stripping 0300-036F
1925 		 *
1926 		 * See #454 for an issue where we were completely stripping non latin characters
1927 		 * See #832 for an issue where we normalize korean characters, which are decomposed
1928 		 */
1929 		String string = Normalizer.normalize(theString, Normalizer.Form.NFD);
1930 		for (int i = 0, n = string.length(); i < n; ++i) {
1931 			char c = string.charAt(i);
1932 			if (c >= '\u0300' && c <= '\u036F') {
1933 				continue;
1934 			} else {
1935 				outBuffer.append(c);
1936 			}
1937 		}
1938 
1939 		return new String(outBuffer.toCharArray()).toUpperCase();
1940 	}
1941 
1942 	private static String parseNarrativeTextIntoWords(IBaseResource theResource) {
1943 
1944 		StringBuilder b = new StringBuilder();
1945 		if (theResource instanceof IResource) {
1946 			IResource resource = (IResource) theResource;
1947 			List<XMLEvent> xmlEvents = XmlUtil.parse(resource.getText().getDiv().getValue());
1948 			if (xmlEvents != null) {
1949 				for (XMLEvent next : xmlEvents) {
1950 					if (next.isCharacters()) {
1951 						Characters characters = next.asCharacters();
1952 						b.append(characters.getData()).append(" ");
1953 					}
1954 				}
1955 			}
1956 		} else if (theResource instanceof IDomainResource) {
1957 			IDomainResource resource = (IDomainResource) theResource;
1958 			try {
1959 				String divAsString = resource.getText().getDivAsString();
1960 				List<XMLEvent> xmlEvents = XmlUtil.parse(divAsString);
1961 				if (xmlEvents != null) {
1962 					for (XMLEvent next : xmlEvents) {
1963 						if (next.isCharacters()) {
1964 							Characters characters = next.asCharacters();
1965 							b.append(characters.getData()).append(" ");
1966 						}
1967 					}
1968 				}
1969 			} catch (Exception e) {
1970 				throw new DataFormatException("Unable to convert DIV to string", e);
1971 			}
1972 
1973 		}
1974 		return b.toString();
1975 	}
1976 
1977 	@VisibleForTesting
1978 	public static void setDisableIncrementOnUpdateForUnitTest(boolean theDisableIncrementOnUpdateForUnitTest) {
1979 		ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
1980 	}
1981 
1982 	/**
1983 	 * Do not call this method outside of unit tests
1984 	 */
1985 	@VisibleForTesting
1986 	public static void setValidationDisabledForUnitTest(boolean theValidationDisabledForUnitTest) {
1987 		ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
1988 	}
1989 
1990 	private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
1991 		ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
1992 		for (IBaseCoding next : theSecurityLabels) {
1993 			retVal.add((BaseCodingDt) next);
1994 		}
1995 		return retVal;
1996 	}
1997 
1998 	protected static Long translateForcedIdToPid(DaoConfig theDaoConfig, String theResourceName, String theResourceId, IForcedIdDao
1999 		theForcedIdDao) {
2000 		return translateForcedIdToPids(theDaoConfig, new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
2001 	}
2002 
2003 	static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
2004 		Validate.isTrue(theId.hasIdPart());
2005 
2006 		if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
2007 			return Collections.singletonList(theId.getIdPartAsLong());
2008 		} else {
2009 			List<ForcedId> forcedId;
2010 			if (theId.hasResourceType()) {
2011 				forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
2012 			} else {
2013 				forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
2014 			}
2015 
2016 			if (forcedId.isEmpty() == false) {
2017 				List<Long> retVal = new ArrayList<>(forcedId.size());
2018 				for (ForcedId next : forcedId) {
2019 					retVal.add(next.getResourcePid());
2020 				}
2021 				return retVal;
2022 			} else {
2023 				throw new ResourceNotFoundException(theId);
2024 			}
2025 		}
2026 	}
2027 
2028 	public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String
2029 		theMatchUrl, RuntimeResourceDefinition resourceDef) {
2030 		SearchParameterMap paramMap = new SearchParameterMap();
2031 		List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
2032 
2033 		ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
2034 		for (NameValuePair next : parameters) {
2035 			if (isBlank(next.getValue())) {
2036 				continue;
2037 			}
2038 
2039 			String paramName = next.getName();
2040 			String qualifier = null;
2041 			for (int i = 0; i < paramName.length(); i++) {
2042 				switch (paramName.charAt(i)) {
2043 					case '.':
2044 					case ':':
2045 						qualifier = paramName.substring(i);
2046 						paramName = paramName.substring(0, i);
2047 						i = Integer.MAX_VALUE - 1;
2048 						break;
2049 				}
2050 			}
2051 
2052 			QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue());
2053 			nameToParamLists.put(paramName, paramList);
2054 		}
2055 
2056 		for (String nextParamName : nameToParamLists.keySet()) {
2057 			List<QualifiedParamList> paramList = nameToParamLists.get(nextParamName);
2058 			if (Constants.PARAM_LASTUPDATED.equals(nextParamName)) {
2059 				if (paramList != null && paramList.size() > 0) {
2060 					if (paramList.size() > 2) {
2061 						throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Can not have more than 2 " + Constants.PARAM_LASTUPDATED + " parameter repetitions");
2062 					} else {
2063 						DateRangeParam p1 = new DateRangeParam();
2064 						p1.setValuesAsQueryTokens(theContext, nextParamName, paramList);
2065 						paramMap.setLastUpdated(p1);
2066 					}
2067 				}
2068 				continue;
2069 			}
2070 
2071 			if (Constants.PARAM_HAS.equals(nextParamName)) {
2072 				IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, RestSearchParameterTypeEnum.HAS, nextParamName, paramList);
2073 				paramMap.add(nextParamName, param);
2074 				continue;
2075 			}
2076 
2077 			if (Constants.PARAM_COUNT.equals(nextParamName)) {
2078 				if (paramList.size() > 0 && paramList.get(0).size() > 0) {
2079 					String intString = paramList.get(0).get(0);
2080 					try {
2081 						paramMap.setCount(Integer.parseInt(intString));
2082 					} catch (NumberFormatException e) {
2083 						throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + intString);
2084 					}
2085 				}
2086 				continue;
2087 			}
2088 
2089 			if (RESOURCE_META_PARAMS.containsKey(nextParamName)) {
2090 				if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) {
2091 					throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier());
2092 				}
2093 				IQueryParameterAnd<?> type = newInstanceAnd(nextParamName);
2094 				type.setValuesAsQueryTokens(theContext, nextParamName, (paramList));
2095 				paramMap.add(nextParamName, type);
2096 			} else if (nextParamName.startsWith("_")) {
2097 				// ignore these since they aren't search params (e.g. _sort)
2098 			} else {
2099 				RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
2100 				if (paramDef == null) {
2101 					throw new InvalidRequestException(
2102 						"Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
2103 				}
2104 
2105 				IQueryParameterAnd<?> param = ParameterUtil.parseQueryParams(theContext, paramDef, nextParamName, paramList);
2106 				paramMap.add(nextParamName, param);
2107 			}
2108 		}
2109 		return paramMap;
2110 	}
2111 
2112 	public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
2113 		List<NameValuePair> parameters;
2114 		String matchUrl = theMatchUrl;
2115 		int questionMarkIndex = matchUrl.indexOf('?');
2116 		if (questionMarkIndex != -1) {
2117 			matchUrl = matchUrl.substring(questionMarkIndex + 1);
2118 		}
2119 		matchUrl = matchUrl.replace("|", "%7C");
2120 		matchUrl = matchUrl.replace("=>=", "=%3E%3D");
2121 		matchUrl = matchUrl.replace("=<=", "=%3C%3D");
2122 		matchUrl = matchUrl.replace("=>", "=%3E");
2123 		matchUrl = matchUrl.replace("=<", "=%3C");
2124 		if (matchUrl.contains(" ")) {
2125 			throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
2126 		}
2127 
2128 		parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
2129 		return parameters;
2130 	}
2131 
2132 	public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
2133 		if (!theResourceName.equals(theEntity.getResourceType())) {
2134 			throw new ResourceNotFoundException(
2135 				"Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
2136 		}
2137 	}
2138 
2139 	@Override
2140 	public ISearchParamExtractor getSearchParamExtractor() {
2141 		return mySearchParamExtractor;
2142 	}
2143 
2144 	@Override
2145 	public ISearchParamRegistry getSearchParamRegistry() {
2146 		return mySearchParamRegistry;
2147 	}
2148 
2149 	@Override
2150 	public EntityManager getEntityManager() {
2151 		return myEntityManager;
2152 	}
2153 
2154 	@Override
2155 	public Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> getResourceTypeToDao() {
2156 		return myResourceTypeToDao;
2157 	}
2158 
2159 	@Override
2160 	public IForcedIdDao getForcedIdDao() {
2161 		return myForcedIdDao;
2162 	}
2163 }