Change IG loader check for existence of SearchParameter resources to use same strategy as validator to avoid first one not finding resource and validator finding it later, producing a duplication exception. (#4164)
Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
4200ccf756
commit
0c5b64cce7
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 4162
|
||||
title: "Loading us-core IG was raising `UnprocessableEntityException: HAPI-2131: Can't process submitted SearchParameter as it is overlapping an existing one`.
|
||||
This problem has been fixed."
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.packages;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
|
@ -28,6 +27,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
|
@ -38,6 +38,7 @@ import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
|
|||
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistryController;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.SearchParameterHelper;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
|
@ -113,6 +114,9 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
private ISearchParamRegistryController mySearchParamRegistryController;
|
||||
@Autowired
|
||||
private PartitionSettings myPartitionSettings;
|
||||
@Autowired
|
||||
private SearchParameterHelper mySearchParameterHelper;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -492,6 +496,8 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
} else if (resource.getClass().getSimpleName().equals("Subscription")) {
|
||||
String id = extractIdFromSubscription(resource);
|
||||
return SearchParameterMap.newSynchronous().add("_id", new TokenParam(id));
|
||||
} else if (resource.getClass().getSimpleName().equals("SearchParameter")) {
|
||||
return buildSearchParameterMapForSearchParameter(resource);
|
||||
} else if (resourceHasUrlElement(resource)) {
|
||||
String url = extractUniqueUrlFromMetadataResource(resource);
|
||||
return SearchParameterMap.newSynchronous().add("url", new UriParam(url));
|
||||
|
@ -501,6 +507,30 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strategy is to build a SearchParameterMap same way the SearchParamValidatingInterceptor does, to make sure that
|
||||
* the loader search detects existing resources and routes process to 'update' path, to avoid treating it as a new
|
||||
* upload which validator later rejects as duplicated.
|
||||
* To achieve this, we try canonicalizing the SearchParameter first (as the validator does) and if that is not possible
|
||||
* we cascade to building the map from 'url' or 'identifier'.
|
||||
*/
|
||||
private SearchParameterMap buildSearchParameterMapForSearchParameter(IBaseResource theResource) {
|
||||
Optional<SearchParameterMap> spmFromCanonicalized = mySearchParameterHelper.buildSearchParameterMapFromCanonical(theResource);
|
||||
if (spmFromCanonicalized.isPresent()) {
|
||||
return spmFromCanonicalized.get();
|
||||
}
|
||||
|
||||
if (resourceHasUrlElement(theResource)) {
|
||||
String url = extractUniqueUrlFromMetadataResource(theResource);
|
||||
return SearchParameterMap.newSynchronous().add("url", new UriParam(url));
|
||||
} else {
|
||||
TokenParam identifierToken = extractIdentifierFromOtherResourceTypes(theResource);
|
||||
return SearchParameterMap.newSynchronous().add("identifier", identifierToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String extractUniqeIdFromNamingSystem(IBaseResource resource) {
|
||||
FhirTerser terser = myFhirContext.newTerser();
|
||||
IBase uniqueIdComponent = (IBase) terser.getSingleValueOrNull(resource, "uniqueId");
|
||||
|
|
|
@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.searchparam.matcher.IndexedSearchParamExtractor;
|
|||
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParameterCanonicalizer;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.SearchParameterHelper;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -131,4 +132,11 @@ public class SearchParamConfig {
|
|||
ResourceChangeListenerCache registeredResourceChangeListener(String theResourceName, IResourceChangeListener theResourceChangeListener, SearchParameterMap theSearchParameterMap, long theRemoteRefreshIntervalMs) {
|
||||
return new ResourceChangeListenerCache(theResourceName, theResourceChangeListener, theSearchParameterMap, theRemoteRefreshIntervalMs);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
SearchParameterHelper searchParameterHelper(FhirContext theFhirContext) {
|
||||
return new SearchParameterHelper(searchParameterCanonicalizer(theFhirContext));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.util;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParameterCanonicalizer;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParameterHelper {
|
||||
private final SearchParameterCanonicalizer mySearchParameterCanonicalizer;
|
||||
|
||||
public SearchParameterHelper(SearchParameterCanonicalizer theSearchParameterCanonicalizer) {
|
||||
mySearchParameterCanonicalizer = theSearchParameterCanonicalizer;
|
||||
}
|
||||
|
||||
|
||||
public Optional<SearchParameterMap> buildSearchParameterMapFromCanonical(IBaseResource theRuntimeSearchParam) {
|
||||
RuntimeSearchParam canonicalSearchParam = mySearchParameterCanonicalizer.canonicalizeSearchParameter(theRuntimeSearchParam);
|
||||
if (canonicalSearchParam == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
SearchParameterMap retVal = new SearchParameterMap();
|
||||
|
||||
String theCode = canonicalSearchParam.getName();
|
||||
List<String> theBases = List.copyOf(canonicalSearchParam.getBase());
|
||||
|
||||
TokenAndListParam codeParam = new TokenAndListParam().addAnd(new TokenParam(theCode));
|
||||
TokenAndListParam basesParam = toTokenAndList(theBases);
|
||||
|
||||
retVal.add("code", codeParam);
|
||||
retVal.add("base", basesParam);
|
||||
|
||||
return Optional.of(retVal);
|
||||
}
|
||||
|
||||
|
||||
private TokenAndListParam toTokenAndList(List<String> theBases) {
|
||||
TokenAndListParam retVal = new TokenAndListParam();
|
||||
|
||||
if (theBases != null) {
|
||||
|
||||
TokenOrListParam tokenOrListParam = new TokenOrListParam();
|
||||
retVal.addAnd(tokenOrListParam);
|
||||
|
||||
for (String next : theBases) {
|
||||
if (isNotBlank(next)) {
|
||||
tokenOrListParam.addOr(new TokenParam(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal.getValuesAsQueryTokens().isEmpty() ? null : retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.util;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParameterCanonicalizer;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SearchParameterHelperTest {
|
||||
|
||||
@Mock
|
||||
private SearchParameterCanonicalizer mockedCanonicalizer;
|
||||
@Mock
|
||||
private IBaseResource mockedSearchParam;
|
||||
@Mock
|
||||
private RuntimeSearchParam mockedRuntimeSearchParam;
|
||||
|
||||
private SearchParameterHelper myTestedHelper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
myTestedHelper = new SearchParameterHelper(mockedCanonicalizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenParamNonCanonicalizableReturnsEmpty() {
|
||||
when(mockedCanonicalizer.canonicalizeSearchParameter(mockedSearchParam)).thenReturn(null);
|
||||
|
||||
Optional<SearchParameterMap> result = myTestedHelper.buildSearchParameterMapFromCanonical(mockedSearchParam);
|
||||
|
||||
assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenParamCanonicalizableReturnsFromCanonical() {
|
||||
String codeParamValue = "code-param-value";
|
||||
String baseParamValue = "base-param-value";
|
||||
|
||||
when(mockedCanonicalizer.canonicalizeSearchParameter(mockedSearchParam)).thenReturn(mockedRuntimeSearchParam);
|
||||
when(mockedRuntimeSearchParam.getName()).thenReturn(codeParamValue);
|
||||
when(mockedRuntimeSearchParam.getBase()).thenReturn(Set.of(baseParamValue));
|
||||
|
||||
Optional<SearchParameterMap> result = myTestedHelper.buildSearchParameterMapFromCanonical(mockedSearchParam);
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
SearchParameterMap spMap = result.get();
|
||||
assertEquals(2, spMap.size());
|
||||
|
||||
List<List<IQueryParameterType>> codeParam = spMap.get("code");
|
||||
assertEquals(1, codeParam.size());
|
||||
assertEquals(1, codeParam.get(0).size());
|
||||
assertTrue(codeParam.get(0).get(0) instanceof TokenParam);
|
||||
TokenParam codeTokenParam = (TokenParam) codeParam.get(0).get(0);
|
||||
assertEquals(codeParamValue, codeTokenParam.getValue());
|
||||
|
||||
List<List<IQueryParameterType>> baseParam = spMap.get("base");
|
||||
assertEquals(1, baseParam.size());
|
||||
assertEquals(1, baseParam.get(0).size());
|
||||
assertTrue(baseParam.get(0).get(0) instanceof TokenParam);
|
||||
TokenParam baseTokenParam = (TokenParam) baseParam.get(0).get(0);
|
||||
assertEquals(baseParamValue, baseTokenParam.getValue());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue