Tests all passing

This commit is contained in:
jamesagnew 2018-03-30 15:18:57 -04:00
parent 673a3d6272
commit c5b286921e
41 changed files with 2152 additions and 1234 deletions

View File

@ -5,9 +5,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
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.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.rp.dstu3.ActivityDefinitionResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider;
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
* with this feature.
*/
registerProvider(myAppCtx.getBean(TerminologyUploaderProvider.class));
registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
}
public IResourceProvider getProvider(String name) {

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.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.*;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
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.validation.IValidatorModule;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
@ -115,8 +118,8 @@ public class BaseDstu3Config extends BaseConfig {
}
@Bean(autowire = Autowire.BY_TYPE)
public TerminologyUploaderProvider terminologyUploaderProvider() {
TerminologyUploaderProvider retVal = new TerminologyUploaderProvider();
public TerminologyUploaderProviderDstu3 terminologyUploaderProvider() {
TerminologyUploaderProviderDstu3 retVal = new TerminologyUploaderProviderDstu3();
retVal.setContext(fhirContextDstu3());
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.SearchParamRegistryR4;
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.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
@ -133,8 +133,8 @@ public class BaseR4Config extends BaseConfig {
}
@Bean(autowire = Autowire.BY_TYPE)
public TerminologyUploaderProvider terminologyUploaderProvider() {
TerminologyUploaderProvider retVal = new TerminologyUploaderProvider();
public TerminologyUploaderProviderR4 terminologyUploaderProvider() {
TerminologyUploaderProviderR4 retVal = new TerminologyUploaderProviderR4();
retVal.setContext(fhirContextR4());
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.RestSearchParameterTypeEnum;
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.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
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.util.*;
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.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Reference;
import org.springframework.beans.factory.annotation.Autowired;
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 String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
private static boolean ourValidationDisabledForUnitTest;
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
static {
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);
}
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
public void injectDependenciesIntoBundleProvider(PersistedJpaBundleProvider theProvider) {
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);
}
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) {
if (theElement == null) {
return;
@ -2133,6 +2200,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return b.toString();
}
@VisibleForTesting
public static void setDisableIncrementOnUpdateForUnitTest(boolean theDisableIncrementOnUpdateForUnitTest) {
ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
}
/**
* 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> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired
@ -592,22 +591,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
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) {
if (theRequestDetails == null || theRequestDetails.getServer() == null) {
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() + "]");
}
// Notify interceptors
ActionRequestDetails requestDetails = null;
if (theRequestDetails != null) {
requestDetails = new ActionRequestDetails(theRequestDetails, theResource, getResourceName(), resourceId);
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
}
IBaseResource oldResource = toResource(entity, false);
// Notify IServerOperationInterceptors about pre-action call
if (theRequestDetails != null) {
theRequestDetails.getRequestOperationCallback().resourcePreUpdate(oldResource, theResource);
/*
* If we aren't indexing, that means we're doing this inside a transaction.
* 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),
* 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
* Otherwise, we're not in a transaction
*/
if (!thePerformIndexing && !savedEntity.isUnchangedInCurrentOperation() && !ourDisableIncrementOnUpdateForUnitTest) {
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);
}
}
}
ResourceTable savedEntity = updateInternal(theResource, thePerformIndexing, theForceUpdateVersion, theRequestDetails, entity, resourceId, oldResource);
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
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.rest.api.MethodOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class DaoMethodOutcome extends MethodOutcome {
private ResourceTable myEntity;
private IBaseResource myPreviousResource;
public ResourceTable getEntity() {
return myEntity;
}
@Override
public DaoMethodOutcome setCreated(Boolean theCreated) {
super.setCreated(theCreated);
return this;
}
public DaoMethodOutcome setEntity(ResourceTable theEntity) {
myEntity = theEntity;
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.
* #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.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.EntryResponse;
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.InstantDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.DataFormatException;
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.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.method.BaseMethodBinding;
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.UrlUtil;
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> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class);
@ -142,116 +149,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
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")
private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleTypeEnum transactionType = theRequest.getTypeElement().getValueAsEnum();
@ -299,10 +196,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
}
Collections.sort(theRequest.getEntry(), new TransactionSorter());
List<IIdType> deletedResources = new ArrayList<IIdType>();
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();
Map<Entry, ResourceTable> entriesToProcess = new IdentityHashMap<Entry, ResourceTable>();
List<IIdType> deletedResources = new ArrayList<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<Entry, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<ResourceTable>();
Set<ResourceTable> updatedEntities = new HashSet<>();
/*
* Loop through the request and process any entries of type
@ -321,7 +219,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
nextResourceId = res.getId();
if (nextResourceId.hasIdPart() == false) {
if (!nextResourceId.hasIdPart()) {
if (isNotBlank(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);
}
if (outcome.getCreated() == Boolean.FALSE) {
updatedEntities.add(outcome.getEntity());
}
handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
@ -435,12 +337,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
* end.
*/
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext(); ) {
DeleteConflict next = iter.next();
if (deletedResources.contains(next.getTargetId().toVersionless())) {
iter.remove();
}
}
deleteConflicts.removeIf(next -> deletedResources.contains(next.getTargetId().toVersionless()));
validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/*
@ -489,8 +386,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
boolean shouldUpdate = !nonUpdatedEntities.contains(nextOutcome.getEntity());
if (shouldUpdate) {
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, updateTime, false, true);
}
}
@ -609,6 +507,115 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
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,
Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();
@ -645,9 +652,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
}
//@formatter:off
/**
* Transaction Order, per the spec:
*
* <p>
* Process any DELETE interactions
* Process any POST interactions
* Process any PUT interactions

View File

@ -1193,7 +1193,7 @@ public class SearchBuilder implements ISearchBuilder {
codes = myTerminologySvc.findCodesBelow(system, code);
}
ArrayList<Predicate> singleCodePredicates = new ArrayList<Predicate>();
ArrayList<Predicate> singleCodePredicates = new ArrayList<>();
if (codes != null) {
if (codes.isEmpty()) {
@ -1581,7 +1581,8 @@ public class SearchBuilder implements ISearchBuilder {
}
}
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) {
if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem();

View File

@ -1,5 +1,12 @@
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;
/*
@ -22,14 +29,12 @@ import java.util.List;
* #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> {
@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")
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")
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
void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid);
List<TermConcept> findByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Query("UPDATE TermConcept t SET t.myIndexStatus = null")
@Modifying

View File

@ -158,13 +158,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
ourLog.debug("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
// long start = System.currentTimeMillis();
final StopWatch transactionSw = new StopWatch();
final Date updateTime = new Date();
final Set<IdType> allIds = new LinkedHashSet<IdType>();
final Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>();
final Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>();
final Set<IdType> allIds = new LinkedHashSet<>();
final Map<IdType, IdType> idSubstitutions = new HashMap<>();
final Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>();
// Do all entries have a verb?
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>>() {
@Override
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()) {
@ -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);
return response;
}
@SuppressWarnings("unchecked")
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date updateTime, Set<IdType> allIds,
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> idToPersistedOutcome, Bundle response, IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder, List<BundleEntryComponent> theEntries, StopWatch theStopWatch) {
private Map<BundleEntryComponent, ResourceTable> doTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set<IdType> theAllIds,
Map<IdType, IdType> theIdSubstitutions, Map<IdType, DaoMethodOutcome> theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap<BundleEntryComponent, Integer> theOriginalRequestOrder, List<BundleEntryComponent> theEntries) {
Set<String> deletedResources = new HashSet<>();
List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/*
@ -333,7 +328,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
nextResourceId = res.getIdElement();
if (nextResourceId.hasIdPart() == false) {
if (!nextResourceId.hasIdPart()) {
if (isNotBlank(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
*/
if (isPlaceholder(nextResourceId)) {
if (!allIds.add(nextResourceId)) {
if (!theAllIds.add(nextResourceId)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId));
}
} else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
IdType nextId = nextResourceId.toUnqualifiedVersionless();
if (!allIds.add(nextId)) {
if (!theAllIds.add(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();
String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
BundleEntryComponent nextRespEntry = response.getEntry().get(originalRequestOrder.get(nextReqEntry));
theStopWatch.startTask("Process entry " + i + ": " + verb + " " + defaultString(resourceType));
BundleEntryComponent nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry));
switch (verb) {
case POST: {
@ -382,7 +375,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
outcome = resourceDao.create(res, matchUrl, false, theRequestDetails);
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());
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());
break;
}
case GET:
case NULL:
break;
}
}
theStopWatch.endCurrentTask();
}
}
/*
* 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.
*/
for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext();) {
DeleteConflict next = iter.next();
if (deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue())) {
iter.remove();
}
}
deleteConflicts.removeIf(next ->
deletedResources.contains(next.getTargetId().toUnqualifiedVersionless().getValue()));
validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
/*
@ -492,13 +484,13 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
*/
FhirTerser terser = getContext().newTerser();
for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) {
for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) {
IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) {
continue;
}
// Refererences
// References
List<IBaseReference> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class);
for (IBaseReference nextRef : allRefs) {
IIdType nextId = nextRef.getReferenceElement();
@ -534,18 +526,16 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
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();
theStopWatch.endCurrentTask();
/*
* 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);
if (replacement == null) {
continue;

View File

@ -19,6 +19,8 @@ import javax.transaction.Transactional.TxType;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -87,6 +89,9 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3 {
@Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
if (isBlank(theSystem)) {
return null;
}
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.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
@ -52,6 +51,7 @@ import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.UrlUtil.UrlParts;
import com.google.common.collect.ArrayListMultimap;
import javolution.io.Struct;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.*;
@ -323,6 +323,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
List<DeleteConflict> deleteConflicts = new ArrayList<>();
Map<BundleEntryComponent, ResourceTable> entriesToProcess = new IdentityHashMap<>();
Set<ResourceTable> nonUpdatedEntities = new HashSet<>();
Set<ResourceTable> updatedEntities = new HashSet<>();
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
/*
@ -467,6 +468,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);
entriesToProcess.put(nextRespEntry, outcome.getEntity());
break;
@ -538,9 +543,11 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
IPrimitiveType<Date> deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
boolean shouldUpdate = !nonUpdatedEntities.contains(nextOutcome.getEntity());
if (shouldUpdate) {
updateEntity(nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, shouldUpdate, false, theUpdateTime, false, true);
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);
}
}

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*;
import java.io.Serializable;
@ -89,4 +92,15 @@ public class TermCodeSystem implements Serializable {
public void setResource(ResourceTable 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.
*/
@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;
@SuppressWarnings("unused")
@OneToOne(mappedBy = "myCurrentVersion", optional = true)

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.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.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
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.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
public class TerminologyUploaderProvider extends BaseJpaProvider {
public abstract class BaseTerminologyUploaderProvider extends BaseJpaProvider {
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
private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc;
@Operation(name = UPLOAD_EXTERNAL_CODE_SYSTEM, idempotent = false, returnParameters= {
@OperationParam(name="conceptCount", type=IntegerType.class, min=1)
})
public Parameters uploadExternalCodeSystem(
protected Parameters handleUploadExternalCodeSystem(
HttpServletRequest theServletRequest,
@OperationParam(name="url", min=1) StringParam theCodeSystemUrl,
@OperationParam(name="localfile", min=1, max=OperationParam.MAX_UNLIMITED) List<StringType> theLocalFile,
RequestDetails theRequestDetails
StringParam theCodeSystemUrl,
List<StringType> theLocalFile,
List<Attachment> thePackage, RequestDetails theRequestDetails
) {
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 {
List<IHapiTerminologyLoaderSvc.FileDescriptor> localFiles = new ArrayList<>();
if (theLocalFile != null && theLocalFile.size() > 0) {
@ -70,7 +70,7 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
if (isNotBlank(nextLocalFile.getValue())) {
ourLog.info("Reading in local 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());
}
localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() {
@ -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;
url = defaultString(url);

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.jpa.provider.dstu3;
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,30 @@
package ca.uhn.fhir.jpa.provider.r4;
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

@ -94,7 +94,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
private boolean myProcessDeferred = true;
@Autowired
private PlatformTransactionManager myTransactionMgr;
@Autowired
@Autowired(required = false)
private IFhirResourceDaoCodeSystem<?, ?, ?> myCodeSystemResourceDao;
private void addCodeIfNotAlreadyAdded(String theCodeSystem, ValueSet.ValueSetExpansionComponent theExpansionComponent, Set<String> theAddedCodes, TermConcept theConcept) {
@ -106,6 +106,19 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
}
}
private void addConceptsToList(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set<String> theAddedCodes, String theSystem, List<CodeSystem.ConceptDefinitionComponent> theConcept) {
for (CodeSystem.ConceptDefinitionComponent next : theConcept) {
if (!theAddedCodes.contains(next.getCode())) {
theAddedCodes.add(next.getCode());
ValueSet.ValueSetExpansionContainsComponent contains = theExpansionComponent.addContains();
contains.setCode(next.getCode());
contains.setSystem(theSystem);
contains.setDisplay(next.getDisplay());
}
addConceptsToList(theExpansionComponent, theAddedCodes, theSystem, next.getConcept());
}
}
private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction<?> bool, ValueSet.ConceptSetFilterComponent nextFilter) {
bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery());
}
@ -142,11 +155,28 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
@Override
public void deleteCodeSystem(TermCodeSystem theCodeSystem) {
ourLog.info(" * Deleting code system {}", theCodeSystem.getPid());
myEntityManager.flush();
TermCodeSystem cs = myCodeSystemDao.findOne(theCodeSystem.getPid());
cs.setCurrentVersion(null);
myCodeSystemDao.save(cs);
myCodeSystemDao.flush();
for (TermCodeSystemVersion next : myCodeSystemVersionDao.findByCodeSystemResource(theCodeSystem.getPid())) {
myConceptParentChildLinkDao.deleteByCodeSystemVersion(next.getPid());
myConceptDao.deleteByCodeSystemVersion(next.getPid());
for (TermConcept nextConcept : myConceptDao.findByCodeSystemVersion(next.getPid())) {
myConceptPropertyDao.delete(nextConcept.getProperties());
myConceptDao.delete(nextConcept);
}
myCodeSystemDao.delete(theCodeSystem.getPid());
if (next.getCodeSystem().getCurrentVersion() == next) {
next.getCodeSystem().setCurrentVersion(null);
myCodeSystemDao.save(next.getCodeSystem());
}
myCodeSystemVersionDao.delete(next);
}
myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem);
myCodeSystemDao.delete(theCodeSystem);
}
private int ensureParentsSaved(Collection<TermConceptParentChildLink> theParents) {
@ -171,22 +201,19 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
@Override
@Transactional(propagation = Propagation.REQUIRED)
public ValueSet expandValueSet(ValueSet theValueSetToExpand) {
ValueSet.ConceptSetComponent include = theValueSetToExpand.getCompose().getIncludeFirstRep();
String system = include.getSystem();
ourLog.info("Starting expansion around code system: {}", system);
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system);
if (cs == null) {
throw new InvalidRequestException("Unknown code system: " + system);
}
TermCodeSystemVersion csv = cs.getCurrentVersion();
ValueSet.ValueSetExpansionComponent expansionComponent = new ValueSet.ValueSetExpansionComponent();
Set<String> addedCodes = new HashSet<>();
boolean haveIncludeCriteria = false;
for (ValueSet.ConceptSetComponent include : theValueSetToExpand.getCompose().getInclude()) {
String system = include.getSystem();
if (isNotBlank(system)) {
ourLog.info("Starting expansion around code system: {}", system);
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system);
if (cs != null) {
TermCodeSystemVersion csv = cs.getCurrentVersion();
/*
* Include Concepts
*/
@ -279,14 +306,53 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
}
}
} else {
// No codesystem matching the URL found in the database
CodeSystem codeSystemFromContext = getCodeSystemFromContext(system);
if (codeSystemFromContext == null) {
throw new InvalidRequestException("Unknown code system: " + system);
}
if (include.getConcept().isEmpty() == false) {
for (ValueSet.ConceptReferenceComponent next : include.getConcept()) {
String nextCode = next.getCode();
if (isNotBlank(nextCode) && !addedCodes.contains(nextCode)) {
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (code != null) {
addedCodes.add(nextCode);
ValueSet.ValueSetExpansionContainsComponent contains = expansionComponent.addContains();
contains.setCode(nextCode);
contains.setSystem(system);
contains.setDisplay(code.getDisplay());
}
}
}
} else {
List<CodeSystem.ConceptDefinitionComponent> concept = codeSystemFromContext.getConcept();
addConceptsToList(expansionComponent, addedCodes, system, concept);
}
}
}
}
ValueSet valueSet = new ValueSet();
valueSet.setExpansion(expansionComponent);
return valueSet;
}
@Override
public List<VersionIndependentConcept> expandValueSet(String theValueSet) {
throw new UnsupportedOperationException(); // FIXME implement
protected List<VersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4) {
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSet(theValueSetToExpandR4).getExpansion();
ArrayList<VersionIndependentConcept> retVal = new ArrayList<>();
for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent nextContains : expandedR4.getContains()) {
retVal.add(
new VersionIndependentConcept()
.setSystem(nextContains.getSystem())
.setCode(nextContains.getCode()));
}
return retVal;
}
private void fetchChildren(TermConcept theConcept, Set<TermConcept> theSetToPopulate) {
@ -312,6 +378,16 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
}
}
private CodeSystem.ConceptDefinitionComponent findCode(List<CodeSystem.ConceptDefinitionComponent> theConcepts, String theCode) {
for (CodeSystem.ConceptDefinitionComponent next : theConcepts) {
if (theCode.equals(next.getCode())) {
return next;
}
findCode(next.getConcept(), theCode);
}
return null;
}
@Override
public TermConcept findCode(String theCodeSystem, String theCode) {
TermCodeSystemVersion csv = findCurrentCodeSystemVersionForSystem(theCodeSystem);
@ -398,6 +474,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
return myCodeSystemDao.findByCodeSystemUri(theSystem);
}
protected abstract CodeSystem getCodeSystemFromContext(String theSystem);
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
return;
@ -636,12 +714,16 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc
for (TermCodeSystemVersion next : existing) {
ourLog.info(" * Deleting code system version {}", next.getPid());
myConceptParentChildLinkDao.deleteByCodeSystemVersion(next.getPid());
myConceptDao.deleteByCodeSystemVersion(next.getPid());
for (TermConcept nextConcept : myConceptDao.findByCodeSystemVersion(next.getPid())) {
myConceptPropertyDao.delete(nextConcept.getProperties());
myConceptDao.delete(nextConcept);
}
}
ourLog.info("Flushing...");
myConceptParentChildLinkDao.flush();
myConceptPropertyDao.flush();
myConceptDao.flush();
ourLog.info("Done flushing");

