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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.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
|
||||||
|
|
|
@ -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...
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in New Issue