loinc updates

This commit is contained in:
James Agnew 2018-04-02 21:28:31 -04:00
parent e31e1c029d
commit b6232b5683
67 changed files with 2606 additions and 1248 deletions

View File

@ -5,9 +5,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.rp.dstu3.ActivityDefinitionResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.ActivityDefinitionResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu3.PlanDefinitionResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.PlanDefinitionResourceProvider;
@ -149,7 +149,7 @@ public class CdsServerExample extends RestfulServer {
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
* with this feature. * with this feature.
*/ */
registerProvider(myAppCtx.getBean(TerminologyUploaderProvider.class)); registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
} }
public IResourceProvider getProvider(String name) { public IResourceProvider getProvider(String name) {

View File

@ -8,9 +8,10 @@ import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -78,10 +79,10 @@ public class JpaServerDemo extends RestfulServer {
systemProvider.add(myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class)); systemProvider.add(myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class));
} else if (fhirVersion == FhirVersionEnum.DSTU3) { } else if (fhirVersion == FhirVersionEnum.DSTU3) {
systemProvider.add(myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class)); systemProvider.add(myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class));
systemProvider.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); systemProvider.add(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
} else if (fhirVersion == FhirVersionEnum.R4) { } else if (fhirVersion == FhirVersionEnum.R4) {
systemProvider.add(myAppCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class)); systemProvider.add(myAppCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class));
systemProvider.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); systemProvider.add(myAppCtx.getBean(TerminologyUploaderProviderR4.class));
} else { } else {
throw new IllegalStateException(); throw new IllegalStateException();
} }

View File

@ -9,8 +9,11 @@ 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.dstu3.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.*; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
@ -115,8 +118,8 @@ public class BaseDstu3Config extends BaseConfig {
} }
@Bean(autowire = Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public TerminologyUploaderProvider terminologyUploaderProvider() { public TerminologyUploaderProviderDstu3 terminologyUploaderProvider() {
TerminologyUploaderProvider retVal = new TerminologyUploaderProvider(); TerminologyUploaderProviderDstu3 retVal = new TerminologyUploaderProviderDstu3();
retVal.setContext(fhirContextDstu3()); retVal.setContext(fhirContextDstu3());
return retVal; return retVal;
} }

View File

@ -10,7 +10,7 @@ import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4; import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices; import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
@ -133,8 +133,8 @@ public class BaseR4Config extends BaseConfig {
} }
@Bean(autowire = Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public TerminologyUploaderProvider terminologyUploaderProvider() { public TerminologyUploaderProviderR4 terminologyUploaderProvider() {
TerminologyUploaderProvider retVal = new TerminologyUploaderProvider(); TerminologyUploaderProviderR4 retVal = new TerminologyUploaderProviderR4();
retVal.setContext(fhirContextR4()); retVal.setContext(fhirContextR4());
return retVal; return retVal;
} }

View File

@ -44,10 +44,12 @@ import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*; import ca.uhn.fhir.util.*;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -67,7 +69,6 @@ import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.BaseResource; import org.hl7.fhir.r4.model.BaseResource;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -110,6 +111,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>(); private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest"; private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
private static boolean ourValidationDisabledForUnitTest; private static boolean ourValidationDisabledForUnitTest;
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
static { static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>(); Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
@ -834,6 +836,22 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return new PersistedJpaBundleProvider(search.getUuid(), this); return new PersistedJpaBundleProvider(search.getUuid(), this);
} }
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
String newVersion;
long newVersionLong;
if (theResourceId == null || theResourceId.getVersionIdPart() == null) {
newVersion = "1";
newVersionLong = 1;
} else {
newVersionLong = theResourceId.getVersionIdPartAsLong() + 1;
newVersion = Long.toString(newVersionLong);
}
IIdType newId = theResourceId.withVersion(newVersion);
theResource.getIdElement().setValue(newId.getValue());
theSavedEntity.setVersion(newVersionLong);
}
@Override @Override
public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) { public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) {
theProvider.setContext(getContext()); theProvider.setContext(getContext());
@ -1816,6 +1834,55 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true); return updateEntity(theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
} }
public ResourceTable updateInternal(T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType theResourceId, IBaseResource theOldResource) {
// Notify interceptors
ActionRequestDetails requestDetails = null;
if (theRequestDetails != null) {
requestDetails = new ActionRequestDetails(theRequestDetails, theResource, theResourceId.getResourceType(), theResourceId);
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
}
// Notify IServerOperationInterceptors about pre-action call
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourcePreUpdate(theOldResource, theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreUpdate(theRequestDetails, theOldResource, theResource);
}
}
// Perform update
ResourceTable savedEntity = updateEntity(theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
* we'll manually increase the version. This is important because we want the updated version number
* to be reflected in the resource shared with interceptors
*/
if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation() && !ourDisableIncrementOnUpdateForUnitTest) {
if (theResourceId.hasVersionIdPart() == false) {
theResourceId = theResourceId.withVersion(Long.toString(savedEntity.getVersion()));
}
incrementId(theResource, savedEntity, theResourceId);
}
// Notify interceptors
if (!savedEntity.isUnchangedInCurrentOperation()) {
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourceUpdated(theResource);
theRequestDetails.getRequestOperationCallback().resourceUpdated(theOldResource, theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theResource);
((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theOldResource, theResource);
}
}
}
return savedEntity;
}
private void validateChildReferences(IBase theElement, String thePath) { private void validateChildReferences(IBase theElement, String thePath) {
if (theElement == null) { if (theElement == null) {
return; return;
@ -2133,6 +2200,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return b.toString(); return b.toString();
} }
@VisibleForTesting
public static void setDisableIncrementOnUpdateForUnitTest(boolean theDisableIncrementOnUpdateForUnitTest) {
ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
}
/** /**
* Do not call this method outside of unit tests * Do not call this method outside of unit tests
*/ */

View File

@ -77,7 +77,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> { public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
@Autowired @Autowired
protected PlatformTransactionManager myPlatformTransactionManager; protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired @Autowired
@ -592,22 +591,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal; return retVal;
} }
private void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
String newVersion;
long newVersionLong;
if (theResourceId == null || theResourceId.getVersionIdPart() == null) {
newVersion = "1";
newVersionLong = 1;
} else {
newVersionLong = theResourceId.getVersionIdPartAsLong() + 1;
newVersion = Long.toString(newVersionLong);
}
IIdType newId = theResourceId.withVersion(newVersion);
theResource.getIdElement().setValue(newId.getValue());
theSavedEntity.setVersion(newVersionLong);
}
protected boolean isPagingProviderDatabaseBacked(RequestDetails theRequestDetails) { protected boolean isPagingProviderDatabaseBacked(RequestDetails theRequestDetails) {
if (theRequestDetails == null || theRequestDetails.getServer() == null) { if (theRequestDetails == null || theRequestDetails.getServer() == null) {
return false; return false;
@ -1229,54 +1212,24 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
"Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]"); "Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]");
} }
// Notify interceptors
ActionRequestDetails requestDetails = null;
if (theRequestDetails != null) {
requestDetails = new ActionRequestDetails(theRequestDetails, theResource, getResourceName(), resourceId);
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
}
IBaseResource oldResource = toResource(entity, false); IBaseResource oldResource = toResource(entity, false);
// Notify IServerOperationInterceptors about pre-action call /*
if (theRequestDetails != null) { * If we aren't indexing, that means we're doing this inside a transaction.
theRequestDetails.getRequestOperationCallback().resourcePreUpdate(oldResource, theResource); * The transaction will do the actual storate to the database a bit later on,
* after placeholder IDs have been replaced, by calling {@link #updateInternal}
* directly. So we just bail now.
*/
if (!thePerformIndexing) {
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(false);
outcome.setPreviousResource(oldResource);
return outcome;
} }
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourcePreUpdate(theRequestDetails, oldResource, theResource);
}
}
// Perform update
ResourceTable savedEntity = updateEntity(theResource, entity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing);
/* /*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), * Otherwise, we're not in a transaction
* we'll manually increase the version. This is important because we want the updated version number
* to be reflected in the resource shared with interceptors
*/ */
if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation() && !ourDisableIncrementOnUpdateForUnitTest) { ResourceTable savedEntity = updateInternal(theResource, thePerformIndexing, theForceUpdateVersion, theRequestDetails, entity, resourceId, oldResource);
if (resourceId.hasVersionIdPart() == false) {
resourceId = resourceId.withVersion(Long.toString(savedEntity.getVersion()));
}
incrementId(theResource, savedEntity, resourceId);
}
// Notify interceptors
if (!savedEntity.isUnchangedInCurrentOperation()) {
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourceUpdated(theResource);
theRequestDetails.getRequestOperationCallback().resourceUpdated(oldResource, theResource);
}
for (IServerInterceptor next : getConfig().getInterceptors()) {
if (next instanceof IServerOperationInterceptor) {
((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, theResource);
((IServerOperationInterceptor) next).resourceUpdated(theRequestDetails, oldResource, theResource);
}
}
}
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false); DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
if (!thePerformIndexing) { if (!thePerformIndexing) {
@ -1366,9 +1319,5 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} }
@VisibleForTesting
public static void setDisableIncrementOnUpdateForUnitTest(boolean theDisableIncrementOnUpdateForUnitTest) {
ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
}
} }

View File

@ -22,24 +22,41 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class DaoMethodOutcome extends MethodOutcome { public class DaoMethodOutcome extends MethodOutcome {
private ResourceTable myEntity; private ResourceTable myEntity;
private IBaseResource myPreviousResource;
public ResourceTable getEntity() { public ResourceTable getEntity() {
return myEntity; return myEntity;
} }
@Override
public DaoMethodOutcome setCreated(Boolean theCreated) {
super.setCreated(theCreated);
return this;
}
public DaoMethodOutcome setEntity(ResourceTable theEntity) { public DaoMethodOutcome setEntity(ResourceTable theEntity) {
myEntity = theEntity; myEntity = theEntity;
return this; return this;
} }
/**
* For update operations, this is the body of the resource as it was before the
* update
*/
public IBaseResource getPreviousResource() {
return myPreviousResource;
}
/**
* For update operations, this is the body of the resource as it was before the
* update
*/
public void setPreviousResource(IBaseResource thePreviousResource) {
myPreviousResource = thePreviousResource;
}
@Override
public DaoMethodOutcome setCreated(Boolean theCreated) {
super.setCreated(theCreated);
return this;
}
} }

View File

@ -19,24 +19,6 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.util.*;
import javax.persistence.TypedQuery;
import ca.uhn.fhir.model.primitive.UriDt;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.*;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.collect.ArrayListMultimap;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
@ -51,15 +33,23 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse; import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.valueset.*; import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
@ -67,6 +57,23 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.UrlUtil.UrlParts; import ca.uhn.fhir.util.UrlUtil.UrlParts;
import com.google.common.collect.ArrayListMultimap;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.TypedQuery;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.*;
public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> { public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class);
@ -137,121 +144,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
} }
long delay = System.currentTimeMillis() - start; long delay = System.currentTimeMillis() - start;
ourLog.info("Batch completed in {}ms", new Object[] { delay }); ourLog.info("Batch completed in {}ms", new Object[] {delay});
return resp; return resp;
} }
private String extractTransactionUrlOrThrowException(Entry nextEntry, HTTPVerbEnum verb) {
String url = nextEntry.getRequest().getUrl();
if (isBlank(url)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
}
return url;
}
/**
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
* outer bundle). This method applies the _summary and _content parameters to the output of
* that bundle.
*
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
*/
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
IParser p = getContext().newJsonParser();
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IResource> theClass) {
IFhirResourceDao<? extends IResource> retVal = getDao(theClass);
if (retVal == null) {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
}
return retVal;
}
@Override
public MetaDt metaGetOperation(RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = toMetaDt(tagDefinitions);
return retVal;
}
protected MetaDt toMetaDt(Collection<TagDefinition> tagDefinitions) {
MetaDt retVal = new MetaDt();
for (TagDefinition next : tagDefinitions) {
switch (next.getTagType()) {
case PROFILE:
retVal.addProfile(next.getCode());
break;
case SECURITY_LABEL:
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
case TAG:
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
}
}
return retVal;
}
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
RuntimeResourceDefinition resType;
try {
resType = getContext().getResourceDefinition(theParts.getResourceType());
} catch (DataFormatException e) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
IFhirResourceDao<? extends IBaseResource> dao = null;
if (resType != null) {
dao = getDao(resType.getImplementingClass());
}
if (dao == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
// throw new InvalidRequestException(msg);
// }
return dao;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
super.markRequestAsProcessingSubRequest(theRequestDetails);
try {
return doTransaction(theRequestDetails, theRequest, theActionName);
} finally {
super.clearRequestAsProcessingSubRequest(theRequestDetails);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) { private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleTypeEnum transactionType = theRequest.getTypeElement().getValueAsEnum(); BundleTypeEnum transactionType = theRequest.getTypeElement().getValueAsEnum();
@ -299,10 +196,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
} }
Collections.sort(theRequest.getEntry(), new TransactionSorter()); Collections.sort(theRequest.getEntry(), new TransactionSorter());
List<IIdType> deletedResources = new ArrayList<IIdType>(); List<IIdType> deletedResources = new ArrayList<>();
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>(); List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<Entry, ResourceTable> entriesToProcess = new IdentityHashMap<Entry, ResourceTable>(); Map<Entry, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<ResourceTable>(); Set<ResourceTable> nonUpdatedEntities = new HashSet<ResourceTable>();
Set<ResourceTable> updatedEntities = new HashSet<>();
/* /*
* Loop through the request and process any entries of type * Loop through the request and process any entries of type
@ -321,7 +219,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
nextResourceId = res.getId(); nextResourceId = res.getId();
if (nextResourceId.hasIdPart() == false) { if (!nextResourceId.hasIdPart()) {
if (isNotBlank(nextReqEntry.getFullUrl())) { if (isNotBlank(nextReqEntry.getFullUrl())) {
nextResourceId = new IdDt(nextReqEntry.getFullUrl()); nextResourceId = new IdDt(nextReqEntry.getFullUrl());
} }
@ -419,6 +317,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false, theRequestDetails); outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false, theRequestDetails);
} }
if (outcome.getCreated() == Boolean.FALSE) {
updatedEntities.add(outcome.getEntity());
}
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
entriesToProcess.put(nextRespEntry, outcome.getEntity()); entriesToProcess.put(nextRespEntry, outcome.getEntity());
break; break;
@ -435,12 +337,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
* end. * end.
*/ */
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext(); ) { deleteConflicts.removeIf(next -> deletedResources.contains(next.getTargetId().toVersionless()));
DeleteConflict next = iter.next();
if (deletedResources.contains(next.getTargetId().toVersionless())) {
iter.remove();
}
}
validateDeleteConflictsEmptyOrThrowException(deleteConflicts); validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/* /*
@ -489,8 +386,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
boolean shouldUpdate = !nonUpdatedEntities.contains(nextOutcome.getEntity()); if (updatedEntities.contains(nextOutcome.getEntity())) {
if (shouldUpdate) { updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, updateTime, false, true); updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, updateTime, false, true);
} }
} }
@ -603,12 +501,121 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
long delay = System.currentTimeMillis() - start; long delay = System.currentTimeMillis() - start;
int numEntries = theRequest.getEntry().size(); int numEntries = theRequest.getEntry().size();
long delayPer = delay / numEntries; long delayPer = delay / numEntries;
ourLog.info("{} completed in {}ms ({} entries at {}ms per entry)", new Object[] { theActionName , delay, numEntries, delayPer }); ourLog.info("{} completed in {}ms ({} entries at {}ms per entry)", new Object[] {theActionName, delay, numEntries, delayPer});
response.setType(BundleTypeEnum.TRANSACTION_RESPONSE); response.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
return response; return response;
} }
private String extractTransactionUrlOrThrowException(Entry nextEntry, HTTPVerbEnum verb) {
String url = nextEntry.getRequest().getUrl();
if (isBlank(url)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
}
return url;
}
/**
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
* outer bundle). This method applies the _summary and _content parameters to the output of
* that bundle.
* <p>
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
*/
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
IParser p = getContext().newJsonParser();
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IResource> theClass) {
IFhirResourceDao<? extends IResource> retVal = getDao(theClass);
if (retVal == null) {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
}
return retVal;
}
@Override
public MetaDt metaGetOperation(RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
notifyInterceptors(RestOperationTypeEnum.META, requestDetails);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = toMetaDt(tagDefinitions);
return retVal;
}
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
RuntimeResourceDefinition resType;
try {
resType = getContext().getResourceDefinition(theParts.getResourceType());
} catch (DataFormatException e) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
IFhirResourceDao<? extends IBaseResource> dao = null;
if (resType != null) {
dao = getDao(resType.getImplementingClass());
}
if (dao == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
// throw new InvalidRequestException(msg);
// }
return dao;
}
protected MetaDt toMetaDt(Collection<TagDefinition> tagDefinitions) {
MetaDt retVal = new MetaDt();
for (TagDefinition next : tagDefinitions) {
switch (next.getTagType()) {
case PROFILE:
retVal.addProfile(next.getCode());
break;
case SECURITY_LABEL:
retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
case TAG:
retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
break;
}
}
return retVal;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
}
String actionName = "Transaction";
return transaction((ServletRequestDetails) theRequestDetails, theRequest, actionName);
}
private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
super.markRequestAsProcessingSubRequest(theRequestDetails);
try {
return doTransaction(theRequestDetails, theRequest, theActionName);
} finally {
super.clearRequestAsProcessingSubRequest(theRequestDetails);
}
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
Entry newEntry, String theResourceType, IResource theRes) { Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless(); IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();
@ -645,9 +652,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
} }
//@formatter:off //@formatter:off
/** /**
* Transaction Order, per the spec: * Transaction Order, per the spec:
* * <p>
* Process any DELETE interactions * Process any DELETE interactions
* Process any POST interactions * Process any POST interactions
* Process any PUT interactions * Process any PUT interactions

View File

@ -1193,7 +1193,7 @@ public class SearchBuilder implements ISearchBuilder {
codes = myTerminologySvc.findCodesBelow(system, code); codes = myTerminologySvc.findCodesBelow(system, code);
} }
ArrayList<Predicate> singleCodePredicates = new ArrayList<Predicate>(); ArrayList<Predicate> singleCodePredicates = new ArrayList<>();
if (codes != null) { if (codes != null) {
if (codes.isEmpty()) { if (codes.isEmpty()) {
@ -1581,7 +1581,8 @@ public class SearchBuilder implements ISearchBuilder {
} }
} }
if (valueSetUris.size() == 1) { if (valueSetUris.size() == 1) {
List<VersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(valueSetUris.iterator().next()); String valueSet = valueSetUris.iterator().next();
List<VersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(valueSet);
for (VersionIndependentConcept nextCandidate : candidateCodes) { for (VersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) { if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem(); retVal = nextCandidate.getSystem();

View File

@ -1,5 +1,12 @@
package ca.uhn.fhir.jpa.dao.data; package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
/* /*
@ -22,14 +29,12 @@ import java.util.List;
* #L% * #L%
*/ */
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
public interface ITermCodeSystemVersionDao extends JpaRepository<TermCodeSystemVersion, Long> { public interface ITermCodeSystemVersionDao extends JpaRepository<TermCodeSystemVersion, Long> {
@Modifying
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myCodeSystem = :cs")
void deleteForCodeSystem(@Param("cs") TermCodeSystem theCodeSystem);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id") @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id")
List<TermCodeSystemVersion> findByCodeSystemResource(@Param("resource_id") Long theCodeSystemResourcePid); List<TermCodeSystemVersion> findByCodeSystemResource(@Param("resource_id") Long theCodeSystemResourcePid);

View File

@ -41,9 +41,9 @@ public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system") @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system")
List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem); List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem);
@Query("DELETE FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid") @Query("SELECT t FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
@Modifying @Modifying
void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid); List<TermConcept> findByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Query("UPDATE TermConcept t SET t.myIndexStatus = null") @Query("UPDATE TermConcept t SET t.myIndexStatus = null")
@Modifying @Modifying

View File