View File

@ -76,6 +76,11 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl {
throw new UnsupportedOperationException();
}
@Override
protected CodeSystem getCodeSystemFromContext(String theSystem) {
return null;
}
@Override
public List<VersionIndependentConcept> expandValueSet(String theValueSet) {
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
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
return null;
@ -222,6 +240,16 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
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
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return myTerminologySvc.supportsSystem(theSystem);

View File

@ -112,6 +112,16 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
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
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSet valueSetToExpand = new ValueSet();
@ -188,6 +198,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
return retVal;
}
@Override
protected CodeSystem getCodeSystemFromContext(String theSystem) {
return myValidationSupport.fetchCodeSystem(myContext, theSystem);
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return myTerminologySvc.supportsSystem(theSystem);

View File

@ -128,6 +128,13 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
CSVParser parsed;
try {
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();
if (theQuoteMode != null) {
format = format.withQuote('"').withQuoteMode(theQuoteMode);
@ -402,16 +409,15 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc {
LoadedFileDescriptors(List<IHapiTerminologyLoaderSvc.FileDescriptor> theFileDescriptors) {
try {
for (FileDescriptor next : theFileDescriptors) {
File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp");
nextTemporaryFile.deleteOnExit();
if (next.getFilename().toLowerCase().endsWith(".zip")) {
ourLog.info("Uncompressing {} into temporary files", next.getFilename());
try (InputStream inputStream = next.getInputStream()) {
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(inputStream));
for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) {
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);
String nextEntryFileName = nextEntry.getName();
myUncompressedFileDescriptors.add(new FileDescriptor() {

View File

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

View File

@ -96,7 +96,7 @@ public class TestUtil {
assertThat(joinColumn.name(), null);
ForeignKey fk = joinColumn.foreignKey();
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_"));
assertThat(fk.name(), theNames);
}

View File

@ -252,6 +252,7 @@ public abstract class BaseJpaTest {
public Void doInTransaction(TransactionStatus theStatus) {
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 " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null").executeUpdate();
return null;
}
});
@ -279,6 +280,7 @@ public abstract class BaseJpaTest {
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("DELETE from " + TermConceptProperty.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()) {
next.setCurrentVersion(null);

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.dstu2;
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.provider.SystemProviderDstu2Test;
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.HTTPVerbEnum;
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.UriDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
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.interceptor.IServerInterceptor.ActionRequestDetails;
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)
public void testTransactionFailsWithDuplicateIds() {
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
public void 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
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.server.IBundleProvider;
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.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@ -1439,6 +1440,213 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
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
public void testTransactionDoubleConditionalCreateOnlyCreatesOne() {
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.server.IBundleProvider;
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.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@ -1515,13 +1516,19 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testTransactionDoesntDoubleCreate() {
public void testTransactionDoesntUpdateUnchangesResourceWithPlaceholderIds() {
Bundle output, input;
BundleEntryResponseComponent respEntry;
IdType createdId;
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");
@ -1542,24 +1549,51 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.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());
createdId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdId.getResourceType());
assertEquals("1", createdId.getVersionIdPart());
createdPatientId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdPatientId.getResourceType());
assertEquals("1", createdPatientId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("201 Created", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdId.getResourceType());
assertEquals("1", createdId.getVersionIdPart());
IdType createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdObservationId.getResourceType());
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();
pat = new Patient();
pat.setId(IdType.newRandomUuid());
pat.addIdentifier().setSystem("foo").setValue("bar");
@ -1580,20 +1614,111 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.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());
createdId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdId.getResourceType());
assertEquals("1", createdId.getVersionIdPart());
createdObservationId = new IdType(respEntry.getLocation());
assertEquals("Patient", createdObservationId.getResourceType());
assertEquals("1", createdObservationId.getVersionIdPart());
respEntry = output.getEntry().get(1).getResponse();
assertEquals("200 OK", respEntry.getStatus());
createdId = new IdType(respEntry.getLocation());
assertEquals("Observation", createdId.getResourceType());
assertEquals("1", createdId.getVersionIdPart());
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());
}

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.dstu3.BaseJpaDstu3Test;
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.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
@ -54,16 +53,16 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
protected static CloseableHttpClient ourHttpClient;
protected static int ourPort;
protected static RestfulServer ourRestServer;
private static Server ourServer;
protected static String ourServerBase;
protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProvider myTerminologyUploaderProvider;
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
protected static DatabaseBackedPagingProvider ourPagingProvider;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor;
protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer;
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
public BaseResourceProviderDstu3Test() {
super();
@ -93,7 +92,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProvider.class);
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProviderDstu3.class);
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 IIdType myExtensionalVsId;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before
@Transactional
public void before02() throws IOException {
@ -64,7 +58,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
ourLog.info(resp);
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("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(2).getName());
@ -84,7 +78,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst
ourLog.info(resp);
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("Parent A", ((StringType)respParam.getParameter().get(1).getValue()).getValue());
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());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@ -14,6 +15,7 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.Attachment;
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);
@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 {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(bos);
@ -206,4 +47,170 @@ public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDs
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.r4.BaseJpaR4Test;
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.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
@ -59,7 +58,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer;
protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProvider myTerminologyUploaderProvider;
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
@ -90,7 +89,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProvider.class);
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProviderR4.class);
ourGraphQLProvider = myAppCtx.getBean("myGraphQLProvider");
ourRestServer.setPlainProviders(mySystemProvider, myTerminologyUploaderProvider, ourGraphQLProvider);

View File

@ -1,9 +1,12 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
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.File;
@ -14,48 +17,59 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.IOUtils;
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 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;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.*;
public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderR4Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
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;
}
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
public void testUploadSct() throws Exception {
public void testUploadInvalidUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@formatter:off
Parameters respParam = myClient
try {
myClient
.operation()
.onServer()
.named("upload-external-code-system")
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI))
.andParameter("package", new Attachment().setData(packageBytes))
.withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO"))
.andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown URL: http://snomed.info/sctFOO", e.getMessage());
}
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertThat(((IntegerType)respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test
@ -68,7 +82,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.onServer()
.named("upload-external-code-system")
.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();
//@formatter:on
@ -87,7 +101,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.onServer()
.named("upload-external-code-system")
.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();
//@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
public void testUploadSctLocalFile() throws Exception {
byte[] packageBytes = createSctZip();
@ -122,88 +192,9 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
assertThat(((IntegerType) respParam.getParameter().get(0).getValue()).getValue(), greaterThan(1));
}
@Test
public void testUploadInvalidUrl() throws Exception {
byte[] packageBytes = createSctZip();
//@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;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -75,7 +75,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters));
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.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.model.CodeSystem;
@ -126,8 +127,8 @@ public class TerminologyLoaderSvcSnomedCtTest {
try {
mySvc.loadSnomedCt(list(bos.toByteArray()), details);
fail();
} catch (InvalidRequestException 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());
} catch (UnprocessableEntityException e) {
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 org.hl7.fhir.dstu3.model.CodeSystem;
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.r4.model.ValueSet;
import org.junit.AfterClass;
import org.junit.Test;
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_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
private ITermCodeSystemDao myTermCodeSystemDao;
@ -219,24 +89,42 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
}
@Test
public void testFindCodesAbove() {
IIdType id = createCodeSystem();
public void testCreateDuplicateCodeSystemUri() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
Set<TermConcept> concepts;
Set<String> codes;
ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong());
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAA");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA"));
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
concepts = myTermSvc.findCodesAbove(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "childAAB");
codes = toCodes(concepts);
assertThat(codes, containsInAnyOrder("ParentA", "childAA", "childAAB"));
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
// 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
@ -305,18 +193,118 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
assertThat(codes, containsInAnyOrder("ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB"));
}
private List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
List<String> retVal = new ArrayList<>();
@Test
public void testFindCodesAbove() {
IIdType id = createCodeSystem();
for (ValueSet.ValueSetExpansionContainsComponent next : theContains) {
retVal.add(next.getCode());
}
Set<TermConcept> concepts;
Set<String> codes;
return retVal;
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 testCreateDuplicateCodeSystemUri() {
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.setUrl(CS_URL);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
@ -327,31 +315,39 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermCodeSystemVersion cs = new TermCodeSystemVersion();
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
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);
TermConcept child = new TermConcept();
child.setCodeSystemVersion(cs);
child.setCode("child");
parent.addChild(child, RelationshipTypeEnum.ISA);
child.addChild(parent, RelationshipTypeEnum.ISA);
// 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);
myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "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/"));
}
} catch (InvalidRequestException e) {
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

@ -279,8 +279,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
supertype = supertype.getSuperclass();
}
try {
count += findResourceMethods(theProvider, clazz);
} catch (ConfigurationException e) {
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage());
}
if (count == 0) {
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.util.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport {
@ -33,19 +34,41 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private Map<String, StructureDefinition> myStructureDefinitions;
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
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
Set<String> wantCodes = new HashSet<String>();
Set<String> wantCodes = new HashSet<>();
for (ConceptReferenceComponent next : theInclude.getConcept()) {
wantCodes.add(next.getCode());
}
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
for (ConceptDefinitionComponent next : system.getConcept()) {
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) {
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
if (system != null) {
List<ConceptDefinitionComponent> concepts = system.getConcept();
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.util.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport {
@ -44,15 +45,37 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
for (ConceptDefinitionComponent next : system.getConcept()) {
if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) {
retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
if (system != null) {
List<ConceptDefinitionComponent> concepts = system.getConcept();
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;
}
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
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
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.model.CodeSystem;
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.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -14,6 +15,7 @@ import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValidationSupportChain implements IValidationSupport {
@ -47,10 +49,18 @@ public class ValidationSupportChain implements IValidationSupport {
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {
for (IValidationSupport next : myChain) {
if (isNotBlank(theInclude.getSystem())) {
if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) {
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);
}