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);
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);
}
}
}

View File

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

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.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

View File

@ -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...
*/

View File

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

View File

@ -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) {

View 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">