@ -158,13 +158,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
ourLog.debug("Beginning {} with {} resources", theActionName, theRequest.getEntry().size()); ourLog.debug("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
// long start = System.currentTimeMillis();
final StopWatch transactionSw = new StopWatch();
final Date updateTime = new Date(); final Date updateTime = new Date();
final Set<IdType> allIds = new LinkedHashSet<IdType>(); final Set<IdType> allIds = new LinkedHashSet<>();
final Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>(); final Map<IdType, IdType> idSubstitutions = new HashMap<>();
final Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>(); final Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>();
// Do all entries have a verb? // Do all entries have a verb?
for (int i = 0; i < theRequest.getEntry().size(); i++) { for (int i = 0; i < theRequest.getEntry().size(); i++) {
@ -223,7 +221,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
Map<BundleEntryComponent, ResourceTable> entriesToProcess = txManager.execute(new TransactionCallback<Map<BundleEntryComponent, ResourceTable>>() { Map<BundleEntryComponent, ResourceTable> entriesToProcess = txManager.execute(new TransactionCallback<Map<BundleEntryComponent, ResourceTable>>() {
@Override @Override
public Map<BundleEntryComponent, ResourceTable> doInTransaction(TransactionStatus status) { public Map<BundleEntryComponent, ResourceTable> doInTransaction(TransactionStatus status) {
return doTransactionWriteOperations(theRequestDetails, theRequest, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionSw); return doTransactionWriteOperations(theRequestDetails, theRequest, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries);
} }
}); });
for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) { for (Entry<BundleEntryComponent, ResourceTable> nextEntry : entriesToProcess.entrySet()) {
@ -300,20 +298,17 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
ourLog.info(theActionName + " completed in {}", transactionSw.toString());
ourLog.info(theActionName + " details:\n{}", transactionSw.formatTaskDurations());
response.setType(BundleType.TRANSACTIONRESPONSE); response.setType(BundleType.TRANSACTIONRESPONSE);
return response; return response;
} }
@SuppressWarnings("unchecked") private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date updateTime, Set<IdType> allIds, Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries) {
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, Bundle response, IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder, List<BundleEntryComponent> theEntries, StopWatch theStopWatch) {
Set<String> deletedResources = new HashSet<>(); Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>(); List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>(); Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>(); Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>(); Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/* /*
@ -333,7 +328,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
nextResourceId = res.getIdElement(); nextResourceId = res.getIdElement();
if (nextResourceId.hasIdPart() == false) { if (!nextResourceId.hasIdPart()) {
if (isNotBlank(nextReqEntry.getFullUrl())) { if (isNotBlank(nextReqEntry.getFullUrl())) {
nextResourceId = new IdType(nextReqEntry.getFullUrl()); nextResourceId = new IdType(nextReqEntry.getFullUrl());
} }
@ -352,12 +347,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
* Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
*/ */
if (isPlaceholder(nextResourceId)) { if (isPlaceholder(nextResourceId)) {
if (!allIds.add(nextResourceId)) { if (!theAllIds.add(nextResourceId)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
} }
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
IdType nextId = nextResourceId.toUnqualifiedVersionless(); IdType nextId = nextResourceId.toUnqualifiedVersionless();
if (!allIds.add(nextId)) { if (!theAllIds.add(nextId)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId));
} }
} }
@ -367,9 +362,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue(); HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
BundleEntryComponent nextRespEntry = response.getEntry().get(originalRequestOrder.get(nextReqEntry)); BundleEntryComponent nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry));
theStopWatch.startTask("Process entry " + i + ": " + verb + " " + defaultString(resourceType));
switch (verb) { switch (verb) {
case POST: { case POST: {
@ -382,7 +375,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails); outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
if (nextResourceId != null) { if (nextResourceId != null) {
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails); handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
} }
entriesToProcess.put(nextRespEntry, outcome.getEntity()); entriesToProcess.put(nextRespEntry, outcome.getEntity());
if (outcome.getCreated() == false) { if (outcome.getCreated() == false) {
@ -460,17 +453,20 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
} }
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails); if (outcome.getCreated() == Boolean.FALSE) {
updatedEntities.add(outcome.getEntity());
}
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity()); entriesToProcess.put(nextRespEntry, outcome.getEntity());
break; break;
} }
case GET: case GET:
case NULL: case NULL:
break; break;
}
}
theStopWatch.endCurrentTask(); }
}
/* /*
* Make sure that there are no conflicts from deletions. E.g. we can't delete something * Make sure that there are no conflicts from deletions. E.g. we can't delete something
@ -479,12 +475,8 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
* end. * end.
*/ */
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext();) { deleteConflicts.removeIf(next ->
DeleteConflict next = iter.next(); deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue()));
if (deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue())) {
iter.remove();
}
}
validateDeleteConflictsEmptyOrThrowException(deleteConflicts); validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/* /*
@ -492,13 +484,13 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
*/ */
FhirTerser terser = getContext().newTerser(); FhirTerser terser = getContext().newTerser();
for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) { for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource(); IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) { if (nextResource == null) {
continue; continue;
} }
// Refererences // References
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class); List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class);
for (IBaseReference nextRef : allRefs) { for (IBaseReference nextRef : allRefs) {
IIdType nextId = nextRef.getReferenceElement(); IIdType nextId = nextRef.getReferenceElement();
@ -534,18 +526,16 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource); IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
boolean shouldUpdate = !nonUpdatedEntities.contains(nextOutcome.getEntity());
if (shouldUpdate) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, shouldUpdate, false, updateTime, false, true);
}
}
theStopWatch.startTask("Flush Session"); if (updatedEntities.contains(nextOutcome.getEntity())) {
updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
}
}
flushJpaSession(); flushJpaSession();
theStopWatch.endCurrentTask();
/* /*
* Double check we didn't allow any duplicates we shouldn't have * Double check we didn't allow any duplicates we shouldn't have
*/ */
@ -562,7 +552,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
} }
for (IdType next : allIds) { for (IdType next : theAllIds) {
IdType replacement = theIdSubstitutions.get(next); IdType replacement = theIdSubstitutions.get(next);
if (replacement == null) { if (replacement == null) {
continue; continue;

View File

@ -19,6 +19,8 @@ import javax.transaction.Transactional.TxType;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -87,6 +89,9 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3 {
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
if (isBlank(theSystem)) {
return null;
}
return fetchResource(theCtx, CodeSystem.class, theSystem); return fetchResource(theCtx, CodeSystem.class, theSystem);
} }

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
@ -266,7 +265,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
paramValues.put(next.getName(), next.getValue()); paramValues.put(next.getName(), next.getValue());
} }
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) { for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[ nextParamEntry.getValue().size() ]); String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue); requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
} }
url = url.substring(0, qIndex); url = url.substring(0, qIndex);
@ -323,6 +322,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
List<DeleteConflict> deleteConflicts = new ArrayList<>(); List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>(); Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>(); Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>(); Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/* /*
@ -467,6 +467,10 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
} }
} }
if (outcome.getCreated() == Boolean.FALSE) {
updatedEntities.add(outcome.getEntity());
}
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails); handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequestDetails);
entriesToProcess.put(nextRespEntry, outcome.getEntity()); entriesToProcess.put(nextRespEntry, outcome.getEntity());
break; break;
@ -538,9 +542,11 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource); IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
boolean shouldUpdate = !nonUpdatedEntities.contains(nextOutcome.getEntity());
if (shouldUpdate) { if (updatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, shouldUpdate, false, theUpdateTime, false, true); updateInternal(nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource());
} else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true);
} }
} }

View File

@ -53,7 +53,7 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
private Long myResourcePid; private Long myResourcePid;
@Field() @Field()
@Column(name = "RES_TYPE", nullable = false, length = 30) @Column(name = "RES_TYPE", nullable = false)
private String myResourceType; private String myResourceType;
@Field() @Field()

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
@ -89,4 +92,15 @@ public class TermCodeSystem implements Serializable {
public void setResource(ResourceTable theResource) { public void setResource(ResourceTable theResource) {
myResource = theResource; myResource = theResource;
} }
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("codeSystemUri", myCodeSystemUri)
.append("currentVersion", myCurrentVersion)
.append("pid", myPid)
.append("resourcePid", myResourcePid)
.append("name", myName)
.toString();
}
} }

View File

@ -56,7 +56,7 @@ public class TermCodeSystemVersion implements Serializable {
* issued. It should be made non-nullable at some point. * issued. It should be made non-nullable at some point.
*/ */
@ManyToOne @ManyToOne
@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", nullable = true) @JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_CODESYSVER_CS_ID"))
private TermCodeSystem myCodeSystem; private TermCodeSystem myCodeSystem;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@OneToOne(mappedBy = "myCurrentVersion", optional = true) @OneToOne(mappedBy = "myCurrentVersion", optional = true)

View File

