Don't allow JPA server to save duplicate or empty tags, and fix #664 by
preventing multiple threads from loading structure definitions for validation at the same time
This commit is contained in:
parent
52a5fcce17
commit
7c6bb01a8b
|
@ -479,30 +479,36 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
|
||||
if (tagList != null) {
|
||||
for (Tag next : tagList) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
|
||||
if (securityLabels != null) {
|
||||
for (BaseCodingDt next : securityLabels) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
|
||||
if (profiles != null) {
|
||||
for (IIdType next : profiles) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -511,30 +517,36 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
|
||||
if (tagList != null) {
|
||||
for (IBaseCoding next : tagList) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
|
||||
if (securityLabels != null) {
|
||||
for (IBaseCoding next : securityLabels) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
|
||||
if (profiles != null) {
|
||||
for (IPrimitiveType<String> next : profiles) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -662,28 +674,28 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
|
||||
}
|
||||
|
||||
protected TagDefinition getTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||
protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||
if (isBlank(theScheme) && isBlank(theTerm) && isBlank(theLabel)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
|
||||
Root<TagDefinition> from = cq.from(TagDefinition.class);
|
||||
|
||||
//@formatter:off
|
||||
if (isNotBlank(theScheme)) {
|
||||
cq.where(
|
||||
builder.and(
|
||||
builder.equal(from.get("myTagType"), theTagType),
|
||||
builder.equal(from.get("mySystem"), theScheme),
|
||||
builder.equal(from.get("myCode"), theTerm))
|
||||
);
|
||||
builder.and(
|
||||
builder.equal(from.get("myTagType"), theTagType),
|
||||
builder.equal(from.get("mySystem"), theScheme),
|
||||
builder.equal(from.get("myCode"), theTerm)));
|
||||
} else {
|
||||
cq.where(
|
||||
builder.and(
|
||||
builder.equal(from.get("myTagType"), theTagType),
|
||||
builder.isNull(from.get("mySystem")),
|
||||
builder.equal(from.get("myCode"), theTerm))
|
||||
);
|
||||
builder.and(
|
||||
builder.equal(from.get("myTagType"), theTagType),
|
||||
builder.isNull(from.get("mySystem")),
|
||||
builder.equal(from.get("myCode"), theTerm)));
|
||||
}
|
||||
//@formatter:on
|
||||
|
||||
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
|
||||
try {
|
||||
|
@ -938,10 +950,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
if (def.isStandardType() == false) {
|
||||
String profile = def.getResourceProfile("");
|
||||
if (isNotBlank(profile)) {
|
||||
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
|
||||
if (tag != null) {
|
||||
allDefs.add(tag);
|
||||
theEntity.addTag(tag);
|
||||
theEntity.setHasTags(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,11 +114,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
entity.setHasTags(true);
|
||||
|
||||
TagDefinition def = getTag(TagTypeEnum.TAG, theScheme, theTerm, theLabel);
|
||||
BaseTag newEntity = entity.addTag(def);
|
||||
TagDefinition def = getTagOrNull(TagTypeEnum.TAG, theScheme, theTerm, theLabel);
|
||||
if (def != null) {
|
||||
BaseTag newEntity = entity.addTag(def);
|
||||
|
||||
myEntityManager.persist(newEntity);
|
||||
myEntityManager.merge(entity);
|
||||
myEntityManager.persist(newEntity);
|
||||
myEntityManager.merge(entity);
|
||||
}
|
||||
|
||||
ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() });
|
||||
}
|
||||
|
@ -439,9 +441,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
if (!hasTag) {
|
||||
entity.setHasTags(true);
|
||||
|
||||
TagDefinition def = getTag(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
|
||||
BaseTag newEntity = entity.addTag(def);
|
||||
myEntityManager.persist(newEntity);
|
||||
TagDefinition def = getTagOrNull(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
|
||||
if (def != null) {
|
||||
BaseTag newEntity = entity.addTag(def);
|
||||
myEntityManager.persist(newEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
//@formatter:on
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
||||
|
||||
public interface IResourceTagDao extends JpaRepository<ResourceTag, Long> {
|
||||
// nothing
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||
|
||||
public interface ITagDefinitionDao extends JpaRepository<TagDefinition, Long> {
|
||||
// nothing
|
||||
}
|
|
@ -77,8 +77,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
|||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
|
@ -105,6 +104,10 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
|
||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||
|
||||
@Autowired
|
||||
protected ITagDefinitionDao myTagDefinitionDao;
|
||||
@Autowired
|
||||
protected IResourceTagDao myResourceTagDao;
|
||||
@Autowired
|
||||
protected ISearchDao mySearchEntityDao;
|
||||
@Autowired
|
||||
|
|
|
@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.empty;
|
|||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -154,6 +155,56 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDuplicateTagsDoesNotCauseDuplicates() {
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
p.getMeta().addTag().setSystem("FOO").setCode("BAR");
|
||||
|
||||
myPatientDao.create(p);
|
||||
|
||||
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
|
||||
assertThat(myResourceTagDao.findAll(), hasSize(1));
|
||||
assertThat(myTagDefinitionDao.findAll(), hasSize(1));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEmptyTagsIsIgnored() {
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
|
||||
// Add an empty tag
|
||||
p.getMeta().addTag();
|
||||
|
||||
// Add another empty tag
|
||||
p.getMeta().addTag().setSystem("");
|
||||
p.getMeta().addTag().setCode("");
|
||||
p.getMeta().addTag().setDisplay("");
|
||||
|
||||
myPatientDao.create(p);
|
||||
|
||||
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
|
||||
assertThat(myResourceTagDao.findAll(), empty());
|
||||
assertThat(myTagDefinitionDao.findAll(), empty());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called from assertGone too! Careful about exceptions...
|
||||
*/
|
||||
|
|
|
@ -22,243 +22,245 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
|
||||
public class DefaultProfileValidationSupport implements IValidationSupport {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
|
||||
|
||||
private Map<String, CodeSystem> myCodeSystems;
|
||||
private Map<String, StructureDefinition> myStructureDefinitions;
|
||||
private Map<String, ValueSet> myValueSets;
|
||||
private Map<String, CodeSystem> myCodeSystems;
|
||||
private Map<String, StructureDefinition> myStructureDefinitions;
|
||||
private Map<String, ValueSet> myValueSets;
|
||||
|
||||
@Override
|
||||
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
|
||||
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
|
||||
@Override
|
||||
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
|
||||
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
|
||||
|
||||
Set<String> wantCodes = new HashSet<String>();
|
||||
for (ConceptReferenceComponent next : theInclude.getConcept()) {
|
||||
wantCodes.add(next.getCode());
|
||||
}
|
||||
Set<String> wantCodes = new HashSet<String>();
|
||||
for (ConceptReferenceComponent next : theInclude.getConcept()) {
|
||||
wantCodes.add(next.getCode());
|
||||
}
|
||||
|
||||
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
|
||||
for (ConceptDefinitionComponent next : system.getConcept()) {
|
||||
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) {
|
||||
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
||||
}
|
||||
}
|
||||
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
|
||||
for (ConceptDefinitionComponent next : system.getConcept()) {
|
||||
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) {
|
||||
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
|
||||
return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values());
|
||||
}
|
||||
@Override
|
||||
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
|
||||
return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
|
||||
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
|
||||
}
|
||||
@Override
|
||||
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
|
||||
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
|
||||
}
|
||||
|
||||
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
|
||||
Map<String, CodeSystem> codeSystems = myCodeSystems;
|
||||
Map<String, ValueSet> valueSets = myValueSets;
|
||||
if (codeSystems == null) {
|
||||
codeSystems = new HashMap<String, CodeSystem>();
|
||||
valueSets = new HashMap<String, ValueSet>();
|
||||
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
|
||||
synchronized (this) {
|
||||
Map<String, CodeSystem> codeSystems = myCodeSystems;
|
||||
Map<String, ValueSet> valueSets = myValueSets;
|
||||
if (codeSystems == null || valueSets == null) {
|
||||
codeSystems = new HashMap<String, CodeSystem>();
|
||||
valueSets = new HashMap<String, ValueSet>();
|
||||
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/valuesets.xml");
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/v2-tables.xml");
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/v3-codesystems.xml");
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/valuesets.xml");
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/v2-tables.xml");
|
||||
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/instance/model/dstu3/valueset/v3-codesystems.xml");
|
||||
|
||||
myCodeSystems = codeSystems;
|
||||
myValueSets = valueSets;
|
||||
}
|
||||
myCodeSystems = codeSystems;
|
||||
myValueSets = valueSets;
|
||||
}
|
||||
|
||||
if (codeSystem) {
|
||||
return codeSystems.get(theSystem);
|
||||
} else {
|
||||
return valueSets.get(theSystem);
|
||||
}
|
||||
}
|
||||
if (codeSystem) {
|
||||
return codeSystems.get(theSystem);
|
||||
} else {
|
||||
return valueSets.get(theSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
|
||||
Validate.notBlank(theUri, "theUri must not be null or blank");
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
|
||||
Validate.notBlank(theUri, "theUri must not be null or blank");
|
||||
|
||||
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
|
||||
return (T) fetchStructureDefinition(theContext, theUri);
|
||||
}
|
||||
if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
return (T) fetchValueSet(theContext, theUri);
|
||||
}
|
||||
// if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
// Map<String, ValueSet> defaultValueSets = myDefaultValueSets;
|
||||
// if (defaultValueSets == null) {
|
||||
// String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml";
|
||||
// InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path);
|
||||
// if (valuesetText == null) {
|
||||
// return null;
|
||||
// }
|
||||
// InputStreamReader reader;
|
||||
// try {
|
||||
// reader = new InputStreamReader(valuesetText, "UTF-8");
|
||||
// } catch (UnsupportedEncodingException e) {
|
||||
// // Shouldn't happen!
|
||||
// throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
|
||||
// }
|
||||
//
|
||||
// defaultValueSets = new HashMap<String, ValueSet>();
|
||||
//
|
||||
// Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
// for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
// IdType nextId = new IdType(next.getFullUrl());
|
||||
// if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
// continue;
|
||||
// }
|
||||
// defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource());
|
||||
// }
|
||||
//
|
||||
// myDefaultValueSets = defaultValueSets;
|
||||
// }
|
||||
//
|
||||
// return (T) defaultValueSets.get(theUri);
|
||||
// }
|
||||
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
|
||||
return (T) fetchStructureDefinition(theContext, theUri);
|
||||
}
|
||||
if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
return (T) fetchValueSet(theContext, theUri);
|
||||
}
|
||||
// if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
// Map<String, ValueSet> defaultValueSets = myDefaultValueSets;
|
||||
// if (defaultValueSets == null) {
|
||||
// String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml";
|
||||
// InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path);
|
||||
// if (valuesetText == null) {
|
||||
// return null;
|
||||
// }
|
||||
// InputStreamReader reader;
|
||||
// try {
|
||||
// reader = new InputStreamReader(valuesetText, "UTF-8");
|
||||
// } catch (UnsupportedEncodingException e) {
|
||||
// // Shouldn't happen!
|
||||
// throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
|
||||
// }
|
||||
//
|
||||
// defaultValueSets = new HashMap<String, ValueSet>();
|
||||
//
|
||||
// Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
// for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
// IdType nextId = new IdType(next.getFullUrl());
|
||||
// if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) {
|
||||
// continue;
|
||||
// }
|
||||
// defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource());
|
||||
// }
|
||||
//
|
||||
// myDefaultValueSets = defaultValueSets;
|
||||
// }
|
||||
//
|
||||
// return (T) defaultValueSets.get(theUri);
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
|
||||
return provideStructureDefinitionMap(theContext).get(theUrl);
|
||||
}
|
||||
@Override
|
||||
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
|
||||
return provideStructureDefinitionMap(theContext).get(theUrl);
|
||||
}
|
||||
|
||||
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
|
||||
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
|
||||
}
|
||||
ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
|
||||
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
myCodeSystems = null;
|
||||
myStructureDefinitions = null;
|
||||
}
|
||||
public void flush() {
|
||||
myCodeSystems = null;
|
||||
myStructureDefinitions = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
|
||||
CodeSystem cs = fetchCodeSystem(theContext, theSystem);
|
||||
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
|
||||
}
|
||||
@Override
|
||||
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
|
||||
CodeSystem cs = fetchCodeSystem(theContext, theSystem);
|
||||
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
|
||||
}
|
||||
|
||||
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
|
||||
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
|
||||
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
|
||||
if (valuesetText != null) {
|
||||
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
|
||||
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
|
||||
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
|
||||
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
|
||||
if (valuesetText != null) {
|
||||
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
|
||||
|
||||
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource() instanceof CodeSystem) {
|
||||
CodeSystem nextValueSet = (CodeSystem) next.getResource();
|
||||
nextValueSet.getText().setDivAsString("");
|
||||
String system = nextValueSet.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theCodeSystems.put(system, nextValueSet);
|
||||
}
|
||||
} else if (next.getResource() instanceof ValueSet) {
|
||||
ValueSet nextValueSet = (ValueSet) next.getResource();
|
||||
nextValueSet.getText().setDivAsString("");
|
||||
String system = nextValueSet.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theValueSets.put(system, nextValueSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ourLog.warn("Unable to load resource: {}", theClasspath);
|
||||
}
|
||||
}
|
||||
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource() instanceof CodeSystem) {
|
||||
CodeSystem nextValueSet = (CodeSystem) next.getResource();
|
||||
nextValueSet.getText().setDivAsString("");
|
||||
String system = nextValueSet.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theCodeSystems.put(system, nextValueSet);
|
||||
}
|
||||
} else if (next.getResource() instanceof ValueSet) {
|
||||
ValueSet nextValueSet = (ValueSet) next.getResource();
|
||||
nextValueSet.getText().setDivAsString("");
|
||||
String system = nextValueSet.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theValueSets.put(system, nextValueSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ourLog.warn("Unable to load resource: {}", theClasspath);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
|
||||
ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
|
||||
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
|
||||
if (valuesetText != null) {
|
||||
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
|
||||
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
|
||||
ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
|
||||
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
|
||||
if (valuesetText != null) {
|
||||
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
|
||||
|
||||
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource() instanceof StructureDefinition) {
|
||||
StructureDefinition nextSd = (StructureDefinition) next.getResource();
|
||||
nextSd.getText().setDivAsString("");
|
||||
String system = nextSd.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theCodeSystems.put(system, nextSd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ourLog.warn("Unable to load resource: {}", theClasspath);
|
||||
}
|
||||
}
|
||||
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
|
||||
for (BundleEntryComponent next : bundle.getEntry()) {
|
||||
if (next.getResource() instanceof StructureDefinition) {
|
||||
StructureDefinition nextSd = (StructureDefinition) next.getResource();
|
||||
nextSd.getText().setDivAsString("");
|
||||
String system = nextSd.getUrl();
|
||||
if (isNotBlank(system)) {
|
||||
theCodeSystems.put(system, nextSd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ourLog.warn("Unable to load resource: {}", theClasspath);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
|
||||
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
|
||||
if (structureDefinitions == null) {
|
||||
structureDefinitions = new HashMap<String, StructureDefinition>();
|
||||
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
|
||||
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
|
||||
if (structureDefinitions == null) {
|
||||
structureDefinitions = new HashMap<String, StructureDefinition>();
|
||||
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-resources.xml");
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-types.xml");
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-others.xml");
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-resources.xml");
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-types.xml");
|
||||
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/instance/model/dstu3/profile/profiles-others.xml");
|
||||
|
||||
myStructureDefinitions = structureDefinitions;
|
||||
}
|
||||
return structureDefinitions;
|
||||
}
|
||||
myStructureDefinitions = structureDefinitions;
|
||||
}
|
||||
return structureDefinitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
|
||||
if (cs != null) {
|
||||
boolean caseSensitive = true;
|
||||
if (cs.hasCaseSensitive()) {
|
||||
caseSensitive = cs.getCaseSensitive();
|
||||
}
|
||||
@Override
|
||||
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
|
||||
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
|
||||
if (cs != null) {
|
||||
boolean caseSensitive = true;
|
||||
if (cs.hasCaseSensitive()) {
|
||||
caseSensitive = cs.getCaseSensitive();
|
||||
}
|
||||
|
||||
CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive);
|
||||
CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive);
|
||||
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
if (retVal != null) {
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode);
|
||||
}
|
||||
return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode);
|
||||
}
|
||||
|
||||
private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
|
||||
String code = theCode;
|
||||
if (theCaseSensitive == false) {
|
||||
code = code.toUpperCase();
|
||||
}
|
||||
private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
|
||||
String code = theCode;
|
||||
if (theCaseSensitive == false) {
|
||||
code = code.toUpperCase();
|
||||
}
|
||||
|
||||
return testIfConceptIsInListInner(conceptList, theCaseSensitive, code);
|
||||
}
|
||||
return testIfConceptIsInListInner(conceptList, theCaseSensitive, code);
|
||||
}
|
||||
|
||||
private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
|
||||
CodeValidationResult retVal = null;
|
||||
for (ConceptDefinitionComponent next : conceptList) {
|
||||
String nextCandidate = next.getCode();
|
||||
if (theCaseSensitive == false) {
|
||||
nextCandidate = nextCandidate.toUpperCase();
|
||||
}
|
||||
if (nextCandidate.equals(code)) {
|
||||
retVal = new CodeValidationResult(next);
|
||||
break;
|
||||
}
|
||||
private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
|
||||
CodeValidationResult retVal = null;
|
||||
for (ConceptDefinitionComponent next : conceptList) {
|
||||
String nextCandidate = next.getCode();
|
||||
if (theCaseSensitive == false) {
|
||||
nextCandidate = nextCandidate.toUpperCase();
|
||||
}
|
||||
if (nextCandidate.equals(code)) {
|
||||
retVal = new CodeValidationResult(next);
|
||||
break;
|
||||
}
|
||||
|
||||
// recurse
|
||||
retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive);
|
||||
if (retVal != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// recurse
|
||||
retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive);
|
||||
if (retVal != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
|
|||
myCodeSystems = null;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
|
||||
|
@ -93,18 +92,20 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
|
|||
|
||||
@Override
|
||||
public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) {
|
||||
Map<String, ValueSet> codeSystems = myCodeSystems;
|
||||
if (codeSystems == null) {
|
||||
codeSystems = new HashMap<String, ValueSet>();
|
||||
synchronized (this) {
|
||||
Map<String, ValueSet> valueSets = myCodeSystems;
|
||||
if (valueSets == null) {
|
||||
valueSets = new HashMap<String, ValueSet>();
|
||||
|
||||
loadCodeSystems(theContext, codeSystems, "/org/hl7/fhir/instance/model/valueset/valuesets.xml");
|
||||
loadCodeSystems(theContext, codeSystems, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml");
|
||||
loadCodeSystems(theContext, codeSystems, "/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml");
|
||||
loadCodeSystems(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/valuesets.xml");
|
||||
loadCodeSystems(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml");
|
||||
loadCodeSystems(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml");
|
||||
|
||||
myCodeSystems = codeSystems;
|
||||
myCodeSystems = valueSets;
|
||||
}
|
||||
|
||||
return valueSets.get(theSystem);
|
||||
}
|
||||
|
||||
return codeSystems.get(theSystem);
|
||||
}
|
||||
|
||||
private void loadCodeSystems(FhirContext theContext, Map<String, ValueSet> codeSystems, String file) {
|
||||
|
|
|
@ -149,6 +149,19 @@
|
|||
<action type="fix">
|
||||
Validator incorrectly rejected references where only an identifier was populated
|
||||
</action>
|
||||
<action type="add">
|
||||
Add a check in JPA server that prevents completely blank tags, profiles, and security labels
|
||||
from being saved to the database. These were filtered out anyhow when the
|
||||
result was returned back to the client but they were persisted which
|
||||
just wasted space.
|
||||
</action>
|
||||
<action type="fix" issue="664">
|
||||
Loading the build-in profile structures (StructureDefinition, ValueSet, etc) is now done in
|
||||
a synchronized block in order to prevent multiple loads happening if the server processes
|
||||
multiple validations in parallel threads right after startup. Previously a heavy load could
|
||||
cause the server to run out of memory and lock up. Thanks to Karl M Davis
|
||||
for analysis and help fixing this!
|
||||
</action>
|
||||
</release>
|
||||
<release version="2.4" date="2017-04-19">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue