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:
James Agnew 2017-06-06 16:40:50 -04:00
parent 52a5fcce17
commit 7c6bb01a8b
9 changed files with 420 additions and 274 deletions

View File

@ -479,30 +479,36 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tagList != null) { if (tagList != null) {
for (Tag next : tagList) { for (Tag next : tagList) {
TagDefinition tag = getTag(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel()); TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getScheme(), next.getTerm(), next.getLabel());
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); theEntity.addTag(tag);
theEntity.setHasTags(true);
}
} }
} }
List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource); List<BaseCodingDt> securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(theResource);
if (securityLabels != null) { if (securityLabels != null) {
for (BaseCodingDt next : securityLabels) { for (BaseCodingDt next : securityLabels) {
TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue()); TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystemElement().getValue(), next.getCodeElement().getValue(), next.getDisplayElement().getValue());
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); theEntity.addTag(tag);
theEntity.setHasTags(true);
}
} }
} }
List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource); List<IdDt> profiles = ResourceMetadataKeyEnum.PROFILES.get(theResource);
if (profiles != null) { if (profiles != null) {
for (IIdType next : profiles) { for (IIdType next : profiles) {
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); 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(); List<? extends IBaseCoding> tagList = theResource.getMeta().getTag();
if (tagList != null) { if (tagList != null) {
for (IBaseCoding next : tagList) { for (IBaseCoding next : tagList) {
TagDefinition tag = getTag(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay()); TagDefinition tag = getTagOrNull(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay());
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); theEntity.addTag(tag);
theEntity.setHasTags(true);
}
} }
} }
List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity(); List<? extends IBaseCoding> securityLabels = theResource.getMeta().getSecurity();
if (securityLabels != null) { if (securityLabels != null) {
for (IBaseCoding next : securityLabels) { for (IBaseCoding next : securityLabels) {
TagDefinition tag = getTag(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay()); TagDefinition tag = getTagOrNull(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay());
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); theEntity.addTag(tag);
theEntity.setHasTags(true);
}
} }
} }
List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile(); List<? extends IPrimitiveType<String>> profiles = theResource.getMeta().getProfile();
if (profiles != null) { if (profiles != null) {
for (IPrimitiveType<String> next : profiles) { for (IPrimitiveType<String> next : profiles) {
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null); TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, next.getValue(), null);
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); 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(); 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(); CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class); CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
Root<TagDefinition> from = cq.from(TagDefinition.class); Root<TagDefinition> from = cq.from(TagDefinition.class);
//@formatter:off
if (isNotBlank(theScheme)) { if (isNotBlank(theScheme)) {
cq.where( cq.where(
builder.and( builder.and(
builder.equal(from.get("myTagType"), theTagType), builder.equal(from.get("myTagType"), theTagType),
builder.equal(from.get("mySystem"), theScheme), builder.equal(from.get("mySystem"), theScheme),
builder.equal(from.get("myCode"), theTerm)) builder.equal(from.get("myCode"), theTerm)));
);
} else { } else {
cq.where( cq.where(
builder.and( builder.and(
builder.equal(from.get("myTagType"), theTagType), builder.equal(from.get("myTagType"), theTagType),
builder.isNull(from.get("mySystem")), builder.isNull(from.get("mySystem")),
builder.equal(from.get("myCode"), theTerm)) builder.equal(from.get("myCode"), theTerm)));
);
} }
//@formatter:on
TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq); TypedQuery<TagDefinition> q = myEntityManager.createQuery(cq);
try { try {
@ -938,10 +950,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
if (def.isStandardType() == false) { if (def.isStandardType() == false) {
String profile = def.getResourceProfile(""); String profile = def.getResourceProfile("");
if (isNotBlank(profile)) { if (isNotBlank(profile)) {
TagDefinition tag = getTag(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null); TagDefinition tag = getTagOrNull(TagTypeEnum.PROFILE, NS_JPA_PROFILE, profile, null);
allDefs.add(tag); if (tag != null) {
theEntity.addTag(tag); allDefs.add(tag);
theEntity.setHasTags(true); theEntity.addTag(tag);
theEntity.setHasTags(true);
}
} }
} }

View File

@ -114,11 +114,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setHasTags(true); entity.setHasTags(true);
TagDefinition def = getTag(TagTypeEnum.TAG, theScheme, theTerm, theLabel); TagDefinition def = getTagOrNull(TagTypeEnum.TAG, theScheme, theTerm, theLabel);
BaseTag newEntity = entity.addTag(def); if (def != null) {
BaseTag newEntity = entity.addTag(def);
myEntityManager.persist(newEntity); myEntityManager.persist(newEntity);
myEntityManager.merge(entity); myEntityManager.merge(entity);
}
ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); 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) { if (!hasTag) {
entity.setHasTags(true); entity.setHasTags(true);
TagDefinition def = getTag(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay()); TagDefinition def = getTagOrNull(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
BaseTag newEntity = entity.addTag(def); if (def != null) {
myEntityManager.persist(newEntity); BaseTag newEntity = entity.addTag(def);
myEntityManager.persist(newEntity);
}
} }
} }
//@formatter:on //@formatter:on

View File

@ -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
}

View File

@ -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
}

View File

@ -77,8 +77,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
@ -105,6 +104,10 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao; private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
@Autowired
protected ITagDefinitionDao myTagDefinitionDao;
@Autowired
protected IResourceTagDao myResourceTagDao;
@Autowired @Autowired
protected ISearchDao mySearchEntityDao; protected ISearchDao mySearchEntityDao;
@Autowired @Autowired

View File

@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; 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... * This gets called from assertGone too! Careful about exceptions...
*/ */

View File

@ -22,243 +22,245 @@ import ca.uhn.fhir.context.FhirContext;
public class DefaultProfileValidationSupport implements IValidationSupport { 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, CodeSystem> myCodeSystems;
private Map<String, StructureDefinition> myStructureDefinitions; private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets; private Map<String, ValueSet> myValueSets;
@Override @Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
Set<String> wantCodes = new HashSet<String>(); Set<String> wantCodes = new HashSet<String>();
for (ConceptReferenceComponent next : theInclude.getConcept()) { for (ConceptReferenceComponent next : theInclude.getConcept()) {
wantCodes.add(next.getCode()); wantCodes.add(next.getCode());
} }
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
for (ConceptDefinitionComponent next : system.getConcept()) { for (ConceptDefinitionComponent next : system.getConcept()) {
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) { if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) {
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
} }
} }
return retVal; return retVal;
} }
@Override @Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) { public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values()); return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values());
} }
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
} }
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
Map<String, CodeSystem> codeSystems = myCodeSystems; synchronized (this) {
Map<String, ValueSet> valueSets = myValueSets; Map<String, CodeSystem> codeSystems = myCodeSystems;
if (codeSystems == null) { Map<String, ValueSet> valueSets = myValueSets;
codeSystems = new HashMap<String, CodeSystem>(); if (codeSystems == null || valueSets == null) {
valueSets = new HashMap<String, ValueSet>(); 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/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/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/v3-codesystems.xml");
myCodeSystems = codeSystems; myCodeSystems = codeSystems;
myValueSets = valueSets; myValueSets = valueSets;
} }
if (codeSystem) { if (codeSystem) {
return codeSystems.get(theSystem); return codeSystems.get(theSystem);
} else { } else {
return valueSets.get(theSystem); return valueSets.get(theSystem);
} }
} }
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank"); Validate.notBlank(theUri, "theUri must not be null or blank");
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return (T) fetchStructureDefinition(theContext, theUri); return (T) fetchStructureDefinition(theContext, theUri);
} }
if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) { if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
return (T) fetchValueSet(theContext, theUri); return (T) fetchValueSet(theContext, theUri);
} }
// if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) { // if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) {
// Map<String, ValueSet> defaultValueSets = myDefaultValueSets; // Map<String, ValueSet> defaultValueSets = myDefaultValueSets;
// if (defaultValueSets == null) { // if (defaultValueSets == null) {
// String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml"; // String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml";
// InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path); // InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path);
// if (valuesetText == null) { // if (valuesetText == null) {
// return null; // return null;
// } // }
// InputStreamReader reader; // InputStreamReader reader;
// try { // try {
// reader = new InputStreamReader(valuesetText, "UTF-8"); // reader = new InputStreamReader(valuesetText, "UTF-8");
// } catch (UnsupportedEncodingException e) { // } catch (UnsupportedEncodingException e) {
// // Shouldn't happen! // // Shouldn't happen!
// throw new InternalErrorException("UTF-8 encoding not supported on this platform", e); // throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
// } // }
// //
// defaultValueSets = new HashMap<String, ValueSet>(); // defaultValueSets = new HashMap<String, ValueSet>();
// //
// Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); // Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
// for (BundleEntryComponent next : bundle.getEntry()) { // for (BundleEntryComponent next : bundle.getEntry()) {
// IdType nextId = new IdType(next.getFullUrl()); // IdType nextId = new IdType(next.getFullUrl());
// if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) { // if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) {
// continue; // continue;
// } // }
// defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource()); // defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource());
// } // }
// //
// myDefaultValueSets = defaultValueSets; // myDefaultValueSets = defaultValueSets;
// } // }
// //
// return (T) defaultValueSets.get(theUri); // return (T) defaultValueSets.get(theUri);
// } // }
return null; return null;
} }
@Override @Override
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
return provideStructureDefinitionMap(theContext).get(theUrl); return provideStructureDefinitionMap(theContext).get(theUrl);
} }
ValueSet fetchValueSet(FhirContext theContext, String theSystem) { ValueSet fetchValueSet(FhirContext theContext, String theSystem) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false);
} }
public void flush() { public void flush() {
myCodeSystems = null; myCodeSystems = null;
myStructureDefinitions = null; myStructureDefinitions = null;
} }
@Override @Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
CodeSystem cs = fetchCodeSystem(theContext, theSystem); CodeSystem cs = fetchCodeSystem(theContext, theSystem);
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
} }
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) { private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
if (valuesetText != null) { if (valuesetText != null) {
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) { for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof CodeSystem) { if (next.getResource() instanceof CodeSystem) {
CodeSystem nextValueSet = (CodeSystem) next.getResource(); CodeSystem nextValueSet = (CodeSystem) next.getResource();
nextValueSet.getText().setDivAsString(""); nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl(); String system = nextValueSet.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theCodeSystems.put(system, nextValueSet); theCodeSystems.put(system, nextValueSet);
} }
} else if (next.getResource() instanceof ValueSet) { } else if (next.getResource() instanceof ValueSet) {
ValueSet nextValueSet = (ValueSet) next.getResource(); ValueSet nextValueSet = (ValueSet) next.getResource();
nextValueSet.getText().setDivAsString(""); nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl(); String system = nextValueSet.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theValueSets.put(system, nextValueSet); theValueSets.put(system, nextValueSet);
} }
} }
} }
} else { } else {
ourLog.warn("Unable to load resource: {}", theClasspath); ourLog.warn("Unable to load resource: {}", theClasspath);
} }
} }
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) { private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
ourLog.info("Loading structure definitions from classpath: {}", theClasspath); ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
if (valuesetText != null) { if (valuesetText != null) {
InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) { for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof StructureDefinition) { if (next.getResource() instanceof StructureDefinition) {
StructureDefinition nextSd = (StructureDefinition) next.getResource(); StructureDefinition nextSd = (StructureDefinition) next.getResource();
nextSd.getText().setDivAsString(""); nextSd.getText().setDivAsString("");
String system = nextSd.getUrl(); String system = nextSd.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theCodeSystems.put(system, nextSd); theCodeSystems.put(system, nextSd);
} }
} }
} }
} else { } else {
ourLog.warn("Unable to load resource: {}", theClasspath); ourLog.warn("Unable to load resource: {}", theClasspath);
} }
} }
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) { private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions; Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
if (structureDefinitions == null) { if (structureDefinitions == null) {
structureDefinitions = new HashMap<String, StructureDefinition>(); 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-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-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-others.xml");
myStructureDefinitions = structureDefinitions; myStructureDefinitions = structureDefinitions;
} }
return structureDefinitions; return structureDefinitions;
} }
@Override @Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) { if (cs != null) {
boolean caseSensitive = true; boolean caseSensitive = true;
if (cs.hasCaseSensitive()) { if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive(); caseSensitive = cs.getCaseSensitive();
} }
CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive);
if (retVal != null) { if (retVal != null) {
return retVal; 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) { private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
String code = theCode; String code = theCode;
if (theCaseSensitive == false) { if (theCaseSensitive == false) {
code = code.toUpperCase(); code = code.toUpperCase();
} }
return testIfConceptIsInListInner(conceptList, theCaseSensitive, code); return testIfConceptIsInListInner(conceptList, theCaseSensitive, code);
} }
private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) { private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
CodeValidationResult retVal = null; CodeValidationResult retVal = null;
for (ConceptDefinitionComponent next : conceptList) { for (ConceptDefinitionComponent next : conceptList) {
String nextCandidate = next.getCode(); String nextCandidate = next.getCode();
if (theCaseSensitive == false) { if (theCaseSensitive == false) {
nextCandidate = nextCandidate.toUpperCase(); nextCandidate = nextCandidate.toUpperCase();
} }
if (nextCandidate.equals(code)) { if (nextCandidate.equals(code)) {
retVal = new CodeValidationResult(next); retVal = new CodeValidationResult(next);
break; break;
} }
// recurse // recurse
retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive); retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive);
if (retVal != null) { if (retVal != null) {
break; break;
} }
} }
return retVal; return retVal;
} }
} }

View File

@ -42,7 +42,6 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
myCodeSystems = null; myCodeSystems = null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
@ -93,18 +92,20 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override @Override
public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) {
Map<String, ValueSet> codeSystems = myCodeSystems; synchronized (this) {
if (codeSystems == null) { Map<String, ValueSet> valueSets = myCodeSystems;
codeSystems = new HashMap<String, ValueSet>(); if (valueSets == null) {
valueSets = new HashMap<String, ValueSet>();
loadCodeSystems(theContext, codeSystems, "/org/hl7/fhir/instance/model/valueset/valuesets.xml"); loadCodeSystems(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/valuesets.xml");
loadCodeSystems(theContext, codeSystems, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml"); loadCodeSystems(theContext, valueSets, "/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/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) { private void loadCodeSystems(FhirContext theContext, Map<String, ValueSet> codeSystems, String file) {

View File

@ -149,6 +149,19 @@
<action type="fix"> <action type="fix">
Validator incorrectly rejected references where only an identifier was populated Validator incorrectly rejected references where only an identifier was populated
</action> </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>
<release version="2.4" date="2017-04-19"> <release version="2.4" date="2017-04-19">
<action type="add"> <action type="add">