@ -31,7 +31,7 @@ public class TermConceptDesignation implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ManyToOne @ManyToOne
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT"))
private TermConcept myConcept; private TermConcept myConcept;
@Id() @Id()
@SequenceGenerator(name = "SEQ_CONCEPT_DESIG_PID", sequenceName = "SEQ_CONCEPT_DESIG_PID") @SequenceGenerator(name = "SEQ_CONCEPT_DESIG_PID", sequenceName = "SEQ_CONCEPT_DESIG_PID")

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.entity; package ca.uhn.fhir.jpa.entity;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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.apache.lucene.document.Document; import org.apache.lucene.document.Document;
import org.hibernate.search.bridge.FieldBridge; import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions; import org.hibernate.search.bridge.LuceneOptions;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.entity; package ca.uhn.fhir.jpa.entity;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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%
*/
/** /**
* @see TermConceptProperty#getType() * @see TermConceptProperty#getType()
* @see TermConceptProperty#MAX_PROPTYPE_ENUM_LENGTH * @see TermConceptProperty#MAX_PROPTYPE_ENUM_LENGTH

View File

@ -22,47 +22,47 @@ package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc.UploadStatistics; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc.UploadStatistics;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.File; import java.io.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TerminologyUploaderProvider extends BaseJpaProvider { public abstract class BaseTerminologyUploaderProvider extends BaseJpaProvider {
public static final String UPLOAD_EXTERNAL_CODE_SYSTEM = "$upload-external-code-system"; public static final String UPLOAD_EXTERNAL_CODE_SYSTEM = "$upload-external-code-system";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProvider.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTerminologyUploaderProvider.class);
@Autowired @Autowired
private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc; private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc;
@Operation(name = UPLOAD_EXTERNAL_CODE_SYSTEM, idempotent = false, returnParameters= { protected Parameters handleUploadExternalCodeSystem(
@OperationParam(name="conceptCount", type=IntegerType.class, min=1)
})
public Parameters uploadExternalCodeSystem(
HttpServletRequest theServletRequest, HttpServletRequest theServletRequest,
@OperationParam(name="url", min=1) StringParam theCodeSystemUrl, StringParam theCodeSystemUrl,
@OperationParam(name="localfile", min=1, max=OperationParam.MAX_UNLIMITED) List<StringType> theLocalFile, List<StringType> theLocalFile,
RequestDetails theRequestDetails List<Attachment> thePackage, RequestDetails theRequestDetails
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
if (theLocalFile == null || theLocalFile.size() == 0) {
if (thePackage == null || thePackage.size() == 0) {
throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data");
}
}
try { try {
List<IHapiTerminologyLoaderSvc.FileDescriptor> localFiles = new ArrayList<>(); List<IHapiTerminologyLoaderSvc.FileDescriptor> localFiles = new ArrayList<>();
if (theLocalFile != null && theLocalFile.size() > 0) { if (theLocalFile != null && theLocalFile.size() > 0) {
@ -70,8 +70,8 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
if (isNotBlank(nextLocalFile.getValue())) { if (isNotBlank(nextLocalFile.getValue())) {
ourLog.info("Reading in local file: {}", nextLocalFile.getValue()); ourLog.info("Reading in local file: {}", nextLocalFile.getValue());
File nextFile = new File(nextLocalFile.getValue()); File nextFile = new File(nextLocalFile.getValue());
if (!nextFile.exists() || nextFile.isFile()) { if (!nextFile.exists() || !nextFile.isFile()) {
throw new InvalidRequestException("Unknown file: " +nextFile.getName()); throw new InvalidRequestException("Unknown file: " + nextFile.getName());
} }
localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() {
@Override @Override
@ -92,6 +92,26 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
} }
} }
if (thePackage != null) {
for (Attachment nextPackage : thePackage) {
if (isBlank(nextPackage.getUrl())) {
throw new UnprocessableEntityException("Package is missing mandatory url element");
}
localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() {
@Override
public String getFilename() {
return nextPackage.getUrl();
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(nextPackage.getData());
}
});
}
}
String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null; String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null;
url = defaultString(url); url = defaultString(url);

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.jpa.provider.dstu3;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.provider.BaseTerminologyUploaderProvider;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.exceptions.FHIRException;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
public class TerminologyUploaderProviderDstu3 extends BaseTerminologyUploaderProvider {
@Operation(name = UPLOAD_EXTERNAL_CODE_SYSTEM, idempotent = false, returnParameters = {
@OperationParam(name = "conceptCount", type = IntegerType.class, min = 1)
})
public Parameters uploadExternalCodeSystem(
HttpServletRequest theServletRequest,
@OperationParam(name = "url", min = 1) StringParam theCodeSystemUrl,
@OperationParam(name = "localfile", min = 1, max = OperationParam.MAX_UNLIMITED) List<StringType> theLocalFile,
@OperationParam(name = "package", min = 0, max = OperationParam.MAX_UNLIMITED) List<Attachment> thePackage,
RequestDetails theRequestDetails
) {
try {
List<org.hl7.fhir.r4.model.StringType> localFile = null;
if (theLocalFile != null) {
localFile = new ArrayList<>();
for (StringType next : theLocalFile) {
localFile.add(VersionConvertor_30_40.convertString(next));
}
}
List<org.hl7.fhir.r4.model.Attachment> pkg = null;
if (thePackage!=null){
pkg = new ArrayList<>();
for (Attachment next : thePackage) {
pkg.add(VersionConvertor_30_40.convertAttachment(next));
}
}
org.hl7.fhir.r4.model.Parameters retValR4 = handleUploadExternalCodeSystem(theServletRequest, theCodeSystemUrl, localFile, pkg, theRequestDetails);
return VersionConvertor_30_40.convertParameters(retValR4);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
}

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.jpa.provider.r4;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.provider.BaseTerminologyUploaderProvider;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
public class TerminologyUploaderProviderR4 extends BaseTerminologyUploaderProvider {
@Operation(name = UPLOAD_EXTERNAL_CODE_SYSTEM, idempotent = false, returnParameters = {
@OperationParam(name = "conceptCount", type = IntegerType.class, min = 1)
})
public Parameters uploadExternalCodeSystem(
HttpServletRequest theServletRequest,
@OperationParam(name = "url", min = 1) StringParam theCodeSystemUrl,
@OperationParam(name = "localfile", min = 1, max = OperationParam.MAX_UNLIMITED) List<StringType> theLocalFile,
@OperationParam(name = "package", min = 0, max = OperationParam.MAX_UNLIMITED) List<Attachment> thePackage,
RequestDetails theRequestDetails
) {
return handleUploadExternalCodeSystem(theServletRequest, theCodeSystemUrl, theLocalFile, thePackage, theRequestDetails);
}
}

View File

@ -105,6 +105,15 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
contains.setCode(theConcept.getCode()); contains.setCode(theConcept.getCode());
contains.setSystem(theCodeSystem); contains.setSystem(theCodeSystem);
contains.setDisplay(theConcept.getDisplay()); contains.setDisplay(theConcept.getDisplay());
for (TermConceptDesignation nextDesignation : theConcept.getDesignations()) {
contains
.addDesignation()
.setValue(nextDesignation.getValue())
.getUse()
.setSystem(nextDesignation.getUseSystem())
.setCode(nextDesignation.getUseCode())
.setDisplay(nextDesignation.getUseDisplay());
}
} }
} }
@ -232,11 +241,13 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
haveIncludeCriteria = true; haveIncludeCriteria = true;
TermConcept code = findCode(system, nextCode); TermConcept code = findCode(system, nextCode);
if (code != null) { if (code != null) {
addedCodes.add(nextCode); addCodeIfNotAlreadyAdded(system, expansionComponent, addedCodes, code);
ValueSet.ValueSetExpansionContainsComponent contains = expansionComponent.addContains(); //
contains.setCode(nextCode); // addedCodes.add(nextCode);
contains.setSystem(system); // ValueSet.ValueSetExpansionContainsComponent contains = expansionComponent.addContains();
contains.setDisplay(code.getDisplay()); // contains.setCode(nextCode);
// contains.setSystem(system);
// contains.setDisplay(code.getDisplay());
} }
} }
} }

View File

@ -76,6 +76,11 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
protected CodeSystem getCodeSystemFromContext(String theSystem) {
return null;
}
@Override @Override
public List<VersionIndependentConcept> expandValueSet(String theValueSet) { public List<VersionIndependentConcept> expandValueSet(String theValueSet) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@ -153,6 +153,24 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
} }
} }
@Override
public List<VersionIndependentConcept> expandValueSet(String theValueSet) {
ValueSet vs = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet);
if (vs == null) {
return Collections.emptyList();
}
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
try {
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(vs);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4);
}
@Override @Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) { public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
return null; return null;
@ -222,6 +240,16 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
return retVal; return retVal;
} }
@Override
protected org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) {
CodeSystem codeSystem = myValidationSupport.fetchCodeSystem(myContext, theSystem);
try {
return VersionConvertor_30_40.convertCodeSystem(codeSystem);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
@Override @Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return myTerminologySvc.supportsSystem(theSystem); return myTerminologySvc.supportsSystem(theSystem);

View File

@ -112,6 +112,16 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
myValueSetResourceDao.update(theValueSet, matchUrl, theRequestDetails); myValueSetResourceDao.update(theValueSet, matchUrl, theRequestDetails);
} }
@Override
public List<VersionIndependentConcept> expandValueSet(String theValueSet) {
ValueSet vs = myValidationSupport.fetchResource(myContext, ValueSet.class, theValueSet);
if (vs == null) {
return Collections.emptyList();
}
return expandValueSetAndReturnVersionIndependentConcepts(vs);
}
@Override @Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSet valueSetToExpand = new ValueSet(); ValueSet valueSetToExpand = new ValueSet();
@ -188,6 +198,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
return retVal; return retVal;
} }
@Override
protected CodeSystem getCodeSystemFromContext(String theSystem) {
return myValidationSupport.fetchCodeSystem(myContext, theSystem);
}
@Override @Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return myTerminologySvc.supportsSystem(theSystem); return myTerminologySvc.supportsSystem(theSystem);

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term; package ca.uhn.fhir.jpa.term;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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.apache.commons.csv.CSVRecord; import org.apache.commons.csv.CSVRecord;
public interface IRecordHandler { public interface IRecordHandler {

View File

@ -128,6 +128,13 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
CSVParser parsed; CSVParser parsed;
try { try {
reader = new InputStreamReader(nextZipBytes.getInputStream(), Charsets.UTF_8); reader = new InputStreamReader(nextZipBytes.getInputStream(), Charsets.UTF_8);
if (ourLog.isTraceEnabled()) {
String contents = IOUtils.toString(reader);
ourLog.info("File contents for: {}\n{}", nextFilename, contents);
reader = new StringReader(contents);
}
CSVFormat format = CSVFormat.newFormat(theDelimiter).withFirstRecordAsHeader(); CSVFormat format = CSVFormat.newFormat(theDelimiter).withFirstRecordAsHeader();
if (theQuoteMode != null) { if (theQuoteMode != null) {
format = format.withQuote('"').withQuoteMode(theQuoteMode); format = format.withQuote('"').withQuoteMode(theQuoteMode);
@ -402,16 +409,15 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
LoadedFileDescriptors(List<IHapiTerminologyLoaderSvc.FileDescriptor> theFileDescriptors) { LoadedFileDescriptors(List<IHapiTerminologyLoaderSvc.FileDescriptor> theFileDescriptors) {
try { try {
for (FileDescriptor next : theFileDescriptors) { for (FileDescriptor next : theFileDescriptors) {
File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp");
nextTemporaryFile.deleteOnExit();
if (next.getFilename().toLowerCase().endsWith(".zip")) { if (next.getFilename().toLowerCase().endsWith(".zip")) {
ourLog.info("Uncompressing {} into temporary files", next.getFilename()); ourLog.info("Uncompressing {} into temporary files", next.getFilename());
try (InputStream inputStream = next.getInputStream()) { try (InputStream inputStream = next.getInputStream()) {
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(inputStream)); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(inputStream));
for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) { for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) {
BOMInputStream fis = new BOMInputStream(zis); BOMInputStream fis = new BOMInputStream(zis);
FileOutputStream fos = new FileOutputStream(nextTemporaryFile); File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp");
nextTemporaryFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(nextTemporaryFile, false);
IOUtils.copy(fis, fos); IOUtils.copy(fis, fos);
String nextEntryFileName = nextEntry.getName(); String nextEntryFileName = nextEntry.getName();
myUncompressedFileDescriptors.add(new FileDescriptor() { myUncompressedFileDescriptors.add(new FileDescriptor() {

View File

@ -25,6 +25,16 @@ public class VersionIndependentConcept {
private String mySystem; private String mySystem;
private String myCode; private String myCode;
/**
* Constructor
*/
public VersionIndependentConcept() {
super();
}
/**
* Constructor
*/
public VersionIndependentConcept(String theSystem, String theCode) { public VersionIndependentConcept(String theSystem, String theCode) {
setSystem(theSystem); setSystem(theSystem);
setCode(theCode); setCode(theCode);
@ -34,16 +44,18 @@ public class VersionIndependentConcept {
return mySystem; return mySystem;
} }
public void setSystem(String theSystem) { public VersionIndependentConcept setSystem(String theSystem) {
mySystem = theSystem; mySystem = theSystem;
return this;
} }
public String getCode() { public String getCode() {
return myCode; return myCode;
} }
public void setCode(String theCode) { public VersionIndependentConcept setCode(String theCode) {
myCode = theCode; myCode = theCode;
return this;
} }
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -66,7 +86,7 @@ abstract class BaseHandler implements IRecordHandler {
} }
void addConceptMapEntry(ConceptMapping theMapping) { void addConceptMapEntry(ConceptMapping theMapping, String theCopyright) {
if (isBlank(theMapping.getSourceCode())) { if (isBlank(theMapping.getSourceCode())) {
return; return;
} }
@ -86,6 +106,7 @@ abstract class BaseHandler implements IRecordHandler {
.addTelecom() .addTelecom()
.setSystem(ContactPoint.ContactPointSystem.URL) .setSystem(ContactPoint.ContactPointSystem.URL)
.setValue("https://loinc.org"); .setValue("https://loinc.org");
conceptMap.setCopyright(theCopyright);
myIdToConceptMaps.put(theMapping.getConceptMapId(), conceptMap); myIdToConceptMaps.put(theMapping.getConceptMapId(), conceptMap);
myConceptMaps.add(conceptMap); myConceptMaps.add(conceptMap);
} else { } else {
@ -151,12 +172,13 @@ abstract class BaseHandler implements IRecordHandler {
vs.setId(theValueSetId); vs.setId(theValueSetId);
vs.setName(theValueSetName); vs.setName(theValueSetName);
vs.setStatus(Enumerations.PublicationStatus.ACTIVE); vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.setPublisher("Regentrief Institute, Inc."); vs.setPublisher("Regenstrief Institute, Inc.");
vs.addContact() vs.addContact()
.setName("Regentrief Institute, Inc.") .setName("Regenstrief Institute, Inc.")
.addTelecom() .addTelecom()
.setSystem(ContactPoint.ContactPointSystem.URL) .setSystem(ContactPoint.ContactPointSystem.URL)
.setValue("https://loinc.org"); .setValue("https://loinc.org");
vs.setCopyright("This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/");
myIdToValueSet.put(theValueSetId, vs); myIdToValueSet.put(theValueSetId, vs);
myValueSets.add(vs); myValueSets.add(vs);
} else { } else {

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
@ -15,8 +35,8 @@ import static org.apache.commons.lang3.StringUtils.trim;
public class LoincDocumentOntologyHandler extends BaseHandler implements IRecordHandler { public class LoincDocumentOntologyHandler extends BaseHandler implements IRecordHandler {
public static final String DOCUMENT_ONTOLOGY_CODES_VS_ID = "DOCUMENT-ONTOLOGY-CODES-VS"; public static final String DOCUMENT_ONTOLOGY_CODES_VS_ID = "loinc-document-ontology";
public static final String DOCUMENT_ONTOLOGY_CODES_VS_URI = "http://loinc.org/document-ontology-codes"; public static final String DOCUMENT_ONTOLOGY_CODES_VS_URI = "http://loinc.org/vs/loinc-document-ontology";
public static final String DOCUMENT_ONTOLOGY_CODES_VS_NAME = "LOINC Document Ontology Codes"; public static final String DOCUMENT_ONTOLOGY_CODES_VS_NAME = "LOINC Document Ontology Codes";
private final Map<String, TermConcept> myCode2Concept; private final Map<String, TermConcept> myCode2Concept;
private final TermCodeSystemVersion myCodeSystemVersion; private final TermCodeSystemVersion myCodeSystemVersion;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
@ -37,6 +57,13 @@ public class LoincHandler implements IRecordHandler {
TermConcept concept = new TermConcept(myCodeSystemVersion, code); TermConcept concept = new TermConcept(myCodeSystemVersion, code);
concept.setDisplay(display); concept.setDisplay(display);
if (!display.equalsIgnoreCase(shortName)) {
concept
.addDesignation()
.setUseDisplay("ShortName")
.setValue(shortName);
}
for (String nextPropertyName : myPropertyNames) { for (String nextPropertyName : myPropertyNames) {
if (!theRecord.toMap().containsKey(nextPropertyName)) { if (!theRecord.toMap().containsKey(nextPropertyName)) {
continue; continue;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
@ -18,6 +38,7 @@ public class LoincIeeeMedicalDeviceCodeHandler extends BaseHandler implements IR
public static final String LOINC_IEEE_CM_ID = "LOINC-IEEE-MEDICAL-DEVICE-CM"; public static final String LOINC_IEEE_CM_ID = "LOINC-IEEE-MEDICAL-DEVICE-CM";
public static final String LOINC_IEEE_CM_URI = "http://loinc.org/fhir/loinc-ieee-device-code-mappings"; public static final String LOINC_IEEE_CM_URI = "http://loinc.org/fhir/loinc-ieee-device-code-mappings";
public static final String LOINC_IEEE_CM_NAME = "LOINC/IEEE Device Code Mappings"; public static final String LOINC_IEEE_CM_NAME = "LOINC/IEEE Device Code Mappings";
private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC/IEEE Medical Device Code Mapping Table contains content from IEEE (http://ieee.org), copyright © 2017 IEEE.";
/** /**
* Constructor * Constructor
@ -48,7 +69,8 @@ public class LoincIeeeMedicalDeviceCodeHandler extends BaseHandler implements IR
.setTargetCodeSystem(targetCodeSystemUri) .setTargetCodeSystem(targetCodeSystemUri)
.setTargetCode(ieeeCode) .setTargetCode(ieeeCode)
.setTargetDisplay(ieeeDisplayName) .setTargetDisplay(ieeeDisplayName)
.setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL),
CM_COPYRIGHT);
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;

View File

@ -1,20 +1,39 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.CSVRecord;
import org.hl7.fhir.r4.model.ValueSet;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static org.apache.commons.lang3.StringUtils.trim; import static org.apache.commons.lang3.StringUtils.trim;
public class LoincPartLinkHandler implements IRecordHandler { public class LoincPartLinkHandler implements IRecordHandler {
private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class);
private final Map<String, TermConcept> myCode2Concept; private final Map<String, TermConcept> myCode2Concept;
private final TermCodeSystemVersion myCodeSystemVersion; private final TermCodeSystemVersion myCodeSystemVersion;
@ -33,11 +52,11 @@ public class LoincPartLinkHandler implements IRecordHandler {
TermConcept loincConcept = myCode2Concept.get(loincNumber); TermConcept loincConcept = myCode2Concept.get(loincNumber);
TermConcept partConcept = myCode2Concept.get(partNumber); TermConcept partConcept = myCode2Concept.get(partNumber);
if (loincConcept==null) { if (loincConcept == null) {
ourLog.warn("No loinc code: {}", loincNumber); ourLog.warn("No loinc code: {}", loincNumber);
return; return;
} }
if (partConcept==null) { if (partConcept == null) {
ourLog.warn("No part code: {}", partNumber); ourLog.warn("No part code: {}", partNumber);
return; return;
} }
@ -45,5 +64,4 @@ public class LoincPartLinkHandler implements IRecordHandler {
// For now we're ignoring these // For now we're ignoring these
} }
private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class);
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
@ -18,8 +38,9 @@ import static org.apache.commons.lang3.StringUtils.trim;
public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements IRecordHandler { public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements IRecordHandler {
public static final String LOINC_PART_MAP_ID = "LOINC-PART-MAP"; public static final String LOINC_PART_MAP_ID = "LOINC-PART-MAP";
public static final String LOINC_PART_MAP_URI = "http://loinc.org/fhir/loinc-part-map"; public static final String LOINC_PART_MAP_URI = "http://loinc.org/cm/loinc-parts-to-snomed-ct";
public static final String LOINC_PART_MAP_NAME = "LOINC Part Map"; public static final String LOINC_PART_MAP_NAME = "LOINC Part Map";
private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC Part File, LOINC/SNOMED CT Expression Association and Map Sets File, RELMA database and associated search index files include SNOMED Clinical Terms (SNOMED CT®) which is used by permission of the International Health Terminology Standards Development Organisation (IHTSDO) under license. All rights are reserved. SNOMED CT® was originally created by The College of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO. Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. Under the terms of the Affiliate License, use of SNOMED CT in countries that are not IHTSDO Members is subject to reporting and fee payment obligations. However, IHTSDO agrees to waive the requirements to report and pay fees for use of SNOMED CT content included in the LOINC Part Mapping and LOINC Term Associations for purposes that support or enable more effective use of LOINC. This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.";
private final Map<String, TermConcept> myCode2Concept; private final Map<String, TermConcept> myCode2Concept;
private final TermCodeSystemVersion myCodeSystemVersion; private final TermCodeSystemVersion myCodeSystemVersion;
private final List<ConceptMap> myConceptMaps; private final List<ConceptMap> myConceptMaps;
@ -76,7 +97,9 @@ public class LoincPartRelatedCodeMappingHandler extends BaseHandler implements I
.setTargetDisplay(extCodeDisplayName) .setTargetDisplay(extCodeDisplayName)
.setTargetCodeSystemVersion(extCodeSystemVersion) .setTargetCodeSystemVersion(extCodeSystemVersion)
.setEquivalence(equivalence) .setEquivalence(equivalence)
.setCopyright(extCodeSystemCopyrightNotice)); .setCopyright(extCodeSystemCopyrightNotice),
CM_COPYRIGHT
);
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
@ -17,17 +37,27 @@ import static org.apache.commons.lang3.StringUtils.trim;
public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHandler { public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHandler {
public static final String RSNA_CODES_VS_ID = "RSNA-LOINC-CODES-VS"; public static final String RSNA_CODES_VS_ID = "loinc-rsna-radiology-playbook";
public static final String RSNA_CODES_VS_URI = "http://loinc.org/rsna-codes"; public static final String RSNA_CODES_VS_URI = "http://loinc.org/vs/loinc-rsna-radiology-playbook";
public static final String RSNA_CODES_VS_NAME = "RSNA Playbook"; public static final String RSNA_CODES_VS_NAME = "LOINC/RSNA Radiology Playbook";
public static final String RID_MAPPING_CM_ID = "LOINC-TO-RID-CODES-CM"; public static final String RID_MAPPING_CM_ID = "LOINC-TO-RID-CODES-CM";
public static final String RID_MAPPING_CM_URI = "http://loinc.org/rid-codes"; public static final String RID_MAPPING_CM_URI = "http://loinc.org/rid-codes";
public static final String RID_MAPPING_CM_NAME = "RSNA Playbook RID Codes Mapping"; public static final String RID_MAPPING_CM_NAME = "RSNA Playbook RID Codes Mapping";
public static final String RID_CS_URI = "http://rid"; public static final String RID_CS_URI = "http://www.radlex.org";
public static final String RPID_MAPPING_CM_ID = "LOINC-TO-RPID-CODES-CM"; public static final String RPID_MAPPING_CM_ID = "LOINC-TO-RPID-CODES-CM";
public static final String RPID_MAPPING_CM_URI = "http://loinc.org/rpid-codes"; public static final String RPID_MAPPING_CM_URI = "http://loinc.org/rpid-codes";
public static final String RPID_MAPPING_CM_NAME = "RSNA Playbook RPID Codes Mapping"; public static final String RPID_MAPPING_CM_NAME = "RSNA Playbook RPID Codes Mapping";
public static final String RPID_CS_URI = "http://rpid"; /*
* About these being the same - Per Dan:
* We had some discussion about this, and both
* RIDs (RadLex clinical terms) and RPIDs (Radlex Playbook Ids)
* belong to the same "code system" since they will never collide.
* The codesystem uri is "http://www.radlex.org". FYI, that's
* now listed on the FHIR page:
* https://www.hl7.org/fhir/terminologies-systems.html
*/
public static final String RPID_CS_URI = RID_CS_URI;
private static final String CM_COPYRIGHT = "This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/. The LOINC/RSNA Radiology Playbook and the LOINC Part File contain content from RadLex® (http://rsna.org/RadLex.aspx), copyright © 2005-2017, The Radiological Society of North America, Inc., available at no cost under the license at http://www.rsna.org/uploadedFiles/RSNA/Content/Informatics/RadLex_License_Agreement_and_Terms_of_Use_V2_Final.pdf.";
private final Map<String, TermConcept> myCode2Concept; private final Map<String, TermConcept> myCode2Concept;
private final TermCodeSystemVersion myCodeSystemVersion; private final TermCodeSystemVersion myCodeSystemVersion;
private final Set<String> myPropertyNames; private final Set<String> myPropertyNames;
@ -118,7 +148,8 @@ public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHand
.setTargetCodeSystem(RID_CS_URI) .setTargetCodeSystem(RID_CS_URI)
.setTargetCode(rid) .setTargetCode(rid)
.setTargetDisplay(preferredName) .setTargetDisplay(preferredName)
.setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL),
CM_COPYRIGHT);
} }
// LOINC Term -> Radlex RPID code mappings // LOINC Term -> Radlex RPID code mappings
@ -134,7 +165,8 @@ public class LoincRsnaPlaybookHandler extends BaseHandler implements IRecordHand
.setTargetCodeSystem(RPID_CS_URI) .setTargetCodeSystem(RPID_CS_URI)
.setTargetCode(rpid) .setTargetCode(rpid)
.setTargetDisplay(longName) .setTargetDisplay(longName)
.setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL)); .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL),
CM_COPYRIGHT);
} }
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.loinc; package ca.uhn.fhir.jpa.term.loinc;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.snomedct; package ca.uhn.fhir.jpa.term.snomedct;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;
import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.CSVRecord;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.snomedct; package ca.uhn.fhir.jpa.term.snomedct;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.jpa.term.IRecordHandler;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.term.snomedct; package ca.uhn.fhir.jpa.term.snomedct;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;

View File

@ -96,7 +96,7 @@ public class TestUtil {
assertThat(joinColumn.name(), null); assertThat(joinColumn.name(), null);
ForeignKey fk = joinColumn.foreignKey(); ForeignKey fk = joinColumn.foreignKey();
Validate.notNull(fk); Validate.notNull(fk);
Validate.isTrue(isNotBlank(fk.name())); Validate.isTrue(isNotBlank(fk.name()), "Foreign key on " + ae.toString() + " has no name()");
Validate.isTrue(fk.name().startsWith("FK_")); Validate.isTrue(fk.name().startsWith("FK_"));
assertThat(fk.name(), theNames); assertThat(fk.name(), theNames);
} }

View File

@ -252,6 +252,7 @@ public abstract class BaseJpaTest {
public Void doInTransaction(TransactionStatus theStatus) { public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate(); entityManager.createQuery("UPDATE " + ResourceHistoryTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
entityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate(); entityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " d SET d.myForcedId = null").executeUpdate();
entityManager.createQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null").executeUpdate();
return null; return null;
} }
}); });
@ -279,6 +280,8 @@ public abstract class BaseJpaTest {
txTemplate.execute(new TransactionCallback<Void>() { txTemplate.execute(new TransactionCallback<Void>() {
@Override @Override
public Void doInTransaction(TransactionStatus theStatus) { public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("DELETE from " + TermConceptProperty.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermConceptDesignation.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + TermConcept.class.getSimpleName() + " d").executeUpdate();
for (TermCodeSystem next : entityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) { for (TermCodeSystem next : entityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
next.setCurrentVersion(null); next.setCurrentVersion(null);

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.dstu2; package ca.uhn.fhir.jpa.dao.dstu2;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
@ -18,11 +19,13 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
@ -831,6 +834,213 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
} }
} }
@Test
public void testTransactionDoesntUpdateUnchangesResourceWithPlaceholderIds() {
Bundle output, input;
Bundle.EntryResponse respEntry;
IdType createdPatientId;
SearchParameterMap map;
IBundleProvider search;
input = new Bundle();
/*
* Create a transaction with a patient and an observation using
* placeholder IDs in them
*/
Patient pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerbEnum.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
Observation obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerbEnum.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Both resrouces should be created and have version 1
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("201 Created", respEntry.getStatus());
createdPatientId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdPatientId.getResourceType());
assertEquals("1", createdPatientId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("201 Created", respEntry.getStatus());
IdType createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference().getValue());
/*
* Now run the same transaction, which should not make any changes this time
* around
*/
input = new Bundle();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerbEnum.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerbEnum.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Should still have version 1 of both resources
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference().getValue());
/*
* Now run the transaction, but this time with an actual
* change to the Observation
*/
input = new Bundle();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerbEnum.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.setStatus(ObservationStatusEnum.FINAL);
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerbEnum.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Observation should now be version 2
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("2", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference().getValue());
assertEquals(ObservationStatusEnum.FINAL.getCode(), obs.getStatus());
}
@Test(expected = InvalidRequestException.class) @Test(expected = InvalidRequestException.class)
public void testTransactionFailsWithDuplicateIds() { public void testTransactionFailsWithDuplicateIds() {
Bundle request = new Bundle(); Bundle request = new Bundle();
@ -1193,62 +1403,6 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
} }
@Test
public void testTransactionUpdateResourceNewVersionCreatedWhenDataChanges() {
Bundle request = new Bundle();
String patientId = "Patient/IShouldUpdate";
Patient p = new Patient();
p.addName().addFamily("Hello");
p.setId(patientId);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.PUT).setUrl(patientId);
Bundle initialBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, initialBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(initialBundleResponse));
Entry initialBundleResponseEntry = initialBundleResponse.getEntry().get(0);
assertEquals("201 Created", initialBundleResponseEntry.getResponse().getStatus());
assertThat(initialBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
p.addName().addFamily("AnotherName");
Bundle secondBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, secondBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(secondBundleResponse));
Entry secondBundleResponseEntry = secondBundleResponse.getEntry().get(0);
assertEquals("200 OK", secondBundleResponseEntry.getResponse().getStatus());
assertThat(secondBundleResponseEntry.getResponse().getEtag(), is(equalTo("2")));
}
@Test
public void testTransactionUpdateResourceNewVersionNotCreatedWhenDataNotChanged() {
Bundle request = new Bundle();
String patientId = "Patient/IShouldNotUpdate";
Patient p = new Patient();
p.addName().addFamily("Hello");
p.setId(patientId);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.PUT).setUrl(patientId);
Bundle initialBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, initialBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(initialBundleResponse));
Entry initialBundleResponseEntry = initialBundleResponse.getEntry().get(0);
assertEquals("201 Created", initialBundleResponseEntry.getResponse().getStatus());
assertThat(initialBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
Bundle secondBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, secondBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(secondBundleResponse));
Entry secondBundleResponseEntry = secondBundleResponse.getEntry().get(0);
assertEquals("200 OK", secondBundleResponseEntry.getResponse().getStatus());
assertThat(secondBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
}
@Test @Test
public void testTransactionUpdateMatchUrlWithTwoMatch() { public void testTransactionUpdateMatchUrlWithTwoMatch() {
String methodName = "testTransactionUpdateMatchUrlWithTwoMatch"; String methodName = "testTransactionUpdateMatchUrlWithTwoMatch";
@ -1362,6 +1516,62 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
} }
@Test
public void testTransactionUpdateResourceNewVersionCreatedWhenDataChanges() {
Bundle request = new Bundle();
String patientId = "Patient/IShouldUpdate";
Patient p = new Patient();
p.addName().addFamily("Hello");
p.setId(patientId);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.PUT).setUrl(patientId);
Bundle initialBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, initialBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(initialBundleResponse));
Entry initialBundleResponseEntry = initialBundleResponse.getEntry().get(0);
assertEquals("201 Created", initialBundleResponseEntry.getResponse().getStatus());
assertThat(initialBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
p.addName().addFamily("AnotherName");
Bundle secondBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, secondBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(secondBundleResponse));
Entry secondBundleResponseEntry = secondBundleResponse.getEntry().get(0);
assertEquals("200 OK", secondBundleResponseEntry.getResponse().getStatus());
assertThat(secondBundleResponseEntry.getResponse().getEtag(), is(equalTo("2")));
}
@Test
public void testTransactionUpdateResourceNewVersionNotCreatedWhenDataNotChanged() {
Bundle request = new Bundle();
String patientId = "Patient/IShouldNotUpdate";
Patient p = new Patient();
p.addName().addFamily("Hello");
p.setId(patientId);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.PUT).setUrl(patientId);
Bundle initialBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, initialBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(initialBundleResponse));
Entry initialBundleResponseEntry = initialBundleResponse.getEntry().get(0);
assertEquals("201 Created", initialBundleResponseEntry.getResponse().getStatus());
assertThat(initialBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
Bundle secondBundleResponse = mySystemDao.transaction(mySrd, request);
assertEquals(1, secondBundleResponse.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(secondBundleResponse));
Entry secondBundleResponseEntry = secondBundleResponse.getEntry().get(0);
assertEquals("200 OK", secondBundleResponseEntry.getResponse().getStatus());
assertThat(secondBundleResponseEntry.getResponse().getEtag(), is(equalTo("1")));
}
@Test @Test
public void testTransactionWhichFailsPersistsNothing() { public void testTransactionWhichFailsPersistsNothing() {

View File

@ -12,6 +12,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
@ -1439,6 +1440,213 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong()); assertTrue(p.getGeneralPractitionerFirstRep().getReferenceElement().isIdPartValidLong());
} }
@Test
public void testTransactionDoesntUpdateUnchangesResourceWithPlaceholderIds() {
Bundle output, input;
BundleEntryResponseComponent respEntry;
IdType createdPatientId;
SearchParameterMap map;
IBundleProvider search;
input = new Bundle();
/*
* Create a transaction with a patient and an observation using
* placeholder IDs in them
*/
Patient pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
Observation obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Both resrouces should be created and have version 1
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("201 Created", respEntry.getStatus());
createdPatientId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdPatientId.getResourceType());
assertEquals("1", createdPatientId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("201 Created", respEntry.getStatus());
IdType createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
/*
* Now run the same transaction, which should not make any changes this time
* around
*/
input = new Bundle();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Should still have version 1 of both resources
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
/*
* Now run the transaction, but this time with an actual
* change to the Observation
*/
input = new Bundle();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.setStatus(ObservationStatus.FINAL);
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Observation should now be version 2
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("2", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0, 1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0, 1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
assertEquals(ObservationStatus.FINAL, obs.getStatus());
}
@Test @Test
public void testTransactionDoubleConditionalCreateOnlyCreatesOne() { public void testTransactionDoubleConditionalCreateOnlyCreatesOne() {
Bundle inputBundle = new Bundle(); Bundle inputBundle = new Bundle();

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
@ -1515,13 +1516,19 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
} }
@Test @Test
public void testTransactionDoesntDoubleCreate() { public void testTransactionDoesntUpdateUnchangesResourceWithPlaceholderIds() {
Bundle output, input; Bundle output, input;
BundleEntryResponseComponent respEntry; BundleEntryResponseComponent respEntry;
IdType createdId; IdType createdPatientId;
SearchParameterMap map;
IBundleProvider search;
input = new Bundle(); input = new Bundle();
/*
* Create a transaction with a patient and an observation using
* placeholder IDs in them
*/
Patient pat = new Patient(); Patient pat = new Patient();
pat.setId(IdType.newRandomUuid()); pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar"); pat.addIdentifier().setSystem("foo").setValue("bar");
@ -1542,24 +1549,51 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.getRequest() .getRequest()
.setMethod(HTTPVerb.PUT) .setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog"); .setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input); output = mySystemDao.transaction(mySrd, input);
/*
* Both resrouces should be created and have version 1
*/
respEntry = output.getEntry().get(0).getResponse(); respEntry = output.getEntry().get(0).getResponse();
assertEquals("201 Created", respEntry.getStatus()); assertEquals("201 Created", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation()); createdPatientId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdId.getResourceType()); assertEquals("Patient", createdPatientId.getResourceType());
assertEquals("1", createdId.getVersionIdPart()); assertEquals("1", createdPatientId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse(); respEntry = output.getEntry().get(1).getResponse();
assertEquals("201 Created", respEntry.getStatus()); assertEquals("201 Created", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation()); IdType createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdId.getResourceType()); assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdId.getVersionIdPart()); assertEquals("1", createdObservationId.getVersionIdPart());
// Same bundle again /*
* Searches for both resources should work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0,1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0,1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
/*
* Now run the same transaction, which should not make any changes this time
* around
*/
input = new Bundle(); input = new Bundle();
pat = new Patient(); pat = new Patient();
pat.setId(IdType.newRandomUuid()); pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar"); pat.addIdentifier().setSystem("foo").setValue("bar");
@ -1580,20 +1614,111 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.getRequest() .getRequest()
.setMethod(HTTPVerb.PUT) .setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog"); .setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input); output = mySystemDao.transaction(mySrd, input);
/*
* Should still have version 1 of both resources
*/
respEntry = output.getEntry().get(0).getResponse(); respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus()); assertEquals("200 OK", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation()); createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdId.getResourceType()); assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdId.getVersionIdPart()); assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse(); respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus()); assertEquals("200 OK", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation()); createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdId.getResourceType()); assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("1", createdId.getVersionIdPart()); assertEquals("1", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0,1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0,1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
/*
* Now run the transaction, but this time with an actual
* change to the Observation
*/
input = new Bundle();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
input
.addEntry()
.setResource(pat)
.setFullUrl(pat.getId())
.getRequest()
.setMethod(HTTPVerb.POST)
.setUrl("/Patient")
.setIfNoneExist("Patient?identifier=foo|bar");
obs = new Observation();
obs.addIdentifier().setSystem("foo").setValue("dog");
obs.setStatus(ObservationStatus.FINAL);
obs.getSubject().setReference(pat.getId());
input
.addEntry()
.setResource(obs)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("/Observation?identifier=foo|dog");
output = mySystemDao.transaction(mySrd, input);
/*
* Observation should now be version 2
*/
respEntry = output.getEntry().get(0).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
assertEquals("2", createdObservationId.getVersionIdPart());
/*
* Searches for both resources should still work and the reference
* should be substituted correctly
*/
// Patient
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Patient.SP_IDENTIFIER, new TokenParam("foo", "bar"));
search = myPatientDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdPatientId.toUnqualifiedVersionless().getValue()));
pat = (Patient) search.getResources(0,1).get(0);
assertEquals("foo", pat.getIdentifierFirstRep().getSystem());
// Observation
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_IDENTIFIER, new TokenParam("foo", "dog"));
search = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(search), contains(createdObservationId.toUnqualifiedVersionless().getValue()));
obs = (Observation) search.getResources(0,1).get(0);
assertEquals("foo", obs.getIdentifierFirstRep().getSystem());
assertEquals(createdPatientId.toUnqualifiedVersionless().getValue(), obs.getSubject().getReference());
assertEquals(ObservationStatus.FINAL, obs.getStatus());
} }

View File

@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
@ -54,16 +53,16 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
protected static CloseableHttpClient ourHttpClient; protected static CloseableHttpClient ourHttpClient;
protected static int ourPort; protected static int ourPort;
protected static RestfulServer ourRestServer; protected static RestfulServer ourRestServer;
private static Server ourServer;
protected static String ourServerBase; protected static String ourServerBase;
protected static GenericWebApplicationContext ourWebApplicationContext; protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProvider myTerminologyUploaderProvider;
protected static SearchParamRegistryDstu3 ourSearchParamRegistry; protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
protected static DatabaseBackedPagingProvider ourPagingProvider; protected static DatabaseBackedPagingProvider ourPagingProvider;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor; protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor;
protected static ISearchDao mySearchEntityDao; protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer;
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
public BaseResourceProviderDstu3Test() { public BaseResourceProviderDstu3Test() {
super(); super();
@ -93,7 +92,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProvider.class); myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProviderDstu3.class);
ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider); ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider);

View File

@ -32,12 +32,6 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3CodeSystemTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3CodeSystemTest.class);
private IIdType myExtensionalVsId; private IIdType myExtensionalVsId;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before @Before
@Transactional @Transactional
public void before02() throws IOException { public void before02() throws IOException {
@ -64,7 +58,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
ourLog.info(resp); ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName()); assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); assertEquals(("SYSTEM NAME"), ((StringType)respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(2).getName()); assertEquals("abstract", respParam.getParameter().get(2).getName());
@ -84,7 +78,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
ourLog.info(resp); ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName()); assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("Unknown"), ((StringType)respParam.getParameter().get(0).getValue()).getValue()); assertEquals(("SYSTEM NAME"), ((StringType)respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue()); assertEquals("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(2).getName()); assertEquals("abstract", respParam.getParameter().get(2).getName());
@ -272,5 +266,10 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).booleanValue()); assertEquals(false, ((BooleanType)respParam.getParameter().get(2).getValue()).booleanValue());
} }
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
} }

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3; package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -14,6 +15,7 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.IntegerType; import org.hl7.fhir.dstu3.model.IntegerType;
@ -31,167 +33,6 @@ public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDs
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderDstu3Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderDstu3Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testUploadSct() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test
public void testUploadLoinc() throws Exception {
byte[] packageBytes = createLoincZip();
//@formatter:off
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
/*
* Try uploading a second time
*/
//@formatter:off
respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
//@formatter:on
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
}
@Test
public void testUploadSctLocalFile() throws Exception {
byte[] packageBytes = createSctZip();
File tempFile = File.createTempFile("tmp", ".zip");
tempFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(packageBytes);
fos.close();
//@formatter:off
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("localfile", new StringType(tempFile.getAbsolutePath()))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test
public void testUploadInvalidUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO"))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadMissingUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "package", new Attachment().setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadMissingPackage() throws Exception {
//@formatter:off
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage());
}
//@formatter:on
}
private byte[] createSctZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
List<String> inputNames = Arrays.asList("sct2_Concept_Full_INT_20160131.txt","sct2_Concept_Full-en_INT_20160131.txt","sct2_Description_Full-en_INT_20160131.txt","sct2_Identifier_Full_INT_20160131.txt","sct2_Relationship_Full_INT_20160131.txt","sct2_StatedRelationship_Full_INT_20160131.txt","sct2_TextDefinition_Full-en_INT_20160131.txt");
for (String nextName : inputNames) {
zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + nextName));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName)));
}
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
}
private byte[] createLoincZip() throws IOException { private byte[] createLoincZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos); ZipOutputStream zos = new ZipOutputStream(bos);
@ -206,4 +47,170 @@ public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDs
return packageBytes; return packageBytes;
} }
private byte[] createSctZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
List<String> inputNames = Arrays.asList("sct2_Concept_Full_INT_20160131.txt","sct2_Concept_Full-en_INT_20160131.txt","sct2_Description_Full-en_INT_20160131.txt","sct2_Identifier_Full_INT_20160131.txt","sct2_Relationship_Full_INT_20160131.txt","sct2_StatedRelationship_Full_INT_20160131.txt","sct2_TextDefinition_Full-en_INT_20160131.txt");
for (String nextName : inputNames) {
zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + nextName));
byte[] b = IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName));
zos.write(b);
}
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
}
@Test
public void testUploadInvalidUrl() throws Exception {
byte[] packageBytes = createSctZip();
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO"))
.andParameter("package", new Attachment().setUrl("foo").setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage());
}
}
@Test
public void testUploadLoinc() throws Exception {
byte[] packageBytes = createLoincZip();
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
/*
* Try uploading a second time
*/
respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
}
@Test
public void testUploadMissingPackage() throws Exception {
byte[] packageBytes = createLoincZip();
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Package is missing mandatory url element"));
}
}
@Test
public void testUploadMissingUrl() throws Exception {
byte[] packageBytes = createSctZip();
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "package", new Attachment().setUrl("foo").setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage());
}
}
@Test
public void testUploadPackageMissingUrl() throws Exception {
try {
ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage());
}
}
@Test
public void testUploadSct() throws Exception {
byte[] packageBytes = createSctZip();
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test
public void testUploadSctLocalFile() throws Exception {
byte[] packageBytes = createSctZip();
File tempFile = File.createTempFile("tmp", ".zip");
tempFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(packageBytes);
fos.close();
ourLog.info("File is: {}", tempFile.getAbsolutePath());
Parameters respParam = ourClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("localfile", new StringType(tempFile.getAbsolutePath()))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
} }

View File

@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
@ -59,7 +58,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer; private static Server ourServer;
protected static GenericWebApplicationContext ourWebApplicationContext; protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProvider myTerminologyUploaderProvider; private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider; private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested; private boolean ourRestHookSubscriptionInterceptorRequested;
@ -90,7 +89,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProvider.class); myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProviderR4.class);
ourGraphQLProvider = myAppCtx.getBean("myGraphQLProvider"); ourGraphQLProvider = myAppCtx.getBean("myGraphQLProvider");
ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider, ourGraphQLProvider); ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider, ourGraphQLProvider);

View File

@ -1,9 +1,12 @@
package ca.uhn.fhir.jpa.provider.r4; package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.Matchers.greaterThan; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import static org.junit.Assert.assertEquals; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import static org.junit.Assert.assertThat; import ca.uhn.fhir.util.TestUtil;
import static org.junit.Assert.fail; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -14,48 +17,59 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.commons.io.IOUtils; import static org.hamcrest.Matchers.greaterThan;
import org.hl7.fhir.r4.model.Attachment; import static org.junit.Assert.*;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Test { public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderR4Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderR4Test.class);
@AfterClass private byte[] createLoincZip() throws IOException {
public static void afterClassClearContext() { ByteArrayOutputStream bos = new ByteArrayOutputStream();
TestUtil.clearAllStaticFieldsForUnitTest(); ZipOutputStream zos = new ZipOutputStream(bos);
zos.putNextEntry(new ZipEntry("loinc.csv"));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/loinc.csv")));
zos.putNextEntry(new ZipEntry("LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV"));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV")));
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
} }
private byte[] createSctZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
List<String> inputNames = Arrays.asList("sct2_Concept_Full_INT_20160131.txt", "sct2_Concept_Full-en_INT_20160131.txt", "sct2_Description_Full-en_INT_20160131.txt", "sct2_Identifier_Full_INT_20160131.txt", "sct2_Relationship_Full_INT_20160131.txt", "sct2_StatedRelationship_Full_INT_20160131.txt", "sct2_TextDefinition_Full-en_INT_20160131.txt");
for (String nextName : inputNames) {
zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + nextName));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName)));
}
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
}
@Test @Test
public void testUploadSct() throws Exception { public void testUploadInvalidUrl() throws Exception {
byte[] packageBytes = createSctZip(); byte[] packageBytes = createSctZip();
//@formatter:off //@formatter:off
Parameters respParam = myClient try {
myClient
.operation() .operation()
.onServer() .onServer()
.named("upload-external-code-system") .named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI)) .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO"))
.andParameter("package", new Attachment().setData(packageBytes)) .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute(); .execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage());
}
//@formatter:on //@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
} }
@Test @Test
@ -68,14 +82,14 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.onServer() .onServer()
.named("upload-external-code-system") .named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI)) .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setData(packageBytes)) .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute(); .execute();
//@formatter:on //@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp); ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); assertThat(((IntegerType) respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
/* /*
* Try uploading a second time * Try uploading a second time
@ -87,7 +101,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.onServer() .onServer()
.named("upload-external-code-system") .named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI)) .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI))
.andParameter("package", new Attachment().setData(packageBytes)) .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute(); .execute();
//@formatter:on //@formatter:on
@ -96,6 +110,62 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
} }
@Test
public void testUploadMissingPackage() throws Exception {
//@formatter:off
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadMissingUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadSct() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
Parameters respParam = myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType) respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test @Test
public void testUploadSctLocalFile() throws Exception { public void testUploadSctLocalFile() throws Exception {
byte[] packageBytes = createSctZip(); byte[] packageBytes = createSctZip();
@ -119,91 +189,12 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp); ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1)); assertThat(((IntegerType) respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
} }
@Test @AfterClass
public void testUploadInvalidUrl() throws Exception { public static void afterClassClearContext() {
byte[] packageBytes = createSctZip(); TestUtil.clearAllStaticFieldsForUnitTest();
//@formatter:off
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO"))
.andParameter("package", new Attachment().setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadMissingUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "package", new Attachment().setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage());
}
//@formatter:on
}
@Test
public void testUploadMissingPackage() throws Exception {
//@formatter:off
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage());
}
//@formatter:on
}
private byte[] createSctZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
List<String> inputNames = Arrays.asList("sct2_Concept_Full_INT_20160131.txt","sct2_Concept_Full-en_INT_20160131.txt","sct2_Description_Full-en_INT_20160131.txt","sct2_Identifier_Full_INT_20160131.txt","sct2_Relationship_Full_INT_20160131.txt","sct2_StatedRelationship_Full_INT_20160131.txt","sct2_TextDefinition_Full-en_INT_20160131.txt");
for (String nextName : inputNames) {
zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + nextName));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName)));
}
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
}
private byte[] createLoincZip() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
zos.putNextEntry(new ZipEntry("loinc.csv"));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/loinc.csv")));
zos.putNextEntry(new ZipEntry("LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV"));
zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/loinc/LOINC_2.54_MULTI-AXIAL_HIERARCHY.CSV")));
zos.close();
byte[] packageBytes = bos.toByteArray();
return packageBytes;
} }
} }

View File

@ -75,7 +75,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters));
assertEquals("SYSTEM", this.<CodeType>getPropertyPart(parameters, "property", "code").get().getValueAsString()); assertEquals("SYSTEM", this.<CodeType>getPropertyPart(parameters, "property", "code").get().getValueAsString());
assertEquals("Heart", this.<CodeType>getPropertyPart(parameters, "property", "value").get().getValueAsString()); assertEquals("Heart", this.<StringType>getPropertyPart(parameters, "property", "value").get().getValueAsString());
} }

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -126,8 +127,8 @@ public class TerminologyLoaderSvcSnomedCtTest {
try { try {
mySvc.loadSnomedCt(list(bos.toByteArray()), details); mySvc.loadSnomedCt(list(bos.toByteArray()), details);
fail(); fail();
} catch (InvalidRequestException e) { } catch (UnprocessableEntityException e) {
assertEquals("Invalid input zip file, expected zip to contain the following name fragments: [Terminology/sct2_Description_Full-en, Terminology/sct2_Relationship_Full, Terminology/sct2_Concept_Full_] but found: []", e.getMessage()); assertThat(e.getMessage(), containsString("Could not find the following mandatory files in input: "));
} }
} }

View File

@ -11,8 +11,8 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -28,136 +28,6 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
private static final String CS_URL = "http://example.com/my_code_system"; private static final String CS_URL = "http://example.com/my_code_system";
private static final String CS_URL_2 = "http://example.com/my_code_system2"; private static final String CS_URL_2 = "http://example.com/my_code_system2";
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testStoreCodeSystemInvalidCyclicLoop() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong());
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept parent = new TermConcept();
parent.setCodeSystemVersion(cs);
parent.setCode("parent");
cs.getConcepts().add(parent);
TermConcept child = new TermConcept();
child.setCodeSystemVersion(cs);
child.setCode("child");
parent.addChild(child, RelationshipTypeEnum.ISA);
child.addChild(parent, RelationshipTypeEnum.ISA);
try {
// myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", , cs);
fail();
} catch (InvalidRequestException e) {
assertEquals("CodeSystem contains circular reference around code parent", e.getMessage());
}
}
@Test
public void testFindCodesAboveAndBelowUnknown() {
createCodeSystem();
assertThat(myTermSvc.findCodesBelow("http://foo", "code"), empty());
assertThat(myTermSvc.findCodesBelow(CS_URL, "code"), empty());
assertThat(myTermSvc.findCodesAbove("http://foo", "code"), empty());
assertThat(myTermSvc.findCodesAbove(CS_URL, "code"), empty());
}
@Test
public void testFindCodesBelowA() {
IIdType id = createCodeSystem();
Set<TermConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "ParentA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA", "childAAB", "childAB"));
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB"));
// Try an unknown code
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "FOO_BAD_CODE");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testFindCodesBelowBuiltInCodeSystem() {
List<VersionIndependentConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "inactive");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("inactive", "resolved"));
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "resolved");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("resolved"));
// Unknown code
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "FOO");
codes = toCodes(concepts);
assertThat(codes, empty());
// Unknown system
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status2222", "active");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testFindCodesAboveBuiltInCodeSystem() {
List<VersionIndependentConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "active");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("active"));
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "resolved");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("inactive", "resolved"));
// Unknown code
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "FOO");
codes = toCodes(concepts);
assertThat(codes, empty());
// Unknown system
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status2222", "active");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testReindexTerminology() {
IIdType id = createCodeSystem();
assertThat(mySystemDao.markAllResourcesForReindexing(), greaterThan(0));
assertThat(mySystemDao.performReindexingPass(100), greaterThan(0));
}
@Autowired @Autowired
private ITermCodeSystemDao myTermCodeSystemDao; private ITermCodeSystemDao myTermCodeSystemDao;
@ -181,16 +51,16 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermConcept childAAA = new TermConcept(cs, "childAAA"); TermConcept childAAA = new TermConcept(cs, "childAAA");
childAAA.addPropertyString("propA", "valueAAA"); childAAA.addPropertyString("propA", "valueAAA");
childAAA.addPropertyString("propB", "foo"); childAAA.addPropertyString("propB", "foo");
childAAA.addDesignation()
.setUseSystem("http://designationsystem")
.setUseCode("somedesig")
.setUseDisplay("Designation Use")
.setValue("Bananas");
childAA.addChild(childAAA, RelationshipTypeEnum.ISA); childAA.addChild(childAAA, RelationshipTypeEnum.ISA);
TermConcept childAAB = new TermConcept(cs, "childAAB"); TermConcept childAAB = new TermConcept(cs, "childAAB");
childAAB.addPropertyString("propA", "valueAAB"); childAAB.addPropertyString("propA", "valueAAB");
childAAB.addPropertyString("propB", "foo"); childAAB.addPropertyString("propB", "foo");
childAAB.addDesignation()
.setUseSystem("D1S")
.setUseCode("D1C")
.setUseDisplay("D1D")
.setValue("D1V");
childAA.addChild(childAAB, RelationshipTypeEnum.ISA); childAA.addChild(childAAB, RelationshipTypeEnum.ISA);
TermConcept childAB = new TermConcept(cs, "childAB"); TermConcept childAB = new TermConcept(cs, "childAB");
@ -199,7 +69,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermConcept parentB = new TermConcept(cs, "ParentB"); TermConcept parentB = new TermConcept(cs, "ParentB");
cs.getConcepts().add(parentB); cs.getConcepts().add(parentB);
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL,"SYSTEM NAME" , cs); myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
return id; return id;
} }
@ -218,30 +88,48 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermConcept parentA = new TermConcept(cs, "CS2"); TermConcept parentA = new TermConcept(cs, "CS2");
cs.getConcepts().add(parentA); cs.getConcepts().add(parentA);
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2,"SYSTEM NAME" , cs); myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", cs);
return id; return id;
} }
@Test @Test
public void testFindCodesAbove() { public void testCreateDuplicateCodeSystemUri() {
IIdType id = createCodeSystem(); CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
Set<TermConcept> concepts; ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong());
Set<String> codes;
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA"); TermCodeSystemVersion cs = new TermCodeSystemVersion();
codes = toCodes(concepts); cs.setResource(table);
assertThat(codes, containsInAnyOrder("ParentA", "childAA"));
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAAB"); myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAB")); // Update
cs = new TermCodeSystemVersion();
TermConcept parentA = new TermConcept(cs, "ParentA");
cs.getConcepts().add(parentA);
id = myCodeSystemDao.update(codeSystem, null, true, true, mySrd).getId().toUnqualified();
table = myResourceTableDao.findOne(id.getIdPartAsLong());
cs.setResource(table);
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
// Try to update to a different resource
codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
table = myResourceTableDao.findOne(id.getIdPartAsLong());
cs.setResource(table);
try {
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/"));
}
// Try an unknown code
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "FOO_BAD_CODE");
codes = toCodes(concepts);
assertThat(codes, empty());
} }
@Test @Test
@ -310,18 +198,143 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
assertThat(codes, containsInAnyOrder("ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB")); assertThat(codes, containsInAnyOrder("ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB"));
} }
private List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) { @Test
List<String> retVal = new ArrayList<>(); public void testPropertiesAndDesignationsPreservedInExpansion() {
createCodeSystem();
for (ValueSet.ValueSetExpansionContainsComponent next : theContains) { List<String> codes;
retVal.add(next.getCode());
}
return retVal; ValueSet vs = new ValueSet();
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(CS_URL);
include.addConcept().setCode("childAAB");
ValueSet outcome = myTermSvc.expandValueSet(vs);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("childAAB"));
ValueSet.ValueSetExpansionContainsComponent concept = outcome.getExpansion().getContains().get(0);
assertEquals("childAAB", concept.getCode());
assertEquals("http://example.com/my_code_system", concept.getSystem());
assertEquals(null, concept.getDisplay());
assertEquals("D1S", concept.getDesignation().get(0).getUse().getSystem());
assertEquals("D1C", concept.getDesignation().get(0).getUse().getCode());
assertEquals("D1D", concept.getDesignation().get(0).getUse().getDisplay());
assertEquals("D1V", concept.getDesignation().get(0).getValue());
} }
@Test @Test
public void testCreateDuplicateCodeSystemUri() { public void testFindCodesAbove() {
IIdType id = createCodeSystem();
Set<TermConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA"));
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAAB");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAB"));
// Try an unknown code
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "FOO_BAD_CODE");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testFindCodesAboveAndBelowUnknown() {
createCodeSystem();
assertThat(myTermSvc.findCodesBelow("http://foo", "code"), empty());
assertThat(myTermSvc.findCodesBelow(CS_URL, "code"), empty());
assertThat(myTermSvc.findCodesAbove("http://foo", "code"), empty());
assertThat(myTermSvc.findCodesAbove(CS_URL, "code"), empty());
}
@Test
public void testFindCodesAboveBuiltInCodeSystem() {
List<VersionIndependentConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "active");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("active"));
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "resolved");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("inactive", "resolved"));
// Unknown code
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status", "FOO");
codes = toCodes(concepts);
assertThat(codes, empty());
// Unknown system
concepts = myTermSvc.findCodesAbove("http://hl7.org/fhir/allergy-clinical-status2222", "active");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testFindCodesBelowA() {
IIdType id = createCodeSystem();
Set<TermConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "ParentA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAA", "childAAB", "childAB"));
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB"));
// Try an unknown code
concepts = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "FOO_BAD_CODE");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testFindCodesBelowBuiltInCodeSystem() {
List<VersionIndependentConcept> concepts;
Set<String> codes;
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "inactive");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("inactive", "resolved"));
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "resolved");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("resolved"));
// Unknown code
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status", "FOO");
codes = toCodes(concepts);
assertThat(codes, empty());
// Unknown system
concepts = myTermSvc.findCodesBelow("http://hl7.org/fhir/allergy-clinical-status2222", "active");
codes = toCodes(concepts);
assertThat(codes, empty());
}
@Test
public void testReindexTerminology() {
IIdType id = createCodeSystem();
assertThat(mySystemDao.markAllResourcesForReindexing(), greaterThan(0));
assertThat(mySystemDao.performReindexingPass(100), greaterThan(0));
}
@Test
public void testStoreCodeSystemInvalidCyclicLoop() {
CodeSystem codeSystem = new CodeSystem(); CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL); codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
@ -332,31 +345,39 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermCodeSystemVersion cs = new TermCodeSystemVersion(); TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table); cs.setResource(table);
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL,"SYSTEM NAME" , cs); TermConcept parent = new TermConcept();
parent.setCodeSystemVersion(cs);
parent.setCode("parent");
cs.getConcepts().add(parent);
// Update TermConcept child = new TermConcept();
cs = new TermCodeSystemVersion(); child.setCodeSystemVersion(cs);
TermConcept parentA = new TermConcept(cs, "ParentA"); child.setCode("child");
cs.getConcepts().add(parentA); parent.addChild(child, RelationshipTypeEnum.ISA);
id = myCodeSystemDao.update(codeSystem, null, true, true, mySrd).getId().toUnqualified();
table = myResourceTableDao.findOne(id.getIdPartAsLong()); child.addChild(parent, RelationshipTypeEnum.ISA);
cs.setResource(table);
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL,"SYSTEM NAME" , cs);
// Try to update to a different resource
codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
table = myResourceTableDao.findOne(id.getIdPartAsLong());
cs.setResource(table);
try { try {
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL,"SYSTEM NAME" , cs); myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", cs);
fail(); fail();
} catch (UnprocessableEntityException e) { } catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); assertEquals("CodeSystem contains circular reference around code parent", e.getMessage());
}
} }
private List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
List<String> retVal = new ArrayList<>();
for (ValueSet.ValueSetExpansionContainsComponent next : theContains) {
retVal.add(next.getCode());
}
return retVal;
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
} }
} }

View File

@ -8,9 +8,10 @@ import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
@ -50,6 +51,13 @@ public class TestRestfulServer extends RestfulServer {
private AnnotationConfigWebApplicationContext myAppCtx; private AnnotationConfigWebApplicationContext myAppCtx;
@Override
public void destroy() {
super.destroy();
ourLog.info("Server is shutting down");
myAppCtx.destroy();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected void initialize() throws ServletException { protected void initialize() throws ServletException {
@ -120,7 +128,7 @@ public class TestRestfulServer extends RestfulServer {
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, myAppCtx.getBean(DaoConfig.class)); JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription(implDesc); confProvider.setImplementationDescription(implDesc);
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
plainProviders.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); plainProviders.add(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
break; break;
} }
case "R4": { case "R4": {
@ -138,7 +146,7 @@ public class TestRestfulServer extends RestfulServer {
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, myAppCtx.getBean(DaoConfig.class)); JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription(implDesc); confProvider.setImplementationDescription(implDesc);
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
plainProviders.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); plainProviders.add(myAppCtx.getBean(TerminologyUploaderProviderR4.class));
break; break;
} }
default: default:
@ -226,13 +234,6 @@ public class TestRestfulServer extends RestfulServer {
} }
@Override
public void destroy() {
super.destroy();
ourLog.info("Server is shutting down");
myAppCtx.destroy();
}
/** /**
* The public server is deployed to http://fhirtest.uhn.ca and the JEE webserver * The public server is deployed to http://fhirtest.uhn.ca and the JEE webserver
* where this FHIR server is deployed is actually fronted by an Apache HTTPd instance, * where this FHIR server is deployed is actually fronted by an Apache HTTPd instance,

View File

@ -1,7 +1,7 @@
package ca.uhn.fhirtest.interceptor; package ca.uhn.fhirtest.interceptor;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.BaseTerminologyUploaderProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
@ -16,8 +16,8 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class PublicSecurityInterceptor extends AuthorizationInterceptor { public class PublicSecurityInterceptor extends AuthorizationInterceptor {
private HashSet<String> myTokens;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PublicSecurityInterceptor.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PublicSecurityInterceptor.class);
private HashSet<String> myTokens;
public PublicSecurityInterceptor() { public PublicSecurityInterceptor() {
String passwordsString = System.getProperty("fhir.tdlpass"); String passwordsString = System.getProperty("fhir.tdlpass");
@ -34,7 +34,7 @@ public class PublicSecurityInterceptor extends AuthorizationInterceptor {
if (isBlank(authHeader)) { if (isBlank(authHeader)) {
return new RuleBuilder() return new RuleBuilder()
.deny().operation().named(BaseJpaSystemProvider.MARK_ALL_RESOURCES_FOR_REINDEXING).onServer().andThen() .deny().operation().named(BaseJpaSystemProvider.MARK_ALL_RESOURCES_FOR_REINDEXING).onServer().andThen()
.deny().operation().named(TerminologyUploaderProvider.UPLOAD_EXTERNAL_CODE_SYSTEM).onServer().andThen() .deny().operation().named(BaseTerminologyUploaderProvider.UPLOAD_EXTERNAL_CODE_SYSTEM).onServer().andThen()
.allowAll() .allowAll()
.build(); .build();
} }

View File

@ -279,8 +279,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
supertype = supertype.getSuperclass(); supertype = supertype.getSuperclass();
} }
try {
count += findResourceMethods(theProvider, clazz); count += findResourceMethods(theProvider, clazz);
} catch (ConfigurationException e) {
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage());
}
if (count == 0) { if (count == 0) {
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName()); throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
} }

View File

@ -19,6 +19,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.*; import java.util.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport { public class DefaultProfileValidationSupport implements IValidationSupport {
@ -33,19 +34,41 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private Map<String, StructureDefinition> myStructureDefinitions; private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets; private Map<String, ValueSet> myValueSets;
private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) {
for (ConceptDefinitionComponent next : theConcepts) {
if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) {
theRetVal
.addContains()
.setSystem(theInclude.getSystem())
.setCode(next.getCode())
.setDisplay(next.getDisplay());
}
addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept());
}
}
@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<>();
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()) { if (system != null) {
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) { List<ConceptDefinitionComponent> concepts = system.getConcept();
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); addConcepts(theInclude, retVal, wantCodes, concepts);
}
for (UriType next : theInclude.getValueSet()) {
ValueSet vs = myValueSets.get(defaultString(next.getValueAsString()));
if (vs != null) {
for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) {
ValueSetExpansionComponent contents = expandValueSet(theContext, nextInclude);
retVal.getContains().addAll(contents.getContains());
}
} }
} }

View File

@ -20,6 +20,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.*; import java.util.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport { public class DefaultProfileValidationSupport implements IValidationSupport {
@ -44,15 +45,37 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
for (ConceptDefinitionComponent next : system.getConcept()) { if (system != null) {
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) { List<ConceptDefinitionComponent> concepts = system.getConcept();
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); addConcepts(theInclude, retVal, wantCodes, concepts);
}
for (UriType next: theInclude.getValueSet()) {
ValueSet vs = myValueSets.get(defaultString(next.getValueAsString()));
if (vs != null) {
for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) {
ValueSetExpansionComponent contents = expandValueSet(theContext, nextInclude);
retVal.getContains().addAll(contents.getContains());
}
} }
} }
return retVal; return retVal;
} }
private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) {
for (ConceptDefinitionComponent next : theConcepts) {
if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) {
theRetVal
.addContains()
.setSystem(theInclude.getSystem())
.setCode(next.getCode())
.setDisplay(next.getDisplay());
}
addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept());
}
}
@Override @Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) { public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
ArrayList<IBaseResource> retVal = new ArrayList<>(); ArrayList<IBaseResource> retVal = new ArrayList<>();

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -14,6 +15,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValidationSupportChain implements IValidationSupport { public class ValidationSupportChain implements IValidationSupport {
@ -47,10 +49,18 @@ public class ValidationSupportChain implements IValidationSupport {
@Override @Override
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
if (isNotBlank(theInclude.getSystem())) {
if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) {
return next.expandValueSet(theCtx, theInclude); return next.expandValueSet(theCtx, theInclude);
} }
} }
for (UriType nextValueSet : theInclude.getValueSet()) {
ValueSetExpansionComponent retVal = next.expandValueSet(theCtx, theInclude);
if (retVal != null && retVal.getContains().size() > 0) {
return retVal;
}
}
}
return myChain.get(0).expandValueSet(theCtx, theInclude); return myChain.get(0).expandValueSet(theCtx, theInclude);
} }