diff --git a/examples/src/main/java/example/ServerMetadataExamples.java b/examples/src/main/java/example/ServerMetadataExamples.java index be28683b9b1..20b5e98b838 100644 --- a/examples/src/main/java/example/ServerMetadataExamples.java +++ b/examples/src/main/java/example/ServerMetadataExamples.java @@ -1,15 +1,13 @@ package example; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.rest.annotation.Search; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Patient; + import java.util.ArrayList; import java.util.List; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.Tag; -import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.annotation.Search; - public class ServerMetadataExamples { // START SNIPPET: serverMethod @@ -21,34 +19,21 @@ public class ServerMetadataExamples { Patient patient = new Patient(); retVal.add(patient); patient.setId("Patient/123"); - patient.addName().addFamily("Smith").addGiven("John"); - - // Create a tag list and add it to the resource - TagList tags = new TagList(); - tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag1.html", "Some tag"); - tags.addTag(Tag.HL7_ORG_FHIR_TAG, "http://foo/tag2.html", "Another tag"); - ResourceMetadataKeyEnum.TAG_LIST.put(patient, tags); - - // Set some links (these can be provided as relative links or absolute) - // and the server will convert to absolute as appropriate - String linkAlternate = "Patient/7736"; - ResourceMetadataKeyEnum.LINK_ALTERNATE.put(patient, linkAlternate); - String linkSearch = "Patient?name=smith&name=john"; - ResourceMetadataKeyEnum.LINK_SEARCH.put(patient, linkSearch); - - // Set the published and updated dates - InstantDt pubDate = new InstantDt("2011-02-22"); - ResourceMetadataKeyEnum.PUBLISHED.put(patient, pubDate); - InstantDt updatedDate = new InstantDt("2014-07-12T11:22:27Z"); - ResourceMetadataKeyEnum.UPDATED.put(patient, updatedDate); - - // Set the resource title (note that if you are using HAPI's narrative - // generation capability, the narrative generator will often create - // useful titles automatically, and the server will create a default - // title if none is provided) - String title = "Patient John SMITH"; - ResourceMetadataKeyEnum.TITLE.put(patient, title); + patient.addName().setFamily("Smith").addGiven("John"); + // Add tags + patient.getMeta().addTag() + .setSystem(Tag.HL7_ORG_FHIR_TAG) + .setCode("some_tag") + .setDisplay("Some tag"); + patient.getMeta().addTag() + .setSystem(Tag.HL7_ORG_FHIR_TAG) + .setCode("another_tag") + .setDisplay("Another tag"); + + // Set the last updated date + patient.getMeta().setLastUpdatedElement(new InstantType("2011-02-22T11:22:00.0122Z")); + return retVal; } // END SNIPPET: serverMethod diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index b7209b8d612..a266fc8403f 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -120,6 +120,11 @@ true + + org.awaitility + awaitility + test + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index 00a92b82bda..3350f2959d7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -20,6 +20,7 @@ import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.validation.FhirValidator; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.jena.riot.Lang; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -896,6 +897,11 @@ public class FhirContext { } } + @Override + public String toString() { + return "FhirContext[" + myVersion.getVersion().name() + "]"; + } + /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java index e84549679ad..de0fd705212 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -21,7 +21,6 @@ package ca.uhn.fhir.model.api; */ import ca.uhn.fhir.model.base.composite.BaseCodingDt; -import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; @@ -29,7 +28,6 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import java.io.Serializable; @@ -92,29 +90,7 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { theResource.setUserData(DELETED_AT.name(), theObject); } }; - /** - * Denotes the search score which a given resource should match in a transaction. See the FHIR transaction definition for information about this. Corresponds to the value in - * Bundle.entry.score in a Bundle resource. - *

- * Note that search URL is only used in FHIR DSTU2 and later. - *

- *

- * Values for this key are of type {@link DecimalDt} - *

- */ - public static final ResourceMetadataKeyEnum ENTRY_SCORE = new ResourceMetadataKeyEnum("ENTRY_SCORE") { - private static final long serialVersionUID = 1L; - @Override - public DecimalDt get(IResource theResource) { - return getDecimalFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ENTRY_SCORE); - } - - @Override - public void put(IResource theResource, DecimalDt theObject) { - theResource.getResourceMetadata().put(ENTRY_SCORE, theObject); - } - }; /** * If present and populated with a {@link BundleEntrySearchModeEnum}, contains the "bundle entry search mode", which is the value of the status field in the Bundle entry containing this resource. * The value for this key corresponds to field Bundle.entry.search.mode. This value can be set to provide a status value of "include" for included resources being returned by a @@ -187,67 +163,7 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { } }; - /** - * If present and populated with a string, provides the "alternate link" (the link element in the bundle entry with rel="alternate"). Server implementations may populate this with a - * complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient/1243") in which case the server will convert this to - * an absolute URL at runtime. - *

- * Values for this key are of type {@link String} - *

- */ - public static final ResourceMetadataKeyEnum LINK_ALTERNATE = new ResourceMetadataKeyEnum("LINK_ALTERNATE") { - private static final long serialVersionUID = 1L; - @Override - public String get(IResource theResource) { - return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_ALTERNATE); - } - - @Override - public void put(IResource theResource, String theObject) { - theResource.getResourceMetadata().put(LINK_ALTERNATE, theObject); - } - }; - /** - * If present and populated with a string, provides the "search link" (the link element in the bundle entry with rel="search"). Server implementations may populate this with a - * complete URL, in which case the URL will be placed as-is in the bundle. They may alternately specify a resource relative URL (e.g. "Patient?name=tester") in which case the server will convert - * this to an absolute URL at runtime. - *

- * Values for this key are of type {@link String} - *

- */ - public static final ResourceMetadataKeyEnum LINK_SEARCH = new ResourceMetadataKeyEnum("LINK_SEARCH") { - private static final long serialVersionUID = 1L; - - @Override - public String get(IResource theResource) { - return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), LINK_SEARCH); - } - - @Override - public void put(IResource theResource, String theObject) { - theResource.getResourceMetadata().put(LINK_SEARCH, theObject); - } - }; - /** - * The value for this key represents a previous ID used to identify this resource. This key is currently only used internally during transaction method processing. - *

- * Values for this key are of type {@link IdDt} - *

- */ - public static final ResourceMetadataKeyEnum PREVIOUS_ID = new ResourceMetadataKeyEnum("PREVIOUS_ID") { - private static final long serialVersionUID = 1L; - - @Override - public IdDt get(IResource theResource) { - return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), PREVIOUS_ID); - } - - @Override - public void put(IResource theResource, IdDt theObject) { - theResource.getResourceMetadata().put(PREVIOUS_ID, theObject); - } - }; /** * The value for this key represents a {@link List} of profile IDs that this resource claims to conform to. *

@@ -301,18 +217,8 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { if (obj == null) { return null; } - try { - @SuppressWarnings("unchecked") - List securityLabels = (List) obj; - if (securityLabels.isEmpty()) { - return null; - } - return securityLabels; - } catch (ClassCastException e) { - throw new InternalErrorException("Found an object of type '" + obj.getClass().getCanonicalName() + "' in resource metadata for key SECURITY_LABELS - Expected " - + BaseCodingDt.class.getCanonicalName()); - } - + //noinspection unchecked + return (List) obj; } @Override @@ -337,14 +243,9 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { Object retValObj = theResource.getResourceMetadata().get(TAG_LIST); if (retValObj == null) { return null; - } else if (retValObj instanceof TagList) { - if (((TagList) retValObj).isEmpty()) { - return null; - } + } else { return (TagList) retValObj; } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + TAG_LIST.name() + " - Expected " - + TagList.class.getCanonicalName()); } @Override @@ -352,25 +253,6 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { theResource.getResourceMetadata().put(TAG_LIST, theObject); } }; - /** - * If present and populated with a string (as an instance of {@link String}), this value contains the title for this resource, as supplied in any bundles containing the resource. - *

- * Values for this key are of type {@link String} - *

- */ - public static final ResourceMetadataKeyEnum TITLE = new ResourceMetadataKeyEnum("TITLE") { - private static final long serialVersionUID = 1L; - - @Override - public String get(IResource theResource) { - return getStringFromMetadataOrNullIfNone(theResource.getResourceMetadata(), TITLE); - } - - @Override - public void put(IResource theResource, String theObject) { - theResource.getResourceMetadata().put(TITLE, theObject); - } - }; /** * The value for this key is the bundle entry Updated time. This is defined by FHIR as "Last Updated for resource". This value is also used for populating the "Last-Modified" header in the * case of methods that return a single resource (read, vread, etc.) @@ -398,7 +280,10 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { *

* Values for this key are of type {@link String} *

+ * + * @deprecated The {@link IResource#getId()} resource ID will now be populated with the version ID via the {@link IdDt#getVersionIdPart()} method */ + @Deprecated public static final ResourceMetadataKeyEnum VERSION = new ResourceMetadataKeyEnum("VERSION") { private static final long serialVersionUID = 1L; @@ -426,7 +311,7 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { @Override public IdDt get(IResource theResource) { - return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata(), VERSION_ID); + return getIdFromMetadataOrNullIfNone(theResource.getResourceMetadata()); } @Override @@ -474,32 +359,45 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { public abstract void put(IResource theResource, T theObject); - @Override - public String toString() { - return myValue; + public static abstract class ResourceMetadataKeySupportingAnyResource extends ResourceMetadataKeyEnum { + + private static final long serialVersionUID = 1L; + + public ResourceMetadataKeySupportingAnyResource(String theValue) { + super(theValue); + } + + public abstract T2 get(IAnyResource theResource); + + public abstract void put(IAnyResource theResource, T2 theObject); + } - private static DecimalDt getDecimalFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { - Object retValObj = theResourceMetadata.get(theKey); - if (retValObj == null) { - return null; - } else if (retValObj instanceof DecimalDt) { - if (((DecimalDt) retValObj).isEmpty()) { - return null; - } - return (DecimalDt) retValObj; - } else if (retValObj instanceof String) { - if (StringUtils.isBlank((String) retValObj)) { - return null; - } - return new DecimalDt((String) retValObj); - } else if (retValObj instanceof Double) { - return new DecimalDt((Double) retValObj); + public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum { + public ExtensionResourceMetadataKey(String theUrl) { + super(theUrl); + } + + @Override + public ExtensionDt get(IResource theResource) { + Object retValObj = theResource.getResourceMetadata().get(this); + if (retValObj == null) { + return null; + } else if (retValObj instanceof ExtensionDt) { + return (ExtensionDt) retValObj; + } + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + + "' in resource metadata for key " + this.name() + " - Expected " + + ExtensionDt.class.getCanonicalName()); + } + + @Override + public void put(IResource theResource, ExtensionDt theObject) { + theResource.getResourceMetadata().put(this, theObject); } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " - + InstantDt.class.getCanonicalName()); } + @SuppressWarnings("unchecked") private static > T getEnumFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey, Class theEnumType, IValueSetEnumBinder theBinder) { @@ -515,8 +413,8 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { + InstantDt.class.getCanonicalName()); } - private static IdDt getIdFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { - return toId(theKey, theResourceMetadata.get(theKey)); + private static IdDt getIdFromMetadataOrNullIfNone(Map, Object> theResourceMetadata) { + return toId(ResourceMetadataKeyEnum.VERSION_ID, theResourceMetadata.get(ResourceMetadataKeyEnum.VERSION_ID)); } private static List getIdListFromMetadataOrNullIfNone(Map, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { @@ -586,49 +484,11 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { } return (IdDt) retValObj; } else if (retValObj instanceof Number) { - return new IdDt(((Number) retValObj).toString()); + return new IdDt(retValObj.toString()); } throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName()); } - public static abstract class ResourceMetadataKeySupportingAnyResource extends ResourceMetadataKeyEnum { - - private static final long serialVersionUID = 1L; - - public ResourceMetadataKeySupportingAnyResource(String theValue) { - super(theValue); - } - - public abstract T2 get(IAnyResource theResource); - - public abstract void put(IAnyResource theResource, T2 theObject); - - } - - public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum { - public ExtensionResourceMetadataKey(String url) { - super(url); - } - - @Override - public ExtensionDt get(IResource theResource) { - Object retValObj = theResource.getResourceMetadata().get(this); - if (retValObj == null) { - return null; - } else if (retValObj instanceof ExtensionDt) { - return (ExtensionDt) retValObj; - } - throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() - + "' in resource metadata for key " + this.name() + " - Expected " - + ExtensionDt.class.getCanonicalName()); - } - - @Override - public void put(IResource theResource, ExtensionDt theObject) { - theResource.getResourceMetadata().put(this, theObject); - } - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Tag.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Tag.java index 7ff4dc1d705..36aa527a369 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Tag.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Tag.java @@ -168,18 +168,6 @@ public class Tag extends BaseElement implements IElement, IBaseCoding { return this; } - public String toHeaderValue() { - StringBuilder b = new StringBuilder(); - b.append(this.getTerm()); - if (isNotBlank(this.getLabel())) { - b.append("; label=\"").append(this.getLabel()).append('"'); - } - if (isNotBlank(this.getScheme())) { - b.append("; scheme=\"").append(this.getScheme()).append('"'); - } - return b.toString(); - } - @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java index e2ea2f99d8e..161e6ba9db1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java @@ -33,12 +33,6 @@ import java.util.Map; */ public interface IHttpResponse { - /** - * @deprecated This method was deprecated in HAPI FHIR 2.2 because its name has a typo. Use {@link #bufferEntity()} instead. - */ - @Deprecated - void bufferEntitity() throws IOException; - /** * Buffer the message entity data. *

diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java index 31a9875def5..490406c424c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java @@ -248,26 +248,30 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ { @Override public boolean equals(Object theO) { - if (this == theO) return true; + if (this == theO) { + return true; + } - if (theO == null || getClass() != theO.getClass()) return false; + if (theO == null || getClass() != theO.getClass()) { + return false; + } TokenParam that = (TokenParam) theO; - return new EqualsBuilder() - .append(myModifier, that.myModifier) - .append(mySystem, that.mySystem) - .append(myValue, that.myValue) - .isEquals(); + EqualsBuilder b = new EqualsBuilder(); + b.append(myModifier, that.myModifier); + b.append(mySystem, that.mySystem); + b.append(myValue, that.myValue); + return b.isEquals(); } @Override public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(myModifier) - .append(mySystem) - .append(myValue) - .toHashCode(); + HashCodeBuilder b = new HashCodeBuilder(17, 37); + b.append(myModifier); + b.append(mySystem); + b.append(myValue); + return b.toHashCode(); } private static String toSystemValue(UriDt theSystem) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AsyncUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AsyncUtil.java new file mode 100644 index 00000000000..a9da8d04d26 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AsyncUtil.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.util; + +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class AsyncUtil { + private static final Logger ourLog = LoggerFactory.getLogger(AsyncUtil.class); + + /** + * Non instantiable + */ + private AsyncUtil() { + } + + /** + * Calls Thread.sleep and if an InterruptedException occurs, logs a warning but otherwise continues + * + * @param theMillis The number of millis to sleep + * @return Did we sleep the whole amount + */ + public static boolean sleep(long theMillis) { + try { + Thread.sleep(theMillis); + return true; + } catch (InterruptedException theE) { + Thread.currentThread().interrupt(); + ourLog.warn("Sleep for {}ms was interrupted", theMillis); + return false; + } + } + + public static boolean awaitLatchAndThrowInternalErrorExceptionOnInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) { + try { + return theInitialCollectionLatch.await(theTime, theTimeUnit); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InternalErrorException(e); + } + } + + public static boolean awaitLatchAndIgnoreInterrupt(CountDownLatch theInitialCollectionLatch, long theTime, TimeUnit theTimeUnit) { + try { + return theInitialCollectionLatch.await(theTime, theTimeUnit); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + ourLog.warn("Interrupted while waiting for latch"); + return false; + } + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java index 8baefd91050..db516ec2a72 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java @@ -20,9 +20,8 @@ package ca.uhn.fhir.util; * #L% */ -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.*; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -35,12 +34,10 @@ public class AttachmentUtil { * Fetches the base64Binary value of Attachment.data, creating it if it does not * already exist. */ - @SuppressWarnings("unchecked") public static IPrimitiveType getOrCreateData(FhirContext theContext, ICompositeType theAttachment) { return getOrCreateChild(theContext, theAttachment, "data", "base64Binary"); } - @SuppressWarnings("unchecked") public static IPrimitiveType getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) { return getOrCreateChild(theContext, theAttachment, "contentType", "string"); } @@ -64,6 +61,16 @@ public class AttachmentUtil { }); } + public static void setUrl(FhirContext theContext, ICompositeType theAttachment, String theUrl) { + BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "url"); + assert entryChild != null : "Version " + theContext + " has no child " + "url"; + String typeName = "uri"; + if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { + typeName = "url"; + } + entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, typeName, theUrl)); + } + public static void setContentType(FhirContext theContext, ICompositeType theAttachment, String theContentType) { BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType"); entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "code", theContentType)); @@ -88,7 +95,9 @@ public class AttachmentUtil { */ @SuppressWarnings("unchecked") static IPrimitiveType newPrimitive(FhirContext theContext, String theType, T theValue) { - IPrimitiveType primitive = (IPrimitiveType) theContext.getElementDefinition(theType).newInstance(); + BaseRuntimeElementDefinition elementDefinition = theContext.getElementDefinition(theType); + Validate.notNull(elementDefinition, "Unknown type %s for %s", theType, theContext); + IPrimitiveType primitive = (IPrimitiveType) elementDefinition.newInstance(); primitive.setValue(theValue); return primitive; } @@ -100,4 +109,8 @@ public class AttachmentUtil { BaseRuntimeElementCompositeDefinition def = (BaseRuntimeElementCompositeDefinition) theContext.getElementDefinition(theElement.getClass()); return def.getChildByName(theName); } + + public static ICompositeType newInstance(FhirContext theFhirCtx) { + return (ICompositeType) theFhirCtx.getElementDefinition("Attachment").newInstance(); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index c57a9f65b22..4ec185a8fd9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -72,6 +72,8 @@ public class ParametersUtil { } private static void addClientParameter(FhirContext theContext, Object theValue, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition paramChildElem, String theName) { + Validate.notNull(theValue, "theValue must not be null"); + if (theValue instanceof IBaseResource) { IBase parameter = createParameterRepetition(theContext, theTargetResource, paramChild, paramChildElem, theName); paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource) theValue); @@ -162,7 +164,6 @@ public class ParametersUtil { IPrimitiveType value = (IPrimitiveType) theCtx.getElementDefinition("boolean").newInstance(); value.setValue(theValue); addParameterToParameters(theCtx, theParameters, theName, value); - } @SuppressWarnings("unchecked") diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StopWatch.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StopWatch.java index a3fc0c475d5..53ca7e3efb3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StopWatch.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StopWatch.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.util; import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import java.text.DecimalFormat; @@ -9,8 +10,6 @@ import java.util.Date; import java.util.LinkedList; import java.util.concurrent.TimeUnit; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR - Core Library @@ -48,12 +47,14 @@ public class StopWatch { private long myStarted = now(); private TaskTiming myCurrentTask; private LinkedList myTasks; + /** * Constructor */ public StopWatch() { super(); } + /** * Constructor * @@ -63,7 +64,13 @@ public class StopWatch { myStarted = theStart.getTime(); } - public StopWatch(long theL) { + /** + * Constructor + * + * @param theStart The time that the stopwatch was started + */ + public StopWatch(long theStart) { + myStarted = theStart; } private void addNewlineIfContentExists(StringBuilder theB) { @@ -120,6 +127,8 @@ public class StopWatch { b.append(": "); b.append(formatMillis(delta)); } + } else { + b.append("No tasks"); } TaskTiming last = null; @@ -257,12 +266,11 @@ public class StopWatch { */ public void startTask(String theTaskName) { endCurrentTask(); - if (isNotBlank(theTaskName)) { - myCurrentTask = new TaskTiming() - .setTaskName(theTaskName) - .setStart(now()); - myTasks.add(myCurrentTask); - } + Validate.notBlank(theTaskName, "Task name must not be blank"); + myCurrentTask = new TaskTiming() + .setTaskName(theTaskName) + .setStart(now()); + myTasks.add(myCurrentTask); } /** @@ -331,18 +339,18 @@ public class StopWatch { /** * Append a right-aligned and zero-padded numeric value to a `StringBuilder`. */ - static private void append(StringBuilder tgt, String pfx, int dgt, long val) { - tgt.append(pfx); - if (dgt > 1) { - int pad = (dgt - 1); - for (long xa = val; xa > 9 && pad > 0; xa /= 10) { + static void appendRightAlignedNumber(StringBuilder theStringBuilder, String thePrefix, int theNumberOfDigits, long theValueToAppend) { + theStringBuilder.append(thePrefix); + if (theNumberOfDigits > 1) { + int pad = (theNumberOfDigits - 1); + for (long xa = theValueToAppend; xa > 9 && pad > 0; xa /= 10) { pad--; } for (int xa = 0; xa < pad; xa++) { - tgt.append('0'); + theStringBuilder.append('0'); } } - tgt.append(val); + theStringBuilder.append(theValueToAppend); } /** @@ -399,11 +407,11 @@ public class StopWatch { } } else { long millisAsLong = (long) theMillis; - append(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR)); - append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE)); - append(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND)); + appendRightAlignedNumber(buf, "", 2, ((millisAsLong % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR)); + appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE)); + appendRightAlignedNumber(buf, ":", 2, ((millisAsLong % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND)); if (theMillis <= DateUtils.MILLIS_PER_MINUTE) { - append(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND)); + appendRightAlignedNumber(buf, ".", 3, (millisAsLong % DateUtils.MILLIS_PER_SECOND)); } } return buf.toString(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java index d3b74d695c9..bbbdf13d23d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java @@ -19,26 +19,32 @@ package ca.uhn.fhir.validation; * limitations under the License. * #L% */ -import java.io.*; -import java.nio.charset.Charset; -import java.util.*; - -import javax.xml.XMLConstants; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.*; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.BOMInputStream; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.w3c.dom.ls.LSInput; -import org.w3c.dom.ls.LSResourceResolver; -import org.xml.sax.*; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXParseException; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.*; public class SchemaBaseValidator implements IValidatorModule { public static final String RESOURCES_JAR_NOTE = "Note that as of HAPI FHIR 1.2, DSTU2 validation files are kept in a separate JAR (hapi-fhir-validation-resources-XXX.jar) which must be added to your classpath. See the HAPI FHIR download page for more information."; @@ -47,7 +53,7 @@ public class SchemaBaseValidator implements IValidatorModule { private static final Set SCHEMA_NAMES; static { - HashSet sn = new HashSet(); + HashSet sn = new HashSet<>(); sn.add("xml.xsd"); sn.add("xhtml1-strict.xsd"); sn.add("fhir-single.xsd"); @@ -59,15 +65,15 @@ public class SchemaBaseValidator implements IValidatorModule { SCHEMA_NAMES = Collections.unmodifiableSet(sn); } - private Map myKeyToSchema = new HashMap(); + private final Map myKeyToSchema = new HashMap<>(); private FhirContext myCtx; public SchemaBaseValidator(FhirContext theContext) { myCtx = theContext; } - private void doValidate(IValidationContext theContext, String schemaName) { - Schema schema = loadSchema("dstu", schemaName); + private void doValidate(IValidationContext theContext) { + Schema schema = loadSchema(); try { Validator validator = schema.newValidator(); @@ -81,14 +87,14 @@ public class SchemaBaseValidator implements IValidatorModule { } try { - /* - * See https://github.com/jamesagnew/hapi-fhir/issues/339 - * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing - */ + /* + * See https://github.com/jamesagnew/hapi-fhir/issues/339 + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing + */ validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - }catch (SAXNotRecognizedException ex){ - ourLog.warn("Jaxp 1.5 Support not found.",ex); + } catch (SAXNotRecognizedException ex) { + ourLog.warn("Jaxp 1.5 Support not found.", ex); } validator.validate(new StreamSource(new StringReader(encodedResource))); @@ -99,17 +105,14 @@ public class SchemaBaseValidator implements IValidatorModule { message.setMessage(e.getLocalizedMessage()); message.setSeverity(ResultSeverityEnum.FATAL); theContext.addValidationMessage(message); - } catch (SAXException e) { - // Catch all - throw new ConfigurationException("Could not load/parse schema file", e); - } catch (IOException e) { + } catch (SAXException | IOException e) { // Catch all throw new ConfigurationException("Could not load/parse schema file", e); } } - private Schema loadSchema(String theVersion, String theSchemaName) { - String key = theVersion + "-" + theSchemaName; + private Schema loadSchema() { + String key = "fhir-single.xsd"; synchronized (myKeyToSchema) { Schema schema = myKeyToSchema.get(key); @@ -117,81 +120,52 @@ public class SchemaBaseValidator implements IValidatorModule { return schema; } - Source baseSource = loadXml(null, theSchemaName); + Source baseSource = loadXml("fhir-single.xsd"); SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schemaFactory.setResourceResolver(new MyResourceResolver()); try { try { - /* - * See https://github.com/jamesagnew/hapi-fhir/issues/339 - * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing - */ + /* + * See https://github.com/jamesagnew/hapi-fhir/issues/339 + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing + */ schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - }catch (SAXNotRecognizedException snex){ - ourLog.warn("Jaxp 1.5 Support not found.",snex); + } catch (SAXNotRecognizedException e) { + ourLog.warn("Jaxp 1.5 Support not found.", e); } - schema = schemaFactory.newSchema(new Source[] { baseSource }); + schema = schemaFactory.newSchema(new Source[]{baseSource}); } catch (SAXException e) { - throw new ConfigurationException("Could not load/parse schema file: " + theSchemaName, e); + throw new ConfigurationException("Could not load/parse schema file: " + "fhir-single.xsd", e); } myKeyToSchema.put(key, schema); return schema; } } - private Source loadXml(String theSystemId, String theSchemaName) { + Source loadXml(String theSchemaName) { String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName; ourLog.debug("Going to load resource: {}", pathToBase); - InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase); - if (baseIs == null) { - throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE); + try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) { + if (baseIs == null) { + throw new InternalErrorException("Schema not found. " + RESOURCES_JAR_NOTE); + } + try (BOMInputStream bomInputStream = new BOMInputStream(baseIs, false)) { + try (InputStreamReader baseReader = new InputStreamReader(bomInputStream, StandardCharsets.UTF_8)) { + // Buffer so that we can close the input stream + String contents = IOUtils.toString(baseReader); + return new StreamSource(new StringReader(contents), null); + } + } + } catch (IOException e) { + throw new InternalErrorException(e); } - baseIs = new BOMInputStream(baseIs, false); - InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8")); - Source baseSource = new StreamSource(baseReader, theSystemId); - //FIXME resource leak - return baseSource; } @Override public void validateResource(IValidationContext theContext) { - doValidate(theContext, "fhir-single.xsd"); - } - - private static class MyErrorHandler implements org.xml.sax.ErrorHandler { - - private IValidationContext myContext; - - public MyErrorHandler(IValidationContext theContext) { - myContext = theContext; - } - - private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) { - SingleValidationMessage message = new SingleValidationMessage(); - message.setLocationLine(theException.getLineNumber()); - message.setLocationCol(theException.getColumnNumber()); - message.setMessage(theException.getLocalizedMessage()); - message.setSeverity(theSeverity); - myContext.addValidationMessage(message); - } - - @Override - public void error(SAXParseException theException) { - addIssue(theException, ResultSeverityEnum.ERROR); - } - - @Override - public void fatalError(SAXParseException theException) { - addIssue(theException, ResultSeverityEnum.FATAL); - } - - @Override - public void warning(SAXParseException theException) { - addIssue(theException, ResultSeverityEnum.WARNING); - } - + doValidate(theContext); } private final class MyResourceResolver implements LSResourceResolver { @@ -225,4 +199,38 @@ public class SchemaBaseValidator implements IValidatorModule { } } + private static class MyErrorHandler implements org.xml.sax.ErrorHandler { + + private IValidationContext myContext; + + MyErrorHandler(IValidationContext theContext) { + myContext = theContext; + } + + private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) { + SingleValidationMessage message = new SingleValidationMessage(); + message.setLocationLine(theException.getLineNumber()); + message.setLocationCol(theException.getColumnNumber()); + message.setMessage(theException.getLocalizedMessage()); + message.setSeverity(theSeverity); + myContext.addValidationMessage(message); + } + + @Override + public void error(SAXParseException theException) { + addIssue(theException, ResultSeverityEnum.ERROR); + } + + @Override + public void fatalError(SAXParseException theException) { + addIssue(theException, ResultSeverityEnum.FATAL); + } + + @Override + public void warning(SAXParseException theException) { + addIssue(theException, ResultSeverityEnum.WARNING); + } + + } + } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 60980158197..84f8cee8412 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -125,9 +125,9 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownPath=Unable to find content ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of type {0} at path {1} is not appropriate for binary storage: {2} -ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} -ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} -ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1} -ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnumTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnumTest.java new file mode 100644 index 00000000000..c491081ffc0 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnumTest.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.model.api; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ResourceMetadataKeyEnumTest { + + @Test + public void testHashCode() { + assertEquals(-60968467, ResourceMetadataKeyEnum.PUBLISHED.hashCode()); + } + + @Test + public void testEquals() { + assertNotEquals(ResourceMetadataKeyEnum.PROFILES, null); + assertNotEquals(ResourceMetadataKeyEnum.PROFILES, ""); + assertNotEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PUBLISHED); + assertEquals(ResourceMetadataKeyEnum.PROFILES, ResourceMetadataKeyEnum.PROFILES); + } + + + @Test + public void testExtensionResourceEquals() { + assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://bar")); + assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), null); + assertNotEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), ""); + assertEquals(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"), new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo")); + + ResourceMetadataKeyEnum.ExtensionResourceMetadataKey foo = new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://foo"); + assertEquals(foo, foo); + } + + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/TagTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/TagTest.java new file mode 100644 index 00000000000..403c96dfe74 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/api/TagTest.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.model.api; + +import org.junit.Test; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.*; + +public class TagTest { + + @Test + public void testEquals() { + Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label"); + Tag tag2 = new Tag().setScheme("scheme").setTerm("term").setLabel("label"); + Tag tag3 = new Tag().setScheme("scheme2").setTerm("term").setLabel("label"); + Tag tag4 = new Tag().setScheme("scheme").setTerm("term2").setLabel("label"); + + assertEquals(tag1, tag1); + assertEquals(tag1, tag2); + assertNotEquals(tag1, tag3); + assertNotEquals(tag1, tag4); + assertNotEquals(tag1, null); + assertNotEquals(tag1, ""); + } + + @Test + public void testHashCode() { + Tag tag1 = new Tag().setScheme("scheme").setTerm("term").setLabel("label"); + assertEquals(1920714536, tag1.hashCode()); + } + + @Test + public void testConstructors() throws URISyntaxException { + assertTrue(new Tag().isEmpty()); + assertFalse(new Tag("http://foo").isEmpty()); + assertFalse(new Tag("http://foo", "http://bar").isEmpty()); + assertFalse(new Tag(new URI("http://foo"), new URI("http://bar"), "Label").isEmpty()); + assertTrue(new Tag((URI)null, null, "Label").isEmpty()); + + assertEquals("http://foo", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getSystem()); + assertEquals("http://bar", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getCode()); + assertEquals("Label", new Tag(new URI("http://foo"), new URI("http://bar"), "Label").getDisplay()); + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java index 640a150e5ec..db934b0d89c 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java @@ -2,13 +2,34 @@ package ca.uhn.fhir.rest.param; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class TokenParamTest { @Test public void testEquals() { TokenParam tokenParam1 = new TokenParam("foo", "bar"); TokenParam tokenParam2 = new TokenParam("foo", "bar"); + TokenParam tokenParam3 = new TokenParam("foo", "baz"); + assertEquals(tokenParam1, tokenParam1); assertEquals(tokenParam1, tokenParam2); + assertNotEquals(tokenParam1, tokenParam3); + assertNotEquals(tokenParam1, null); + assertNotEquals(tokenParam1, ""); } + + @Test + public void testHashCode() { + TokenParam tokenParam1 = new TokenParam("foo", "bar"); + assertEquals(4716638, tokenParam1.hashCode()); + } + + + @Test + public void testIsEmpty() { + assertFalse(new TokenParam("foo", "bar").isEmpty()); + assertTrue(new TokenParam("", "").isEmpty()); + assertTrue(new TokenParam().isEmpty()); + assertEquals("", new TokenParam().getValueNotNull()); + } + } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/AsyncUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/AsyncUtilTest.java new file mode 100644 index 00000000000..7cbb8022c25 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/AsyncUtilTest.java @@ -0,0 +1,62 @@ +package ca.uhn.fhir.util; + +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.awaitility.Awaitility.await; + +public class AsyncUtilTest { + + @Test + public void testSleep() { + AsyncUtil.sleep(10); + } + + @Test + public void testSleepWithInterrupt() { + AtomicBoolean outcomeHolder = new AtomicBoolean(true); + Thread thread = new Thread(() -> { + boolean outcome = AsyncUtil.sleep(10000); + outcomeHolder.set(outcome); + }); + thread.start(); + thread.interrupt(); + await().until(()-> outcomeHolder.get() == false); + } + + @Test + public void testAwaitLatchAndThrowInternalErrorException() { + AtomicBoolean outcomeHolder = new AtomicBoolean(false); + + CountDownLatch latch = new CountDownLatch(1); + Thread thread = new Thread(() -> { + try { + AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(latch, 10, TimeUnit.SECONDS); + } catch (InternalErrorException e) { + outcomeHolder.set(true); + } + }); + thread.start(); + thread.interrupt(); + await().until(()-> outcomeHolder.get()); + } + + @Test + public void testAwaitLatchIgnoreInterruption() { + AtomicBoolean outcomeHolder = new AtomicBoolean(true); + + CountDownLatch latch = new CountDownLatch(1); + Thread thread = new Thread(() -> { + boolean outcome = AsyncUtil.awaitLatchAndIgnoreInterrupt(latch, 10, TimeUnit.SECONDS); + outcomeHolder.set(outcome); + }); + thread.start(); + thread.interrupt(); + await().until(()-> outcomeHolder.get() == false); + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/StopWatchTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/StopWatchTest.java index 192c47f6b0e..dc79e0cdb66 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/StopWatchTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/StopWatchTest.java @@ -128,6 +128,56 @@ public class StopWatchTest { assertEquals("TASK1: 500ms\nTASK2: 100ms", taskDurations); } + @Test + public void testFormatTaskDurationsDelayBetweenTasks() { + StopWatch sw = new StopWatch(); + + StopWatch.setNowForUnitTestForUnitTest(1000L); + sw.startTask("TASK1"); + + StopWatch.setNowForUnitTestForUnitTest(1500L); + sw.endCurrentTask(); + + StopWatch.setNowForUnitTestForUnitTest(2000L); + sw.startTask("TASK2"); + + StopWatch.setNowForUnitTestForUnitTest(2100L); + sw.endCurrentTask(); + + StopWatch.setNowForUnitTestForUnitTest(2200L); + String taskDurations = sw.formatTaskDurations(); + ourLog.info(taskDurations); + assertEquals("TASK1: 500ms\n" + + "Between: 500ms\n" + + "TASK2: 100ms\n" + + "After last task: 100ms", taskDurations); + } + + @Test + public void testFormatTaskDurationsLongDelayBeforeStart() { + StopWatch sw = new StopWatch(0); + + StopWatch.setNowForUnitTestForUnitTest(1000L); + sw.startTask("TASK1"); + + StopWatch.setNowForUnitTestForUnitTest(1500L); + sw.startTask("TASK2"); + + StopWatch.setNowForUnitTestForUnitTest(1600L); + String taskDurations = sw.formatTaskDurations(); + ourLog.info(taskDurations); + assertEquals("Before first task: 1000ms\nTASK1: 500ms\nTASK2: 100ms", taskDurations); + } + + @Test + public void testFormatTaskDurationsNoTasks() { + StopWatch sw = new StopWatch(0); + + String taskDurations = sw.formatTaskDurations(); + ourLog.info(taskDurations); + assertEquals("No tasks", taskDurations); + } + @Test public void testFormatThroughput60Ops4Min() { StopWatch sw = new StopWatch(DateUtils.addMinutes(new Date(), -4)); @@ -210,4 +260,34 @@ public class StopWatchTest { assertThat(string, matchesPattern("^[0-9]{3,4}ms$")); } + + @Test + public void testAppendRightAlignedNumber() { + StringBuilder b= new StringBuilder(); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 0, 100); + assertEquals("PFX100", b.toString()); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 1, 100); + assertEquals("PFX100", b.toString()); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 2, 100); + assertEquals("PFX100", b.toString()); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 3, 100); + assertEquals("PFX100", b.toString()); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 4, 100); + assertEquals("PFX0100", b.toString()); + + b.setLength(0); + StopWatch.appendRightAlignedNumber(b, "PFX", 10, 100); + assertEquals("PFX0000000100", b.toString()); + } + } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ToggleSearchParametersCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ToggleSearchParametersCommand.java index 4aa3991980a..fbfdcf0e82b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ToggleSearchParametersCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ToggleSearchParametersCommand.java @@ -20,7 +20,7 @@ package ca.uhn.fhir.cli; * #L% */ -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; @@ -44,7 +44,7 @@ public class ToggleSearchParametersCommand extends BaseCommand { Options options = new Options(); addFhirVersionOption(options); addBaseUrlOption(options); - addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")"); + addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")"); addBasicAuthOption(options); return options; } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java index 2194c4cff91..42a0aa66143 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/UploadTerminologyCommand.java @@ -20,35 +20,32 @@ package ca.uhn.fhir.cli; * #L% */ -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; -import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.util.AttachmentUtil; import ca.uhn.fhir.util.ParametersUtil; +import com.google.common.base.Charsets; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.instance.model.api.ICompositeType; -import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import static org.apache.commons.lang3.StringUtils.isBlank; public class UploadTerminologyCommand extends BaseCommand { - public static final String UPLOAD_TERMINOLOGY = "upload-terminology"; + static final String UPLOAD_TERMINOLOGY = "upload-terminology"; // TODO: Don't use qualified names for loggers in HAPI CLI. private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class); @@ -68,9 +65,8 @@ public class UploadTerminologyCommand extends BaseCommand { addFhirVersionOption(options); addBaseUrlOption(options); - addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + IHapiTerminologyLoaderSvc.SCT_URI + ")"); + addRequiredOption(options, "u", "url", true, "The code system URL associated with this upload (e.g. " + ITermLoaderSvc.SCT_URI + ")"); addOptionalOption(options, "d", "data", true, "Local file to use to upload (can be a raw file or a ZIP containing the raw file)"); - addOptionalOption(options, null, "custom", false, "Indicates that this upload uses the HAPI FHIR custom external terminology format"); addOptionalOption(options, "m", "mode", true, "The upload mode: SNAPSHOT (default), ADD, REMOVE"); addBasicAuthOption(options); addVerboseLoggingOption(options); @@ -109,104 +105,86 @@ public class UploadTerminologyCommand extends BaseCommand { switch (mode) { case SNAPSHOT: - uploadSnapshot(inputParameters, termUrl, datafile, theCommandLine, client); + invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM); break; case ADD: - uploadDelta(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD, false); + invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD); break; case REMOVE: - uploadDelta(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE, true); + invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE); break; } } - private void uploadDelta(CommandLine theCommandLine, String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName, boolean theFlatten) { - ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, "url", theTermUrl); + private void invokeOperation(CommandLine theCommandLine, String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException { + ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl); - List fileDescriptors = new ArrayList<>(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8); + boolean haveCompressedContents = false; + try { + for (String nextDataFile : theDatafile) { + + try (FileInputStream fileInputStream = new FileInputStream(nextDataFile)) { + if (!nextDataFile.endsWith(".zip")) { + + ourLog.info("Compressing and adding file: {}", nextDataFile); + ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile)); + zipOutputStream.putNextEntry(nextEntry); + + IOUtils.copy(fileInputStream, zipOutputStream); + haveCompressedContents = true; + + zipOutputStream.flush(); + ourLog.info("Finished compressing {} into {}", nextEntry.getSize(), nextEntry.getCompressedSize()); + + } else { + + ourLog.info("Adding file: {}", nextDataFile); + ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx); + AttachmentUtil.setUrl(myFhirCtx, attachment, "file:" + nextDataFile); + AttachmentUtil.setData(myFhirCtx, attachment, IOUtils.toByteArray(fileInputStream)); + ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment); - for (String next : theDatafile) { - try (FileInputStream inputStream = new FileInputStream(next)) { - byte[] bytes = IOUtils.toByteArray(inputStream); - fileDescriptors.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { - @Override - public String getFilename() { - return next; } + } - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(bytes); - } - }); - } catch (IOException e) { - throw new CommandFailureException("Failed to read from file \"" + next + "\": " + e.getMessage()); } + zipOutputStream.flush(); + zipOutputStream.close(); + } catch (IOException e) { + throw new ParseException(e.toString()); } - TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); - TerminologyLoaderSvcImpl.LoadedFileDescriptors descriptors = new TerminologyLoaderSvcImpl.LoadedFileDescriptors(fileDescriptors); - TerminologyLoaderSvcImpl.processCustomTerminologyFiles(descriptors, codeSystemVersion); - - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl(theTermUrl); - addCodesToCodeSystem(codeSystemVersion.getConcepts(), codeSystem.getConcept(), theFlatten); - - ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, "value", codeSystem); - - if (theCommandLine.hasOption("custom")) { - ParametersUtil.addParameterToParametersCode(myFhirCtx, theInputParameters, "contentMode", "custom"); + if (haveCompressedContents) { + ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx); + AttachmentUtil.setUrl(myFhirCtx, attachment, "file:/files.zip"); + AttachmentUtil.setData(myFhirCtx, attachment, byteArrayOutputStream.toByteArray()); + ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment); } ourLog.info("Beginning upload - This may take a while..."); - IBaseParameters response = theClient - .operation() - .onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass()) - .named(theOperationName) - .withParameters(theInputParameters) - .execute(); + if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) { + ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters)); + } - ourLog.info("Upload complete!"); - ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); - } - - private void addCodesToCodeSystem(Collection theSourceConcepts, List theTargetConcept, boolean theFlatten) { - for (TermConcept nextSourceConcept : theSourceConcepts) { - - CodeSystem.ConceptDefinitionComponent nextTarget = new CodeSystem.ConceptDefinitionComponent(); - nextTarget.setCode(nextSourceConcept.getCode()); - nextTarget.setDisplay(nextSourceConcept.getDisplay()); - theTargetConcept.add(nextTarget); - - List children = nextSourceConcept.getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList()); - if (theFlatten) { - addCodesToCodeSystem(children, theTargetConcept, theFlatten); - } else { - addCodesToCodeSystem(children, nextTarget.getConcept(), theFlatten); + IBaseParameters response; + try { + response = theClient + .operation() + .onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass()) + .named(theOperationName) + .withParameters(theInputParameters) + .execute(); + } catch (BaseServerResponseException e) { + if (e.getOperationOutcome() != null) { + ourLog.error("Received the following response:\n{}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome())); } - - } - } - - private void uploadSnapshot(IBaseParameters theInputparameters, String theTermUrl, String[] theDatafile, CommandLine theCommandLine, IGenericClient theClient) { - ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputparameters, "url", theTermUrl); - for (String next : theDatafile) { - ParametersUtil.addParameterToParametersString(myFhirCtx, theInputparameters, "localfile", next); - } - if (theCommandLine.hasOption("custom")) { - ParametersUtil.addParameterToParametersCode(myFhirCtx, theInputparameters, "contentMode", "custom"); + throw e; } - ourLog.info("Beginning upload - This may take a while..."); - - IBaseParameters response = theClient - .operation() - .onType(myFhirCtx.getResourceDefinition("CodeSystem").getImplementingClass()) - .named(JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM) - .withParameters(theInputparameters) - .execute(); ourLog.info("Upload complete!"); ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); @@ -216,4 +194,12 @@ public class UploadTerminologyCommand extends BaseCommand { SNAPSHOT, ADD, REMOVE } + public static String stripPath(String thePath) { + String retVal = thePath; + if (retVal.contains("/")) { + retVal = retVal.substring(retVal.lastIndexOf("/")); + } + return retVal; + } + } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml index 848186fb3b1..61dc270245e 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml @@ -34,7 +34,7 @@ - + diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java index 42c8eccd95a..568f9e49557 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/UploadTerminologyCommandTest.java @@ -1,18 +1,20 @@ package ca.uhn.fhir.cli; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.BaseTest; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.UploadStatistics; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.test.utilities.JettyUtil; +import com.google.common.base.Charsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.Matchers; -import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.IdType; import org.junit.After; import org.junit.Before; @@ -25,15 +27,19 @@ import org.mockito.junit.MockitoJUnitRunner; import java.io.*; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) -public class UploadTerminologyCommandTest { +public class UploadTerminologyCommandTest extends BaseTest { static { System.setProperty("test", "true"); @@ -42,26 +48,24 @@ public class UploadTerminologyCommandTest { private Server myServer; private FhirContext myCtx = FhirContext.forR4(); @Mock - private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc; - @Mock - private IHapiTerminologySvc myTerminologySvc; + private ITermLoaderSvc myTermLoaderSvc; @Captor - private ArgumentCaptor> myDescriptorList; - @Captor - private ArgumentCaptor myCodeSystemCaptor; + private ArgumentCaptor> myDescriptorListCaptor; private int myPort; private String myConceptsFileName = "target/concepts.csv"; private String myHierarchyFileName = "target/hierarchy.csv"; private File myConceptsFile = new File(myConceptsFileName); private File myHierarchyFile = new File(myHierarchyFileName); + private File myArchiveFile; + private String myArchiveFileName; @Test - public void testTerminologyUpload_AddDelta() throws IOException { + public void testAddDelta() throws IOException { writeConceptAndHierarchyFiles(); - when(myTerminologySvc.applyDeltaCodesystemsAdd(eq("http://foo"), any(), any())).thenReturn(new AtomicInteger(100)); + when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101"))); App.main(new String[]{ UploadTerminologyCommand.UPLOAD_TERMINOLOGY, @@ -73,25 +77,69 @@ public class UploadTerminologyCommandTest { "-d", myHierarchyFileName }); - verify(myTerminologySvc, times(1)).applyDeltaCodesystemsAdd(any(), isNull(), myCodeSystemCaptor.capture()); + verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any()); - CodeSystem codeSystem = myCodeSystemCaptor.getValue(); - assertEquals(1, codeSystem.getConcept().size()); - assertEquals("http://foo", codeSystem.getUrl()); - assertEquals("ANIMALS", codeSystem.getConcept().get(0).getCode()); - assertEquals("Animals", codeSystem.getConcept().get(0).getDisplay()); - assertEquals(2, codeSystem.getConcept().get(0).getConcept().size()); - assertEquals("CATS", codeSystem.getConcept().get(0).getConcept().get(0).getCode()); - assertEquals("Cats", codeSystem.getConcept().get(0).getConcept().get(0).getDisplay()); - assertEquals("DOGS", codeSystem.getConcept().get(0).getConcept().get(1).getCode()); - assertEquals("Dogs", codeSystem.getConcept().get(0).getConcept().get(1).getDisplay()); + List listOfDescriptors = myDescriptorListCaptor.getValue(); + assertEquals(1, listOfDescriptors.size()); + assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename()); + assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100)); } @Test - public void testTerminologyUpload_RemoveDelta() throws IOException { + public void testAddDeltaUsingCompressedFile() throws IOException { + + writeConceptAndHierarchyFiles(); + writeArchiveFile(myConceptsFile, myHierarchyFile); + + when(myTermLoaderSvc.loadDeltaAdd(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101"))); + + App.main(new String[]{ + UploadTerminologyCommand.UPLOAD_TERMINOLOGY, + "-v", "r4", + "-m", "ADD", + "-t", "http://localhost:" + myPort, + "-u", "http://foo", + "-d", myArchiveFileName + }); + + verify(myTermLoaderSvc, times(1)).loadDeltaAdd(eq("http://foo"), myDescriptorListCaptor.capture(), any()); + + List listOfDescriptors = myDescriptorListCaptor.getValue(); + assertEquals(1, listOfDescriptors.size()); + assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern("^file:.*temp.*\\.zip$")); + assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100)); + } + + private void writeArchiveFile(File... theFiles) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8); + + for (File next : theFiles) { + ZipEntry nextEntry = new ZipEntry(UploadTerminologyCommand.stripPath(next.getAbsolutePath())); + zipOutputStream.putNextEntry(nextEntry); + + try (FileInputStream fileInputStream = new FileInputStream(next)) { + IOUtils.copy(fileInputStream, zipOutputStream); + } + + } + + zipOutputStream.flush(); + zipOutputStream.close(); + + myArchiveFile = File.createTempFile("temp", ".zip"); + myArchiveFile.deleteOnExit(); + myArchiveFileName = myArchiveFile.getAbsolutePath(); + try (FileOutputStream fos = new FileOutputStream(myArchiveFile, false)) { + fos.write(byteArrayOutputStream.toByteArray()); + } + } + + @Test + public void testRemoveDelta() throws IOException { writeConceptAndHierarchyFiles(); - when(myTerminologySvc.applyDeltaCodesystemsRemove(eq("http://foo"), any())).thenReturn(new AtomicInteger(100)); + when(myTermLoaderSvc.loadDeltaRemove(eq("http://foo"), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101"))); App.main(new String[]{ UploadTerminologyCommand.UPLOAD_TERMINOLOGY, @@ -103,46 +151,38 @@ public class UploadTerminologyCommandTest { "-d", myHierarchyFileName }); - verify(myTerminologySvc, times(1)).applyDeltaCodesystemsRemove(any(), myCodeSystemCaptor.capture()); + verify(myTermLoaderSvc, times(1)).loadDeltaRemove(eq("http://foo"), myDescriptorListCaptor.capture(), any()); + + List listOfDescriptors = myDescriptorListCaptor.getValue(); + assertEquals(1, listOfDescriptors.size()); + assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename()); + assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100)); - CodeSystem codeSystem = myCodeSystemCaptor.getValue(); - assertEquals(3, codeSystem.getConcept().size()); - assertEquals("http://foo", codeSystem.getUrl()); - assertEquals("ANIMALS", codeSystem.getConcept().get(0).getCode()); - assertEquals("Animals", codeSystem.getConcept().get(0).getDisplay()); - assertEquals("CATS", codeSystem.getConcept().get(1).getCode()); - assertEquals("Cats", codeSystem.getConcept().get(1).getDisplay()); - assertEquals("DOGS", codeSystem.getConcept().get(2).getCode()); - assertEquals("Dogs", codeSystem.getConcept().get(2).getDisplay()); } @Test - public void testTerminologyUpload_Snapshot() throws IOException { + public void testSnapshot() throws IOException { writeConceptAndHierarchyFiles(); - when(myTerminologyLoaderSvc.loadCustom(eq("http://foo"), any(), any())).thenReturn(new IHapiTerminologyLoaderSvc.UploadStatistics(100, new IdType("CodeSystem/123"))); + when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101"))); App.main(new String[]{ UploadTerminologyCommand.UPLOAD_TERMINOLOGY, "-v", "r4", "-m", "SNAPSHOT", - "--custom", "-t", "http://localhost:" + myPort, "-u", "http://foo", "-d", myConceptsFileName, "-d", myHierarchyFileName }); - verify(myTerminologyLoaderSvc, times(1)).loadCustom(any(), myDescriptorList.capture(), any()); + verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any()); - List listOfDescriptors = myDescriptorList.getValue(); - assertEquals(2, listOfDescriptors.size()); - - assertThat(listOfDescriptors.get(0).getFilename(), Matchers.endsWith("concepts.csv")); - assertInputStreamEqualsFile(myConceptsFile, listOfDescriptors.get(0).getInputStream()); - assertThat(listOfDescriptors.get(1).getFilename(), Matchers.endsWith("hierarchy.csv")); - assertInputStreamEqualsFile(myHierarchyFile, listOfDescriptors.get(1).getInputStream()); + List listOfDescriptors = myDescriptorListCaptor.getValue(); + assertEquals(1, listOfDescriptors.size()); + assertEquals("file:/files.zip", listOfDescriptors.get(0).getFilename()); + assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100)); } @@ -161,27 +201,41 @@ public class UploadTerminologyCommandTest { } } - private void assertInputStreamEqualsFile(File theExpectedFile, InputStream theActualInputStream) throws IOException { - try (FileInputStream fis = new FileInputStream(theExpectedFile)) { - byte[] expectedBytes = IOUtils.toByteArray(fis); - byte[] actualBytes = IOUtils.toByteArray(theActualInputStream); - assertArrayEquals(expectedBytes, actualBytes); + @Test + public void testAddInvalidFileName() throws IOException { + + writeConceptAndHierarchyFiles(); + + try { + App.main(new String[]{ + UploadTerminologyCommand.UPLOAD_TERMINOLOGY, + "-v", "r4", + "-m", "ADD", + "-t", "http://localhost:" + myPort, + "-u", "http://foo", + "-d", myConceptsFileName + "/foo.csv", + "-d", myHierarchyFileName + }); + } catch (Error e) { + assertThat(e.toString(), Matchers.containsString("FileNotFoundException: target/concepts.csv/foo.csv")); } } + @After public void after() throws Exception { JettyUtil.closeServer(myServer); FileUtils.deleteQuietly(myConceptsFile); FileUtils.deleteQuietly(myHierarchyFile); + FileUtils.deleteQuietly(myArchiveFile); } @Before - public void start() throws Exception { + public void before() throws Exception { myServer = new Server(0); - TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTerminologyLoaderSvc, myTerminologySvc); + TerminologyUploaderProvider provider = new TerminologyUploaderProvider(myCtx, myTermLoaderSvc); ServletHandler proxyHandler = new ServletHandler(); RestfulServer servlet = new RestfulServer(myCtx); diff --git a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulResponse.java b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulResponse.java index 9aa1b688369..9f6fd70abe4 100644 --- a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulResponse.java +++ b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulResponse.java @@ -51,23 +51,19 @@ public class OkHttpRestfulResponse extends BaseHttpResponse implements IHttpResp this.myResponse = theResponse; } - @Override - public void bufferEntitity() throws IOException { - bufferEntity(); - } - @Override public void bufferEntity() throws IOException { if (myEntityBuffered) { return; } - InputStream responseEntity = readEntity(); - if (responseEntity != null) { - myEntityBuffered = true; - try { - myEntityBytes = IOUtils.toByteArray(responseEntity); - } catch (IllegalStateException e) { - throw new InternalErrorException(e); + try (InputStream responseEntity = readEntity()) { + if (responseEntity != null) { + myEntityBuffered = true; + try { + myEntityBytes = IOUtils.toByteArray(responseEntity); + } catch (IllegalStateException e) { + throw new InternalErrorException(e); + } } } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java index 1ec72030ba8..da0f1c17f17 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.client.apache; */ import java.io.*; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.*; import ca.uhn.fhir.rest.client.impl.BaseHttpResponse; @@ -53,25 +54,19 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons this.myResponse = theResponse; } - @Deprecated // override deprecated method - @Override - public void bufferEntitity() throws IOException { - bufferEntity(); - } - @Override public void bufferEntity() throws IOException { if (myEntityBuffered) { return; } - InputStream respEntity = readEntity(); - if (respEntity != null) { - this.myEntityBuffered = true; - try { - this.myEntityBytes = IOUtils.toByteArray(respEntity); - } catch (IllegalStateException e) { - // FIXME resouce leak - throw new InternalErrorException(e); + try (InputStream respEntity = readEntity()) { + if (respEntity != null) { + this.myEntityBuffered = true; + try { + this.myEntityBytes = IOUtils.toByteArray(respEntity); + } catch (IllegalStateException e) { + throw new InternalErrorException(e); + } } } } @@ -103,7 +98,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons if (Constants.STATUS_HTTP_204_NO_CONTENT != myResponse.getStatusLine().getStatusCode()) { ourLog.debug("Response did not specify a charset, defaulting to utf-8"); } - charset = Charset.forName("UTF-8"); + charset = StandardCharsets.UTF_8; } return new InputStreamReader(readEntity(), charset); @@ -115,11 +110,7 @@ public class ApacheHttpResponse extends BaseHttpResponse implements IHttpRespons if (myResponse.getAllHeaders() != null) { for (Header next : myResponse.getAllHeaders()) { String name = next.getName().toLowerCase(); - List list = headers.get(name); - if (list == null) { - list = new ArrayList<>(); - headers.put(name, list); - } + List list = headers.computeIfAbsent(name, k -> new ArrayList<>()); list.add(next.getValue()); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java index d46fdaf6b0b..a7952a8b6e2 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.client.interceptor; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -94,9 +95,7 @@ public class LoggingInterceptor implements IClientInterceptor { if (content != null) { myLog.info("Client request body:\n{}", content); } - } catch (IllegalStateException e) { - myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); - } catch (IOException e) { + } catch (IllegalStateException | IOException e) { myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); } } @@ -147,11 +146,8 @@ public class LoggingInterceptor implements IClientInterceptor { } if (myLogResponseBody) { - //TODO: Use of a deprecated method should be resolved. - theResponse.bufferEntitity(); - InputStream respEntity = null; - try { - respEntity = theResponse.readEntity(); + theResponse.bufferEntity(); + try (InputStream respEntity = theResponse.readEntity()) { if (respEntity != null) { final byte[] bytes; try { @@ -159,12 +155,10 @@ public class LoggingInterceptor implements IClientInterceptor { } catch (IllegalStateException e) { throw new InternalErrorException(e); } - myLog.info("Client response body:\n{}", new String(bytes, "UTF-8")); + myLog.info("Client response body:\n{}", new String(bytes, StandardCharsets.UTF_8)); } else { myLog.info("Client response body: (none)"); } - } finally { - IOUtils.closeQuietly(respEntity); } } } @@ -178,7 +172,9 @@ public class LoggingInterceptor implements IClientInterceptor { Iterator values = theHeaders.get(key).iterator(); while(values.hasNext()) { String value = values.next(); - b.append(key + ": " + value); + b.append(key); + b.append(": "); + b.append(value); if (nameEntries.hasNext() || values.hasNext()) { b.append('\n'); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java index f29c0ca9037..1f1479507a7 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java @@ -51,11 +51,6 @@ public class JaxRsHttpResponse extends BaseHttpResponse implements IHttpResponse this.myResponse = theResponse; } - @Override - public void bufferEntitity() throws IOException { - bufferEntity(); - } - @Override public void bufferEntity() throws IOException { if(!myBufferedEntity && myResponse.hasEntity()) { diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 1ea96f82d6a..50aabaec8dc 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -46,7 +46,6 @@ org.apache.commons commons-csv - 1.3 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/BaseTest.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/BaseTest.java new file mode 100644 index 00000000000..fae18f88d9c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/BaseTest.java @@ -0,0 +1,19 @@ +package ca.uhn.fhir.jpa; + +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; + +public class BaseTest { + + protected String loadResource(String theClasspath) throws IOException { + InputStream stream = BaseTest.class.getResourceAsStream(theClasspath); + if (stream==null) { + throw new IllegalArgumentException("Unable to find resource: " + theClasspath); + } + return IOUtils.toString(stream, Charsets.UTF_8); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index 8545b5f05cb..ecd496d08ef 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -32,6 +32,13 @@ import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribable import ca.uhn.fhir.jpa.subscription.module.channel.ISubscribableChannelFactory; import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; +import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; +import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices; import org.hibernate.jpa.HibernatePersistenceProvider; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; @@ -256,7 +263,6 @@ public abstract class BaseConfig { } - public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer())); theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java new file mode 100644 index 00000000000..6a63d4093d0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfigDstu3Plus.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.config; + +import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; +import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public abstract class BaseConfigDstu3Plus extends BaseConfig { + + @Bean + public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() { + return new TermCodeSystemStorageSvcImpl(); + } + + @Bean + public ITermDeferredStorageSvc termDeferredStorageSvc() { + return new TermDeferredStorageSvcImpl(); + } + + @Bean + public ITermReindexingSvc termReindexingSvc() { + return new TermReindexingSvcImpl(); + } + + @Bean + public abstract ITermVersionAdapterSvc terminologyVersionAdapterSvc(); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index f4ccd0a2bd7..d3e5019c89a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -7,8 +7,10 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu2; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.TermReadSvcDstu2; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu2; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.validation.IValidatorModule; @@ -134,8 +136,8 @@ public class BaseDstu2Config extends BaseConfig { } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvc terminologyService() { - return new HapiTerminologySvcDstu2(); + public ITermReadSvc terminologyService() { + return new TermReadSvcDstu2(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 95499fcb207..6eb16b01356 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.dstu3; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; @@ -12,10 +13,12 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; -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.term.TermReadSvcDstu3; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu3; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.validation.IValidatorModule; @@ -26,7 +29,6 @@ import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.r5.utils.IResourceValidator; -import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -55,13 +57,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement -public class BaseDstu3Config extends BaseConfig { +public class BaseDstu3Config extends BaseConfigDstu3Plus { @Override public FhirContext fhirContext() { return fhirContextDstu3(); } + @Bean + @Override + public ITermVersionAdapterSvc terminologyVersionAdapterSvc() { + return new TermVersionAdapterSvcDstu3(); + } + @Bean @Primary public FhirContext fhirContextDstu3() { @@ -109,10 +117,9 @@ public class BaseDstu3Config extends BaseConfig { return new JpaValidationSupportChainDstu3(); } - @Bean(name = "myJpaValidationSupportDstu3", autowire = Autowire.BY_NAME) + @Bean(name = "myJpaValidationSupportDstu3") public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 jpaValidationSupportDstu3() { - ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3(); - return retVal; + return new ca.uhn.fhir.jpa.dao.dstu3.JpaValidationSupportDstu3(); } @Bean(name = "myResourceCountsCache") @@ -122,13 +129,12 @@ public class BaseDstu3Config extends BaseConfig { return retVal; } - @Bean(autowire = Autowire.BY_TYPE) + @Bean public IFulltextSearchSvc searchDaoDstu3() { - FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl(); - return searchDao; + return new FulltextSearchSvcImpl(); } - @Bean(autowire = Autowire.BY_TYPE) + @Bean public SearchParamExtractorDstu3 searchParamExtractor() { return new SearchParamExtractorDstu3(); } @@ -138,10 +144,9 @@ public class BaseDstu3Config extends BaseConfig { return new SearchParamRegistryDstu3(); } - @Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME) + @Bean(name = "mySystemDaoDstu3") public IFhirSystemDao systemDaoDstu3() { - ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); - return retVal; + return new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); } @Bean(name = "mySystemProviderDstu3") @@ -152,18 +157,18 @@ public class BaseDstu3Config extends BaseConfig { return retVal; } - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvcImpl(); + @Bean + public ITermLoaderSvc termLoaderService() { + return new TermLoaderSvcImpl(); } - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcDstu3 terminologyService() { - return new HapiTerminologySvcDstu3(); + @Bean + public ITermReadSvcDstu3 terminologyService() { + return new TermReadSvcDstu3(); } @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3") + @Bean(name = "myJpaValidationSupportChainDstu3") public IValidationSupport validationSupportChainDstu3() { return new CachingValidationSupport(jpaValidationSupportChain()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 9c5cfd5d17b..98924125348 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; @@ -12,10 +13,10 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.*; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.validation.IValidatorModule; @@ -55,13 +56,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement -public class BaseR4Config extends BaseConfig { +public class BaseR4Config extends BaseConfigDstu3Plus { @Override public FhirContext fhirContext() { return fhirContextR4(); } + @Bean + @Override + public ITermVersionAdapterSvc terminologyVersionAdapterSvc() { + return new TermVersionAdapterSvcR4(); + } + @Bean @Primary public FhirContext fhirContextR4() { @@ -154,13 +161,13 @@ public class BaseR4Config extends BaseConfig { } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvcImpl(); + public ITermLoaderSvc termLoaderService() { + return new TermLoaderSvcImpl(); } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcR4 terminologyService() { - return new HapiTerminologySvcR4(); + public ITermReadSvcR4 terminologyService() { + return new TermReadSvcR4(); } @Primary diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java index 3bce313f662..e8726d12b1a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config.r5; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; @@ -12,10 +13,10 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR5; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcR5; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR5; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.*; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5; import ca.uhn.fhir.validation.IValidatorModule; @@ -55,13 +56,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement -public class BaseR5Config extends BaseConfig { +public class BaseR5Config extends BaseConfigDstu3Plus { @Override public FhirContext fhirContext() { return fhirContextR5(); } + @Bean + @Override + public ITermVersionAdapterSvc terminologyVersionAdapterSvc() { + return new TermVersionAdapterSvcR5(); + } + @Bean @Primary public FhirContext fhirContextR5() { @@ -154,13 +161,13 @@ public class BaseR5Config extends BaseConfig { } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvcImpl(); + public ITermLoaderSvc terminologyLoaderService() { + return new TermLoaderSvcImpl(); } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcR5 terminologyService() { - return new HapiTerminologySvcR5(); + public ITermReadSvcR5 terminologyService() { + return new TermReadSvcR5(); } @Primary diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 3753f7a8411..f602b4905c8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -20,13 +20,13 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; -import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.AddRemoveCount; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.api.IResource; @@ -53,7 +53,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.MetaUtil; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.XmlUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; @@ -62,8 +61,6 @@ import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.Validate; -import org.hibernate.Session; -import org.hibernate.internal.SessionImpl; import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.slf4j.Logger; @@ -81,7 +78,6 @@ import javax.annotation.PostConstruct; import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.xml.stream.events.Characters; import javax.xml.stream.events.XMLEvent; @@ -139,7 +135,7 @@ public abstract class BaseHapiFhirDao implements IDao, @Autowired protected ISearchParamRegistry mySerarchParamRegistry; @Autowired - protected IHapiTerminologySvc myTerminologySvc; + protected ITermReadSvc myTerminologySvc; @Autowired protected IResourceHistoryTableDao myResourceHistoryTableDao; @Autowired @@ -161,8 +157,6 @@ public abstract class BaseHapiFhirDao implements IDao, @Autowired private ISearchCacheSvc mySearchCacheSvc; @Autowired - private ISearchResultCacheSvc mySearchResultCacheSvc; - @Autowired private ISearchParamPresenceSvc mySearchParamPresenceSvc; @Autowired private DaoRegistry myDaoRegistry; @@ -192,20 +186,18 @@ public abstract class BaseHapiFhirDao implements IDao, * none was created, returns null. */ protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) { + ForcedId retVal = null; if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) { - if (!theCreateForPureNumericIds && IdHelperService.isValidPid(theId)) { - return null; + if (theCreateForPureNumericIds || !IdHelperService.isValidPid(theId)) { + retVal = new ForcedId(); + retVal.setResourceType(theEntity.getResourceType()); + retVal.setForcedId(theId.getIdPart()); + retVal.setResource(theEntity); + theEntity.setForcedId(retVal); } - - ForcedId fid = new ForcedId(); - fid.setResourceType(theEntity.getResourceType()); - fid.setForcedId(theId.getIdPart()); - fid.setResource(theEntity); - theEntity.setForcedId(fid); - return fid; } - return null; + return retVal; } private void extractTagsHapi(IResource theResource, ResourceTable theEntity, Set allDefs) { @@ -285,39 +277,6 @@ public abstract class BaseHapiFhirDao implements IDao, } - private void findMatchingTagIds(RequestDetails theRequest, String theResourceName, IIdType theResourceId, Set tagIds, Class entityClass) { - { - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(entityClass); - cq.multiselect(from.get("myTagId").as(Long.class)).distinct(true); - - if (theResourceName != null) { - Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName); - if (theResourceId != null) { - cq.where(typePredicate, builder.equal(from.get("myResourceId"), myIdHelperService.translateForcedIdToPid(theResourceName, theResourceId.getIdPart(), theRequest))); - } else { - cq.where(typePredicate); - } - } - - TypedQuery query = myEntityManager.createQuery(cq); - for (Tuple next : query.getResultList()) { - tagIds.add(next.get(0, Long.class)); - } - } - } - - protected void flushJpaSession() { - SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class); - int insertionCount = session.getActionQueue().numberOfInsertions(); - int updateCount = session.getActionQueue().numberOfUpdates(); - - StopWatch sw = new StopWatch(); - myEntityManager.flush(); - ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount); - } - private Set getAllTagDefinitions(ResourceTable theEntity) { HashSet retVal = Sets.newHashSet(); if (theEntity.isHasTags()) { @@ -358,7 +317,6 @@ public abstract class BaseHapiFhirDao implements IDao, } } - @SuppressWarnings("unchecked") public IFhirResourceDao getDao(Class theType) { return myDaoRegistry.getResourceDaoOrNull(theType); } @@ -446,6 +404,7 @@ public abstract class BaseHapiFhirDao implements IDao, newVersion = Long.toString(newVersionLong); } + assert theResourceId != null; IIdType newId = theResourceId.withVersion(newVersion); theResource.getIdElement().setValue(newId.getValue()); theSavedEntity.setVersion(newVersionLong); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 6c174bc1dfe..363bdf005b0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -39,7 +39,7 @@ import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.*; import ca.uhn.fhir.model.api.*; @@ -64,7 +64,6 @@ import ca.uhn.fhir.util.UrlUtil; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -135,7 +134,7 @@ public class SearchBuilder implements ISearchBuilder { @Autowired private ISearchParamRegistry mySearchParamRegistry; @Autowired - private IHapiTerminologySvc myTerminologySvc; + private ITermReadSvc myTerminologySvc; @Autowired private MatchUrlService myMatchUrlService; private List myAlsoIncludePids; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 6759858c732..21a4e28a615 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; @@ -41,6 +42,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -54,21 +56,22 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { - List valueSetIds; Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest ); - valueSetIds = new ArrayList<>(); + List valueSetIds = new ArrayList<>(); for (Long next : ids) { valueSetIds.add(new IdType("CodeSystem", next)); } return valueSetIds; } + @Nonnull @Override public IContextValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); @@ -119,7 +122,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoConceptMap { @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Override public TranslationResult translate(TranslationRequest theTranslationRequest, RequestDetails theRequestDetails) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java index 891c243e76a..2a090d57a5b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -62,7 +62,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class); @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Autowired private DefaultProfileValidationSupport myDefaultProfileValidationSupport; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index bc9768ca162..46d38a2fd06 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; @@ -56,6 +57,8 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4 i private ITermCodeSystemDao myCsDao; @Autowired private ValidationSupportChain myValidationSupport; + @Autowired + protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; @Override public List findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { @@ -122,7 +125,7 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4 i if (isNotBlank(codeSystemUrl)) { TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl); if (persCs != null) { - myTerminologySvc.deleteCodeSystem(persCs); + myTerminologyCodeSystemStorageSvc.deleteCodeSystem(persCs); } } } @@ -135,7 +138,7 @@ public class FhirResourceDaoCodeSystemR4 extends FhirResourceDaoR4 i CodeSystem cs = (CodeSystem) theResource; addPidToResource(theEntity, theResource); - myTerminologySvc.storeNewCodeSystemVersionIfNeeded(cs, theEntity); + myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(cs, theEntity); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java index b553e42baa6..d715b273018 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoConceptMapR4.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.TranslationMatch; import ca.uhn.fhir.jpa.term.TranslationRequest; import ca.uhn.fhir.jpa.term.TranslationResult; @@ -38,11 +38,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - public class FhirResourceDaoConceptMapR4 extends FhirResourceDaoR4 implements IFhirResourceDaoConceptMap { @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Override public TranslationResult translate(TranslationRequest theTranslationRequest, RequestDetails theRequestDetails) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index 60f83003197..d94509e6bb7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -56,7 +56,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 implements IFhirResourceDaoValueSet { @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Autowired private DefaultProfileValidationSupport myDefaultProfileValidationSupport; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 515355bc044..fbdea21197e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; @@ -56,6 +57,8 @@ public class FhirResourceDaoCodeSystemR5 extends FhirResourceDaoR5 i private ITermCodeSystemDao myCsDao; @Autowired private ValidationSupportChain myValidationSupport; + @Autowired + protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; @Override public List findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { @@ -122,7 +125,7 @@ public class FhirResourceDaoCodeSystemR5 extends FhirResourceDaoR5 i if (isNotBlank(codeSystemUrl)) { TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl); if (persCs != null) { - myTerminologySvc.deleteCodeSystem(persCs); + myTerminologyCodeSystemStorageSvc.deleteCodeSystem(persCs); } } } @@ -135,7 +138,7 @@ public class FhirResourceDaoCodeSystemR5 extends FhirResourceDaoR5 i CodeSystem cs = (CodeSystem) theResource; addPidToResource(theEntity, theResource); - myTerminologySvc.storeNewCodeSystemVersionIfNeeded(org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(cs), theEntity); + myTerminologyCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(cs), theEntity); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java index dab3f9c764d..ea7b2525463 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoConceptMapR5.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.TranslationMatch; import ca.uhn.fhir.jpa.term.TranslationRequest; import ca.uhn.fhir.jpa.term.TranslationResult; @@ -41,7 +41,7 @@ import java.util.Set; public class FhirResourceDaoConceptMapR5 extends FhirResourceDaoR5 implements IFhirResourceDaoConceptMap { @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Override public TranslationResult translate(TranslationRequest theTranslationRequest, RequestDetails theRequestDetails) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java index a5bb4fcbf0f..ce1cbdcc515 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -56,7 +56,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetR5 extends FhirResourceDaoR5 implements IFhirResourceDaoValueSet { @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; + private ITermReadSvc myHapiTerminologySvc; @Autowired private DefaultProfileValidationSupport myDefaultProfileValidationSupport; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java index 2c9ec0bd4ce..a15ca22fd5c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystem.java @@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.entity; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -39,17 +41,17 @@ import static org.apache.commons.lang3.StringUtils.length; @Entity() //@formatter:on public class TermCodeSystem implements Serializable { - private static final long serialVersionUID = 1L; - - private static final int MAX_NAME_LENGTH = 200; public static final int MAX_URL_LENGTH = 200; - + private static final long serialVersionUID = 1L; + private static final int MAX_NAME_LENGTH = 200; @Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH) private String myCodeSystemUri; @OneToOne() @JoinColumn(name = "CURRENT_VERSION_PID", referencedColumnName = "PID", nullable = true, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_CURVER")) private TermCodeSystemVersion myCurrentVersion; + @Column(name = "CURRENT_VERSION_PID", nullable = true, insertable = false, updatable = false) + private Long myCurrentVersionPid; @Id() @SequenceGenerator(name = "SEQ_CODESYSTEM_PID", sequenceName = "SEQ_CODESYSTEM_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODESYSTEM_PID") @@ -70,12 +72,32 @@ public class TermCodeSystem implements Serializable { super(); } - public String getCodeSystemUri() { - return myCodeSystemUri; + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + TermCodeSystem that = (TermCodeSystem) theO; + + EqualsBuilder b = new EqualsBuilder(); + b.append(myCodeSystemUri, that.myCodeSystemUri); + return b.isEquals(); } - public String getName() { - return myName; + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(17, 37); + b.append(myCodeSystemUri); + return b.toHashCode(); + } + + public String getCodeSystemUri() { + return myCodeSystemUri; } public TermCodeSystem setCodeSystemUri(@Nonnull String theCodeSystemUri) { @@ -86,6 +108,15 @@ public class TermCodeSystem implements Serializable { return this; } + public String getName() { + return myName; + } + + public TermCodeSystem setName(String theName) { + myName = left(theName, MAX_NAME_LENGTH); + return this; + } + public TermCodeSystemVersion getCurrentVersion() { return myCurrentVersion; } @@ -103,11 +134,6 @@ public class TermCodeSystem implements Serializable { return myResource; } - public TermCodeSystem setName(String theName) { - myName = left(theName, MAX_NAME_LENGTH); - return this; - } - public TermCodeSystem setResource(ResourceTable theResource) { myResource = theResource; return this; @@ -115,12 +141,13 @@ public class TermCodeSystem implements Serializable { @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) + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("pid", myPid); + b.append("codeSystemUri", myCodeSystemUri); + b.append("currentVersionPid", myCurrentVersionPid); + b.append("resourcePid", myResourcePid); + b.append("name", myName); + return b .toString(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java index 25a74c21dc1..a0de29a76a8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java @@ -21,8 +21,11 @@ package ca.uhn.fhir.jpa.entity; */ import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import javax.persistence.*; import java.io.Serializable; @@ -36,10 +39,8 @@ import static org.apache.commons.lang3.StringUtils.length; ) @Entity() public class TermCodeSystemVersion implements Serializable { - private static final long serialVersionUID = 1L; - public static final int MAX_VERSION_LENGTH = 200; - + private static final long serialVersionUID = 1L; @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") private Collection myConcepts; @@ -84,34 +85,6 @@ public class TermCodeSystemVersion implements Serializable { super(); } - @CoverageIgnore - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof TermCodeSystemVersion)) { - return false; - } - TermCodeSystemVersion other = (TermCodeSystemVersion) obj; - if ((myResource.getId() == null) != (other.myResource.getId() == null)) { - return false; - } else if (!myResource.getId().equals(other.myResource.getId())) { - return false; - } - - if (myCodeSystemVersionId == null) { - if (other.myCodeSystemVersionId != null) { - return false; - } - } else if (!myCodeSystemVersionId.equals(other.myCodeSystemVersionId)) { - return false; - } - return true; - } public TermCodeSystem getCodeSystem() { return myCodeSystem; @@ -154,13 +127,30 @@ public class TermCodeSystemVersion implements Serializable { return this; } + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + TermCodeSystemVersion that = (TermCodeSystemVersion) theO; + + return new EqualsBuilder() + .append(myCodeSystemVersionId, that.myCodeSystemVersionId) + .append(myCodeSystemPid, that.myCodeSystemPid) + .isEquals(); + } + @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((myResource.getId() == null) ? 0 : myResource.getId().hashCode()); - result = prime * result + ((myCodeSystemVersionId == null) ? 0 : myCodeSystemVersionId.hashCode()); - return result; + HashCodeBuilder b = new HashCodeBuilder(17, 37); + b.append(myCodeSystemVersionId); + b.append(myCodeSystemPid); + return b.toHashCode(); } public String getCodeSystemDisplayName() { @@ -180,4 +170,19 @@ public class TermCodeSystemVersion implements Serializable { getConcepts().add(concept); return concept; } + + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("pid", myId); + b.append("codeSystemResourcePid", myResourcePid); + b.append("codeSystemPid", myCodeSystemPid); + b.append("codeSystemVersionId", myCodeSystemVersionId); + return b.toString(); + } + + TermCodeSystemVersion setCodeSystemPidForUnitTest(long theCodeSystemPid) { + myCodeSystemPid = theCodeSystemPid; + return this; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 1ee569d8867..fae709aa307 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -37,6 +37,7 @@ import javax.persistence.Index; import javax.persistence.*; import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.left; import static org.apache.commons.lang3.StringUtils.length; @@ -108,6 +109,13 @@ public class TermConcept implements Serializable { setCode(theCode); } + public TermConcept addChild(RelationshipTypeEnum theRelationshipType) { + TermConcept child = new TermConcept(); + child.setCodeSystemVersion(myCodeSystem); + addChild(child, theRelationshipType); + return child; + } + public TermConceptParentChildLink addChild(TermConcept theChild, RelationshipTypeEnum theRelationshipType) { Validate.notNull(theRelationshipType, "theRelationshipType must not be null"); TermConceptParentChildLink link = new TermConceptParentChildLink(); @@ -200,7 +208,7 @@ public class TermConcept implements Serializable { public TermConcept setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { myCodeSystem = theCodeSystemVersion; - if (theCodeSystemVersion.getPid() != null) { + if (theCodeSystemVersion != null && theCodeSystemVersion.getPid() != null) { myCodeSystemVersionPid = theCodeSystemVersion.getPid(); } return this; @@ -365,10 +373,13 @@ public class TermConcept implements Serializable { @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("code", myCode) - .append("display", myDisplay) - .build(); + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("code", myCode); + b.append("display", myDisplay); + if (mySequence != null) { + b.append("sequence", mySequence); + } + return b.build(); } public List toValidationProperties() { @@ -387,4 +398,13 @@ public class TermConcept implements Serializable { } return retVal; } + + /** + * Returns a view of {@link #getChildren()} but containing the actual child codes + */ + public List getChildCodes() { + return getChildren().stream().map(t -> t.getChild()).collect(Collectors.toList()); + } + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java index c7d38eed131..87ebf0c05b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptParentChildLink.java @@ -20,11 +20,16 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Fields; + import javax.persistence.*; import java.io.Serializable; @Entity -@Table(name = "TRM_CONCEPT_PC_LINK") +@Table(name = "TRM_CONCEPT_PC_LINK", indexes = { + @Index(name = "IDX_TRMCONCPCLNK_CSV", columnList = "CODESYSTEM_PID") +}) public class TermConceptParentChildLink implements Serializable { private static final long serialVersionUID = 1L; @@ -39,6 +44,10 @@ public class TermConceptParentChildLink implements Serializable { @JoinColumn(name = "CODESYSTEM_PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CS")) private TermCodeSystemVersion myCodeSystem; + @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false, nullable = false) + @Fields({@Field(name = "myCodeSystemVersionPid")}) + private long myCodeSystemVersionPid; + @ManyToOne(cascade = {}) @JoinColumn(name = "PARENT_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_PARENT")) private TermConcept myParent; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/TerminologyUploaderProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/TerminologyUploaderProvider.java index ddb78d7c6b9..8665fe744d8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/TerminologyUploaderProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/TerminologyUploaderProvider.java @@ -22,9 +22,8 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc.UploadStatistics; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.UploadStatistics; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -34,140 +33,46 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.AttachmentUtil; import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ValidateUtil; -import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.CodeSystem; import org.springframework.beans.factory.annotation.Autowired; +import javax.annotation.Nonnull; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import static org.apache.commons.lang3.StringUtils.*; public class TerminologyUploaderProvider extends BaseJpaProvider { - public static final String CONCEPT_COUNT = "conceptCount"; - public static final String TARGET = "target"; - public static final String PARENT_CODE = "parentCode"; - public static final String VALUE = "value"; + public static final String PARAM_FILE = "file"; + public static final String PARAM_SYSTEM = "system"; + private static final String RESP_PARAM_CONCEPT_COUNT = "conceptCount"; + private static final String RESP_PARAM_TARGET = "target"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProvider.class); - private static final String PACKAGE = "package"; + private static final String RESP_PARAM_SUCCESS = "success"; @Autowired private FhirContext myCtx; @Autowired - private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc; - @Autowired - private IHapiTerminologySvc myTerminologySvc; + private ITermLoaderSvc myTerminologyLoaderSvc; /** * Constructor */ public TerminologyUploaderProvider() { - this(null, null, null); + this(null, null); } /** * Constructor */ - public TerminologyUploaderProvider(FhirContext theContext, IHapiTerminologyLoaderSvc theTerminologyLoaderSvc, IHapiTerminologySvc theTerminologySvc) { + public TerminologyUploaderProvider(FhirContext theContext, ITermLoaderSvc theTerminologyLoaderSvc) { myCtx = theContext; myTerminologyLoaderSvc = theTerminologyLoaderSvc; - myTerminologySvc = theTerminologySvc; - } - - - /** - * - * $apply-codesystem-delta-add - * - */ - @Operation(typeName = "CodeSystem", name = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD, idempotent = false, returnParameters = { - }) - public IBaseParameters applyCodeSystemDeltaAdd( - HttpServletRequest theServletRequest, - @OperationParam(name = PARENT_CODE, min = 0, max = 1) IPrimitiveType theParentCode, - @OperationParam(name = VALUE, min = 0, max = 1) IBaseResource theValue, - RequestDetails theRequestDetails - ) { - - startRequest(theServletRequest); - try { - - CodeSystem value; - if (theValue instanceof CodeSystem) { - value = (CodeSystem) theValue; - } else if (theValue instanceof org.hl7.fhir.dstu3.model.CodeSystem) { - value = VersionConvertor_30_40.convertCodeSystem((org.hl7.fhir.dstu3.model.CodeSystem) theValue); - } else if (theValue instanceof org.hl7.fhir.r5.model.CodeSystem) { - value = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem((org.hl7.fhir.r5.model.CodeSystem) theValue); - } else { - throw new InvalidRequestException("Value must be present and be a CodeSystem"); - } - - String system = value.getUrl(); - String parentCode = theParentCode != null ? theParentCode.getValue() : null; - - AtomicInteger counter = myTerminologySvc.applyDeltaCodesystemsAdd(system, parentCode, value); - - IBaseParameters retVal = ParametersUtil.newInstance(myCtx); - ParametersUtil.addParameterToParametersBoolean(myCtx, retVal, "success", true); - ParametersUtil.addParameterToParametersInteger(myCtx, retVal, "addedConcepts", counter.get()); - return retVal; - - } finally { - endRequest(theServletRequest); - } - - } - - - /** - * - * $apply-codesystem-delta-remove - * - */ - @Operation(typeName = "CodeSystem", name = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE, idempotent = false, returnParameters = { - }) - public IBaseParameters applyCodeSystemDeltaRemove( - HttpServletRequest theServletRequest, - @OperationParam(name = VALUE, min = 1, max = 1) IBaseResource theValue, - RequestDetails theRequestDetails - ) { - - startRequest(theServletRequest); - try { - - CodeSystem value; - if (theValue instanceof CodeSystem) { - value = (CodeSystem) theValue; - } else if (theValue instanceof org.hl7.fhir.dstu3.model.CodeSystem) { - value = VersionConvertor_30_40.convertCodeSystem((org.hl7.fhir.dstu3.model.CodeSystem) theValue); - } else if (theValue instanceof org.hl7.fhir.r5.model.CodeSystem) { - value = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem((org.hl7.fhir.r5.model.CodeSystem) theValue); - } else { - throw new InvalidRequestException("Value must be present and be a CodeSystem"); - } - - String system = value.getUrl(); - - AtomicInteger counter = myTerminologySvc.applyDeltaCodesystemsRemove(system, value); - - IBaseParameters retVal = ParametersUtil.newInstance(myCtx); - ParametersUtil.addParameterToParametersBoolean(myCtx, retVal, "success", true); - ParametersUtil.addParameterToParametersInteger(myCtx, retVal, "removedConcepts", counter.get()); - return retVal; - - } finally { - endRequest(theServletRequest); - } - } /** @@ -178,28 +83,31 @@ public class TerminologyUploaderProvider extends BaseJpaProvider { @Operation(typeName = "CodeSystem", name = JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM, idempotent = false, returnParameters = { // @OperationParam(name = "conceptCount", type = IntegerType.class, min = 1) }) - public IBaseParameters uploadExternalCodeSystem( + public IBaseParameters uploadSnapshot( HttpServletRequest theServletRequest, - @OperationParam(name = "url", min = 1, typeName = "uri") IPrimitiveType theCodeSystemUrl, - @OperationParam(name = "contentMode", min = 0, typeName = "code") IPrimitiveType theContentMode, + @OperationParam(name = PARAM_SYSTEM, min = 1, typeName = "uri") IPrimitiveType theCodeSystemUrl, @OperationParam(name = "localfile", min = 1, max = OperationParam.MAX_UNLIMITED, typeName = "string") List> theLocalFile, - @OperationParam(name = PACKAGE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List thePackage, + @OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List theFiles, RequestDetails theRequestDetails ) { startRequest(theServletRequest); + if (theCodeSystemUrl == null || isBlank(theCodeSystemUrl.getValueAsString())) { + throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_SYSTEM); + } + if (theLocalFile == null || theLocalFile.size() == 0) { - if (thePackage == null || thePackage.size() == 0) { + if (theFiles == null || theFiles.size() == 0) { throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data"); } - for (ICompositeType next : thePackage) { + for (ICompositeType next : theFiles) { ValidateUtil.isTrueOrThrowInvalidRequest(myCtx.getElementDefinition(next.getClass()).getName().equals("Attachment"), "Package must be of type Attachment"); } } try { - List localFiles = new ArrayList<>(); + List localFiles = new ArrayList<>(); if (theLocalFile != null && theLocalFile.size() > 0) { for (IPrimitiveType nextLocalFile : theLocalFile) { if (isNotBlank(nextLocalFile.getValue())) { @@ -208,7 +116,7 @@ public class TerminologyUploaderProvider extends BaseJpaProvider { if (!nextFile.exists() || !nextFile.isFile()) { throw new InvalidRequestException("Unknown file: " + nextFile.getName()); } - localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { + localFiles.add(new ITermLoaderSvc.FileDescriptor() { @Override public String getFilename() { return nextFile.getAbsolutePath(); @@ -227,15 +135,15 @@ public class TerminologyUploaderProvider extends BaseJpaProvider { } } - if (thePackage != null) { - for (ICompositeType nextPackage : thePackage) { + if (theFiles != null) { + for (ICompositeType nextPackage : theFiles) { final String url = AttachmentUtil.getOrCreateUrl(myCtx, nextPackage).getValueAsString(); if (isBlank(url)) { throw new UnprocessableEntityException("Package is missing mandatory url element"); } - localFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { + localFiles.add(new ITermLoaderSvc.FileDescriptor() { @Override public String getFilename() { return url; @@ -250,33 +158,29 @@ public class TerminologyUploaderProvider extends BaseJpaProvider { } } - String codeSystemUrl = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null; - codeSystemUrl = defaultString(codeSystemUrl); + String codeSystemUrl = theCodeSystemUrl.getValue(); + codeSystemUrl = trim(codeSystemUrl); - String contentMode = theContentMode != null ? theContentMode.getValue() : null; UploadStatistics stats; - if ("custom".equals(contentMode)) { - stats = myTerminologyLoaderSvc.loadCustom(codeSystemUrl, localFiles, theRequestDetails); - } else { - switch (codeSystemUrl) { - case IHapiTerminologyLoaderSvc.SCT_URI: - stats = myTerminologyLoaderSvc.loadSnomedCt(localFiles, theRequestDetails); - break; - case IHapiTerminologyLoaderSvc.LOINC_URI: - stats = myTerminologyLoaderSvc.loadLoinc(localFiles, theRequestDetails); - break; - case IHapiTerminologyLoaderSvc.IMGTHLA_URI: - stats = myTerminologyLoaderSvc.loadImgthla(localFiles, theRequestDetails); - break; - default: - throw new InvalidRequestException("Unknown URL: " + codeSystemUrl); - } + switch (codeSystemUrl) { + case ITermLoaderSvc.SCT_URI: + stats = myTerminologyLoaderSvc.loadSnomedCt(localFiles, theRequestDetails); + break; + case ITermLoaderSvc.LOINC_URI: + stats = myTerminologyLoaderSvc.loadLoinc(localFiles, theRequestDetails); + break; + case ITermLoaderSvc.IMGTHLA_URI: + stats = myTerminologyLoaderSvc.loadImgthla(localFiles, theRequestDetails); + break; + default: + stats = myTerminologyLoaderSvc.loadCustom(codeSystemUrl, localFiles, theRequestDetails); + break; } IBaseParameters retVal = ParametersUtil.newInstance(myCtx); - ParametersUtil.addParameterToParametersBoolean(myCtx, retVal, "success", true); - ParametersUtil.addParameterToParametersInteger(myCtx, retVal, CONCEPT_COUNT, stats.getConceptCount()); - ParametersUtil.addParameterToParametersReference(myCtx, retVal, TARGET, stats.getTarget().getValue()); + ParametersUtil.addParameterToParametersBoolean(myCtx, retVal, RESP_PARAM_SUCCESS, true); + ParametersUtil.addParameterToParametersInteger(myCtx, retVal, RESP_PARAM_CONCEPT_COUNT, stats.getUpdatedConceptCount()); + ParametersUtil.addParameterToParametersReference(myCtx, retVal, RESP_PARAM_TARGET, stats.getTarget().getValue()); return retVal; } finally { @@ -284,5 +188,100 @@ public class TerminologyUploaderProvider extends BaseJpaProvider { } } + /** + * + * $apply-codesystem-delta-add + * + */ + @Operation(typeName = "CodeSystem", name = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD, idempotent = false, returnParameters = { + }) + public IBaseParameters uploadDeltaAdd( + HttpServletRequest theServletRequest, + @OperationParam(name = PARAM_SYSTEM, min = 1, max = 1, typeName = "uri") IPrimitiveType theSystem, + @OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List theFiles, + RequestDetails theRequestDetails + ) { + + startRequest(theServletRequest); + try { + validateHaveSystem(theSystem); + validateHaveFiles(theFiles); + + List files = convertAttachmentsToFileDescriptors(theFiles); + UploadStatistics outcome = myTerminologyLoaderSvc.loadDeltaAdd(theSystem.getValue(), files, theRequestDetails); + return toDeltaResponse(outcome); + } finally { + endRequest(theServletRequest); + } + + } + + + /** + * + * $apply-codesystem-delta-remove + * + */ + @Operation(typeName = "CodeSystem", name = JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE, idempotent = false, returnParameters = { + }) + public IBaseParameters uploadDeltaRemove( + HttpServletRequest theServletRequest, + @OperationParam(name = PARAM_SYSTEM, min = 1, max = 1, typeName = "uri") IPrimitiveType theSystem, + @OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List theFiles, + RequestDetails theRequestDetails + ) { + + startRequest(theServletRequest); + try { + validateHaveSystem(theSystem); + validateHaveFiles(theFiles); + + List files = convertAttachmentsToFileDescriptors(theFiles); + UploadStatistics outcome = myTerminologyLoaderSvc.loadDeltaRemove(theSystem.getValue(), files, theRequestDetails); + return toDeltaResponse(outcome); + } finally { + endRequest(theServletRequest); + } + + } + + private void validateHaveSystem(IPrimitiveType theSystem) { + if (theSystem == null || isBlank(theSystem.getValueAsString())) { + throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_SYSTEM); + } + } + + private void validateHaveFiles(List theFiles) { + if (theFiles != null) { + for (ICompositeType nextFile : theFiles) { + if (!nextFile.isEmpty()) { + return; + } + } + } + throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_FILE); + } + + @Nonnull + private List convertAttachmentsToFileDescriptors(@OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List theFiles) { + List files = new ArrayList<>(); + for (ICompositeType next : theFiles) { + byte[] nextData = AttachmentUtil.getOrCreateData(myCtx, next).getValue(); + String nextUrl = AttachmentUtil.getOrCreateUrl(myCtx, next).getValue(); + ValidateUtil.isTrueOrThrowInvalidRequest(nextData != null && nextData.length > 0, "Missing Attachment.data value"); + ValidateUtil.isNotBlankOrThrowUnprocessableEntity(nextUrl, "Missing Attachment.url value"); + + files.add(new ITermLoaderSvc.ByteArrayFileDescriptor(nextUrl, nextData)); + } + return files; + } + + private IBaseParameters toDeltaResponse(UploadStatistics theOutcome) { + IBaseParameters retVal = ParametersUtil.newInstance(myCtx); + ParametersUtil.addParameterToParametersInteger(myCtx, retVal, RESP_PARAM_CONCEPT_COUNT, theOutcome.getUpdatedConceptCount()); + ParametersUtil.addParameterToParametersReference(myCtx, retVal, RESP_PARAM_TARGET, theOutcome.getTarget().getValue()); + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index d7869c6118a..9f048eca18f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -53,6 +53,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.method.PageMethodBinding; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.ICachedSearchDetails; +import ca.uhn.fhir.util.AsyncUtil; import ca.uhn.fhir.util.StopWatch; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; @@ -76,6 +77,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; @@ -154,14 +156,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public void cancelAllActiveSearches() { for (SearchTask next : myIdToSearchTask.values()) { next.requestImmediateAbort(); - try { - next.getCompletionLatch().await(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - ourLog.warn("Failed to wait for completion", e); - } + AsyncUtil.awaitLatchAndIgnoreInterrupt(next.getCompletionLatch(), 30, TimeUnit.SECONDS); } } + @SuppressWarnings("SameParameterValue") + @VisibleForTesting + void setMaxMillisToWaitForRemoteResultsForUnitTest(long theMaxMillisToWaitForRemoteResults) { + myMaxMillisToWaitForRemoteResults = theMaxMillisToWaitForRemoteResults; + } + /** * This method is called by the HTTP client processing thread in order to * fetch resources. @@ -189,18 +193,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (searchTask != null) { ourLog.trace("Local search found"); List resourcePids = searchTask.getResourcePids(theFrom, theTo); - if (resourcePids != null) { - ourLog.trace("Local search returned {} pids, wanted {}-{} - Search: {}", resourcePids.size(), theFrom, theTo, searchTask.getSearch()); + ourLog.trace("Local search returned {} pids, wanted {}-{} - Search: {}", resourcePids.size(), theFrom, theTo, searchTask.getSearch()); - /* - * Generally, if a search task is open, the fastest possible thing is to just return its results. This - * will work most of the time, but can fail if the task hit a search threshold and the client is requesting - * results beyond that threashold. In that case, we'll keep going below, since that will trigger another - * task. - */ - if ((searchTask.getSearch().getNumFound() - searchTask.getSearch().getNumBlocked()) >= theTo || resourcePids.size() == (theTo - theFrom)) { - return resourcePids; - } + /* + * Generally, if a search task is open, the fastest possible thing is to just return its results. This + * will work most of the time, but can fail if the task hit a search threshold and the client is requesting + * results beyond that threashold. In that case, we'll keep going below, since that will trigger another + * task. + */ + if ((searchTask.getSearch().getNumFound() - searchTask.getSearch().getNumBlocked()) >= theTo || resourcePids.size() == (theTo - theFrom)) { + return resourcePids; } } } @@ -244,11 +246,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } } - try { - Thread.sleep(500); - } catch (InterruptedException e) { - // ignore - } + AsyncUtil.sleep(500); } ourLog.trace("Finished looping"); @@ -627,14 +625,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { Integer awaitInitialSync() { ourLog.trace("Awaiting initial sync"); do { - try { - if (getInitialCollectionLatch().await(250, TimeUnit.MILLISECONDS)) { - break; - } - } catch (InterruptedException e) { - // Shouldn't happen - Thread.currentThread().interrupt(); - throw new InternalErrorException(e); + if (AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(getInitialCollectionLatch(), 250L, TimeUnit.MILLISECONDS)) { + break; } } while (getSearch().getStatus() == SearchStatusEnum.LOADING); ourLog.trace("Initial sync completed"); @@ -663,7 +655,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return sb; } - public List getResourcePids(int theFromIndex, int theToIndex) { + @Nonnull + List getResourcePids(int theFromIndex, int theToIndex) { ourLog.debug("Requesting search PIDs from {}-{}", theFromIndex, theToIndex); boolean keepWaiting; @@ -698,11 +691,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (keepWaiting) { ourLog.info("Waiting as we only have {} results - Search status: {}", mySyncedPids.size(), mySearch.getStatus()); - try { - Thread.sleep(500); - } catch (InterruptedException theE) { - // ignore - } + AsyncUtil.sleep(500L); } } while (keepWaiting); @@ -1081,11 +1070,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } if (myLoadingThrottleForUnitTests != null) { - try { - Thread.sleep(myLoadingThrottleForUnitTests); - } catch (InterruptedException e) { - // ignore - } + AsyncUtil.sleep(myLoadingThrottleForUnitTests); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java index 678c457c601..5bf82275086 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImpl.java @@ -48,17 +48,15 @@ import java.util.List; import java.util.Optional; public class DatabaseSearchCacheSvcImpl extends BaseSearchCacheSvcImpl { - private static final Logger ourLog = LoggerFactory.getLogger(DatabaseSearchCacheSvcImpl.class); - /* * Be careful increasing this number! We use the number of params here in a - * DELETE FROM foo WHERE params IN (aaaa) + * DELETE FROM foo WHERE params IN (term,term,term...) * type query and this can fail if we have 1000s of params */ public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT = 500; public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS = 20000; public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND; - + private static final Logger ourLog = LoggerFactory.getLogger(DatabaseSearchCacheSvcImpl.class); private static int ourMaximumResultsToDeleteInOneStatement = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT; private static int ourMaximumResultsToDeleteInOnePass = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS; private static Long ourNowForUnitTests; @@ -108,6 +106,14 @@ public class DatabaseSearchCacheSvcImpl extends BaseSearchCacheSvcImpl { } + void setSearchDaoForUnitTest(ISearchDao theSearchDao) { + mySearchDao = theSearchDao; + } + + void setTxManagerForUnitTest(PlatformTransactionManager theTxManager) { + myTxManager = theTxManager; + } + @Override @Transactional(Transactional.TxType.NEVER) public Optional tryToMarkSearchAsInProgress(Search theSearch) { @@ -185,7 +191,7 @@ public class DatabaseSearchCacheSvcImpl extends BaseSearchCacheSvcImpl { int count = toDelete.getContent().size(); if (count > 0) { - if (ourLog.isDebugEnabled()) { + if (ourLog.isDebugEnabled() || "true".equalsIgnoreCase(System.getProperty("test"))) { Long total = tt.execute(t -> mySearchDao.count()); ourLog.debug("Deleted {} searches, {} remaining", count, total); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java similarity index 69% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index a9983c9d13b..94b48481a87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -30,13 +30,15 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; -import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.ValidateUtil; @@ -44,7 +46,6 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; -import com.google.common.collect.ArrayListMultimap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; @@ -59,8 +60,6 @@ import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.QueryBuilder; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseCoding; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; @@ -70,16 +69,12 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; 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.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Nonnull; @@ -94,17 +89,14 @@ import javax.validation.constraints.NotNull; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.*; -public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ApplicationContextAware { +public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationContextAware { public static final int DEFAULT_FETCH_SIZE = 250; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiTerminologySvcImpl.class); - private static final Object PLACEHOLDER_OBJECT = new Object(); - private static boolean ourForceSaveDeferredAlwaysForUnitTest; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseTermReadSvcImpl.class); private static boolean ourLastResultsFromTranslationCache; // For testing. private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. @Autowired @@ -135,22 +127,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, protected FhirContext myContext; @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; - private ArrayListMultimap myChildToParentPidCache; @Autowired private ITermCodeSystemVersionDao myCodeSystemVersionDao; - private List myConceptLinksToSaveLater = new ArrayList<>(); - @Autowired - private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; - private List myDeferredConcepts = Collections.synchronizedList(new ArrayList<>()); - private List myDeferredValueSets = Collections.synchronizedList(new ArrayList<>()); - private List myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>()); @Autowired private DaoConfig myDaoConfig; - private long myNextReindexPass; - private boolean myProcessDeferred = true; - @Autowired - private PlatformTransactionManager myTransactionMgr; - private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; private IFhirResourceDaoValueSet myValueSetResourceDao; private Cache> myTranslationCache; private Cache> myTranslationWithReverseCache; @@ -165,8 +145,12 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private PlatformTransactionManager myTxManager; @Autowired private ITermValueSetConceptViewDao myTermValueSetConceptViewDao; - @Autowired - private ISchedulerService mySchedulerService; + @Autowired + private ISchedulerService mySchedulerService; + @Autowired(required = false) + private ITermDeferredStorageSvc myDeferredStorageSvc; + @Autowired(required = false) + private ITermCodeSystemStorageSvc myConceptStorageSvc; private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri(); @@ -208,7 +192,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, boolean retVal = theSetToPopulate.add(theConcept); if (retVal) { if (theSetToPopulate.size() >= myDaoConfig.getMaximumExpansionSize()) { - String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize()); + String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize()); throw new InvalidRequestException(msg); } } @@ -232,16 +216,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, .build(); } - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting - public void clearDeferred() { - myDeferredValueSets.clear(); - myDeferredConceptMaps.clear(); - myDeferredConcepts.clear(); - } - /** * This method is present only for unit tests, do not call from client code */ @@ -258,88 +232,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myTranslationWithReverseCache.invalidateAll(); } - protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource); - - protected void validateCodeSystemForStorage(CodeSystem theCodeSystemResource) { - ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theCodeSystemResource.getUrl(), "Can not store a CodeSystem without a valid URL"); - } - - protected abstract void createOrUpdateConceptMap(ConceptMap theNextConceptMap); - - abstract void createOrUpdateValueSet(ValueSet theValueSet); - - @Override - public void deleteCodeSystem(TermCodeSystem theCodeSystem) { - ourLog.info(" * Deleting code system {}", theCodeSystem.getPid()); - - myEntityManager.flush(); - TermCodeSystem cs = myCodeSystemDao.findById(theCodeSystem.getPid()).orElseThrow(IllegalStateException::new); - cs.setCurrentVersion(null); - myCodeSystemDao.save(cs); - myCodeSystemDao.flush(); - - int i = 0; - List codeSystemVersions = myCodeSystemVersionDao.findByCodeSystemPid(theCodeSystem.getPid()); - for (TermCodeSystemVersion next : codeSystemVersions) { - deleteCodeSystemVersion(next.getPid()); - } - myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem); - myCodeSystemDao.delete(theCodeSystem); - - myEntityManager.flush(); - } - - public void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) { - ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid); - - PageRequest page1000 = PageRequest.of(0, 1000); - - // Parent/Child links - { - String descriptor = "parent/child links"; - Supplier> loader = () -> myConceptParentChildLinkDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); - Supplier counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid); - doDelete(descriptor, loader, counter, myConceptParentChildLinkDao); - } - - // Properties - { - String descriptor = "concept properties"; - Supplier> loader = () -> myConceptPropertyDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); - Supplier counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid); - doDelete(descriptor, loader, counter, myConceptPropertyDao); - } - - // Designations - { - String descriptor = "concept designations"; - Supplier> loader = () -> myConceptDesignationDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); - Supplier counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid); - doDelete(descriptor, loader, counter, myConceptDesignationDao); - } - - // Concepts - { - String descriptor = "concepts"; - // For some reason, concepts are much slower to delete, so use a smaller batch size - PageRequest page100 = PageRequest.of(0, 100); - Supplier> loader = () -> myConceptDao.findByCodeSystemVersion(page100, theCodeSystemVersionPid); - Supplier counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid); - doDelete(descriptor, loader, counter, myConceptDao); - } - - Optional codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid); - if (codeSystemOpt.isPresent()) { - TermCodeSystem codeSystem = codeSystemOpt.get(); - ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); - codeSystem.setCurrentVersion(null); - myCodeSystemDao.save(codeSystem); - } - - ourLog.info(" * Deleting code system version"); - myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); - - } public void deleteConceptMap(ResourceTable theResourceTable) { // Get existing entity so it can be deleted. @@ -396,50 +288,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, deleteValueSet(theResourceTable); } - private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { - int count; - ourLog.info(" * Deleting {}", theDescriptor); - int totalCount = theCounter.get(); - StopWatch sw = new StopWatch(); - count = 0; - while (true) { - Slice link = theLoader.get(); - if (!link.hasContent()) { - break; - } - - TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); - txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - txTemplate.execute(t -> { - theDao.deleteAll(link); - return null; - }); - - count += link.getNumberOfElements(); - ourLog.info(" * {} {} deleted - {}/sec - ETA: {}", count, theDescriptor, sw.formatThroughput(count, TimeUnit.SECONDS), sw.getEstimatedTimeRemaining(count, totalCount)); - } - theDao.flush(); - } - - private int ensureParentsSaved(Collection theParents) { - ourLog.trace("Checking {} parents", theParents.size()); - int retVal = 0; - - for (TermConceptParentChildLink nextLink : theParents) { - if (nextLink.getRelationshipType() == RelationshipTypeEnum.ISA) { - TermConcept nextParent = nextLink.getParent(); - retVal += ensureParentsSaved(nextParent.getParents()); - if (nextParent.getId() == null) { - nextParent.setUpdated(new Date()); - myConceptDao.saveAndFlush(nextParent); - retVal++; - ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId()); - } - } - } - - return retVal; - } @Override @Transactional(propagation = Propagation.REQUIRED) @@ -469,7 +317,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, Optional optionalTermValueSet; if (theValueSetToExpand.hasId()) { - Long valueSetResourcePid = getValueSetResourcePid(theValueSetToExpand.getIdElement()); + Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSetToExpand.getIdElement()); optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid); } else if (theValueSetToExpand.hasUrl()) { optionalTermValueSet = myValueSetDao.findByUrl(theValueSetToExpand.getUrl()); @@ -915,7 +763,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, } private boolean isCodeSystemLoinc(String theSystem) { - return IHapiTerminologyLoaderSvc.LOINC_URI.equals(theSystem); + return ITermLoaderSvc.LOINC_URI.equals(theSystem); } private void handleFilterDisplay(QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { @@ -958,6 +806,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, } } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void handleFilterLoincParentChild(QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: @@ -990,6 +839,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return new Term(TermConceptPropertyFieldBridge.CONCEPT_FIELD_PROPERTY_PREFIX + theProperty, theValue); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void handleFilterLoincAncestor(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: @@ -1033,6 +883,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return retVal; } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void handleFilterLoincDescendant(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: @@ -1178,7 +1029,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Override public boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet) { - Long valueSetResourcePid = getValueSetResourcePid(theValueSet.getIdElement()); + Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); Optional optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid); if (!optionalTermValueSet.isPresent()) { @@ -1201,7 +1052,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required"); - Long valueSetResourcePid = getValueSetResourcePid(theValueSet.getIdElement()); + Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); List concepts = new ArrayList<>(); if (isNotBlank(theCode)) { @@ -1241,9 +1092,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private List findByValueSetResourcePidSystemAndCode(Long theResourcePid, String theSystem, String theCode) { List retVal = new ArrayList<>(); Optional optionalTermValueSetConcept = myValueSetConceptDao.findByValueSetResourcePidSystemAndCode(theResourcePid, theSystem, theCode); - if (optionalTermValueSetConcept.isPresent()) { - retVal.add(optionalTermValueSetConcept.get()); - } + optionalTermValueSetConcept.ifPresent(retVal::add); return retVal; } @@ -1293,7 +1142,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY); return txTemplate.execute(t -> { - TermCodeSystemVersion csv = findCurrentCodeSystemVersionForSystem(theCodeSystem); + TermCodeSystemVersion csv = null; + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theCodeSystem); + if (cs != null && cs.getCurrentVersion() != null) { + csv = cs.getCurrentVersion(); + } return myConceptDao.findByCodeSystemAndCode(csv, theCode); }); } @@ -1360,621 +1213,30 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return toVersionIndependentConcepts(theSystem, codes); } - private TermCodeSystemVersion findCurrentCodeSystemVersionForSystem(String theCodeSystem) { - TermCodeSystem cs = getCodeSystem(theCodeSystem); - if (cs == null || cs.getCurrentVersion() == null) { - return null; - } - return cs.getCurrentVersion(); - } - private TermCodeSystem getCodeSystem(String theSystem) { return myCodeSystemDao.findByCodeSystemUri(theSystem); } - protected abstract CodeSystem getCodeSystemFromContext(String theSystem); - - private Long getCodeSystemResourcePid(IIdType theIdType) { - return getCodeSystemResourcePid(theIdType, null); - } - - private Long getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { - return getResourcePid(myCodeSystemResourceDao, theIdType, theRequestDetails); - } - - private Long getValueSetResourcePid(IIdType theIdType) { - return getValueSetResourcePid(theIdType, null); - } - - private Long getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { - return getResourcePid(myValueSetResourceDao, theIdType, theRequestDetails); - } - - private Long getResourcePid(IFhirResourceDao theResourceDao, IIdType theIdType, RequestDetails theRequestDetails) { - ResourceTable resourceTable = (ResourceTable) theResourceDao.readEntity(theIdType, theRequestDetails); - return resourceTable.getId(); - } - - private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack, int theTotalConcepts) { - if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) { - return; - } - - if (theConceptsStack.size() == 1 || theConceptsStack.size() % 10000 == 0) { - float pct = (float) theConceptsStack.size() / (float) theTotalConcepts; - ourLog.info("Have processed {}/{} concepts ({}%)", theConceptsStack.size(), theTotalConcepts, (int) (pct * 100.0f)); - } - - theConcept.setCodeSystemVersion(theCodeSystem); - theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); - - if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { - saveConcept(theConcept); - } else { - myDeferredConcepts.add(theConcept); - } - - for (TermConceptParentChildLink next : theConcept.getChildren()) { - persistChildren(next.getChild(), theCodeSystem, theConceptsStack, theTotalConcepts); - } - - for (TermConceptParentChildLink next : theConcept.getChildren()) { - if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { - saveConceptLink(next); - } else { - myConceptLinksToSaveLater.add(next); - } - } - - } - - private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) { - if (theNext.getCodeSystemVersion() != null) { - return; - } - theNext.setCodeSystemVersion(theCodeSystemVersion); - for (TermConceptParentChildLink next : theNext.getChildren()) { - populateVersion(next.getChild(), theCodeSystemVersion); - } - } - - private void processDeferredConceptMaps() { - int count = Math.min(myDeferredConceptMaps.size(), 20); - for (ConceptMap nextConceptMap : new ArrayList<>(myDeferredConceptMaps.subList(0, count))) { - ourLog.info("Creating ConceptMap: {}", nextConceptMap.getId()); - createOrUpdateConceptMap(nextConceptMap); - myDeferredConceptMaps.remove(nextConceptMap); - } - ourLog.info("Saved {} deferred ConceptMap resources, have {} remaining", count, myDeferredConceptMaps.size()); - } - - private void processDeferredConcepts() { - int codeCount = 0, relCount = 0; - StopWatch stopwatch = new StopWatch(); - - int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myDeferredConcepts.size()); - ourLog.info("Saving {} deferred concepts...", count); - while (codeCount < count && myDeferredConcepts.size() > 0) { - TermConcept next = myDeferredConcepts.remove(0); - codeCount += saveConcept(next); - } - - if (codeCount > 0) { - ourLog.info("Saved {} deferred concepts ({} codes remain and {} relationships remain) in {}ms ({}ms / code)", - codeCount, myDeferredConcepts.size(), myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)); - } - - if (codeCount == 0) { - count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptLinksToSaveLater.size()); - ourLog.info("Saving {} deferred concept relationships...", count); - while (relCount < count && myConceptLinksToSaveLater.size() > 0) { - TermConceptParentChildLink next = myConceptLinksToSaveLater.remove(0); - - if (!myConceptDao.findById(next.getChild().getId()).isPresent() || !myConceptDao.findById(next.getParent().getId()).isPresent()) { - ourLog.warn("Not inserting link from child {} to parent {} because it appears to have been deleted", next.getParent().getCode(), next.getChild().getCode()); - continue; - } - - saveConceptLink(next); - relCount++; - } - } - - if (relCount > 0) { - ourLog.info("Saved {} deferred relationships ({} remain) in {}ms ({}ms / entry)", - relCount, myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(relCount)); - } - - if ((myDeferredConcepts.size() + myConceptLinksToSaveLater.size()) == 0) { - ourLog.info("All deferred concepts and relationships have now been synchronized to the database"); - } - } - - private void processDeferredValueSets() { - int count = Math.min(myDeferredValueSets.size(), 20); - for (ValueSet nextValueSet : new ArrayList<>(myDeferredValueSets.subList(0, count))) { - ourLog.info("Creating ValueSet: {}", nextValueSet.getId()); - createOrUpdateValueSet(nextValueSet); - myDeferredValueSets.remove(nextValueSet); - } - ourLog.info("Saved {} deferred ValueSet resources, have {} remaining", count, myDeferredValueSets.size()); - } - - private void processReindexing() { - if (System.currentTimeMillis() < myNextReindexPass && !ourForceSaveDeferredAlwaysForUnitTest) { - return; - } - - TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - tt.execute(new TransactionCallbackWithoutResult() { - private void createParentsString(StringBuilder theParentsBuilder, Long theConceptPid) { - Validate.notNull(theConceptPid, "theConceptPid must not be null"); - List parents = myChildToParentPidCache.get(theConceptPid); - if (parents.contains(-1L)) { - return; - } else if (parents.isEmpty()) { - Collection parentLinks = myConceptParentChildLinkDao.findAllWithChild(theConceptPid); - if (parentLinks.isEmpty()) { - myChildToParentPidCache.put(theConceptPid, -1L); - ourLog.info("Found {} parent concepts of concept {} (cache has {})", 0, theConceptPid, myChildToParentPidCache.size()); - return; - } else { - for (Long next : parentLinks) { - myChildToParentPidCache.put(theConceptPid, next); - } - int parentCount = myChildToParentPidCache.get(theConceptPid).size(); - ourLog.info("Found {} parent concepts of concept {} (cache has {})", parentCount, theConceptPid, myChildToParentPidCache.size()); - } - } - - for (Long nextParent : parents) { - if (theParentsBuilder.length() > 0) { - theParentsBuilder.append(' '); - } - theParentsBuilder.append(nextParent); - createParentsString(theParentsBuilder, nextParent); - } - - } - - @Override - protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - int maxResult = 1000; - Page concepts = myConceptDao.findResourcesRequiringReindexing(PageRequest.of(0, maxResult)); - if (!concepts.hasContent()) { - if (myChildToParentPidCache != null) { - ourLog.info("Clearing parent concept cache"); - myNextReindexPass = System.currentTimeMillis() + DateUtils.MILLIS_PER_MINUTE; - myChildToParentPidCache = null; - } - return; - } - - if (myChildToParentPidCache == null) { - myChildToParentPidCache = ArrayListMultimap.create(); - } - - ourLog.info("Indexing {} / {} concepts", concepts.getContent().size(), concepts.getTotalElements()); - - int count = 0; - StopWatch stopwatch = new StopWatch(); - - for (TermConcept nextConcept : concepts) { - - if (isBlank(nextConcept.getParentPidsAsString())) { - StringBuilder parentsBuilder = new StringBuilder(); - createParentsString(parentsBuilder, nextConcept.getId()); - nextConcept.setParentPids(parentsBuilder.toString()); - } - - saveConcept(nextConcept); - count++; - } - - ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", count, concepts.getContent().size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(count)); - } - }); - - } - - /** - * Returns the number of saved concepts - */ - private int saveOrUpdateConcept(TermConcept theConcept) { - - TermCodeSystemVersion csv = theConcept.getCodeSystemVersion(); - Optional existing = myConceptDao.findByCodeSystemAndCode(csv, theConcept.getCode()); - if (existing.isPresent()) { - TermConcept existingConcept = existing.get(); - boolean haveChanges = false; - if (!StringUtils.equals(existingConcept.getDisplay(), theConcept.getDisplay())) { - existingConcept.setDisplay(theConcept.getDisplay()); - haveChanges = true; - } - - if (!haveChanges) { - return 0; - } - - myConceptDao.save(existingConcept); - return 1; - - } else { - return saveConcept(theConcept); - } - - } - - /** - * Returns the number of saved concepts - */ - private int saveConcept(TermConcept theConcept) { - int retVal = 0; - - /* - * If the concept has an ID, we're reindexing, so there's no need to - * save parent concepts first (it's way too slow to do that) - */ - if (theConcept.getId() == null) { - retVal += ensureParentsSaved(theConcept.getParents()); - } - - if (theConcept.getId() == null || theConcept.getIndexStatus() == null) { - retVal++; - theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); - theConcept.setUpdated(new Date()); - myConceptDao.save(theConcept); - - for (TermConceptProperty next : theConcept.getProperties()) { - myConceptPropertyDao.save(next); - } - - for (TermConceptDesignation next : theConcept.getDesignations()) { - myConceptDesignationDao.save(next); - } - } - - ourLog.trace("Saved {} and got PID {}", theConcept.getCode(), theConcept.getId()); - return retVal; - } - - private void saveConceptLink(TermConceptParentChildLink next) { - if (next.getId() == null) { - myConceptParentChildLinkDao.save(next); - } - } - - @Transactional(propagation = Propagation.NEVER) - @Override - public synchronized void saveDeferred() { - if (isProcessDeferredPaused()) { - return; - } else if (isNoDeferredConceptsAndNoConceptLinksToSaveLater()) { - processReindexing(); - } - - TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); - tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - if (isDeferredConceptsOrConceptLinksToSaveLater()) { - tt.execute(t -> { - processDeferredConcepts(); - return null; - }); - } - - if (isDeferredValueSets()) { - tt.execute(t -> { - processDeferredValueSets(); - return null; - }); - } - if (isDeferredConceptMaps()) { - tt.execute(t -> { - processDeferredConceptMaps(); - return null; - }); - } - - } - - private boolean isProcessDeferredPaused() { - return !myProcessDeferred; - } - - private boolean isNoDeferredConceptsAndNoConceptLinksToSaveLater() { - return isNoDeferredConcepts() && isNoConceptLinksToSaveLater(); - } - - private boolean isDeferredConceptsOrConceptLinksToSaveLater() { - return isDeferredConcepts() || isConceptLinksToSaveLater(); - } - - private boolean isDeferredConcepts() { - return !myDeferredConcepts.isEmpty(); - } - - private boolean isNoDeferredConcepts() { - return myDeferredConcepts.isEmpty(); - } - - private boolean isConceptLinksToSaveLater() { - return !myConceptLinksToSaveLater.isEmpty(); - } - - private boolean isNoConceptLinksToSaveLater() { - return myConceptLinksToSaveLater.isEmpty(); - } - - private boolean isDeferredValueSets() { - return !myDeferredValueSets.isEmpty(); - } - - private boolean isDeferredConceptMaps() { - return !myDeferredConceptMaps.isEmpty(); - } @Override public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { myApplicationContext = theApplicationContext; } - @Override - public void setProcessDeferred(boolean theProcessDeferred) { - myProcessDeferred = theProcessDeferred; - } - @PostConstruct public void start() { - myCodeSystemResourceDao = myApplicationContext.getBean(IFhirResourceDaoCodeSystem.class); myValueSetResourceDao = myApplicationContext.getBean(IFhirResourceDaoValueSet.class); myTxTemplate = new TransactionTemplate(myTransactionManager); } @PostConstruct public void registerScheduledJob() { - // Register scheduled job to save deferred concepts - // In the future it would be great to make this a cluster-aware task somehow - ScheduledJobDefinition jobDefinition = new ScheduledJobDefinition(); - jobDefinition.setId(BaseHapiTerminologySvcImpl.class.getName() + "_saveDeferred"); - jobDefinition.setJobClass(SaveDeferredJob.class); - mySchedulerService.scheduleFixedDelay(5000, false, jobDefinition); - // Register scheduled job to pre-expand ValueSets // In the future it would be great to make this a cluster-aware task somehow ScheduledJobDefinition vsJobDefinition = new ScheduledJobDefinition(); - vsJobDefinition.setId(BaseHapiTerminologySvcImpl.class.getName() + "_preExpandValueSets"); + vsJobDefinition.setId(BaseTermReadSvcImpl.class.getName() + "_preExpandValueSets"); vsJobDefinition.setJobClass(PreExpandValueSetsJob.class); mySchedulerService.scheduleFixedDelay(10 * DateUtils.MILLIS_PER_MINUTE, true, vsJobDefinition); - - } - - @Override - @Transactional(propagation = Propagation.REQUIRED) - public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) { - ourLog.info("Storing code system"); - - ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied"); - ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); - - // Grab the existing versions so we can delete them later - List existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid); - - /* - * For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. - */ - - ourLog.info("Deleting old code system versions"); - for (TermCodeSystemVersion next : existing) { - Long codeSystemVersionPid = next.getPid(); - deleteCodeSystemVersion(codeSystemVersionPid); - } - - ourLog.info("Flushing..."); - myConceptDao.flush(); - ourLog.info("Done flushing"); - - /* - * Do the upload - */ - - TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable); - - theCodeSystemVersion.setCodeSystem(codeSystem); - - theCodeSystemVersion.setCodeSystemDisplayName(theSystemName); - theCodeSystemVersion.setCodeSystemVersionId(theSystemVersionId); - - ourLog.info("Validating all codes in CodeSystem for storage (this can take some time for large sets)"); - - // Validate the code system - ArrayList conceptsStack = new ArrayList<>(); - IdentityHashMap allConcepts = new IdentityHashMap<>(); - int totalCodeCount = 0; - for (TermConcept next : theCodeSystemVersion.getConcepts()) { - totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack, allConcepts); - } - - ourLog.info("Saving version containing {} concepts", totalCodeCount); - - TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.saveAndFlush(theCodeSystemVersion); - - ourLog.info("Saving code system"); - - codeSystem.setCurrentVersion(theCodeSystemVersion); - codeSystem = myCodeSystemDao.saveAndFlush(codeSystem); - - ourLog.info("Setting CodeSystemVersion[{}] on {} concepts...", codeSystem.getPid(), totalCodeCount); - - for (TermConcept next : theCodeSystemVersion.getConcepts()) { - populateVersion(next, codeSystemVersion); - } - - ourLog.info("Saving {} concepts...", totalCodeCount); - - IdentityHashMap conceptsStack2 = new IdentityHashMap<>(); - for (TermConcept next : theCodeSystemVersion.getConcepts()) { - persistChildren(next, codeSystemVersion, conceptsStack2, totalCodeCount); - } - - ourLog.info("Done saving concepts, flushing to database"); - - myConceptDao.flush(); - myConceptParentChildLinkDao.flush(); - - ourLog.info("Done deleting old code system versions"); - - if (myDeferredConcepts.size() > 0 || myConceptLinksToSaveLater.size() > 0) { - ourLog.info("Note that some concept saving was deferred - still have {} concepts and {} relationships", myDeferredConcepts.size(), myConceptLinksToSaveLater.size()); - } - } - - @Nonnull - private TermCodeSystem getOrCreateTermCodeSystem(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { - TermCodeSystem codeSystem = getCodeSystem(theSystemUri); - if (codeSystem == null) { - codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid); - if (codeSystem == null) { - codeSystem = new TermCodeSystem(); - } - codeSystem.setResource(theCodeSystemResourceTable); - } else { - if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemResourceTable.getId())) { - String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, - codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); - throw new UnprocessableEntityException(msg); - } - } - - codeSystem.setCodeSystemUri(theSystemUri); - codeSystem.setName(theSystemName); - codeSystem = myCodeSystemDao.save(codeSystem); - return codeSystem; - } - - @Override - @Transactional(propagation = Propagation.REQUIRED) - public IIdType storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequest, List theValueSets, List theConceptMaps) { - Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); - - IIdType csId = createOrUpdateCodeSystem(theCodeSystemResource); - - ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId, theRequest); - Long codeSystemResourcePid = resource.getId(); - - ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); - - populateCodeSystemVersionProperties(theCodeSystemVersion, theCodeSystemResource, resource); - - storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemResource.getName(), theCodeSystemResource.getVersion(), theCodeSystemVersion, resource); - - myDeferredConceptMaps.addAll(theConceptMaps); - myDeferredValueSets.addAll(theValueSets); - - return csId; - } - - private void populateCodeSystemVersionProperties(TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystemResource, ResourceTable theResourceTable) { - theCodeSystemVersion.setResource(theResourceTable); - theCodeSystemVersion.setCodeSystemDisplayName(theCodeSystemResource.getName()); - theCodeSystemVersion.setCodeSystemVersionId(theCodeSystemResource.getVersion()); - } - - @Override - @Transactional(propagation = Propagation.MANDATORY) - public void storeNewCodeSystemVersionIfNeeded(CodeSystem theCodeSystem, ResourceTable theResourceEntity) { - if (theCodeSystem != null && isNotBlank(theCodeSystem.getUrl())) { - String codeSystemUrl = theCodeSystem.getUrl(); - if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.COMPLETE || theCodeSystem.getContent() == null || theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { - ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", theResourceEntity.getIdDt().getValue(), theCodeSystem.getContentElement().getValueAsString()); - - Long codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); - - /* - * If this is a not-present codesystem, we don't want to store a new version if one - * already exists, since that will wipe out the existing concepts. We do create or update - * the TermCodeSystem table though, since that allows the DB to reject changes - * that would result in duplicate CodeSysten.url values. - */ - if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { - TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl()); - if (codeSystem != null) { - getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); - return; - } - } - - TermCodeSystemVersion persCs = new TermCodeSystemVersion(); - - populateCodeSystemVersionProperties(persCs, theCodeSystem, theResourceEntity); - - persCs.getConcepts().addAll(toPersistedConcepts(theCodeSystem.getConcept(), persCs)); - ourLog.info("Code system has {} concepts", persCs.getConcepts().size()); - storeNewCodeSystemVersion(codeSystemResourcePid, codeSystemUrl, theCodeSystem.getName(), theCodeSystem.getVersion(), persCs, theResourceEntity); - } - - } - } - - private List toPersistedConcepts(List theConcept, TermCodeSystemVersion theCodeSystemVersion) { - ArrayList retVal = new ArrayList<>(); - - for (CodeSystem.ConceptDefinitionComponent next : theConcept) { - if (isNotBlank(next.getCode())) { - TermConcept termConcept = toTermConcept(next, theCodeSystemVersion); - retVal.add(termConcept); - } - } - - return retVal; - } - - @Nonnull - private TermConcept toTermConcept(CodeSystem.ConceptDefinitionComponent theConceptDefinition, TermCodeSystemVersion theCodeSystemVersion) { - TermConcept termConcept = new TermConcept(); - termConcept.setCode(theConceptDefinition.getCode()); - termConcept.setCodeSystemVersion(theCodeSystemVersion); - termConcept.setDisplay(theConceptDefinition.getDisplay()); - termConcept.addChildren(toPersistedConcepts(theConceptDefinition.getConcept(), theCodeSystemVersion), RelationshipTypeEnum.ISA); - - for (CodeSystem.ConceptDefinitionDesignationComponent designationComponent : theConceptDefinition.getDesignation()) { - if (isNotBlank(designationComponent.getValue())) { - TermConceptDesignation designation = termConcept.addDesignation(); - designation.setLanguage(designationComponent.hasLanguage() ? designationComponent.getLanguage() : null); - if (designationComponent.hasUse()) { - designation.setUseSystem(designationComponent.getUse().hasSystem() ? designationComponent.getUse().getSystem() : null); - designation.setUseCode(designationComponent.getUse().hasCode() ? designationComponent.getUse().getCode() : null); - designation.setUseDisplay(designationComponent.getUse().hasDisplay() ? designationComponent.getUse().getDisplay() : null); - } - designation.setValue(designationComponent.getValue()); - } - } - - for (CodeSystem.ConceptPropertyComponent next : theConceptDefinition.getProperty()) { - TermConceptProperty property = new TermConceptProperty(); - - property.setKey(next.getCode()); - property.setConcept(termConcept); - property.setCodeSystemVersion(theCodeSystemVersion); - - if (next.getValue() instanceof StringType) { - property.setType(TermConceptPropertyTypeEnum.STRING); - property.setValue(next.getValueStringType().getValue()); - } else if (next.getValue() instanceof Coding) { - Coding nextCoding = next.getValueCoding(); - property.setType(TermConceptPropertyTypeEnum.CODING); - property.setCodeSystem(nextCoding.getSystem()); - property.setValue(nextCoding.getCode()); - property.setDisplay(nextCoding.getDisplay()); - } else if (next.getValue() != null) { - // TODO: LOINC has properties of type BOOLEAN that we should handle - ourLog.warn("Don't know how to handle properties of type: " + next.getValue().getClass()); - continue; - } - - termConcept.getProperties().add(property); - } - return termConcept; } @Override @@ -2083,7 +1345,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, TermConceptMap existingTermConceptMap = optionalExistingTermConceptMapByUrl.get(); String msg = myContext.getLocalizer().getMessage( - BaseHapiTerminologySvcImpl.class, + BaseTermReadSvcImpl.class, "cannotCreateDuplicateConceptMapUrl", conceptMapUrl, existingTermConceptMap.getResource().getIdDt().toUnqualifiedVersionless().getValue()); @@ -2144,31 +1406,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, } private boolean isNotSafeToPreExpandValueSets() { - return !isSafeToPreExpandValueSets(); - } - - private boolean isSafeToPreExpandValueSets() { - if (isProcessDeferredPaused()) { - return false; - } - - if (isDeferredConcepts()) { - return false; - } - - if (isConceptLinksToSaveLater()) { - return false; - } - - if (isDeferredValueSets()) { - return false; - } - - if (isDeferredConceptMaps()) { - return false; - } - - return true; + return !myDeferredStorageSvc.isStorageQueueEmpty(); } protected abstract ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable); @@ -2213,7 +1451,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, TermValueSet existingTermValueSet = optionalExistingTermValueSetByUrl.get(); String msg = myContext.getLocalizer().getMessage( - BaseHapiTerminologySvcImpl.class, + BaseTermReadSvcImpl.class, "cannotCreateDuplicateValueSetUrl", url, existingTermValueSet.getResource().getIdDt().toUnqualifiedVersionless().getValue()); @@ -2254,91 +1492,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return new IFhirResourceDaoCodeSystem.SubsumesResult(subsumes); } - @Transactional - @Override - public AtomicInteger applyDeltaCodesystemsAdd(String theSystem, @Nullable String theParent, CodeSystem theValue) { - TermCodeSystem cs = getCodeSystem(theSystem); - if (cs == null) { - List codes = theValue.getConcept(); - theValue.setConcept(null); - createOrUpdateCodeSystem(theValue); - cs = getCodeSystem(theSystem); - theValue.setConcept(codes); - } - - TermCodeSystemVersion csv = cs.getCurrentVersion(); - - AtomicInteger addedCodeCounter = new AtomicInteger(0); - - TermConcept parentCode = null; - if (isNotBlank(theParent)) { - parentCode = myConceptDao - .findByCodeSystemAndCode(csv, theParent) - .orElseThrow(() -> new InvalidRequestException("Unknown code [" + theSystem + "|" + theParent + "]")); - } - - List concepts = new ArrayList<>(); - for (CodeSystem.ConceptDefinitionComponent next : theValue.getConcept()) { - TermConcept concept = toTermConcept(next, csv); - if (parentCode != null) { - parentCode.addChild(concept, RelationshipTypeEnum.ISA); - } - concepts.add(concept); - } - - // The first pass just saves any concepts that were added to the - // root of the CodeSystem - List links = new ArrayList<>(); - for (TermConcept next : concepts) { - int addedCount = saveOrUpdateConcept(next); - addedCodeCounter.addAndGet(addedCount); - extractLinksFromConceptAndChildren(next, links); - } - - // This second pass saves any child concepts - for (TermConceptParentChildLink next : links) { - next.setCodeSystem(csv); - int addedCount = saveOrUpdateConcept(next.getChild()); - addedCodeCounter.addAndGet(addedCount); - myConceptParentChildLinkDao.save(next); - } - - return addedCodeCounter; - } - - @Transactional - @Override - public AtomicInteger applyDeltaCodesystemsRemove(String theSystem, CodeSystem theValue) { - TermCodeSystem cs = getCodeSystem(theSystem); - if (cs == null) { - throw new InvalidRequestException("Unknown code system: " + theSystem); - } - - AtomicInteger removeCounter = new AtomicInteger(0); - - for (CodeSystem.ConceptDefinitionComponent next : theValue.getConcept()) { - Optional conceptOpt = findCode(theSystem, next.getCode()); - if (conceptOpt.isPresent()) { - TermConcept concept = conceptOpt.get(); - deleteConceptChildrenAndConcept(concept, removeCounter); - } - } - - return removeCounter; - } - - private void deleteConceptChildrenAndConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) { - for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) { - deleteConceptChildrenAndConcept(nextChildLink.getChild(), theRemoveCounter); - myConceptParentChildLinkDao.delete(nextChildLink); - } - - myConceptDesignationDao.deleteAll(theConcept.getDesignations()); - myConceptPropertyDao.deleteAll(theConcept.getProperties()); - myConceptDao.delete(theConcept); - theRemoveCounter.incrementAndGet(); - } - protected IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); return txTemplate.execute(t -> { @@ -2600,37 +1753,81 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return retVal; } - private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList theConceptsStack, - IdentityHashMap theAllConcepts) { - ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodeSystemVersion is null"); - ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() == theCodeSystem, "CodeSystems are not equal"); - ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "CodeSystem contains a code with no code value"); - if (theConceptsStack.contains(theConcept.getCode())) { - throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode()); + protected void throwInvalidValueSet(String theValueSet) { + throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSet)); + } + + public static class PreExpandValueSetsJob implements Job { + + @Autowired + private ITermReadSvc myTerminologySvc; + + @Override + public void execute(JobExecutionContext theContext) { + myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables(); } - theConceptsStack.add(theConcept.getCode()); + } - int retVal = 0; - if (theAllConcepts.put(theConcept, theAllConcepts) == null) { - if (theAllConcepts.size() % 1000 == 0) { - ourLog.info("Have validated {} concepts", theAllConcepts.size()); + static List toPersistedConcepts(List theConcept, TermCodeSystemVersion theCodeSystemVersion) { + ArrayList retVal = new ArrayList<>(); + + for (CodeSystem.ConceptDefinitionComponent next : theConcept) { + if (isNotBlank(next.getCode())) { + TermConcept termConcept = toTermConcept(next, theCodeSystemVersion); + retVal.add(termConcept); } - retVal = 1; } - for (TermConceptParentChildLink next : theConcept.getChildren()) { - next.setCodeSystem(theCodeSystem); - retVal += validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack, theAllConcepts); - } - - theConceptsStack.remove(theConceptsStack.size() - 1); - return retVal; } - protected void throwInvalidValueSet(String theValueSet) { - throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSet)); + @Nonnull + static TermConcept toTermConcept(CodeSystem.ConceptDefinitionComponent theConceptDefinition, TermCodeSystemVersion theCodeSystemVersion) { + TermConcept termConcept = new TermConcept(); + termConcept.setCode(theConceptDefinition.getCode()); + termConcept.setCodeSystemVersion(theCodeSystemVersion); + termConcept.setDisplay(theConceptDefinition.getDisplay()); + termConcept.addChildren(toPersistedConcepts(theConceptDefinition.getConcept(), theCodeSystemVersion), RelationshipTypeEnum.ISA); + + for (CodeSystem.ConceptDefinitionDesignationComponent designationComponent : theConceptDefinition.getDesignation()) { + if (isNotBlank(designationComponent.getValue())) { + TermConceptDesignation designation = termConcept.addDesignation(); + designation.setLanguage(designationComponent.hasLanguage() ? designationComponent.getLanguage() : null); + if (designationComponent.hasUse()) { + designation.setUseSystem(designationComponent.getUse().hasSystem() ? designationComponent.getUse().getSystem() : null); + designation.setUseCode(designationComponent.getUse().hasCode() ? designationComponent.getUse().getCode() : null); + designation.setUseDisplay(designationComponent.getUse().hasDisplay() ? designationComponent.getUse().getDisplay() : null); + } + designation.setValue(designationComponent.getValue()); + } + } + + for (CodeSystem.ConceptPropertyComponent next : theConceptDefinition.getProperty()) { + TermConceptProperty property = new TermConceptProperty(); + + property.setKey(next.getCode()); + property.setConcept(termConcept); + property.setCodeSystemVersion(theCodeSystemVersion); + + if (next.getValue() instanceof StringType) { + property.setType(TermConceptPropertyTypeEnum.STRING); + property.setValue(next.getValueStringType().getValue()); + } else if (next.getValue() instanceof Coding) { + Coding nextCoding = next.getValueCoding(); + property.setType(TermConceptPropertyTypeEnum.CODING); + property.setCodeSystem(nextCoding.getSystem()); + property.setValue(nextCoding.getCode()); + property.setDisplay(nextCoding.getDisplay()); + } else if (next.getValue() != null) { + // TODO: LOINC has properties of type BOOLEAN that we should handle + ourLog.warn("Don't know how to handle properties of type: " + next.getValue().getClass()); + continue; + } + + termConcept.getProperties().add(property); + } + return termConcept; } private static void extractLinksFromConceptAndChildren(TermConcept theConcept, List theLinks) { @@ -2651,28 +1848,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return new VersionIndependentConcept(system, code); } - public static class SaveDeferredJob implements Job { - - @Autowired - private IHapiTerminologySvc myTerminologySvc; - - @Override - public void execute(JobExecutionContext theContext) { - myTerminologySvc.saveDeferred(); - } - } - - public static class PreExpandValueSetsJob implements Job { - - @Autowired - private IHapiTerminologySvc myTerminologySvc; - - @Override - public void execute(JobExecutionContext theContext) { - myTerminologySvc.preExpandDeferredValueSetsToTerminologyTables(); - } - } - /** * This method is present only for unit tests, do not call from client code */ @@ -2705,13 +1880,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, return ourLastResultsFromTranslationWithReverseCache; } - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting - public static void setForceSaveDeferredAlwaysForUnitTest(boolean theForceSaveDeferredAlwaysForUnitTest) { - ourForceSaveDeferredAlwaysForUnitTest = theForceSaveDeferredAlwaysForUnitTest; - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermVersionAdapterSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermVersionAdapterSvcImpl.java new file mode 100644 index 00000000000..f6dfca9973c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermVersionAdapterSvcImpl.java @@ -0,0 +1,14 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.util.ValidateUtil; +import org.hl7.fhir.r4.model.CodeSystem; + +public abstract class BaseTermVersionAdapterSvcImpl implements ITermVersionAdapterSvc { + + + protected void validateCodeSystemForStorage(CodeSystem theCodeSystemResource) { + ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theCodeSystemResource.getUrl(), "Can not store a CodeSystem without a valid URL"); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/LoadedFileDescriptors.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/LoadedFileDescriptors.java new file mode 100644 index 00000000000..a12f14c78ae --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/LoadedFileDescriptors.java @@ -0,0 +1,123 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class LoadedFileDescriptors implements Closeable { + private static final Logger ourLog = LoggerFactory.getLogger(LoadedFileDescriptors.class); + private List myTemporaryFiles = new ArrayList<>(); + private List myUncompressedFileDescriptors = new ArrayList<>(); + + LoadedFileDescriptors(List theFileDescriptors) { + try { + for (ITermLoaderSvc.FileDescriptor next : theFileDescriptors) { + if (next.getFilename().toLowerCase().endsWith(".zip")) { + ourLog.info("Uncompressing {} into temporary files", next.getFilename()); + try (InputStream inputStream = next.getInputStream()) { + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { + try (ZipInputStream zis = new ZipInputStream(bufferedInputStream)) { + for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) { + BOMInputStream fis = new BOMInputStream(zis); + File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp"); + ourLog.info("Creating temporary file: {}", nextTemporaryFile.getAbsolutePath()); + nextTemporaryFile.deleteOnExit(); + try (FileOutputStream fos = new FileOutputStream(nextTemporaryFile, false)) { + IOUtils.copy(fis, fos); + String nextEntryFileName = nextEntry.getName(); + myUncompressedFileDescriptors.add(new ITermLoaderSvc.FileDescriptor() { + @Override + public String getFilename() { + return nextEntryFileName; + } + + @Override + public InputStream getInputStream() { + try { + return new FileInputStream(nextTemporaryFile); + } catch (FileNotFoundException e) { + throw new InternalErrorException(e); + } + } + }); + myTemporaryFiles.add(nextTemporaryFile); + } + } + } + } + } + } else { + myUncompressedFileDescriptors.add(next); + } + + } + } catch (IOException e) { + throw new InternalErrorException(e); + } + + } + + public boolean hasFile(String theFilename) { + return myUncompressedFileDescriptors + .stream() + .map(t -> t.getFilename().replaceAll(".*[\\\\/]", "")) // Strip the path from the filename + .anyMatch(t -> t.equals(theFilename)); + } + + @Override + public void close() { + for (File next : myTemporaryFiles) { + ourLog.info("Deleting temporary file: {}", next.getAbsolutePath()); + FileUtils.deleteQuietly(next); + } + } + + List getUncompressedFileDescriptors() { + return myUncompressedFileDescriptors; + } + + private List notFound(List theExpectedFilenameFragments) { + Set foundFragments = new HashSet<>(); + for (String nextExpected : theExpectedFilenameFragments) { + for (ITermLoaderSvc.FileDescriptor next : myUncompressedFileDescriptors) { + if (next.getFilename().contains(nextExpected)) { + foundFragments.add(nextExpected); + break; + } + } + } + + ArrayList notFoundFileNameFragments = new ArrayList<>(theExpectedFilenameFragments); + notFoundFileNameFragments.removeAll(foundFragments); + return notFoundFileNameFragments; + } + + void verifyMandatoryFilesExist(List theExpectedFilenameFragments) { + List notFound = notFound(theExpectedFilenameFragments); + if (!notFound.isEmpty()) { + throw new UnprocessableEntityException("Could not find the following mandatory files in input: " + notFound); + } + } + + void verifyOptionalFilesExist(List theExpectedFilenameFragments) { + List notFound = notFound(theExpectedFilenameFragments); + if (!notFound.isEmpty()) { + ourLog.warn("Could not find the following optional files: " + notFound); + } + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java new file mode 100644 index 00000000000..6ea0d6c1274 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -0,0 +1,757 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; +import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; +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.ObjectUtil; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.ValidateUtil; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import org.apache.commons.lang3.Validate; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.Nonnull; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { + private static final Logger ourLog = LoggerFactory.getLogger(TermCodeSystemStorageSvcImpl.class); + private static final Object PLACEHOLDER_OBJECT = new Object(); + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; + @Autowired + protected ITermCodeSystemDao myCodeSystemDao; + @Autowired + protected ITermCodeSystemVersionDao myCodeSystemVersionDao; + @Autowired + protected ITermConceptDao myConceptDao; + @Autowired + protected ITermConceptPropertyDao myConceptPropertyDao; + @Autowired + protected ITermConceptDesignationDao myConceptDesignationDao; + @Autowired + protected IdHelperService myIdHelperService; + @Autowired + private PlatformTransactionManager myTransactionManager; + @Autowired + private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; + @Autowired + private ITermVersionAdapterSvc myTerminologyVersionAdapterSvc; + @Autowired + private ITermDeferredStorageSvc myDeferredStorageSvc; + @Autowired + private FhirContext myContext; + @Autowired + private ITermReadSvc myTerminologySvc; + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private IResourceTableDao myResourceTableDao; + + @Override + public Long getValueSetResourcePid(IIdType theIdType) { + return getValueSetResourcePid(theIdType, null); + } + + private Long getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { + return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); + } + + @Transactional + @Override + public UploadStatistics applyDeltaCodeSystemsAdd(String theSystem, CustomTerminologySet theAdditions) { + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "No system provided"); + validateDstu3OrNewer(); + theAdditions.validateNoCycleOrThrowInvalidRequest(); + + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + if (cs == null) { + CodeSystem codeSystemResource = new CodeSystem(); + codeSystemResource.setUrl(theSystem); + codeSystemResource.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); + myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(codeSystemResource); + + cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + } + + TermCodeSystemVersion csv = cs.getCurrentVersion(); + Validate.notNull(csv); + + CodeSystem codeSystem = myTerminologySvc.getCodeSystemFromContext(theSystem); + if (codeSystem.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { + throw new InvalidRequestException("CodeSystem with url[" + theSystem + "] can not apply a delta - wrong content mode: " + codeSystem.getContent()); + } + + Validate.notNull(cs); + Validate.notNull(cs.getPid()); + + IIdType codeSystemId = cs.getResource().getIdDt(); + + // Load all concepts for the code system + Map codeToConceptPid = new HashMap<>(); + { + ourLog.info("Loading all concepts in CodeSystem versionPid[{}] and url[{}]", cs.getPid(), theSystem); + StopWatch sw = new StopWatch(); + CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(TermConcept.class); + Root root = query.from(TermConcept.class); + Predicate predicate = criteriaBuilder.equal(root.get("myCodeSystemVersionPid").as(Long.class), csv.getPid()); + query.where(predicate); + TypedQuery typedQuery = myEntityManager.createQuery(query.select(root)); + org.hibernate.query.Query hibernateQuery = (org.hibernate.query.Query) typedQuery; + ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY); + try (ScrollableResultsIterator scrollableResultsIterator = new ScrollableResultsIterator<>(scrollableResults)) { + while (scrollableResultsIterator.hasNext()) { + TermConcept next = scrollableResultsIterator.next(); + codeToConceptPid.put(next.getCode(), next.getId()); + } + } + ourLog.info("Loaded {} concepts in {}", codeToConceptPid.size(), sw.toString()); + } + + // Load all parent/child links + ListMultimap parentCodeToChildCodes = ArrayListMultimap.create(); + ListMultimap childCodeToParentCodes = ArrayListMultimap.create(); + { + ourLog.info("Loading all parent/child relationships in CodeSystem url[" + theSystem + "]"); + int count = 0; + StopWatch sw = new StopWatch(); + CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(TermConceptParentChildLink.class); + Root root = query.from(TermConceptParentChildLink.class); + Predicate predicate = criteriaBuilder.equal(root.get("myCodeSystemVersionPid").as(Long.class), csv.getPid()); + root.fetch("myChild"); + root.fetch("myParent"); + query.where(predicate); + TypedQuery typedQuery = myEntityManager.createQuery(query.select(root)); + org.hibernate.query.Query hibernateQuery = (org.hibernate.query.Query) typedQuery; + ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY); + try (ScrollableResultsIterator scrollableResultsIterator = new ScrollableResultsIterator<>(scrollableResults)) { + while (scrollableResultsIterator.hasNext()) { + TermConceptParentChildLink next = scrollableResultsIterator.next(); + String parentCode = next.getParent().getCode(); + String childCode = next.getChild().getCode(); + parentCodeToChildCodes.put(parentCode, childCode); + childCodeToParentCodes.put(childCode, parentCode); + count++; + } + } + ourLog.info("Loaded {} parent/child relationships in {}", count, sw.toString()); + } + + // Account for root codes in the parent->child map + for (String nextCode : codeToConceptPid.keySet()) { + if (childCodeToParentCodes.get(nextCode).isEmpty()) { + parentCodeToChildCodes.put("", nextCode); + } + } + + UploadStatistics retVal = new UploadStatistics(codeSystemId); + + // Add root concepts + for (TermConcept nextRootConcept : theAdditions.getRootConcepts()) { + List parentCodes = Collections.emptyList(); + addConcept(csv, codeToConceptPid, parentCodes, nextRootConcept, parentCodeToChildCodes, retVal, true); + } + + // Add unanchored child concepts + for (TermConcept nextUnanchoredChild : theAdditions.getUnanchoredChildConceptsToParentCodes().keySet()) { + List nextParentCodes = theAdditions.getUnanchoredChildConceptsToParentCodes().get(nextUnanchoredChild); + addConcept(csv, codeToConceptPid, nextParentCodes, nextUnanchoredChild, parentCodeToChildCodes, retVal, true); + } + + return retVal; + } + + @Transactional + @Override + public UploadStatistics applyDeltaCodeSystemsRemove(String theSystem, CustomTerminologySet theValue) { + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "No system provided"); + validateDstu3OrNewer(); + + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + if (cs == null) { + throw new InvalidRequestException("Unknown code system: " + theSystem); + } + + AtomicInteger removeCounter = new AtomicInteger(0); + + for (TermConcept nextSuppliedConcept : theValue.getRootConcepts()) { + Optional conceptOpt = myTerminologySvc.findCode(theSystem, nextSuppliedConcept.getCode()); + if (conceptOpt.isPresent()) { + TermConcept concept = conceptOpt.get(); + deleteConceptChildrenAndConcept(concept, removeCounter); + } + } + + IIdType target = cs.getResource().getIdDt(); + return new UploadStatistics(removeCounter.get(), target); + } + + @Override + public void deleteCodeSystem(TermCodeSystem theCodeSystem) { + ourLog.info(" * Deleting code system {}", theCodeSystem.getPid()); + + myEntityManager.flush(); + TermCodeSystem cs = myCodeSystemDao.findById(theCodeSystem.getPid()).orElseThrow(IllegalStateException::new); + cs.setCurrentVersion(null); + myCodeSystemDao.save(cs); + myCodeSystemDao.flush(); + + List codeSystemVersions = myCodeSystemVersionDao.findByCodeSystemPid(theCodeSystem.getPid()); + for (TermCodeSystemVersion next : codeSystemVersions) { + deleteCodeSystemVersion(next.getPid()); + } + myCodeSystemVersionDao.deleteForCodeSystem(theCodeSystem); + myCodeSystemDao.delete(theCodeSystem); + + myEntityManager.flush(); + } + + /** + * Returns the number of saved concepts + */ + @Override + public int saveConcept(TermConcept theConcept) { + int retVal = 0; + + /* + * If the concept has an ID, we're reindexing, so there's no need to + * save parent concepts first (it's way too slow to do that) + */ + if (theConcept.getId() == null) { + retVal += ensureParentsSaved(theConcept.getParents()); + } + + if (theConcept.getId() == null || theConcept.getIndexStatus() == null) { + retVal++; + theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); + theConcept.setUpdated(new Date()); + myConceptDao.save(theConcept); + + for (TermConceptProperty next : theConcept.getProperties()) { + myConceptPropertyDao.save(next); + } + + for (TermConceptDesignation next : theConcept.getDesignations()) { + myConceptDesignationDao.save(next); + } + } + + ourLog.trace("Saved {} and got PID {}", theConcept.getCode(), theConcept.getId()); + return retVal; + } + + @Override + @Transactional(propagation = Propagation.MANDATORY) + public void storeNewCodeSystemVersionIfNeeded(CodeSystem theCodeSystem, ResourceTable theResourceEntity) { + if (theCodeSystem != null && isNotBlank(theCodeSystem.getUrl())) { + String codeSystemUrl = theCodeSystem.getUrl(); + if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.COMPLETE || theCodeSystem.getContent() == null || theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { + ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", theResourceEntity.getIdDt().getValue(), theCodeSystem.getContentElement().getValueAsString()); + + Long codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); + + /* + * If this is a not-present codesystem, we don't want to store a new version if one + * already exists, since that will wipe out the existing concepts. We do create or update + * the TermCodeSystem table though, since that allows the DB to reject changes + * that would result in duplicate CodeSysten.url values. + */ + if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { + TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl()); + if (codeSystem != null) { + getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); + return; + } + } + + TermCodeSystemVersion persCs = new TermCodeSystemVersion(); + + populateCodeSystemVersionProperties(persCs, theCodeSystem, theResourceEntity); + + persCs.getConcepts().addAll(BaseTermReadSvcImpl.toPersistedConcepts(theCodeSystem.getConcept(), persCs)); + ourLog.info("Code system has {} concepts", persCs.getConcepts().size()); + storeNewCodeSystemVersion(codeSystemResourcePid, codeSystemUrl, theCodeSystem.getName(), theCodeSystem.getVersion(), persCs, theResourceEntity); + } + + } + } + + @Override + @Transactional(propagation = Propagation.REQUIRED) + public IIdType storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequest, List theValueSets, List theConceptMaps) { + Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); + + IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource); + Long codeSystemResourcePid = myIdHelperService.translateForcedIdToPid(csId, theRequest); + ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid); + + ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); + + populateCodeSystemVersionProperties(theCodeSystemVersion, theCodeSystemResource, resource); + + storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemResource.getName(), theCodeSystemResource.getVersion(), theCodeSystemVersion, resource); + + myDeferredStorageSvc.addConceptMapsToStorageQueue(theConceptMaps); + myDeferredStorageSvc.addValueSetsToStorageQueue(theValueSets); + + return csId; + } + + @Override + @Transactional(propagation = Propagation.REQUIRED) + public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) { + ourLog.info("Storing code system"); + + ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); + + // Grab the existing versions so we can delete them later + List existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid); + + /* + * For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. + */ + + ourLog.info("Deleting old code system versions"); + for (TermCodeSystemVersion next : existing) { + Long codeSystemVersionPid = next.getPid(); + deleteCodeSystemVersion(codeSystemVersionPid); + } + + ourLog.info("Flushing..."); + myConceptDao.flush(); + ourLog.info("Done flushing"); + + /* + * Do the upload + */ + + TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable); + + theCodeSystemVersion.setCodeSystem(codeSystem); + + theCodeSystemVersion.setCodeSystemDisplayName(theSystemName); + theCodeSystemVersion.setCodeSystemVersionId(theSystemVersionId); + + ourLog.info("Validating all codes in CodeSystem for storage (this can take some time for large sets)"); + + // Validate the code system + ArrayList conceptsStack = new ArrayList<>(); + IdentityHashMap allConcepts = new IdentityHashMap<>(); + int totalCodeCount = 0; + for (TermConcept next : theCodeSystemVersion.getConcepts()) { + totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack, allConcepts); + } + + ourLog.info("Saving version containing {} concepts", totalCodeCount); + + TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.saveAndFlush(theCodeSystemVersion); + + ourLog.info("Saving code system"); + + codeSystem.setCurrentVersion(theCodeSystemVersion); + codeSystem = myCodeSystemDao.saveAndFlush(codeSystem); + + ourLog.info("Setting CodeSystemVersion[{}] on {} concepts...", codeSystem.getPid(), totalCodeCount); + + for (TermConcept next : theCodeSystemVersion.getConcepts()) { + populateVersion(next, codeSystemVersion); + } + + ourLog.info("Saving {} concepts...", totalCodeCount); + + IdentityHashMap conceptsStack2 = new IdentityHashMap<>(); + for (TermConcept next : theCodeSystemVersion.getConcepts()) { + persistChildren(next, codeSystemVersion, conceptsStack2, totalCodeCount); + } + + ourLog.info("Done saving concepts, flushing to database"); + + myConceptDao.flush(); + myConceptParentChildLinkDao.flush(); + + ourLog.info("Done deleting old code system versions"); + + if (myDeferredStorageSvc.isStorageQueueEmpty() == false) { + ourLog.info("Note that some concept saving has been deferred"); + } + } + + private void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) { + ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid); + + PageRequest page1000 = PageRequest.of(0, 1000); + + // Parent/Child links + { + String descriptor = "parent/child links"; + Supplier> loader = () -> myConceptParentChildLinkDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptParentChildLinkDao); + } + + // Properties + { + String descriptor = "concept properties"; + Supplier> loader = () -> myConceptPropertyDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptPropertyDao); + } + + // Designations + { + String descriptor = "concept designations"; + Supplier> loader = () -> myConceptDesignationDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptDesignationDao); + } + + // Concepts + { + String descriptor = "concepts"; + // For some reason, concepts are much slower to delete, so use a smaller batch size + PageRequest page100 = PageRequest.of(0, 100); + Supplier> loader = () -> myConceptDao.findByCodeSystemVersion(page100, theCodeSystemVersionPid); + Supplier counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptDao); + } + + Optional codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid); + if (codeSystemOpt.isPresent()) { + TermCodeSystem codeSystem = codeSystemOpt.get(); + ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); + codeSystem.setCurrentVersion(null); + myCodeSystemDao.save(codeSystem); + } + + ourLog.info(" * Deleting code system version"); + myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); + + } + + private void validateDstu3OrNewer() { + Validate.isTrue(myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), "Terminology operations only supported in DSTU3+ mode"); + } + + private void addConcept(TermCodeSystemVersion theCsv, Map theCodeToConceptPid, Collection theParentCodes, TermConcept theConceptToAdd, ListMultimap theParentCodeToChildCodes, UploadStatistics theStatisticsTracker, boolean theForceResequence) { + TermConcept nextConceptToAdd = theConceptToAdd; + + String nextCodeToAdd = nextConceptToAdd.getCode(); + String parentDescription = "(root concept)"; + Set parentConcepts = new HashSet<>(); + if (!theParentCodes.isEmpty()) { + parentDescription = "[" + String.join(", ", theParentCodes) + "]"; + for (String nextParentCode : theParentCodes) { + Long nextParentCodePid = theCodeToConceptPid.get(nextParentCode); + if (nextParentCodePid == null) { + throw new InvalidRequestException("Unable to add code \"" + nextCodeToAdd + "\" to unknown parent: " + nextParentCode); + } + parentConcepts.add(myConceptDao.getOne(nextParentCodePid)); + } + } + + ourLog.info("Saving concept {} with parent {}", theStatisticsTracker.getUpdatedConceptCount(), parentDescription); + + if (theCodeToConceptPid.containsKey(nextCodeToAdd)) { + + TermConcept existingCode = myConceptDao.getOne(theCodeToConceptPid.get(nextCodeToAdd)); + existingCode.setIndexStatus(null); + existingCode.setDisplay(nextConceptToAdd.getDisplay()); + nextConceptToAdd = existingCode; + + } + + if (theConceptToAdd.getSequence() == null || theForceResequence) { + // If this is a new code, give it a sequence number based on how many concepts the + // parent already has (or the highest number, if the code has multiple parents) + int sequence = 0; + for (String nextParentCode : theParentCodes) { + theParentCodeToChildCodes.put(nextParentCode, nextCodeToAdd); + sequence = Math.max(sequence, theParentCodeToChildCodes.get(nextParentCode).size()); + } + if (theParentCodes.isEmpty()) { + theParentCodeToChildCodes.put("", nextCodeToAdd); + sequence = Math.max(sequence, theParentCodeToChildCodes.get("").size()); + } + nextConceptToAdd.setSequence(sequence); + } + + + // Drop any old parent-child links if they aren't explicitly specified in the + // hierarchy being added + for (Iterator iter = nextConceptToAdd.getParents().iterator(); iter.hasNext(); ) { + TermConceptParentChildLink nextLink = iter.next(); + String parentCode = nextLink.getParent().getCode(); + boolean shouldRemove = !theParentCodes.contains(parentCode); + if (shouldRemove) { + ourLog.info("Dropping existing parent/child link from {} -> {}", parentCode, nextCodeToAdd); + myConceptParentChildLinkDao.delete(nextLink); + iter.remove(); + + List parentChildrenList = nextLink.getParent().getChildren(); + parentChildrenList.remove(nextLink); + } + } + + nextConceptToAdd.setParentPids(null); + nextConceptToAdd.setCodeSystemVersion(theCsv); + nextConceptToAdd = myConceptDao.save(nextConceptToAdd); + + Long nextConceptPid = nextConceptToAdd.getId(); + Validate.notNull(nextConceptPid); + theCodeToConceptPid.put(nextCodeToAdd, nextConceptPid); + theStatisticsTracker.incrementUpdatedConceptCount(); + + // Add link to new child to the parent if this link doesn't already exist (this will be the + // case for concepts being added to an existing child concept, but won't be the case when + // we're recursively adding children) + for (TermConcept nextParentConcept : parentConcepts) { + if (nextParentConcept.getChildren().stream().noneMatch(t->t.getChild().getCode().equals(nextCodeToAdd))) { + TermConceptParentChildLink parentLink = new TermConceptParentChildLink(); + parentLink.setParent(nextParentConcept); + parentLink.setChild(nextConceptToAdd); + parentLink.setCodeSystem(theCsv); + parentLink.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA); + nextParentConcept.getChildren().add(parentLink); + nextConceptToAdd.getParents().add(parentLink); + myConceptParentChildLinkDao.save(parentLink); + } + } + + // Save children recursively + for (TermConceptParentChildLink nextChildConceptLink : nextConceptToAdd.getChildren()) { + + TermConcept nextChild = nextChildConceptLink.getChild(); + Collection parentCodes = nextChild.getParents().stream().map(t -> t.getParent().getCode()).collect(Collectors.toList()); + addConcept(theCsv, theCodeToConceptPid, parentCodes, nextChild, theParentCodeToChildCodes, theStatisticsTracker, false); + + if (nextChildConceptLink.getId() == null) { + nextChildConceptLink.setCodeSystem(theCsv); + myConceptParentChildLinkDao.save(nextChildConceptLink); + } + } + + } + + private Long getCodeSystemResourcePid(IIdType theIdType) { + return getCodeSystemResourcePid(theIdType, null); + } + + private Long getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { + return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); + } + + private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap theConceptsStack, int theTotalConcepts) { + if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) { + return; + } + + if (theConceptsStack.size() == 1 || theConceptsStack.size() % 10000 == 0) { + float pct = (float) theConceptsStack.size() / (float) theTotalConcepts; + ourLog.info("Have processed {}/{} concepts ({}%)", theConceptsStack.size(), theTotalConcepts, (int) (pct * 100.0f)); + } + + theConcept.setCodeSystemVersion(theCodeSystem); + theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED); + + if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { + saveConcept(theConcept); + } else { + myDeferredStorageSvc.addConceptToStorageQueue(theConcept); + } + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + persistChildren(next.getChild(), theCodeSystem, theConceptsStack, theTotalConcepts); + } + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + if (theConceptsStack.size() <= myDaoConfig.getDeferIndexingForCodesystemsOfSize()) { + saveConceptLink(next); + } else { + myDeferredStorageSvc.addConceptLinkToStorageQueue(next); + } + } + + } + + private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) { + if (theNext.getCodeSystemVersion() != null) { + return; + } + theNext.setCodeSystemVersion(theCodeSystemVersion); + for (TermConceptParentChildLink next : theNext.getChildren()) { + populateVersion(next.getChild(), theCodeSystemVersion); + } + } + + private void saveConceptLink(TermConceptParentChildLink next) { + if (next.getId() == null) { + myConceptParentChildLinkDao.save(next); + } + } + + private int ensureParentsSaved(Collection theParents) { + ourLog.trace("Checking {} parents", theParents.size()); + int retVal = 0; + + for (TermConceptParentChildLink nextLink : theParents) { + if (nextLink.getRelationshipType() == TermConceptParentChildLink.RelationshipTypeEnum.ISA) { + TermConcept nextParent = nextLink.getParent(); + retVal += ensureParentsSaved(nextParent.getParents()); + if (nextParent.getId() == null) { + nextParent.setUpdated(new Date()); + myConceptDao.saveAndFlush(nextParent); + retVal++; + ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId()); + } + } + } + + return retVal; + } + + @Nonnull + private TermCodeSystem getOrCreateTermCodeSystem(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { + TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); + if (codeSystem == null) { + codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid); + if (codeSystem == null) { + codeSystem = new TermCodeSystem(); + } + codeSystem.setResource(theCodeSystemResourceTable); + } else { + if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemResourceTable.getId())) { + String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, + codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + throw new UnprocessableEntityException(msg); + } + } + + codeSystem.setCodeSystemUri(theSystemUri); + codeSystem.setName(theSystemName); + codeSystem = myCodeSystemDao.save(codeSystem); + return codeSystem; + } + + private void populateCodeSystemVersionProperties(TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystemResource, ResourceTable theResourceTable) { + theCodeSystemVersion.setResource(theResourceTable); + theCodeSystemVersion.setCodeSystemDisplayName(theCodeSystemResource.getName()); + theCodeSystemVersion.setCodeSystemVersionId(theCodeSystemResource.getVersion()); + } + + private void deleteConceptChildrenAndConcept(TermConcept theConcept, AtomicInteger theRemoveCounter) { + for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) { + deleteConceptChildrenAndConcept(nextChildLink.getChild(), theRemoveCounter); + myConceptParentChildLinkDao.delete(nextChildLink); + } + + myConceptDesignationDao.deleteAll(theConcept.getDesignations()); + myConceptPropertyDao.deleteAll(theConcept.getProperties()); + myConceptDao.delete(theConcept); + theRemoveCounter.incrementAndGet(); + } + + + private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { + int count; + ourLog.info(" * Deleting {}", theDescriptor); + int totalCount = theCounter.get(); + StopWatch sw = new StopWatch(); + count = 0; + while (true) { + Slice link = theLoader.get(); + if (!link.hasContent()) { + break; + } + + TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + txTemplate.execute(t -> { + theDao.deleteAll(link); + return null; + }); + + count += link.getNumberOfElements(); + ourLog.info(" * {} {} deleted - {}/sec - ETA: {}", count, theDescriptor, sw.formatThroughput(count, TimeUnit.SECONDS), sw.getEstimatedTimeRemaining(count, totalCount)); + } + theDao.flush(); + } + + + private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList theConceptsStack, + IdentityHashMap theAllConcepts) { + ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodeSystemVersion is null"); + ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() == theCodeSystem, "CodeSystems are not equal"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "CodeSystem contains a code with no code value"); + + if (theConceptsStack.contains(theConcept.getCode())) { + throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode()); + } + theConceptsStack.add(theConcept.getCode()); + + int retVal = 0; + if (theAllConcepts.put(theConcept, theAllConcepts) == null) { + if (theAllConcepts.size() % 1000 == 0) { + ourLog.info("Have validated {} concepts", theAllConcepts.size()); + } + retVal = 1; + } + + for (TermConceptParentChildLink next : theConcept.getChildren()) { + next.setCodeSystem(theCodeSystem); + retVal += validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack, theAllConcepts); + } + + theConceptsStack.remove(theConceptsStack.size() - 1); + + return retVal; + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java new file mode 100644 index 00000000000..4793c1bbac9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermDeferredStorageSvcImpl.java @@ -0,0 +1,260 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.model.sched.FireAtIntervalJob; +import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.util.StopWatch; +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc { + + private static final int SCHEDULE_INTERVAL_MILLIS = 5000; + private static final Logger ourLog = LoggerFactory.getLogger(TermDeferredStorageSvcImpl.class); + @Autowired + protected ITermConceptDao myConceptDao; + @Autowired + protected PlatformTransactionManager myTransactionMgr; + private boolean myProcessDeferred = true; + private List myDeferredConcepts = Collections.synchronizedList(new ArrayList<>()); + private List myDeferredValueSets = Collections.synchronizedList(new ArrayList<>()); + private List myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>()); + private List myConceptLinksToSaveLater = Collections.synchronizedList(new ArrayList<>()); + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; + @Autowired + private ISchedulerService mySchedulerService; + @Autowired + private ITermVersionAdapterSvc myTerminologyVersionAdapterSvc; + @Autowired + private ITermCodeSystemStorageSvc myConceptStorageSvc; + + @Override + public void addConceptToStorageQueue(TermConcept theConcept) { + Validate.notNull(theConcept); + myDeferredConcepts.add(theConcept); + } + + @Override + public void addConceptLinkToStorageQueue(TermConceptParentChildLink theConceptLink) { + Validate.notNull(theConceptLink); + myConceptLinksToSaveLater.add(theConceptLink); + } + + @Override + public void addConceptMapsToStorageQueue(List theConceptMaps) { + Validate.notNull(theConceptMaps); + myDeferredConceptMaps.addAll(theConceptMaps); + } + + @Override + public void addValueSetsToStorageQueue(List theValueSets) { + Validate.notNull(theValueSets); + myDeferredValueSets.addAll(theValueSets); + } + + @Override + public void setProcessDeferred(boolean theProcessDeferred) { + myProcessDeferred = theProcessDeferred; + } + + private void processDeferredConceptMaps() { + int count = Math.min(myDeferredConceptMaps.size(), 20); + for (ConceptMap nextConceptMap : new ArrayList<>(myDeferredConceptMaps.subList(0, count))) { + ourLog.info("Creating ConceptMap: {}", nextConceptMap.getId()); + myTerminologyVersionAdapterSvc.createOrUpdateConceptMap(nextConceptMap); + myDeferredConceptMaps.remove(nextConceptMap); + } + ourLog.info("Saved {} deferred ConceptMap resources, have {} remaining", count, myDeferredConceptMaps.size()); + } + + private void processDeferredConcepts() { + int codeCount = 0, relCount = 0; + StopWatch stopwatch = new StopWatch(); + + int count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myDeferredConcepts.size()); + ourLog.info("Saving {} deferred concepts...", count); + while (codeCount < count && myDeferredConcepts.size() > 0) { + TermConcept next = myDeferredConcepts.remove(0); + codeCount += myConceptStorageSvc.saveConcept(next); + } + + if (codeCount > 0) { + ourLog.info("Saved {} deferred concepts ({} codes remain and {} relationships remain) in {}ms ({}ms / code)", + codeCount, myDeferredConcepts.size(), myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(codeCount)); + } + + if (codeCount == 0) { + count = Math.min(myDaoConfig.getDeferIndexingForCodesystemsOfSize(), myConceptLinksToSaveLater.size()); + ourLog.info("Saving {} deferred concept relationships...", count); + while (relCount < count && myConceptLinksToSaveLater.size() > 0) { + TermConceptParentChildLink next = myConceptLinksToSaveLater.remove(0); + + if (!myConceptDao.findById(next.getChild().getId()).isPresent() || !myConceptDao.findById(next.getParent().getId()).isPresent()) { + ourLog.warn("Not inserting link from child {} to parent {} because it appears to have been deleted", next.getParent().getCode(), next.getChild().getCode()); + continue; + } + + saveConceptLink(next); + relCount++; + } + } + + if (relCount > 0) { + ourLog.info("Saved {} deferred relationships ({} remain) in {}ms ({}ms / entry)", + relCount, myConceptLinksToSaveLater.size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(relCount)); + } + + if ((myDeferredConcepts.size() + myConceptLinksToSaveLater.size()) == 0) { + ourLog.info("All deferred concepts and relationships have now been synchronized to the database"); + } + } + + private void processDeferredValueSets() { + int count = Math.min(myDeferredValueSets.size(), 20); + for (ValueSet nextValueSet : new ArrayList<>(myDeferredValueSets.subList(0, count))) { + ourLog.info("Creating ValueSet: {}", nextValueSet.getId()); + myTerminologyVersionAdapterSvc.createOrUpdateValueSet(nextValueSet); + myDeferredValueSets.remove(nextValueSet); + } + ourLog.info("Saved {} deferred ValueSet resources, have {} remaining", count, myDeferredValueSets.size()); + } + + /** + * This method is present only for unit tests, do not call from client code + */ + @VisibleForTesting + public synchronized void clearDeferred() { + myDeferredValueSets.clear(); + myDeferredConceptMaps.clear(); + myDeferredConcepts.clear(); + } + + @Transactional(propagation = Propagation.NEVER) + @Override + public synchronized void saveDeferred() { + if (isProcessDeferredPaused()) { + return; + } + + TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); + tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + if (isDeferredConceptsOrConceptLinksToSaveLater()) { + tt.execute(t -> { + processDeferredConcepts(); + return null; + }); + } + + if (isDeferredValueSets()) { + tt.execute(t -> { + processDeferredValueSets(); + return null; + }); + } + if (isDeferredConceptMaps()) { + tt.execute(t -> { + processDeferredConceptMaps(); + return null; + }); + } + + } + + @Override + public boolean isStorageQueueEmpty() { + boolean retVal = true; + retVal &= !isProcessDeferredPaused(); + retVal &= !isDeferredConcepts(); + retVal &= !isConceptLinksToSaveLater(); + retVal &= !isDeferredValueSets(); + retVal &= !isDeferredConceptMaps(); + return retVal; + } + + + private void saveConceptLink(TermConceptParentChildLink next) { + if (next.getId() == null) { + myConceptParentChildLinkDao.save(next); + } + } + + private boolean isProcessDeferredPaused() { + return !myProcessDeferred; + } + + private boolean isDeferredConceptsOrConceptLinksToSaveLater() { + return isDeferredConcepts() || isConceptLinksToSaveLater(); + } + + private boolean isDeferredConcepts() { + return !myDeferredConcepts.isEmpty(); + } + + private boolean isConceptLinksToSaveLater() { + return !myConceptLinksToSaveLater.isEmpty(); + } + + private boolean isDeferredValueSets() { + return !myDeferredValueSets.isEmpty(); + } + + private boolean isDeferredConceptMaps() { + return !myDeferredConceptMaps.isEmpty(); + } + + @PostConstruct + public void registerScheduledJob() { + // Register scheduled job to save deferred concepts + // In the future it would be great to make this a cluster-aware task somehow + ScheduledJobDefinition jobDefinition = new ScheduledJobDefinition(); + jobDefinition.setId(BaseTermReadSvcImpl.class.getName() + "_saveDeferred"); + jobDefinition.setJobClass(SaveDeferredJob.class); + mySchedulerService.scheduleFixedDelay(SCHEDULE_INTERVAL_MILLIS, false, jobDefinition); + } + + public static class SaveDeferredJob extends FireAtIntervalJob { + + @Autowired + private ITermDeferredStorageSvc myTerminologySvc; + + /** + * Constructor + */ + public SaveDeferredJob() { + super(SCHEDULE_INTERVAL_MILLIS); + } + + @Override + protected void doExecute(JobExecutionContext theContext) { + myTerminologySvc.saveDeferred(); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java similarity index 78% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImpl.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java index 7d728e733b0..9a9ec55712f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java @@ -4,8 +4,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; -import ca.uhn.fhir.jpa.term.custom.ConceptHandler; -import ca.uhn.fhir.jpa.term.custom.HierarchyHandler; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.loinc.*; import ca.uhn.fhir.jpa.term.snomedct.SctHandlerConcept; import ca.uhn.fhir.jpa.term.snomedct.SctHandlerDescription; @@ -15,7 +17,6 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; 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 ca.uhn.fhir.util.ValidateUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; @@ -23,9 +24,7 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.QuoteMode; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -41,8 +40,7 @@ import javax.validation.constraints.NotNull; import java.io.*; import java.util.*; import java.util.Map.Entry; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.*; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -67,64 +65,29 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { - public static final String SCT_FILE_CONCEPT = "Terminology/sct2_Concept_Full_"; - public static final String SCT_FILE_DESCRIPTION = "Terminology/sct2_Description_Full-en"; - public static final String SCT_FILE_RELATIONSHIP = "Terminology/sct2_Relationship_Full"; - - public static final String IMGTHLA_HLA_NOM_TXT = "hla_nom.txt"; - public static final String IMGTHLA_HLA_XML = "hla.xml"; - +public class TermLoaderSvcImpl implements ITermLoaderSvc { public static final String CUSTOM_CONCEPTS_FILE = "concepts.csv"; public static final String CUSTOM_HIERARCHY_FILE = "hierarchy.csv"; - public static final String CUSTOM_CODESYSTEM_JSON = "codesystem.json"; - public static final String CUSTOM_CODESYSTEM_XML = "codesystem.xml"; + static final String IMGTHLA_HLA_NOM_TXT = "hla_nom.txt"; + static final String IMGTHLA_HLA_XML = "hla.xml"; + static final String CUSTOM_CODESYSTEM_JSON = "codesystem.json"; + private static final String SCT_FILE_CONCEPT = "Terminology/sct2_Concept_Full_"; + private static final String SCT_FILE_DESCRIPTION = "Terminology/sct2_Description_Full-en"; + private static final String SCT_FILE_RELATIONSHIP = "Terminology/sct2_Relationship_Full"; + private static final String CUSTOM_CODESYSTEM_XML = "codesystem.xml"; private static final int LOG_INCREMENT = 1000; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcImpl.class); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermLoaderSvcImpl.class); // FYI: Hardcoded to R4 because that's what the term svc uses internally private final FhirContext myCtx = FhirContext.forR4(); @Autowired - private IHapiTerminologySvc myTermSvc; - - private void dropCircularRefs(TermConcept theConcept, ArrayList theChain, Map theCode2concept, Counter theCircularCounter) { - - theChain.add(theConcept.getCode()); - for (Iterator childIter = theConcept.getChildren().iterator(); childIter.hasNext(); ) { - TermConceptParentChildLink next = childIter.next(); - TermConcept nextChild = next.getChild(); - if (theChain.contains(nextChild.getCode())) { - - StringBuilder b = new StringBuilder(); - b.append("Removing circular reference code "); - b.append(nextChild.getCode()); - b.append(" from parent "); - b.append(next.getParent().getCode()); - b.append(". Chain was: "); - for (String nextInChain : theChain) { - TermConcept nextCode = theCode2concept.get(nextInChain); - b.append(nextCode.getCode()); - b.append('['); - b.append(StringUtils.substring(nextCode.getDisplay(), 0, 20).replace("[", "").replace("]", "").trim()); - b.append("] "); - } - ourLog.info(b.toString(), theConcept.getCode()); - childIter.remove(); - nextChild.getParents().remove(next); - - } else { - dropCircularRefs(nextChild, theChain, theCode2concept, theCircularCounter); - } - } - theChain.remove(theChain.size() - 1); - - } + private ITermDeferredStorageSvc myDeferredStorageSvc; + @Autowired + private ITermCodeSystemStorageSvc myCodeSystemStorageSvc; @Override public UploadStatistics loadImgthla(List theFiles, RequestDetails theRequestDetails) { - LoadedFileDescriptors descriptors = null; - try { - descriptors = new LoadedFileDescriptors(theFiles); + try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { List mandatoryFilenameFragments = Arrays.asList( IMGTHLA_HLA_NOM_TXT, IMGTHLA_HLA_XML @@ -134,15 +97,13 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { ourLog.info("Beginning IMGTHLA processing"); return processImgthlaFiles(descriptors, theRequestDetails); - } finally { - IOUtils.closeQuietly(descriptors); } } @Override public UploadStatistics loadLoinc(List theFiles, RequestDetails theRequestDetails) { try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { - List loincUploadPropertiesFragment = Arrays.asList( + List loincUploadPropertiesFragment = Collections.singletonList( LOINC_UPLOAD_PROPERTIES_FILE.getCode() ); descriptors.verifyMandatoryFilesExist(loincUploadPropertiesFragment); @@ -180,23 +141,6 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { } } - @NotNull - private Properties getProperties(LoadedFileDescriptors theDescriptors, String thePropertiesFile) { - Properties retVal = new Properties(); - for (FileDescriptor next : theDescriptors.getUncompressedFileDescriptors()) { - if (next.getFilename().endsWith(thePropertiesFile)) { - try { - try (InputStream inputStream = next.getInputStream()) { - retVal.load(inputStream); - } - } catch (IOException e) { - throw new InternalErrorException("Failed to read " + thePropertiesFile, e); - } - } - } - return retVal; - } - @Override public UploadStatistics loadSnomedCt(List theFiles, RequestDetails theRequestDetails) { try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { @@ -216,8 +160,6 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { @Override public UploadStatistics loadCustom(String theSystem, List theFiles, RequestDetails theRequestDetails) { try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { - IRecordHandler handler; - Optional codeSystemContent = loadFile(descriptors, CUSTOM_CODESYSTEM_JSON, CUSTOM_CODESYSTEM_XML); CodeSystem codeSystem; if (codeSystemContent.isPresent()) { @@ -233,14 +175,83 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); } - TermCodeSystemVersion csv = new TermCodeSystemVersion(); - final Map code2concept = processCustomTerminologyFiles(descriptors, csv); + CustomTerminologySet terminologySet = CustomTerminologySet.load(descriptors, false); + TermCodeSystemVersion csv = terminologySet.toCodeSystemVersion(); IIdType target = storeCodeSystem(theRequestDetails, csv, codeSystem, null, null); - return new UploadStatistics(code2concept.size(), target); + return new UploadStatistics(terminologySet.getSize(), target); } } + + @Override + public UploadStatistics loadDeltaAdd(String theSystem, List theFiles, RequestDetails theRequestDetails) { + ourLog.info("Processing terminology delta ADD for system[{}] with files: {}", theSystem, theFiles.stream().map(t -> t.getFilename()).collect(Collectors.toList())); + try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { + CustomTerminologySet terminologySet = CustomTerminologySet.load(descriptors, false); + return myCodeSystemStorageSvc.applyDeltaCodeSystemsAdd(theSystem, terminologySet); + } + } + + @Override + public UploadStatistics loadDeltaRemove(String theSystem, List theFiles, RequestDetails theRequestDetails) { + ourLog.info("Processing terminology delta REMOVE for system[{}] with files: {}", theSystem, theFiles.stream().map(t -> t.getFilename()).collect(Collectors.toList())); + try (LoadedFileDescriptors descriptors = new LoadedFileDescriptors(theFiles)) { + CustomTerminologySet terminologySet = CustomTerminologySet.load(descriptors, true); + return myCodeSystemStorageSvc.applyDeltaCodeSystemsRemove(theSystem, terminologySet); + } + } + + private void dropCircularRefs(TermConcept theConcept, ArrayList theChain, Map theCode2concept) { + + theChain.add(theConcept.getCode()); + for (Iterator childIter = theConcept.getChildren().iterator(); childIter.hasNext(); ) { + TermConceptParentChildLink next = childIter.next(); + TermConcept nextChild = next.getChild(); + if (theChain.contains(nextChild.getCode())) { + + StringBuilder b = new StringBuilder(); + b.append("Removing circular reference code "); + b.append(nextChild.getCode()); + b.append(" from parent "); + b.append(next.getParent().getCode()); + b.append(". Chain was: "); + for (String nextInChain : theChain) { + TermConcept nextCode = theCode2concept.get(nextInChain); + b.append(nextCode.getCode()); + b.append('['); + b.append(StringUtils.substring(nextCode.getDisplay(), 0, 20).replace("[", "").replace("]", "").trim()); + b.append("] "); + } + ourLog.info(b.toString(), theConcept.getCode()); + childIter.remove(); + nextChild.getParents().remove(next); + + } else { + dropCircularRefs(nextChild, theChain, theCode2concept); + } + } + theChain.remove(theChain.size() - 1); + + } + + @NotNull + private Properties getProperties(LoadedFileDescriptors theDescriptors, String thePropertiesFile) { + Properties retVal = new Properties(); + for (FileDescriptor next : theDescriptors.getUncompressedFileDescriptors()) { + if (next.getFilename().endsWith(thePropertiesFile)) { + try { + try (InputStream inputStream = next.getInputStream()) { + retVal.load(inputStream); + } + } catch (IOException e) { + throw new InternalErrorException("Failed to read " + thePropertiesFile, e); + } + } + } + return retVal; + } + private Optional loadFile(LoadedFileDescriptors theDescriptors, String... theFilenames) { for (FileDescriptor next : theDescriptors.getUncompressedFileDescriptors()) { for (String nextFilename : theFilenames) { @@ -257,15 +268,14 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { return Optional.empty(); } - UploadStatistics processImgthlaFiles(LoadedFileDescriptors theDescriptors, RequestDetails theRequestDetails) { + private UploadStatistics processImgthlaFiles(LoadedFileDescriptors theDescriptors, RequestDetails theRequestDetails) { final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); - final Map code2concept = new HashMap<>(); final List valueSets = new ArrayList<>(); final List conceptMaps = new ArrayList<>(); CodeSystem imgthlaCs; try { - String imgthlaCsString = IOUtils.toString(BaseHapiTerminologySvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/imgthla/imgthla.xml"), Charsets.UTF_8); + String imgthlaCsString = IOUtils.toString(BaseTermReadSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/imgthla/imgthla.xml"), Charsets.UTF_8); imgthlaCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, imgthlaCsString); } catch (IOException e) { throw new InternalErrorException("Failed to load imgthla.xml", e); @@ -353,7 +363,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { int valueSetCount = valueSets.size(); int rootConceptCount = codeSystemVersion.getConcepts().size(); - int conceptCount = code2concept.size(); + int conceptCount = rootConceptCount; ourLog.info("Have {} total concepts, {} root concepts, {} ValueSets", conceptCount, rootConceptCount, valueSetCount); // remove this when fully implemented ... @@ -372,7 +382,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { CodeSystem loincCs; try { - String loincCsString = IOUtils.toString(BaseHapiTerminologySvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); + String loincCsString = IOUtils.toString(BaseTermReadSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); loincCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, loincCsString); } catch (IOException e) { throw new InternalErrorException("Failed to load loinc.xml", e); @@ -501,7 +511,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { retVal.setPublisher("Regenstrief Institute, Inc."); retVal.setDescription("A value set that includes all LOINC codes"); retVal.setCopyright("This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/"); - retVal.getCompose().addInclude().setSystem(IHapiTerminologyLoaderSvc.LOINC_URI); + retVal.getCompose().addInclude().setSystem(ITermLoaderSvc.LOINC_URI); return retVal; } @@ -540,7 +550,7 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { long count = circularCounter.getThenAdd(); float pct = ((float) count / rootConcepts.size()) * 100.0f; ourLog.info(" * Scanning for circular refs - have scanned {} / {} codes ({}%)", count, rootConcepts.size(), pct); - dropCircularRefs(next, new ArrayList<>(), code2concept, circularCounter); + dropCircularRefs(next, new ArrayList<>(), code2concept); } codeSystemVersion.getConcepts().addAll(rootConcepts.values()); @@ -555,8 +565,13 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { } @VisibleForTesting - void setTermSvcForUnitTests(IHapiTerminologySvc theTermSvc) { - myTermSvc = theTermSvc; + void setTermDeferredStorageSvc(ITermDeferredStorageSvc theDeferredStorageSvc) { + myDeferredStorageSvc = theDeferredStorageSvc; + } + + @VisibleForTesting + void setTermCodeSystemStorageSvcForUnitTests(ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc) { + myCodeSystemStorageSvc = theTermCodeSystemStorageSvc; } private IIdType storeCodeSystem(RequestDetails theRequestDetails, final TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystem, List theValueSets, List theConceptMaps) { @@ -566,135 +581,14 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { List conceptMaps = ObjectUtils.defaultIfNull(theConceptMaps, Collections.emptyList()); IIdType retVal; - myTermSvc.setProcessDeferred(false); - retVal = myTermSvc.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets, conceptMaps); - myTermSvc.setProcessDeferred(true); + myDeferredStorageSvc.setProcessDeferred(false); + retVal = myCodeSystemStorageSvc.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets, conceptMaps); + myDeferredStorageSvc.setProcessDeferred(true); return retVal; } - public static class LoadedFileDescriptors implements Closeable { - - private List myTemporaryFiles = new ArrayList<>(); - private List myUncompressedFileDescriptors = new ArrayList<>(); - - public LoadedFileDescriptors(List theFileDescriptors) { - try { - for (FileDescriptor next : theFileDescriptors) { - 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); - 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() { - @Override - public String getFilename() { - return nextEntryFileName; - } - - @Override - public InputStream getInputStream() { - try { - return new FileInputStream(nextTemporaryFile); - } catch (FileNotFoundException e) { - throw new InternalErrorException(e); - } - } - }); - myTemporaryFiles.add(nextTemporaryFile); - } - } - } else { - myUncompressedFileDescriptors.add(next); - } - - } - } catch (Exception e) { - close(); - throw new InternalErrorException(e); - } - } - - boolean hasFile(String theFilename) { - return myUncompressedFileDescriptors - .stream() - .map(t -> t.getFilename().replaceAll(".*[\\\\/]", "")) // Strip the path from the filename - .anyMatch(t -> t.equals(theFilename)); - } - - @Override - public void close() { - for (File next : myTemporaryFiles) { - FileUtils.deleteQuietly(next); - } - } - - List getUncompressedFileDescriptors() { - return myUncompressedFileDescriptors; - } - - private List notFound(List theExpectedFilenameFragments) { - Set foundFragments = new HashSet<>(); - for (String nextExpected : theExpectedFilenameFragments) { - for (FileDescriptor next : myUncompressedFileDescriptors) { - if (next.getFilename().contains(nextExpected)) { - foundFragments.add(nextExpected); - break; - } - } - } - - ArrayList notFoundFileNameFragments = new ArrayList<>(theExpectedFilenameFragments); - notFoundFileNameFragments.removeAll(foundFragments); - return notFoundFileNameFragments; - } - - private void verifyMandatoryFilesExist(List theExpectedFilenameFragments) { - List notFound = notFound(theExpectedFilenameFragments); - if (!notFound.isEmpty()) { - throw new UnprocessableEntityException("Could not find the following mandatory files in input: " + notFound); - } - } - - private void verifyOptionalFilesExist(List theExpectedFilenameFragments) { - List notFound = notFound(theExpectedFilenameFragments); - if (!notFound.isEmpty()) { - ourLog.warn("Could not find the following optional files: " + notFound); - } - } - - - } - - @Nonnull - public static Map processCustomTerminologyFiles(LoadedFileDescriptors theDescriptors, TermCodeSystemVersion theCsv) { - IRecordHandler handler;// Concept File - final Map code2concept = new HashMap<>(); - handler = new ConceptHandler(code2concept, theCsv); - iterateOverZipFile(theDescriptors, CUSTOM_CONCEPTS_FILE, handler, ',', QuoteMode.NON_NUMERIC, false); - - // Hierarchy - if (theDescriptors.hasFile(CUSTOM_HIERARCHY_FILE)) { - handler = new HierarchyHandler(code2concept); - iterateOverZipFile(theDescriptors, CUSTOM_HIERARCHY_FILE, handler, ',', QuoteMode.NON_NUMERIC, false); - } - - // Add root concepts to CodeSystemVersion - for (TermConcept nextConcept : code2concept.values()) { - if (nextConcept.getParents().isEmpty()) { - theCsv.getConcepts().add(nextConcept); - } - } - return code2concept; - } - - private static void iterateOverZipFile(LoadedFileDescriptors theDescriptors, String theFileNamePart, IRecordHandler theHandler, char theDelimiter, QuoteMode theQuoteMode, boolean theIsPartialFilename) { + public static void iterateOverZipFile(LoadedFileDescriptors theDescriptors, String theFileNamePart, IRecordHandler theHandler, char theDelimiter, QuoteMode theQuoteMode, boolean theIsPartialFilename) { boolean foundMatch = false; for (FileDescriptor nextZipBytes : theDescriptors.getUncompressedFileDescriptors()) { @@ -748,9 +642,12 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { } @Nonnull - public static CSVParser newCsvRecords(char theDelimiter, QuoteMode theQuoteMode, Reader theReader) throws IOException { + private static CSVParser newCsvRecords(char theDelimiter, QuoteMode theQuoteMode, Reader theReader) throws IOException { CSVParser parsed; - CSVFormat format = CSVFormat.newFormat(theDelimiter).withFirstRecordAsHeader(); + CSVFormat format = CSVFormat + .newFormat(theDelimiter) + .withFirstRecordAsHeader() + .withTrim(); if (theQuoteMode != null) { format = format.withQuote('"').withQuoteMode(theQuoteMode); } @@ -769,12 +666,11 @@ public class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { return retVal; } - public static TermConcept getOrCreateConcept(TermCodeSystemVersion codeSystemVersion, Map id2concept, String id) { + public static TermConcept getOrCreateConcept(Map id2concept, String id) { TermConcept concept = id2concept.get(id); if (concept == null) { concept = new TermConcept(); id2concept.put(id, concept); - concept.setCodeSystemVersion(codeSystemVersion); } return concept; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java similarity index 90% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java index 33f7d792b7d..1993579f0d6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java @@ -25,9 +25,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import org.hl7.fhir.instance.hapi.validation.IValidationSupport; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +34,7 @@ import java.util.List; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { +public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { @Autowired private IValidationSupport myValidationSupport; @@ -65,22 +63,7 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { } @Override - protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource) { - throw new UnsupportedOperationException(); - } - - @Override - protected void createOrUpdateConceptMap(ConceptMap theNextConceptMap) { - throw new UnsupportedOperationException(); - } - - @Override - protected void createOrUpdateValueSet(ValueSet theValueSet) { - throw new UnsupportedOperationException(); - } - - @Override - protected CodeSystem getCodeSystemFromContext(String theSystem) { + public CodeSystem getCodeSystemFromContext(String theSystem) { return null; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java similarity index 83% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java index b265ba26f39..852ec6bfc1b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java @@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.ValidateUtil; import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; @@ -19,7 +19,6 @@ import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -32,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -55,27 +53,22 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcDstu3 { +public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidationSupport, ITermReadSvcDstu3 { @Autowired @Qualifier("myValueSetDaoDstu3") private IFhirResourceDao myValueSetResourceDao; @Autowired - @Qualifier("myConceptMapDaoDstu3") - private IFhirResourceDao myConceptMapResourceDao; - @Autowired - private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; - @Autowired private IValidationSupport myValidationSupport; @Autowired - private IHapiTerminologySvc myTerminologySvc; + private ITermReadSvc myTerminologySvc; @Autowired private PlatformTransactionManager myTransactionManager; /** * Constructor */ - public HapiTerminologySvcDstu3() { + public TermReadSvcDstu3() { super(); } @@ -102,55 +95,6 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen return false; } - @Override - protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { - CodeSystem resourceToStore; - try { - resourceToStore = VersionConvertor_30_40.convertCodeSystem(theCodeSystemResource); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - validateCodeSystemForStorage(theCodeSystemResource); - if (isBlank(resourceToStore.getIdElement().getIdPart())) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); - return myCodeSystemResourceDao.update(resourceToStore, matchUrl).getId(); - } else { - return myCodeSystemResourceDao.update(resourceToStore).getId(); - } - } - - @Override - protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { - ConceptMap resourceToStore; - try { - resourceToStore = VersionConvertor_30_40.convertConceptMap(theConceptMap); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - if (isBlank(resourceToStore.getIdElement().getIdPart())) { - String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); - myConceptMapResourceDao.update(resourceToStore, matchUrl); - } else { - myConceptMapResourceDao.update(resourceToStore); - } - } - - @Override - protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { - ValueSet valueSetDstu3; - try { - valueSetDstu3 = VersionConvertor_30_40.convertValueSet(theValueSet); - } catch (FHIRException e) { - throw new InternalErrorException(e); - } - - if (isBlank(valueSetDstu3.getIdElement().getIdPart())) { - String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); - myValueSetResourceDao.update(valueSetDstu3, matchUrl); - } else { - myValueSetResourceDao.update(valueSetDstu3); - } - } @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { @@ -303,7 +247,7 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen } @Override - protected org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { + public org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { CodeSystem codeSystem = myValidationSupport.fetchCodeSystem(myContext, theSystem); try { return VersionConvertor_30_40.convertCodeSystem(codeSystem); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java similarity index 84% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java index 06378a6cd10..b1aa2821d98 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java @@ -5,11 +5,10 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.UrlUtil; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; @@ -27,7 +26,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -50,14 +48,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements IHapiTerminologySvcR4 { +public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4 { - @Autowired - @Qualifier("myConceptMapDaoR4") - private IFhirResourceDao myConceptMapResourceDao; - @Autowired - @Qualifier("myCodeSystemDaoR4") - private IFhirResourceDao myCodeSystemResourceDao; @Autowired @Qualifier("myValueSetDaoR4") private IFhirResourceDao myValueSetResourceDao; @@ -89,36 +81,6 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements return false; } - @Override - protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { - validateCodeSystemForStorage(theCodeSystemResource); - if (isBlank(theCodeSystemResource.getIdElement().getIdPart())) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); - return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl).getId(); - } else { - return myCodeSystemResourceDao.update(theCodeSystemResource).getId(); - } - } - - @Override - protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { - if (isBlank(theConceptMap.getIdElement().getIdPart())) { - String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); - myConceptMapResourceDao.update(theConceptMap, matchUrl); - } else { - myConceptMapResourceDao.update(theConceptMap); - } - } - - @Override - protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { - if (isBlank(theValueSet.getIdElement().getIdPart())) { - String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); - myValueSetResourceDao.update(theValueSet, matchUrl); - } else { - myValueSetResourceDao.update(theValueSet); - } - } @Override public List expandValueSet(String theValueSet) { @@ -233,7 +195,7 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements } @Override - protected CodeSystem getCodeSystemFromContext(String theSystem) { + public CodeSystem getCodeSystemFromContext(String theSystem) { return myValidationSupport.fetchCodeSystem(myContext, theSystem); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java similarity index 84% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR5.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java index 9c2cdb059af..8b937aaf7e1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java @@ -5,12 +5,11 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.ValidateUtil; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; @@ -28,7 +27,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -51,14 +49,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcR5 { +public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSupport, ITermReadSvcR5 { - @Autowired - @Qualifier("myConceptMapDaoR5") - private IFhirResourceDao myConceptMapResourceDao; - @Autowired - @Qualifier("myCodeSystemDaoR5") - private IFhirResourceDao myCodeSystemResourceDao; @Autowired @Qualifier("myValueSetDaoR5") private IFhirResourceDao myValueSetResourceDao; @@ -90,44 +82,6 @@ public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements return false; } - @Override - protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { - validateCodeSystemForStorage(theCodeSystemResource); - - CodeSystem codeSystemR4 = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(theCodeSystemResource); - if (isBlank(theCodeSystemResource.getIdElement().getIdPart())) { - String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); - return myCodeSystemResourceDao.update(codeSystemR4, matchUrl).getId(); - } else { - return myCodeSystemResourceDao.update(codeSystemR4).getId(); - } - } - - @Override - protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { - - ConceptMap conceptMapR4 = org.hl7.fhir.convertors.conv40_50.ConceptMap.convertConceptMap(theConceptMap); - - if (isBlank(theConceptMap.getIdElement().getIdPart())) { - String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); - myConceptMapResourceDao.update(conceptMapR4, matchUrl); - } else { - myConceptMapResourceDao.update(conceptMapR4); - } - } - - @Override - protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { - - ValueSet valueSetR4 = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(theValueSet); - - if (isBlank(theValueSet.getIdElement().getIdPart())) { - String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); - myValueSetResourceDao.update(valueSetR4, matchUrl); - } else { - myValueSetResourceDao.update(valueSetR4); - } - } @Override public List expandValueSet(String theValueSet) { @@ -244,7 +198,7 @@ public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements } @Override - protected org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { + public org.hl7.fhir.r4.model.CodeSystem getCodeSystemFromContext(String theSystem) { CodeSystem codeSystemR5 = myValidationSupport.fetchCodeSystem(myContext, theSystem); return org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(codeSystemR5); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReindexingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReindexingSvcImpl.java new file mode 100644 index 00000000000..f1a53c83e0f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReindexingSvcImpl.java @@ -0,0 +1,169 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.model.sched.FireAtIntervalJob; +import ca.uhn.fhir.jpa.model.sched.ISchedulerService; +import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; +import ca.uhn.fhir.util.StopWatch; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ArrayListMultimap; +import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.time.DateUtils; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.PostConstruct; +import java.util.Collection; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class TermReindexingSvcImpl implements ITermReindexingSvc { + private static final Logger ourLog = LoggerFactory.getLogger(TermReindexingSvcImpl.class); + private static final long SCHEDULE_INTERVAL_MILLIS = DateUtils.MILLIS_PER_MINUTE; + private static boolean ourForceSaveDeferredAlwaysForUnitTest; + @Autowired + protected ITermConceptDao myConceptDao; + private ArrayListMultimap myChildToParentPidCache; + @Autowired + private PlatformTransactionManager myTransactionMgr; + @Autowired + private ITermConceptParentChildLinkDao myConceptParentChildLinkDao; + @Autowired + private ITermCodeSystemStorageSvc myConceptStorageSvc; + @Autowired + private ITermDeferredStorageSvc myDeferredStorageSvc; + @Autowired + private ISchedulerService mySchedulerService; + + @Override + public void processReindexing() { + if (myDeferredStorageSvc.isStorageQueueEmpty() == false && !ourForceSaveDeferredAlwaysForUnitTest) { + return; + } + + TransactionTemplate tt = new TransactionTemplate(myTransactionMgr); + tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + tt.execute(new TransactionCallbackWithoutResult() { + private void createParentsString(StringBuilder theParentsBuilder, Long theConceptPid) { + Validate.notNull(theConceptPid, "theConceptPid must not be null"); + List parents = myChildToParentPidCache.get(theConceptPid); + if (parents.contains(-1L)) { + return; + } else if (parents.isEmpty()) { + Collection parentLinks = myConceptParentChildLinkDao.findAllWithChild(theConceptPid); + if (parentLinks.isEmpty()) { + myChildToParentPidCache.put(theConceptPid, -1L); + ourLog.info("Found {} parent concepts of concept {} (cache has {})", 0, theConceptPid, myChildToParentPidCache.size()); + return; + } else { + for (Long next : parentLinks) { + myChildToParentPidCache.put(theConceptPid, next); + } + int parentCount = myChildToParentPidCache.get(theConceptPid).size(); + ourLog.info("Found {} parent concepts of concept {} (cache has {})", parentCount, theConceptPid, myChildToParentPidCache.size()); + } + } + + for (Long nextParent : parents) { + if (theParentsBuilder.length() > 0) { + theParentsBuilder.append(' '); + } + theParentsBuilder.append(nextParent); + createParentsString(theParentsBuilder, nextParent); + } + + } + + + @Override + protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + int maxResult = 1000; + Page concepts = myConceptDao.findResourcesRequiringReindexing(PageRequest.of(0, maxResult)); + if (!concepts.hasContent()) { + if (myChildToParentPidCache != null) { + ourLog.info("Clearing parent concept cache"); + myChildToParentPidCache = null; + } + return; + } + + if (myChildToParentPidCache == null) { + myChildToParentPidCache = ArrayListMultimap.create(); + } + + ourLog.info("Indexing {} / {} concepts", concepts.getContent().size(), concepts.getTotalElements()); + + int count = 0; + StopWatch stopwatch = new StopWatch(); + + for (TermConcept nextConcept : concepts) { + + if (isBlank(nextConcept.getParentPidsAsString())) { + StringBuilder parentsBuilder = new StringBuilder(); + createParentsString(parentsBuilder, nextConcept.getId()); + nextConcept.setParentPids(parentsBuilder.toString()); + } + + myConceptStorageSvc.saveConcept(nextConcept); + count++; + } + + ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", count, concepts.getContent().size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(count)); + } + }); + + } + + @PostConstruct + public void registerScheduledJob() { + // Register scheduled job to save deferred concepts + // In the future it would be great to make this a cluster-aware task somehow + ScheduledJobDefinition jobDefinition = new ScheduledJobDefinition(); + jobDefinition.setId(TermReindexingSvcImpl.class.getName() + "_reindex"); + jobDefinition.setJobClass(SaveDeferredJob.class); + mySchedulerService.scheduleFixedDelay(SCHEDULE_INTERVAL_MILLIS, false, jobDefinition); + } + + public static class SaveDeferredJob extends FireAtIntervalJob { + + @Autowired + private ITermDeferredStorageSvc myTerminologySvc; + + /** + * Constructor + */ + public SaveDeferredJob() { + super(SCHEDULE_INTERVAL_MILLIS); + } + + @Override + protected void doExecute(JobExecutionContext theContext) { + myTerminologySvc.saveDeferred(); + } + } + + /** + * This method is present only for unit tests, do not call from client code + */ + @VisibleForTesting + public static void setForceSaveDeferredAlwaysForUnitTest(boolean theForceSaveDeferredAlwaysForUnitTest) { + ourForceSaveDeferredAlwaysForUnitTest = theForceSaveDeferredAlwaysForUnitTest; + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu2.java new file mode 100644 index 00000000000..6809d596d7b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu2.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; + +public class TermVersionAdapterSvcDstu2 implements ITermVersionAdapterSvc { + + @Override + public IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource) { + throw new UnsupportedOperationException(); + } + + @Override + public void createOrUpdateConceptMap(ConceptMap theNextConceptMap) { + throw new UnsupportedOperationException(); + } + + @Override + public void createOrUpdateValueSet(ValueSet theValueSet) { + throw new UnsupportedOperationException(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java new file mode 100644 index 00000000000..35dc04e35ea --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcDstu3.java @@ -0,0 +1,101 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.convertors.VersionConvertor_30_40; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.ContextStartedEvent; +import org.springframework.context.event.EventListener; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class TermVersionAdapterSvcDstu3 extends BaseTermVersionAdapterSvcImpl implements ITermVersionAdapterSvc { + + private IFhirResourceDao myConceptMapResourceDao; + private IFhirResourceDao myCodeSystemResourceDao; + private IFhirResourceDao myValueSetResourceDao; + + @Autowired + private ApplicationContext myAppCtx; + + public TermVersionAdapterSvcDstu3() { + super(); + } + + + /** + * Initialize the beans that are used by this service. + * + * Note: There is a circular dependency here where the CodeSystem DAO + * needs terminology services, and the term services need the CodeSystem DAO. + * So we look these up in a refresh event instead of just autowiring them + * in order to avoid weird circular reference errors. + */ + @SuppressWarnings({"unchecked", "unused"}) + @EventListener + public void start(ContextRefreshedEvent theEvent) { + myCodeSystemResourceDao = (IFhirResourceDao) myAppCtx.getBean("myCodeSystemDaoDstu3"); + myValueSetResourceDao = (IFhirResourceDao) myAppCtx.getBean("myValueSetDaoDstu3"); + myConceptMapResourceDao = (IFhirResourceDao) myAppCtx.getBean("myConceptMapDaoDstu3"); + } + + @Override + public IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { + CodeSystem resourceToStore; + try { + resourceToStore = VersionConvertor_30_40.convertCodeSystem(theCodeSystemResource); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + validateCodeSystemForStorage(theCodeSystemResource); + if (isBlank(resourceToStore.getIdElement().getIdPart())) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(resourceToStore, matchUrl).getId(); + } else { + return myCodeSystemResourceDao.update(resourceToStore).getId(); + } + } + + @Override + public void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { + ConceptMap resourceToStore; + try { + resourceToStore = VersionConvertor_30_40.convertConceptMap(theConceptMap); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + if (isBlank(resourceToStore.getIdElement().getIdPart())) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(resourceToStore, matchUrl); + } else { + myConceptMapResourceDao.update(resourceToStore); + } + } + + @Override + public void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { + ValueSet valueSetDstu3; + try { + valueSetDstu3 = VersionConvertor_30_40.convertValueSet(theValueSet); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + + if (isBlank(valueSetDstu3.getIdElement().getIdPart())) { + String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(valueSetDstu3, matchUrl); + } else { + myValueSetResourceDao.update(valueSetDstu3); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java new file mode 100644 index 00000000000..46e71206514 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR4.java @@ -0,0 +1,75 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; + +import javax.annotation.PostConstruct; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class TermVersionAdapterSvcR4 extends BaseTermVersionAdapterSvcImpl implements ITermVersionAdapterSvc { + private IFhirResourceDao myConceptMapResourceDao; + private IFhirResourceDao myCodeSystemResourceDao; + private IFhirResourceDao myValueSetResourceDao; + + @Autowired + private ApplicationContext myAppCtx; + + /** + * Initialize the beans that are used by this service. + * + * Note: There is a circular dependency here where the CodeSystem DAO + * needs terminology services, and the term services need the CodeSystem DAO. + * So we look these up in a refresh event instead of just autowiring them + * in order to avoid weird circular reference errors. + */ + @SuppressWarnings({"unchecked", "unused"}) + @EventListener + public void start(ContextRefreshedEvent theEvent) { + myCodeSystemResourceDao = (IFhirResourceDao) myAppCtx.getBean("myCodeSystemDaoR4"); + myValueSetResourceDao = (IFhirResourceDao) myAppCtx.getBean("myValueSetDaoR4"); + myConceptMapResourceDao = (IFhirResourceDao) myAppCtx.getBean("myConceptMapDaoR4"); + } + + @Override + public IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { + validateCodeSystemForStorage(theCodeSystemResource); + if (isBlank(theCodeSystemResource.getIdElement().getIdPart())) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl).getId(); + } else { + return myCodeSystemResourceDao.update(theCodeSystemResource).getId(); + } + } + + @Override + public void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { + if (isBlank(theConceptMap.getIdElement().getIdPart())) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(theConceptMap, matchUrl); + } else { + myConceptMapResourceDao.update(theConceptMap); + } + } + + @Override + public void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { + if (isBlank(theValueSet.getIdElement().getIdPart())) { + String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(theValueSet, matchUrl); + } else { + myValueSetResourceDao.update(theValueSet); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java new file mode 100644 index 00000000000..f609002e86e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermVersionAdapterSvcR5.java @@ -0,0 +1,80 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; +import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.ConceptMap; +import org.hl7.fhir.r5.model.ValueSet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class TermVersionAdapterSvcR5 extends BaseTermVersionAdapterSvcImpl implements ITermVersionAdapterSvc { + private IFhirResourceDao myConceptMapResourceDao; + private IFhirResourceDao myCodeSystemResourceDao; + private IFhirResourceDao myValueSetResourceDao; + + @Autowired + private ApplicationContext myAppCtx; + + /** + * Initialize the beans that are used by this service. + * + * Note: There is a circular dependency here where the CodeSystem DAO + * needs terminology services, and the term services need the CodeSystem DAO. + * So we look these up in a refresh event instead of just autowiring them + * in order to avoid weird circular reference errors. + */ + @SuppressWarnings({"unchecked", "unused"}) + @EventListener + public void start(ContextRefreshedEvent theEvent) { + myCodeSystemResourceDao = (IFhirResourceDao) myAppCtx.getBean("myCodeSystemDaoR5"); + myValueSetResourceDao = (IFhirResourceDao) myAppCtx.getBean("myValueSetDaoR5"); + myConceptMapResourceDao = (IFhirResourceDao) myAppCtx.getBean("myConceptMapDaoR5"); + } + + @Override + public IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource) { + validateCodeSystemForStorage(theCodeSystemResource); + + CodeSystem codeSystemR4 = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertCodeSystem(theCodeSystemResource); + if (isBlank(theCodeSystemResource.getIdElement().getIdPart())) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(codeSystemR4, matchUrl).getId(); + } else { + return myCodeSystemResourceDao.update(codeSystemR4).getId(); + } + } + + @Override + public void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap) { + + ConceptMap conceptMapR4 = org.hl7.fhir.convertors.conv40_50.ConceptMap.convertConceptMap(theConceptMap); + + if (isBlank(theConceptMap.getIdElement().getIdPart())) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(conceptMapR4, matchUrl); + } else { + myConceptMapResourceDao.update(conceptMapR4); + } + } + + @Override + public void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet) { + + ValueSet valueSetR4 = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(theValueSet); + + if (isBlank(theValueSet.getIdElement().getIdPart())) { + String matchUrl = "ValueSet?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(valueSetR4, matchUrl); + } else { + myValueSetResourceDao.update(valueSetR4); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/UploadStatistics.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/UploadStatistics.java new file mode 100644 index 00000000000..90b0a839bc1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/UploadStatistics.java @@ -0,0 +1,30 @@ +package ca.uhn.fhir.jpa.term; + +import org.hl7.fhir.instance.model.api.IIdType; + +public class UploadStatistics { + private final IIdType myTarget; + private int myUpdatedConceptCount; + + public UploadStatistics(IIdType theTarget) { + this(0, theTarget); + } + + public UploadStatistics(int theUpdatedConceptCount, IIdType theTarget) { + myUpdatedConceptCount = theUpdatedConceptCount; + myTarget = theTarget; + } + + public void incrementUpdatedConceptCount() { + myUpdatedConceptCount++; + } + + public int getUpdatedConceptCount() { + return myUpdatedConceptCount; + } + + public IIdType getTarget() { + return myTarget; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java new file mode 100644 index 00000000000..81dbb5cceab --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jpa.term.api; + +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.UploadStatistics; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.List; + +/** + * This service handles writes to the CodeSystem/Concept tables within the terminology services + */ +public interface ITermCodeSystemStorageSvc { + + void deleteCodeSystem(TermCodeSystem theCodeSystem); + + void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); + + /** + * @return Returns the ID of the created/updated code system + */ + IIdType storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets, List theConceptMaps); + + void storeNewCodeSystemVersionIfNeeded(CodeSystem theCodeSystem, ResourceTable theResourceEntity); + + UploadStatistics applyDeltaCodeSystemsAdd(String theSystem, CustomTerminologySet theAdditions); + + UploadStatistics applyDeltaCodeSystemsRemove(String theSystem, CustomTerminologySet theRemovals); + + int saveConcept(TermConcept theNextConcept); + + Long getValueSetResourcePid(IIdType theIdElement); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java new file mode 100644 index 00000000000..1016506125b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java @@ -0,0 +1,33 @@ +package ca.uhn.fhir.jpa.term.api; + +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.List; + +/** + * This service handles processing "deferred" concept writes, meaning concepts that have neen + * queued for storage because there are too many of them to handle in a single transaction. + */ +public interface ITermDeferredStorageSvc { + + void saveDeferred(); + + boolean isStorageQueueEmpty(); + + /** + * This is mostly for unit tests - we can disable processing of deferred concepts + * by changing this flag + */ + void setProcessDeferred(boolean theProcessDeferred); + + void addConceptToStorageQueue(TermConcept theConcept); + + void addConceptLinkToStorageQueue(TermConceptParentChildLink theConceptLink); + + void addConceptMapsToStorageQueue(List theConceptMaps); + + void addValueSetsToStorageQueue(List theValueSets); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermLoaderSvc.java similarity index 56% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermLoaderSvc.java index 30f795ee3af..35176ce6b46 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermLoaderSvc.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.term.api; /* * #%L @@ -20,13 +20,18 @@ package ca.uhn.fhir.jpa.term; * #L% */ +import ca.uhn.fhir.jpa.term.UploadStatistics; import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.instance.model.api.IIdType; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.List; -public interface IHapiTerminologyLoaderSvc { +/** + * This service handles bulk loading concepts into the terminology service concept tables + * using any of several predefined input formats + */ +public interface ITermLoaderSvc { String IMGTHLA_URI = "http://www.ebi.ac.uk/ipd/imgt/hla"; String LOINC_URI = "http://loinc.org"; @@ -39,9 +44,15 @@ public interface IHapiTerminologyLoaderSvc { UploadStatistics loadSnomedCt(List theFiles, RequestDetails theRequestDetails); - // FIXME: remove the default implementation before 4.0.0 + // FIXME: remove the default implementation before 4.1.0 default UploadStatistics loadCustom(String theSystem, List theFiles, RequestDetails theRequestDetails) { return null; }; + // FIXME: remove the default implementation before 4.1.0 + default UploadStatistics loadDeltaAdd(String theSystem, List theFiles, RequestDetails theRequestDetails) { return null; }; + + // FIXME: remove the default implementation before 4.1.0 + default UploadStatistics loadDeltaRemove(String theSystem, List theFiles, RequestDetails theRequestDetails) { return null; }; + interface FileDescriptor { String getFilename(); @@ -50,23 +61,25 @@ public interface IHapiTerminologyLoaderSvc { } - class UploadStatistics { - private final int myConceptCount; - private final IIdType myTarget; + class ByteArrayFileDescriptor implements FileDescriptor { - public UploadStatistics(int theConceptCount, IIdType theTarget) { - myConceptCount = theConceptCount; - myTarget = theTarget; + private final String myNextUrl; + private final byte[] myNextData; + + public ByteArrayFileDescriptor(String theNextUrl, byte[] theNextData) { + myNextUrl = theNextUrl; + myNextData = theNextData; } - public int getConceptCount() { - return myConceptCount; + @Override + public String getFilename() { + return myNextUrl; } - public IIdType getTarget() { - return myTarget; + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(myNextData); } - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java similarity index 72% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 211185f719f..d86867a5200 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -1,20 +1,25 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.term.api; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.instance.model.api.*; +import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; +import ca.uhn.fhir.jpa.term.TranslationRequest; +import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; /* * #%L @@ -36,9 +41,17 @@ import java.util.concurrent.atomic.AtomicInteger; * #L% */ -public interface IHapiTerminologySvc { - - void deleteCodeSystem(TermCodeSystem theCodeSystem); +/** + * This interface is the "read" interface for the terminology service. It handles things like + * lookups, code validations, expansions, concept mappings, etc. + *

+ * It is intended to only handle read operations, leaving various write operations to + * other services within the terminology service APIs. + * (Note that at present, a few write operations remain here- they should be moved but haven't + * been moved yet) + *

+ */ +public interface ITermReadSvc { ValueSet expandValueSet(ValueSet theValueSetToExpand); @@ -74,22 +87,7 @@ public interface IHapiTerminologySvc { List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode); - void saveDeferred(); - - /** - * This is mostly for unit tests - we can disable processing of deferred concepts - * by changing this flag - */ - void setProcessDeferred(boolean theProcessDeferred); - - void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); - - /** - * @return Returns the ID of the created/updated code system - */ - IIdType storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets, List theConceptMaps); - - void storeNewCodeSystemVersionIfNeeded(CodeSystem theCodeSystem, ResourceTable theResourceEntity); + CodeSystem getCodeSystemFromContext(String theSystem); void deleteConceptMapAndChildren(ResourceTable theResourceTable); @@ -107,10 +105,6 @@ public interface IHapiTerminologySvc { IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB); - AtomicInteger applyDeltaCodesystemsAdd(String theSystem, @Nullable String theParent, CodeSystem theValue); - - AtomicInteger applyDeltaCodesystemsRemove(String theSystem, CodeSystem theDelta); - void preExpandDeferredValueSetsToTerminologyTables(); /** @@ -124,4 +118,6 @@ public interface IHapiTerminologySvc { * Version independent */ boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet); + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java similarity index 85% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java index ce9a9fdc549..15547b4cd97 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcDstu3.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.term.api; /* * #%L @@ -22,6 +22,6 @@ package ca.uhn.fhir.jpa.term; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -public interface IHapiTerminologySvcDstu3 extends IHapiTerminologySvc, IValidationSupport { +public interface ITermReadSvcDstu3 extends ITermReadSvc, IValidationSupport { // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java similarity index 86% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java index 21d20e17adb..275e9765488 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR4.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.term.api; /*- * #%L @@ -22,6 +22,6 @@ package ca.uhn.fhir.jpa.term; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -public interface IHapiTerminologySvcR4 extends IHapiTerminologySvc, IValidationSupport { +public interface ITermReadSvcR4 extends ITermReadSvc, IValidationSupport { // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java similarity index 86% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR5.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java index b9eafbec1c8..ceced8e261c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvcR5.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.term; +package ca.uhn.fhir.jpa.term.api; /*- * #%L @@ -22,6 +22,6 @@ package ca.uhn.fhir.jpa.term; import org.hl7.fhir.r5.hapi.ctx.IValidationSupport; -public interface IHapiTerminologySvcR5 extends IHapiTerminologySvc, IValidationSupport { +public interface ITermReadSvcR5 extends ITermReadSvc, IValidationSupport { // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReindexingSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReindexingSvc.java new file mode 100644 index 00000000000..db7f64e620b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReindexingSvc.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.jpa.term.api; + +public interface ITermReindexingSvc { + + void processReindexing(); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermVersionAdapterSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermVersionAdapterSvc.java new file mode 100644 index 00000000000..9d934adeaed --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermVersionAdapterSvc.java @@ -0,0 +1,21 @@ +package ca.uhn.fhir.jpa.term.api; + +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.ValueSet; + +/** + * This interface is used to handle differences in versions of FHIR for the terminology + * server. It is really just an internal interface used by the + * {@link ITermReadSvc terminology read service}. + */ +public interface ITermVersionAdapterSvc { + + IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource); + + void createOrUpdateConceptMap(ConceptMap theNextConceptMap); + + void createOrUpdateValueSet(ValueSet theValueSet); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/ConceptHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/ConceptHandler.java index ab51fedcb21..9fd42bcf325 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/ConceptHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/ConceptHandler.java @@ -20,10 +20,9 @@ package ca.uhn.fhir.jpa.term.custom; * #L% */ -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IRecordHandler; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; @@ -40,11 +39,9 @@ public class ConceptHandler implements IRecordHandler { public static final String CODE = "CODE"; public static final String DISPLAY = "DISPLAY"; private final Map myCode2Concept; - private final TermCodeSystemVersion myCodeSystemVersion; - public ConceptHandler(Map theCode2concept, TermCodeSystemVersion theCodeSystemVersion) { + public ConceptHandler(Map theCode2concept) { myCode2Concept = theCode2concept; - myCodeSystemVersion = theCodeSystemVersion; } @Override @@ -55,7 +52,7 @@ public class ConceptHandler implements IRecordHandler { Validate.isTrue(!myCode2Concept.containsKey(code), "The code %s has appeared more than once", code); - TermConcept concept = TerminologyLoaderSvcImpl.getOrCreateConcept(myCodeSystemVersion, myCode2Concept, code); + TermConcept concept = TermLoaderSvcImpl.getOrCreateConcept(myCode2Concept, code); concept.setCode(code); concept.setDisplay(display); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/CustomTerminologySet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/CustomTerminologySet.java new file mode 100644 index 00000000000..f08d8eef223 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/CustomTerminologySet.java @@ -0,0 +1,158 @@ +package ca.uhn.fhir.jpa.term.custom; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.IRecordHandler; +import ca.uhn.fhir.jpa.term.LoadedFileDescriptors; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; +import org.apache.commons.csv.QuoteMode; +import org.apache.commons.lang3.Validate; + +import javax.annotation.Nonnull; +import java.util.*; + +public class CustomTerminologySet { + + private final int mySize; + private final ListMultimap myUnanchoredChildConceptsToParentCodes; + private final List myRootConcepts; + + /** + * Constructor for an empty object + */ + public CustomTerminologySet() { + this(0, ArrayListMultimap.create(), new ArrayList<>()); + } + + /** + * Constructor + */ + private CustomTerminologySet(int theSize, ListMultimap theUnanchoredChildConceptsToParentCodes, Collection theRootConcepts) { + this(theSize, theUnanchoredChildConceptsToParentCodes, new ArrayList<>(theRootConcepts)); + } + + /** + * Constructor + */ + private CustomTerminologySet(int theSize, ListMultimap theUnanchoredChildConceptsToParentCodes, List theRootConcepts) { + mySize = theSize; + myUnanchoredChildConceptsToParentCodes = theUnanchoredChildConceptsToParentCodes; + myRootConcepts = theRootConcepts; + } + + public void addRootConcept(String theCode) { + addRootConcept(theCode, null); + } + + public TermConcept addRootConcept(String theCode, String theDisplay) { + Validate.notBlank(theCode, "theCode must not be blank"); + Validate.isTrue(myRootConcepts.stream().noneMatch(t -> t.getCode().equals(theCode)), "Already have code %s", theCode); + TermConcept retVal = new TermConcept(); + retVal.setCode(theCode); + retVal.setDisplay(theDisplay); + myRootConcepts.add(retVal); + return retVal; + } + + + public ListMultimap getUnanchoredChildConceptsToParentCodes() { + return Multimaps.unmodifiableListMultimap(myUnanchoredChildConceptsToParentCodes); + } + + public int getSize() { + return mySize; + } + + public TermCodeSystemVersion toCodeSystemVersion() { + TermCodeSystemVersion csv = new TermCodeSystemVersion(); + + for (TermConcept next : myRootConcepts) { + csv.getConcepts().add(next); + } + + populateVersionToChildCodes(csv, myRootConcepts); + + return csv; + } + + private void populateVersionToChildCodes(TermCodeSystemVersion theCsv, List theConcepts) { + for (TermConcept next : theConcepts) { + next.setCodeSystemVersion(theCsv); + populateVersionToChildCodes(theCsv, next.getChildCodes()); + } + } + + public List getRootConcepts() { + return Collections.unmodifiableList(myRootConcepts); + } + + public void addUnanchoredChildConcept(String theParentCode, String theCode, String theDisplay) { + Validate.notBlank(theParentCode); + Validate.notBlank(theCode); + + TermConcept code = new TermConcept() + .setCode(theCode) + .setDisplay(theDisplay); + myUnanchoredChildConceptsToParentCodes.put(code, theParentCode); + } + + public void validateNoCycleOrThrowInvalidRequest() { + Set codes = new HashSet<>(); + validateNoCycleOrThrowInvalidRequest(codes, getRootConcepts()); + for (TermConcept next : myUnanchoredChildConceptsToParentCodes.keySet()) { + validateNoCycleOrThrowInvalidRequest(codes, next); + } + } + + private void validateNoCycleOrThrowInvalidRequest(Set theCodes, List theRootConcepts) { + for (TermConcept next : theRootConcepts) { + validateNoCycleOrThrowInvalidRequest(theCodes, next); + } + } + + private void validateNoCycleOrThrowInvalidRequest(Set theCodes, TermConcept next) { + if (!theCodes.add(next.getCode())) { + throw new InvalidRequestException("Cycle detected around code " + next.getCode()); + } + validateNoCycleOrThrowInvalidRequest(theCodes, next.getChildCodes()); + } + + + @Nonnull + public static CustomTerminologySet load(LoadedFileDescriptors theDescriptors, boolean theFlat) { + + final Map code2concept = new LinkedHashMap<>(); + ArrayListMultimap unanchoredChildConceptsToParentCodes = ArrayListMultimap.create(); + + // Concepts + IRecordHandler conceptHandler = new ConceptHandler(code2concept); + TermLoaderSvcImpl.iterateOverZipFile(theDescriptors, TermLoaderSvcImpl.CUSTOM_CONCEPTS_FILE, conceptHandler, ',', QuoteMode.NON_NUMERIC, false); + if (theFlat) { + + return new CustomTerminologySet(code2concept.size(), ArrayListMultimap.create(), code2concept.values()); + + } else { + + // Hierarchy + if (theDescriptors.hasFile(TermLoaderSvcImpl.CUSTOM_HIERARCHY_FILE)) { + IRecordHandler hierarchyHandler = new HierarchyHandler(code2concept, unanchoredChildConceptsToParentCodes); + TermLoaderSvcImpl.iterateOverZipFile(theDescriptors, TermLoaderSvcImpl.CUSTOM_HIERARCHY_FILE, hierarchyHandler, ',', QuoteMode.NON_NUMERIC, false); + } + + // Find root concepts + List rootConcepts = new ArrayList<>(); + for (TermConcept nextConcept : code2concept.values()) { + if (nextConcept.getParents().isEmpty()) { + rootConcepts.add(nextConcept); + } + } + + return new CustomTerminologySet(code2concept.size(), unanchoredChildConceptsToParentCodes, rootConcepts); + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/HierarchyHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/HierarchyHandler.java index f57935dba4f..c9676d98994 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/HierarchyHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/custom/HierarchyHandler.java @@ -24,9 +24,8 @@ import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.util.ValidateUtil; +import com.google.common.collect.ArrayListMultimap; import org.apache.commons.csv.CSVRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Map; @@ -36,9 +35,11 @@ import static org.apache.commons.lang3.StringUtils.trim; public class HierarchyHandler implements IRecordHandler { private final Map myCode2Concept; + private final ArrayListMultimap myUnanchoredChildConceptsToParentCodes; - public HierarchyHandler(Map theCode2concept) { + public HierarchyHandler(Map theCode2concept, ArrayListMultimap theunanchoredChildConceptsToParentCodes) { myCode2Concept = theCode2concept; + myUnanchoredChildConceptsToParentCodes = theunanchoredChildConceptsToParentCodes; } @Override @@ -47,12 +48,15 @@ public class HierarchyHandler implements IRecordHandler { String child = trim(theRecord.get("CHILD")); if (isNotBlank(parent) && isNotBlank(child)) { - TermConcept parentConcept = myCode2Concept.get(parent); - ValidateUtil.isNotNullOrThrowUnprocessableEntity(parentConcept, "Parent code %s not found", parent); TermConcept childConcept = myCode2Concept.get(child); ValidateUtil.isNotNullOrThrowUnprocessableEntity(childConcept, "Child code %s not found", child); - parentConcept.addChild(childConcept, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + TermConcept parentConcept = myCode2Concept.get(parent); + if (parentConcept == null) { + myUnanchoredChildConceptsToParentCodes.put(childConcept, parent); + } else { + parentConcept.addChild(childConcept, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + } } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincTop2000LabResultsHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincTop2000LabResultsHandler.java index fc9f6472b40..680997ae7ef 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincTop2000LabResultsHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincTop2000LabResultsHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; @@ -52,7 +52,7 @@ public class BaseLoincTop2000LabResultsHandler extends BaseLoincHandler implemen String displayName = trim(theRecord.get("Long Common Name")); ValueSet valueSet = getValueSet(myValueSetId, myValueSetUri, myValueSetName, null); - addCodeAsIncludeToValueSet(valueSet, IHapiTerminologyLoaderSvc.LOINC_URI, loincNumber, displayName); + addCodeAsIncludeToValueSet(valueSet, ITermLoaderSvc.LOINC_URI, loincNumber, displayName); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java index 5e6700da2cd..f484a483d03 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java @@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.term.loinc; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; @@ -96,7 +96,7 @@ public class LoincAnswerListHandler extends BaseLoincHandler { vs .getCompose() .getIncludeFirstRep() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addConcept() .setCode(answerString) .setDisplay(displayText); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java index 756198a9287..ac463c756b3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java @@ -20,18 +20,14 @@ package ca.uhn.fhir.jpa.term.loinc; * #L% */ -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; -import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java index bcd152ceb71..9e8ccf71fdf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.csv.CSVRecord; @@ -58,7 +58,7 @@ public class LoincDocumentOntologyHandler extends BaseLoincHandler implements IR // RSNA Codes VS ValueSet vs = getValueSet(DOCUMENT_ONTOLOGY_CODES_VS_ID, DOCUMENT_ONTOLOGY_CODES_VS_URI, DOCUMENT_ONTOLOGY_CODES_VS_NAME, null); - addCodeAsIncludeToValueSet(vs, IHapiTerminologyLoaderSvc.LOINC_URI, loincNumber, null); + addCodeAsIncludeToValueSet(vs, ITermLoaderSvc.LOINC_URI, loincNumber, null); // Part Properties String loincCodePropName; @@ -84,7 +84,7 @@ public class LoincDocumentOntologyHandler extends BaseLoincHandler implements IR TermConcept code = myCode2Concept.get(loincNumber); if (code != null) { - code.addPropertyCoding(loincCodePropName, IHapiTerminologyLoaderSvc.LOINC_URI, partNumber, partName); + code.addPropertyCoding(loincCodePropName, ITermLoaderSvc.LOINC_URI, partNumber, partName); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincGroupTermsFileHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincGroupTermsFileHandler.java index 6a63f2fb921..ac844b4db1c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincGroupTermsFileHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincGroupTermsFileHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; @@ -46,7 +46,7 @@ public class LoincGroupTermsFileHandler extends BaseLoincHandler implements IRec String loincNumber = trim(theRecord.get("LoincNumber")); ValueSet valueSet = getValueSet(groupId, LoincGroupFileHandler.VS_URI_PREFIX + groupId, null, null); - addCodeAsIncludeToValueSet(valueSet, IHapiTerminologyLoaderSvc.LOINC_URI, loincNumber, null); + addCodeAsIncludeToValueSet(valueSet, ITermLoaderSvc.LOINC_URI, loincNumber, null); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java index c30c843f569..5f37bec22cb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java @@ -22,9 +22,9 @@ package ca.uhn.fhir.jpa.term.loinc; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.Validate; @@ -58,7 +58,7 @@ public class LoincHandler implements IRecordHandler { String longCommonName = trim(theRecord.get("LONG_COMMON_NAME")); String shortName = trim(theRecord.get("SHORTNAME")); String consumerName = trim(theRecord.get("CONSUMER_NAME")); - String display = TerminologyLoaderSvcImpl.firstNonBlank(longCommonName, shortName, consumerName); + String display = TermLoaderSvcImpl.firstNonBlank(longCommonName, shortName, consumerName); TermConcept concept = new TermConcept(myCodeSystemVersion, code); concept.setDisplay(display); @@ -117,7 +117,7 @@ public class LoincHandler implements IRecordHandler { } if (isNotBlank(partNumber)) { - concept.addPropertyCoding(nextPropertyName, IHapiTerminologyLoaderSvc.LOINC_URI, partNumber, nextPropertyValue); + concept.addPropertyCoding(nextPropertyName, ITermLoaderSvc.LOINC_URI, partNumber, nextPropertyValue); } else { String msg = "Unable to find part code with TYPE[" + key.getPartType() + "] and NAME[" + nextPropertyValue + "] (using name " + propertyValue + ")"; ourLog.warn(msg); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHierarchyHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHierarchyHandler.java index b5474f283eb..8afac741c0b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHierarchyHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHierarchyHandler.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.term.loinc; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; @@ -56,13 +56,13 @@ public class LoincHierarchyHandler implements IRecordHandler { parent.addPropertyCoding( "child", - IHapiTerminologyLoaderSvc.LOINC_URI, + ITermLoaderSvc.LOINC_URI, child.getCode(), child.getDisplay()); child.addPropertyCoding( "parent", - IHapiTerminologyLoaderSvc.LOINC_URI, + ITermLoaderSvc.LOINC_URI, parent.getCode(), parent.getDisplay()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java index c67874e42f4..6ba85e6422d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincIeeeMedicalDeviceCodeHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; @@ -57,8 +57,8 @@ public class LoincIeeeMedicalDeviceCodeHandler extends BaseLoincHandler implemen String ieeeDisplayName = trim(theRecord.get("IEEE_REFID")); // LOINC Part -> IEEE 11073:10101 Mappings - String sourceCodeSystemUri = IHapiTerminologyLoaderSvc.LOINC_URI; - String targetCodeSystemUri = IHapiTerminologyLoaderSvc.IEEE_11073_10101_URI; + String sourceCodeSystemUri = ITermLoaderSvc.LOINC_URI; + String targetCodeSystemUri = ITermLoaderSvc.IEEE_11073_10101_URI; addConceptMapEntry( new ConceptMapping() .setConceptMapId(LOINC_IEEE_CM_ID) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincImagingDocumentCodeHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincImagingDocumentCodeHandler.java index d38f85c518b..674b7a9ecf1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincImagingDocumentCodeHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincImagingDocumentCodeHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; @@ -49,7 +49,7 @@ public class LoincImagingDocumentCodeHandler extends BaseLoincHandler implements String displayName = trim(theRecord.get("LONG_COMMON_NAME")); ValueSet valueSet = getValueSet(VS_ID, VS_URI, VS_NAME,null); - addCodeAsIncludeToValueSet(valueSet, IHapiTerminologyLoaderSvc.LOINC_URI, loincNumber, displayName); + addCodeAsIncludeToValueSet(valueSet, ITermLoaderSvc.LOINC_URI, loincNumber, displayName); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java index 57898fdc4f9..c12fc6532da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.csv.CSVRecord; @@ -101,7 +101,7 @@ public class LoincPartRelatedCodeMappingHandler extends BaseLoincHandler impleme String loincPartMapUri; String loincPartMapName; switch (extCodeSystem) { - case IHapiTerminologyLoaderSvc.SCT_URI: + case ITermLoaderSvc.SCT_URI: loincPartMapId = LOINC_SCT_PART_MAP_ID; loincPartMapUri = LOINC_SCT_PART_MAP_URI; loincPartMapName = LOINC_SCT_PART_MAP_NAME; @@ -133,7 +133,7 @@ public class LoincPartRelatedCodeMappingHandler extends BaseLoincHandler impleme .setConceptMapId(loincPartMapId) .setConceptMapUri(loincPartMapUri) .setConceptMapName(loincPartMapName) - .setSourceCodeSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI) .setSourceCode(partNumber) .setSourceDisplay(partName) .setTargetCodeSystem(extCodeSystem) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java index e1cd0c6cf96..ef76aa82781 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.csv.CSVRecord; @@ -98,7 +98,7 @@ public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IRecor vs .getCompose() .getIncludeFirstRep() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addConcept() .setCode(loincNumber) .setDisplay(longCommonName); @@ -167,7 +167,7 @@ public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IRecor TermConcept code = myCode2Concept.get(loincNumber); if (code != null) { - code.addPropertyCoding(loincCodePropName, IHapiTerminologyLoaderSvc.LOINC_URI, partNumber, partName); + code.addPropertyCoding(loincCodePropName, ITermLoaderSvc.LOINC_URI, partNumber, partName); } // LOINC Part -> Radlex RID code mappings @@ -177,7 +177,7 @@ public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IRecor .setConceptMapId(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_ID) .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_URI) .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_NAME) - .setSourceCodeSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI) .setSourceCode(partNumber) .setSourceDisplay(partName) .setTargetCodeSystem(RID_CS_URI) @@ -194,7 +194,7 @@ public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IRecor .setConceptMapId(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_ID) .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_URI) .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_NAME) - .setSourceCodeSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI) .setSourceCode(loincNumber) .setSourceDisplay(longCommonName) .setTargetCodeSystem(RPID_CS_URI) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUniversalOrderSetHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUniversalOrderSetHandler.java index 0a1fd594a30..0b47142e360 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUniversalOrderSetHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUniversalOrderSetHandler.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.term.loinc; */ import ca.uhn.fhir.jpa.entity.TermConcept; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.IRecordHandler; import org.apache.commons.csv.CSVRecord; import org.hl7.fhir.r4.model.ConceptMap; @@ -48,7 +48,7 @@ public class LoincUniversalOrderSetHandler extends BaseLoincHandler implements I String orderObs = trim(theRecord.get("ORDER_OBS")); ValueSet valueSet = getValueSet(VS_ID, VS_URI, VS_NAME, null); - addCodeAsIncludeToValueSet(valueSet, IHapiTerminologyLoaderSvc.LOINC_URI, loincNumber, displayName); + addCodeAsIncludeToValueSet(valueSet, ITermLoaderSvc.LOINC_URI, loincNumber, displayName); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java index a1f1712f941..3ca15b0872b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.term.snomedct; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IRecordHandler; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import org.apache.commons.csv.CSVRecord; import java.util.Map; @@ -56,9 +56,10 @@ public final class SctHandlerDescription implements IRecordHandler { String term = theRecord.get("term"); - TermConcept concept = TerminologyLoaderSvcImpl.getOrCreateConcept(myCodeSystemVersion, myId2concept, id); + TermConcept concept = TermLoaderSvcImpl.getOrCreateConcept(myId2concept, id); concept.setCode(conceptId); concept.setDisplay(term); + concept.setCodeSystemVersion(myCodeSystemVersion); myCode2concept.put(conceptId, concept); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java index 312841e0f26..9cd2ab5402b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.validation; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; import org.hl7.fhir.dstu3.hapi.validation.SnapshotGeneratingValidationSupport; @@ -41,7 +41,7 @@ public class JpaValidationSupportChainDstu3 extends ValidationSupportChain { @Autowired private DefaultProfileValidationSupport myDefaultProfileValidationSupport; @Autowired - private IHapiTerminologySvcDstu3 myTerminologyService; + private ITermReadSvcDstu3 myTerminologyService; @Autowired private FhirContext myFhirContext; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java index 710650ac6b1..182da276ced 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR4.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.validation; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.validation.SnapshotGeneratingValidationSupport; @@ -46,7 +46,7 @@ public class JpaValidationSupportChainR4 extends ValidationSupportChain { public ca.uhn.fhir.jpa.dao.r4.IJpaValidationSupportR4 myJpaValidationSupportR4; @Autowired - private IHapiTerminologySvcR4 myTerminologyService; + private ITermReadSvcR4 myTerminologyService; public JpaValidationSupportChainR4() { super(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java index 8101855809a..21ffcb84df7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainR5.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.validation; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR5; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r5.hapi.validation.SnapshotGeneratingValidationSupport; @@ -46,7 +46,7 @@ public class JpaValidationSupportChainR5 extends ValidationSupportChain { public ca.uhn.fhir.jpa.dao.r5.IJpaValidationSupportR5 myJpaValidationSupportR5; @Autowired - private IHapiTerminologySvcR5 myTerminologyService; + private ITermReadSvcR5 myTerminologyService; public JpaValidationSupportChainR5() { super(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 34e66c70b49..516414b4233 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; +import ca.uhn.fhir.jpa.BaseTest; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.util.JpaConstants; @@ -66,7 +67,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -public abstract class BaseJpaTest { +public abstract class BaseJpaTest extends BaseTest { protected static final String CM_URL = "http://example.com/my_concept_map"; protected static final String CS_URL = "http://example.com/my_code_system"; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java index b7e675a32b1..f43f47a5522 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java @@ -921,7 +921,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { Patient patient = new Patient(); patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().addFamily("testSearchNameParam01Fam").addGiven("testSearchNameParam01Giv"); - ResourceMetadataKeyEnum.TITLE.put(patient, "P1TITLE"); id1 = myPatientDao.create(patient, mySrd).getId(); } { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 35941ac6bb9..45bb5e84837 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -18,8 +17,11 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; +import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceProviderFactory; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; @@ -67,6 +69,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static IFhirResourceDaoValueSet ourValueSetDao; + @Autowired + protected ITermDeferredStorageSvc myTerminologyDeferredStorageSvc; @Autowired @Qualifier("myResourceCountsCache") protected ResourceCountCache myResourceCountsCache; @@ -243,7 +247,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired protected ITermCodeSystemDao myTermCodeSystemDao; @Autowired - protected IHapiTerminologySvc myTermSvc; + protected ITermReadSvc myTermSvc; @Autowired protected PlatformTransactionManager myTransactionMgr; @Autowired @@ -262,6 +266,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { private JpaValidationSupportChainDstu3 myJpaValidationSupportChainDstu3; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; + @Autowired + protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @After() public void afterCleanupDao() { @@ -278,12 +284,13 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @After public void afterClearTerminologyCaches() { - BaseHapiTerminologySvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); + BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); baseHapiTerminologySvc.clearTranslationCache(); baseHapiTerminologySvc.clearTranslationWithReverseCache(); - baseHapiTerminologySvc.clearDeferred(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationCache(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + TermDeferredStorageSvcImpl deferredSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); + deferredSvc.clearDeferred(); } @After() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java index ea57aa1b754..b59a83b60fa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java @@ -1,34 +1,30 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.*; - -import java.nio.charset.StandardCharsets; - -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.Enumerations; -import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class FhirResourceDaoDstu3CodeSystemTest extends BaseJpaDstu3Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3CodeSystemTest.class); - @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Test public void testIndexContained() throws Exception { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); String input = IOUtils.toString(getClass().getResource("/dstu3_codesystem_complete.json"), StandardCharsets.UTF_8); CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input); @@ -39,13 +35,13 @@ public class FhirResourceDaoDstu3CodeSystemTest extends BaseJpaDstu3Test { int outcome= myResourceReindexingSvc.forceReindexingPass(); assertNotEquals(-1, outcome); // -1 means there was a failure - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); } @Test public void testDeleteCodeSystemComplete() { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); // Create the code system CodeSystem cs = new CodeSystem(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index db60ac2c6e8..054ab6fef92 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -7,8 +7,7 @@ import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; @@ -45,8 +44,6 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; - @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; @Autowired private CachingValidationSupport myCachingValidationSupport; @@ -54,7 +51,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Before @@ -103,11 +100,11 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, RelationshipTypeEnum.ISA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } - private CodeSystem createExternalCsLarge() { + private void createExternalCsLarge() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -134,8 +131,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { parentB.addChild(childI, RelationshipTypeEnum.ISA); } - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); - return codeSystem; + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); } private void createExternalCsAndLocalVs() { @@ -170,7 +166,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle"); dogs.addChild(beagle, RelationshipTypeEnum.ISA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } @@ -490,7 +486,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @Test public void testExpandWithIsAInExternalValueSetReindex() { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); createExternalCsAndLocalVs(); @@ -499,9 +495,9 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myHapiTerminologySvc.saveDeferred(); - myHapiTerminologySvc.saveDeferred(); - myHapiTerminologySvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); IContextValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM),null, mySrd); assertEquals(true, lookupResults.isFound()); @@ -640,7 +636,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { public void testIndexingIsDeferredForLargeCodeSystems() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); - myTermSvc.setProcessDeferred(false); + myTerminologyDeferredStorageSvc.setProcessDeferred(false); createExternalCsAndLocalVs(); @@ -654,14 +650,14 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { assertEquals(0, result.getExpansion().getContains().size()); - myTermSvc.setProcessDeferred(true); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.setProcessDeferred(true); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); vs = new ValueSet(); include = vs.getCompose().addInclude(); @@ -689,7 +685,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { cs.setResource(table); TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); cs.getConcepts().add(parentA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); @@ -743,15 +739,15 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); // Again myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); } @@ -765,11 +761,11 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { Observation obsBA = new Observation(); obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); @@ -803,6 +799,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); } catch (ResourceNotFoundException e) { + //noinspection SpellCheckingInspection assertEquals("Unknown ValueSet: http%3A%2F%2Fexample.com%2Fmy_value_set", e.getMessage()); } } @@ -819,7 +816,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { AllergyIntolerance ai3 = new AllergyIntolerance(); ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); - String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); SearchParameterMap params; params = new SearchParameterMap(); @@ -865,7 +862,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { AllergyIntolerance ai3 = new AllergyIntolerance(); ai3.setClinicalStatus(AllergyIntoleranceClinicalStatus.INACTIVE); ai1.addCategory(org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory.FOOD); - String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); SearchParameterMap params; params = new SearchParameterMap(); @@ -905,11 +902,11 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { Observation obsBA = new Observation(); obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.BELOW)); @@ -939,7 +936,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { Observation obs3 = new Observation(); obs3.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("subCodeB3"); - IIdType id3 = myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "codeA").setModifier(TokenParamModifier.BELOW)); @@ -1028,7 +1025,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); @@ -1058,7 +1055,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { AuditEvent aeOut1 = new AuditEvent(); aeOut1.getType().setSystem("http://example.com").setCode("foo"); - IIdType idOut1 = myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); + myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); @@ -1084,7 +1081,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); @@ -1097,7 +1094,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { ValueSet valueSet = new ValueSet(); valueSet.getCompose().addInclude().addValueSet("http://non_existant_VS"); valueSet.setUrl(URL_MY_VALUE_SET); - IIdType vsid = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); + IIdType vsId = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params; @@ -1113,7 +1110,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { // Now let's update valueSet = new ValueSet(); - valueSet.setId(vsid); + valueSet.setId(vsId); valueSet.getCompose().addInclude().setSystem("http://hl7.org/fhir/v3/MaritalStatus").addConcept().setCode("A"); valueSet.setUrl(URL_MY_VALUE_SET); myValueSetDao.update(valueSet, mySrd).getId().toUnqualifiedVersionless(); @@ -1123,6 +1120,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); params.add(Observation.SP_STATUS, new TokenParam(null, "final")); } catch (ResourceNotFoundException e) { + //noinspection SpellCheckingInspection assertEquals("Unknown ValueSet: http%3A%2F%2Fexample.com%2Fmy_value_set", e.getMessage()); } @@ -1168,7 +1166,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { } private ArrayList toCodesContains(List theContains) { - ArrayList retVal = new ArrayList(); + ArrayList retVal = new ArrayList<>(); for (ValueSetExpansionContainsComponent next : theContains) { retVal.add(next.getCode()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 3e5201d5b3e..b0886bca423 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -21,8 +21,9 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; +import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; +import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; +import ca.uhn.fhir.jpa.term.api.*; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceProviderFactory; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; @@ -36,12 +37,12 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; -import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent; @@ -77,6 +78,12 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { private static JpaValidationSupportChainR4 ourJpaValidationSupportChainR4; private static IFhirResourceDaoValueSet ourValueSetDao; + @Autowired + protected ITermReadSvc myHapiTerminologySvc; + @Autowired + protected CachingValidationSupport myCachingValidationSupport; + @Autowired + protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Autowired protected ISearchDao mySearchEntityDao; @Autowired @@ -288,7 +295,11 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myTaskDaoR4") protected IFhirResourceDao myTaskDao; @Autowired - protected IHapiTerminologySvcR4 myTermSvc; + protected ITermReadSvcR4 myTermSvc; + @Autowired + protected ITermDeferredStorageSvc myTerminologyDeferredStorageSvc; + @Autowired + protected ITermLoaderSvc myTerminologyLoaderSvc; @Autowired protected PlatformTransactionManager myTransactionMgr; @Autowired @@ -343,12 +354,13 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @After public void afterClearTerminologyCaches() { - BaseHapiTerminologySvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); + BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); baseHapiTerminologySvc.clearTranslationCache(); baseHapiTerminologySvc.clearTranslationWithReverseCache(); - baseHapiTerminologySvc.clearDeferred(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationCache(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); + termDeferredStorageSvc.clearDeferred(); } @After() @@ -439,10 +451,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { dao.update(resourceParsed); } - protected String loadResource(String theClasspath) throws IOException { - return IOUtils.toString(FhirResourceDaoR4ValidateTest.class.getResourceAsStream(theClasspath), Charsets.UTF_8); - } - @AfterClass public static void afterClassClearContextBaseJpaR4Test() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java index a44c132d352..f6036decf5f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java @@ -1,30 +1,28 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.junit.Assert.assertNotEquals; - -import java.nio.charset.StandardCharsets; - -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.CodeSystem; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertNotEquals; public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4CodeSystemTest.class); @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Test public void testIndexContained() throws Exception { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); String input = IOUtils.toString(getClass().getResource("/r4/codesystem_complete.json"), StandardCharsets.UTF_8); CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input); @@ -34,7 +32,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { int outcome = myResourceReindexingSvc.forceReindexingPass(); assertNotEquals(-1, outcome); // -1 means there was a failure - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java index 67ebd6d3916..d820b1ee805 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java @@ -9,13 +9,13 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.param.StringParam; @@ -23,7 +23,6 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; import org.junit.Before; @@ -35,7 +34,6 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; -import javax.persistence.EntityManager; import java.util.ArrayList; import java.util.List; @@ -63,7 +61,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest { @Qualifier("myValueSetDaoR4") protected IFhirResourceDaoValueSet myValueSetDao; @Autowired - protected IHapiTerminologySvcR4 myTermSvc; + protected ITermReadSvcR4 myTermSvc; @Autowired protected IResourceTableDao myResourceTableDao; @Autowired @@ -81,6 +79,8 @@ public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest { private IResourceReindexingSvc myResourceReindexingSvc; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; + @Autowired + private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Before public void beforePurgeDatabase() { @@ -181,7 +181,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, TermConceptParentChildLink.RelationshipTypeEnum.ISA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 8b3e478efd1..555d92da146 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -7,8 +7,7 @@ import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; @@ -19,14 +18,12 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.ValueSet.*; import org.junit.*; -import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.Date; @@ -43,17 +40,13 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TerminologyTest.class); - @Autowired - private IHapiTerminologySvc myHapiTerminologySvc; - @Autowired - private CachingValidationSupport myCachingValidationSupport; @After public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Before @@ -101,7 +94,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, RelationshipTypeEnum.ISA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } @@ -137,11 +130,11 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle"); dogs.addChild(beagle, RelationshipTypeEnum.ISA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } - private CodeSystem createExternalCsLarge() { + private void createExternalCsLarge() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -168,8 +161,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { parentB.addChild(childI, RelationshipTypeEnum.ISA); } - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); - return codeSystem; + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); } private void createLocalCsAndVs() { @@ -481,7 +473,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { concept = new TermConcept(cs, "LA9999-7"); cs.getConcepts().add(concept); - myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); ValueSet valueSet = new ValueSet(); valueSet.setUrl(URL_MY_VALUE_SET); @@ -490,9 +482,9 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { .setSystem(codeSystem.getUrl()) .addConcept(new ConceptReferenceComponent().setCode("LA2222-2")) .addConcept(new ConceptReferenceComponent().setCode("LA1122-2")); - IIdType vsid = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); + IIdType vsId = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); - ValueSet expansion = myValueSetDao.expand(vsid, null, null); + ValueSet expansion = myValueSetDao.expand(vsId, null, null); Set codes = expansion .getExpansion() .getContains() @@ -544,16 +536,16 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { @Test public void testExpandWithIsAInExternalValueSetReindex() { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); createExternalCsAndLocalVs(); myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myHapiTerminologySvc.saveDeferred(); - myHapiTerminologySvc.saveDeferred(); - myHapiTerminologySvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); ValueSet vs = new ValueSet(); ConceptSetComponent include = vs.getCompose().addInclude(); @@ -759,7 +751,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { public void testIndexingIsDeferredForLargeCodeSystems() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); - myTermSvc.setProcessDeferred(false); + myTerminologyDeferredStorageSvc.setProcessDeferred(false); createExternalCsAndLocalVs(); @@ -773,14 +765,14 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { assertEquals(0, result.getExpansion().getContains().size()); - myTermSvc.setProcessDeferred(true); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.setProcessDeferred(true); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); vs = new ValueSet(); include = vs.getCompose().addInclude(); @@ -808,7 +800,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { cs.setResource(table); TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); cs.getConcepts().add(parentA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); @@ -862,15 +854,15 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); // Again myResourceReindexingSvc.markAllResourcesForReindexing(); myResourceReindexingSvc.forceReindexingPass(); myResourceReindexingSvc.forceReindexingPass(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); } @@ -884,11 +876,11 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { Observation obsBA = new Observation(); obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.ABOVE)); @@ -925,7 +917,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { AllergyIntolerance ai3 = new AllergyIntolerance(); ai3.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical").setCode("inactive"); - String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); SearchParameterMap params; params = new SearchParameterMap(); @@ -971,7 +963,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { AllergyIntolerance ai3 = new AllergyIntolerance(); ai3.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical").setCode("inactive"); ai3.addCategory(org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory.FOOD); - String id3 = myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); + myAllergyIntoleranceDao.create(ai3, mySrd).getId().toUnqualifiedVersionless().getValue(); SearchParameterMap params; params = new SearchParameterMap(); @@ -1018,7 +1010,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { Observation obs3 = new Observation(); obs3.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("subCodeB3"); - IIdType id3 = myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obs3, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "codeA").setModifier(TokenParamModifier.BELOW)); @@ -1040,11 +1032,11 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { Observation obsBA = new Observation(); obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA"); - IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless(); Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.BELOW)); @@ -1133,7 +1125,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); @@ -1163,7 +1155,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { AuditEvent aeOut1 = new AuditEvent(); aeOut1.getType().setSystem("http://example.com").setCode("foo"); - IIdType idOut1 = myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); + myAuditEventDao.create(aeOut1, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/audit-event-type").setModifier(TokenParamModifier.IN)); @@ -1173,6 +1165,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { params = new SearchParameterMap(); params.add(AuditEvent.SP_TYPE, new TokenParam(null, "http://hl7.org/fhir/ValueSet/v3-PurposeOfUse").setModifier(TokenParamModifier.IN)); } catch (ResourceNotFoundException e) { + //noinspection SpellCheckingInspection assertEquals("Unknown ValueSet: http%3A%2F%2Fhl7.org%2Ffhir%2FValueSet%2Fv3-PurposeOfUse", e.getMessage()); } } @@ -1191,7 +1184,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { Observation obsCA = new Observation(); obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); - IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); @@ -1208,6 +1201,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); } catch (ResourceNotFoundException e) { + //noinspection SpellCheckingInspection assertEquals("Unknown ValueSet: http%3A%2F%2Fexample.com%2Fmy_value_set", e.getMessage()); } } @@ -1217,7 +1211,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { ValueSet valueSet = new ValueSet(); valueSet.getCompose().addInclude().addValueSet("http://non_existant_VS"); valueSet.setUrl(URL_MY_VALUE_SET); - IIdType vsid = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); + IIdType vsId = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap params; @@ -1233,7 +1227,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { // Now let's update valueSet = new ValueSet(); - valueSet.setId(vsid); + valueSet.setId(vsId); valueSet.getCompose().addInclude().setSystem("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus").addConcept().setCode("A"); valueSet.setUrl(URL_MY_VALUE_SET); myValueSetDao.update(valueSet, mySrd).getId().toUnqualifiedVersionless(); @@ -1243,6 +1237,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); params.add(Observation.SP_STATUS, new TokenParam(null, "final")); } catch (ResourceNotFoundException e) { + //noinspection SpellCheckingInspection assertEquals("Unknown ValueSet: http%3A%2F%2Fnon_existant_VS", e.getMessage()); } @@ -1288,7 +1283,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { } private ArrayList toCodesContains(List theContains) { - ArrayList retVal = new ArrayList(); + ArrayList retVal = new ArrayList<>(); for (ValueSetExpansionContainsComponent next : theContains) { retVal.add(next.getCode()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index 639529df3ac..2594daff253 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.ValidationModeEnum; @@ -43,7 +43,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { @Autowired private IValidatorModule myValidatorModule; @Autowired - private IHapiTerminologySvc myTerminologySvc; + private ITermReadSvc myTerminologySvc; @Test public void testValidateStructureDefinition() throws Exception { @@ -121,7 +121,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { TermCodeSystemVersion csv = new TermCodeSystemVersion(); csv.addConcept().setCode("bar").setDisplay("Bar Code"); - myTerminologySvc.storeNewCodeSystemVersion(codeSystem, csv, mySrd, Collections.emptyList(), Collections.emptyList()); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(codeSystem, csv, mySrd, Collections.emptyList(), Collections.emptyList()); // Validate a resource containing this codesystem in a field with an extendable binding Patient patient = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java index 0f1baea4010..5e29c6b3210 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -142,7 +142,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -182,7 +182,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); assertTrue(result.isResult()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); @@ -194,7 +194,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { } @Test - public void testExpandById() throws IOException { + public void testExpandById() { String resp; ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index 012ba7a9888..7c6c843bc47 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -21,8 +21,11 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR5; +import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; +import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceProviderFactory; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5; @@ -36,7 +39,6 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; -import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; @@ -64,6 +66,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -76,6 +79,8 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { private static JpaValidationSupportChainR5 ourJpaValidationSupportChainR5; private static IFhirResourceDaoValueSet ourValueSetDao; + @Autowired + protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Autowired @Qualifier("myResourceCountsCache") protected ResourceCountCache myResourceCountsCache; @@ -288,7 +293,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @Qualifier("myTaskDaoR5") protected IFhirResourceDao myTaskDao; @Autowired - protected IHapiTerminologySvcR5 myTermSvc; + protected ITermReadSvcR5 myTermSvc; @Autowired protected PlatformTransactionManager myTransactionMgr; @Autowired @@ -322,6 +327,8 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { private DaoRegistry myDaoRegistry; @Autowired private IBulkDataExportSvc myBulkDataExportSvc; + @Autowired + protected ITermDeferredStorageSvc myTermDeferredStorageSvc; @After() public void afterCleanupDao() { @@ -343,12 +350,13 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @After public void afterClearTerminologyCaches() { - BaseHapiTerminologySvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); + BaseTermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); baseHapiTerminologySvc.clearTranslationCache(); baseHapiTerminologySvc.clearTranslationWithReverseCache(); - baseHapiTerminologySvc.clearDeferred(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationCache(); - BaseHapiTerminologySvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationCache(); + BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); + TermDeferredStorageSvcImpl deferredStorageSvc = AopTestUtils.getTargetObject(myTermDeferredStorageSvc); + deferredStorageSvc.clearDeferred(); } @After() @@ -388,9 +396,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @Before public void beforeResetConfig() { - myDaoConfig.setHardSearchLimit(1000); myDaoConfig.setHardTagListLimit(1000); - myDaoConfig.setIncludeLimit(2000); myFhirCtx.setParserErrorHandler(new StrictErrorHandler()); } @@ -409,7 +415,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { if (stream == null) { fail("Unable to load resource: " + resourceName); } - String string = IOUtils.toString(stream, "UTF-8"); + String string = IOUtils.toString(stream, StandardCharsets.UTF_8); IParser newJsonParser = EncodingEnum.detectEncodingNoDefault(string).newParser(myFhirCtx); return newJsonParser.parseResource(type, string); } @@ -434,11 +440,6 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { dao.update(resourceParsed); } - protected String loadResource(String theClasspath) throws IOException { - return IOUtils.toString(BaseJpaR5Test.class.getResourceAsStream(theClasspath), Charsets.UTF_8); - } - - @AfterClass public static void afterClassClearContextBaseJpaR5Test() { ourValueSetDao.purgeCaches(); @@ -548,8 +549,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { linkNext = linkNext.substring(linkNext.indexOf('?')); Map params = UrlUtil.parseQueryString(linkNext); String[] uuidParams = params.get(Constants.PARAM_PAGINGACTION); - String uuid = uuidParams[0]; - return uuid; + return uuidParams[0]; } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemTest.java new file mode 100644 index 00000000000..3558233dba6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemTest.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.jpa.entity; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class TermCodeSystemTest { + + @Test + public void testEquals() { + TermCodeSystem cs1 = new TermCodeSystem().setCodeSystemUri("http://foo"); + TermCodeSystem cs2 = new TermCodeSystem().setCodeSystemUri("http://foo"); + TermCodeSystem cs3 = new TermCodeSystem().setCodeSystemUri("http://foo2"); + assertEquals(cs1, cs2); + assertNotEquals(cs1, cs3); + assertNotEquals(cs1, null); + assertNotEquals(cs1, ""); + } + + @Test + public void testHashCode() { + TermCodeSystem cs = new TermCodeSystem().setCodeSystemUri("http://foo"); + assertEquals(155243497, cs.hashCode()); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersionTest.java new file mode 100644 index 00000000000..2438bf38fa6 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersionTest.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.jpa.entity; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TermCodeSystemVersionTest { + + @Test + public void testEquals() { + TermCodeSystemVersion csv1 = new TermCodeSystemVersion().setCodeSystemVersionId("1").setCodeSystemPidForUnitTest(123L); + TermCodeSystemVersion csv2 = new TermCodeSystemVersion().setCodeSystemVersionId("1").setCodeSystemPidForUnitTest(123L); + TermCodeSystemVersion csv3 = new TermCodeSystemVersion().setCodeSystemVersionId("1").setCodeSystemPidForUnitTest(124L); + assertEquals(csv1, csv2); + assertNotEquals(csv1, csv3); + assertNotEquals(csv1, null); + assertNotEquals(csv1, ""); + } + + @Test + public void testHashCode() { + TermCodeSystemVersion csv1 = new TermCodeSystemVersion().setCodeSystemVersionId("1").setCodeSystemPidForUnitTest(123L); + assertEquals(25209, csv1.hashCode()); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java index f55df8f64b2..374644e846c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemTest.java @@ -2,32 +2,27 @@ package ca.uhn.fhir.jpa.provider.dstu3; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest; -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.TestUtil; -import com.google.common.base.Charsets; -import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; -import java.io.InputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDstu3Test { - public static FhirContext ourCtx = FhirContext.forDstu3(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3CodeSystemTest.class); - private IIdType myExtensionalVsId; + public static FhirContext ourCtx = FhirContext.forDstu3(); @Before @Transactional @@ -36,12 +31,12 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst myCodeSystemDao.create(cs, mySrd); ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); - myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); + myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); } @Test public void testLookupOnExternalCode() { - ResourceProviderDstu3ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermSvc, mySrd); + ResourceProviderDstu3ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd); Parameters respParam = ourClient .operation() @@ -89,7 +84,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst @Test public void testDeleteCodeSystemComplete2() { - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); String input = "{\n" + " \"resourceType\": \"CodeSystem\",\n" + @@ -119,21 +114,15 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst // Create the code system CodeSystem cs = (CodeSystem) myFhirCtx.newJsonParser().parseResource(input); ourClient.update().resource(cs).execute(); - runInTransaction(()->{ - assertEquals(26, myConceptDao.count()); - }); + runInTransaction(() -> assertEquals(26, myConceptDao.count())); // Delete the code system ourClient.delete().resource(cs).execute(); - runInTransaction(()->{ - assertEquals(24L, myConceptDao.count()); - }); + runInTransaction(() -> assertEquals(24L, myConceptDao.count())); } - - @Test public void testLookupOperationByCodeAndSystemBuiltInCode() { Parameters respParam = ourClient @@ -193,7 +182,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue()); } @Test @@ -231,7 +220,7 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); assertEquals("abstract", respParam.getParameter().get(2).getName()); - assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue().booleanValue()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(2).getValue()).getValue()); } @Test @@ -335,14 +324,6 @@ public class ResourceProviderDstu3CodeSystemTest extends BaseResourceProviderDst ourLog.info("Encoded:\n{}", encoded); } - private String loadResource(String theFileName) throws IOException { - InputStream resourceAsStream = ResourceProviderDstu3CodeSystemTest.class.getResourceAsStream(theFileName); - if (resourceAsStream == null) { - resourceAsStream = ResourceProviderDstu3CodeSystemTest.class.getResourceAsStream(theFileName.substring(1)); - } - return IOUtils.toString(resourceAsStream, Charsets.UTF_8); - } - private T loadResource(String theFilename, Class theType) throws IOException { return ourCtx.newJsonParser().parseResource(theType, loadResource(theFilename)); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index cfdc8ea9c8e..c7a5975c6c2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -3922,7 +3922,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { { Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); } patient.setId(patientid); @@ -3930,12 +3930,12 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { ourClient.update().resource(patient).execute(); { Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); readPatient.addName().setFamily("testUpdateWithSource"); ourClient.update().resource(readPatient).execute(); readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE), matchesPattern("#[a-zA-Z0-9]+")); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index 4adc61472fb..e24b99a3298 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -8,7 +8,7 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -27,7 +27,6 @@ import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; -import org.hl7.fhir.dstu3.model.codesystems.HttpVerb; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; @@ -36,6 +35,7 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nonnull; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -48,84 +48,50 @@ import static org.junit.Assert.*; public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3ValueSetTest.class); - private IIdType myExtensionalCsId; private IIdType myExtensionalVsId; private IIdType myLocalValueSetId; - private Long myExtensionalCsIdOnResourceTable; - private Long myExtensionalVsIdOnResourceTable; private ValueSet myLocalVs; - private void loadAndPersistCodeSystemAndValueSet(HttpVerb theVerb) throws IOException { - loadAndPersistCodeSystem(theVerb); - loadAndPersistValueSet(theVerb); + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(); } - private void loadAndPersistCodeSystem(HttpVerb theVerb) throws IOException { + private void loadAndPersistCodeSystem() throws IOException { CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); codeSystem.setId("CodeSystem/cs"); - persistCodeSystem(codeSystem, theVerb); + persistCodeSystem(codeSystem); } - private void persistCodeSystem(CodeSystem theCodeSystem, HttpVerb theVerb) { - switch (theVerb) { - case POST: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - case PUT: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalCsId = myCodeSystemDao.update(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - default: - throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); - } - myExtensionalCsIdOnResourceTable = myCodeSystemDao.readEntity(myExtensionalCsId, null).getId(); + private void persistCodeSystem(CodeSystem theCodeSystem) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); } - private void loadAndPersistValueSet(HttpVerb theVerb) throws IOException { + private void loadAndPersistValueSet() throws IOException { ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); valueSet.setId("ValueSet/vs"); - persistValueSet(valueSet, theVerb); + persistValueSet(valueSet); } - private void persistValueSet(ValueSet theValueSet, HttpVerb theVerb) { - switch (theVerb) { - case POST: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - case PUT: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalVsId = myValueSetDao.update(theValueSet, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - default: - throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); - } - myExtensionalVsIdOnResourceTable = myValueSetDao.readEntity(myExtensionalVsId, null).getId(); + private void persistValueSet(ValueSet theValueSet) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + }); } private CodeSystem createExternalCs() { IFhirResourceDao codeSystemDao = myCodeSystemDao; IResourceTableDao resourceTableDao = myResourceTableDao; - IHapiTerminologySvc termSvc = myTermSvc; - return createExternalCs(codeSystemDao, resourceTableDao, termSvc, mySrd); + return createExternalCs(codeSystemDao, resourceTableDao, myTermCodeSystemStorageSvc, mySrd); } private void createExternalCsAndLocalVs() { @@ -185,7 +151,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 code.addPropertyString("HELLO", "12345-2"); cs.getConcepts().add(code); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); }); } @@ -229,7 +195,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandValueSetPropertySearchWithRegexExcludeUsingOr() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); createLoincSystemWithSomeCodes(); @@ -270,7 +236,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandValueSetPropertySearchWithRegexExcludeNoFilter() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); createLoincSystemWithSomeCodes(); @@ -299,7 +265,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandById() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -331,7 +297,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 public void testExpandByIdWithPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); Parameters respParam = ourClient @@ -362,7 +328,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandByIdWithFilter() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -388,7 +354,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 */ @Test public void testExpandByIdentifier() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -408,7 +374,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandByUrl() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -428,7 +394,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandByUrlWithBogusUrl() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); try { ourClient @@ -447,7 +413,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 public void testExpandByUrlWithPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); Parameters respParam = ourClient @@ -470,11 +436,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 public void testExpandByUrlWithPreExpansionAndBogusUrl() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); try { - Parameters respParam = ourClient + ourClient .operation() .onType(ValueSet.class) .named("expand") @@ -488,7 +454,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandByValueSet() throws IOException { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); @@ -552,7 +518,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testExpandInvalidParams() throws IOException { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); try { ourClient @@ -708,8 +674,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 HttpPost post = new HttpPost(ourServerBase + "/ValueSet/%24expand"); post.setEntity(new StringEntity(string, ContentType.parse(ca.uhn.fhir.rest.api.Constants.CT_FHIR_JSON_NEW))); - CloseableHttpResponse resp = ourHttpClient.execute(post); - try { + try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(respString); @@ -719,14 +684,12 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 assertEquals(400, resp.getStatusLine().getStatusCode()); assertThat(respString, Matchers.containsString("Unknown FilterOperator code 'n'")); - } finally { - IOUtils.closeQuietly(resp); } } @Test public void testValidateCodeOperationByCodeAndSystemInstance() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -744,7 +707,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); createLocalCs(); createLocalVsWithIncludeConcept(); @@ -769,7 +732,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 @Test public void testValidateCodeOperationByCodeAndSystemType() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -790,7 +753,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 */ @Test public void testValidateCodeAgainstBuiltInSystem() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -820,7 +783,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 */ @Test public void testValidateCodeAgainstBuiltInSystemByUrl() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -855,7 +818,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 TestUtil.clearAllStaticFieldsForUnitTest(); } - public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, IHapiTerminologySvc theTermSvc, ServletRequestDetails theRequestDetails) { + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails) { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -886,7 +849,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java deleted file mode 100644 index 007e9f63b22..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3Test.java +++ /dev/null @@ -1,231 +0,0 @@ -package ca.uhn.fhir.jpa.provider.dstu3; - -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; -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.dstu3.model.*; -import org.junit.AfterClass; -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.*; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.*; - -public class TerminologyUploaderProviderDstu3Test extends BaseResourceProviderDstu3Test { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyUploaderProviderDstu3Test.class); - - private static void addFile(ZipOutputStream theZos, String theFileName) throws IOException { - theZos.putNextEntry(new ZipEntry(theFileName)); - theZos.write(IOUtils.toByteArray(TerminologyUploaderProviderDstu3Test.class.getResourceAsStream("/loinc/" + theFileName))); - } - - public static byte[] createLoincZip() throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ZipOutputStream zos = new ZipOutputStream(bos); - - addFile(zos, LOINC_UPLOAD_PROPERTIES_FILE.getCode()); - addFile(zos, LOINC_PART_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_HIERARCHY_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_ANSWERLIST_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_ANSWERLIST_LINK_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_GROUP_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_GROUP_TERMS_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_PART_LINK_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_UNIVERSAL_LAB_ORDER_VALUESET_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_IMAGING_DOCUMENT_CODES_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE_DEFAULT.getCode()); - addFile(zos, LOINC_TOP2000_COMMON_LAB_RESULTS_US_FILE_DEFAULT.getCode()); - - zos.close(); - - - byte[] packageBytes = bos.toByteArray(); - return packageBytes; - } - - private byte[] createSctZip() throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ZipOutputStream zos = new ZipOutputStream(bos); - - List 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() - .onType(CodeSystem.class) - .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() - .onType(CodeSystem.class) - .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(1).getValue()).getValue(), greaterThan(1)); - - /* - * Try uploading a second time - */ - - respParam = ourClient - .operation() - .onType(CodeSystem.class) - .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() - .onType(CodeSystem.class) - .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() - .onType(CodeSystem.class) - .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() { - try { - ourClient - .operation() - .onType(CodeSystem.class) - .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() - .onType(CodeSystem.class) - .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(1).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() - .onType(CodeSystem.class) - .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(1).getValue()).getValue(), greaterThan(1)); - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java index 140e7d0603e..60c65528c52 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatchProviderR4Test.java @@ -371,7 +371,10 @@ public class PatchProviderR4Test extends BaseResourceProviderR4Test { .setMethod(Bundle.HTTPVerb.PATCH); HttpPost post = new HttpPost(ourServerBase); - post.setEntity(new StringEntity(myFhirCtx.newJsonParser().encodeResourceToString(input), ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX))); + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input); + ourLog.info("Encoded output: {}", encoded); + + post.setEntity(new StringEntity(encoded, ContentType.parse(Constants.CT_FHIR_JSON_NEW+ Constants.CHARSET_UTF8_CTSUFFIX))); try (CloseableHttpResponse response = ourHttpClient.execute(post)) { assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java index 05c44c1e46f..dd12eff75e4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java @@ -49,7 +49,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test @Test public void testLookupOnExternalCode() { - ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermSvc, mySrd); + ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd); Parameters respParam = ourClient .operation() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index b569dd546e3..b2b6fffd47f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -5130,7 +5130,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { { Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-zA-Z0-9]+")); } patient.setId(patientid); @@ -5138,12 +5138,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourClient.update().resource(patient).execute(); { Patient readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-zA-Z0-9]+")); readPatient.addName().setFamily("testUpdateWithSource"); ourClient.update().resource(readPatient).execute(); readPatient = (Patient) ourClient.read().resource("Patient").withId(patientid).execute(); - assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-f0-9]+")); + assertThat(readPatient.getMeta().getSource(), matchesPattern("#[a-zA-Z0-9]+")); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index 4e0e0bb530a..a6f5d44f38d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -6,7 +6,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -33,6 +33,7 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nonnull; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Optional; @@ -48,79 +49,54 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { private IIdType myExtensionalCsId; private IIdType myExtensionalVsId; private IIdType myLocalValueSetId; - private Long myExtensionalCsIdOnResourceTable; private Long myExtensionalVsIdOnResourceTable; private ValueSet myLocalVs; - private void loadAndPersistCodeSystemAndValueSet(HttpVerb theVerb) throws IOException { - loadAndPersistCodeSystem(theVerb); - loadAndPersistValueSet(theVerb); + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(); } - private void loadAndPersistCodeSystemAndValueSetWithDesignations(HttpVerb theVerb) throws IOException { - loadAndPersistCodeSystemWithDesignations(theVerb); - loadAndPersistValueSet(theVerb); + private void loadAndPersistCodeSystemAndValueSetWithDesignations() throws IOException { + loadAndPersistCodeSystemWithDesignations(); + loadAndPersistValueSet(); } - private void loadAndPersistCodeSystemAndValueSetWithDesignationsAndExclude(HttpVerb theVerb) throws IOException { - loadAndPersistCodeSystemWithDesignations(theVerb); - loadAndPersistValueSetWithExclude(theVerb); - } - - private void loadAndPersistCodeSystem(HttpVerb theVerb) throws IOException { + private void loadAndPersistCodeSystem() throws IOException { CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); codeSystem.setId("CodeSystem/cs"); - persistCodeSystem(codeSystem, theVerb); + persistCodeSystem(codeSystem); } - private void loadAndPersistCodeSystemWithDesignations(HttpVerb theVerb) throws IOException { + private void loadAndPersistCodeSystemWithDesignations() throws IOException { CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-with-designations.xml"); codeSystem.setId("CodeSystem/cs"); - persistCodeSystem(codeSystem, theVerb); + persistCodeSystem(codeSystem); } - private void persistCodeSystem(CodeSystem theCodeSystem, HttpVerb theVerb) { - switch (theVerb) { - case POST: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - case PUT: - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { - myExtensionalCsId = myCodeSystemDao.update(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); - } - }); - break; - default: - throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); - } - myExtensionalCsIdOnResourceTable = myCodeSystemDao.readEntity(myExtensionalCsId, null).getId(); + private void persistCodeSystem(CodeSystem theCodeSystem) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); + myCodeSystemDao.readEntity(myExtensionalCsId, null).getId(); } - private void loadAndPersistValueSet(HttpVerb theVerb) throws IOException { + private void loadAndPersistValueSet() throws IOException { ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); valueSet.setId("ValueSet/vs"); - persistValueSet(valueSet, theVerb); - } - - private void loadAndPersistValueSetWithExclude(HttpVerb theVerb) throws IOException { - ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs-with-exclude.xml"); - valueSet.setId("ValueSet/vs"); - persistValueSet(valueSet, theVerb); + persistValueSet(valueSet, HttpVerb.POST); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistValueSet(ValueSet theValueSet, HttpVerb theVerb) { switch (theVerb) { case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -128,7 +104,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { case PUT: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalVsId = myValueSetDao.update(theValueSet, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -142,9 +118,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { private CodeSystem createExternalCs() { IFhirResourceDao codeSystemDao = myCodeSystemDao; IResourceTableDao resourceTableDao = myResourceTableDao; - IHapiTerminologySvc termSvc = myTermSvc; - return createExternalCs(codeSystemDao, resourceTableDao, termSvc, mySrd); + return createExternalCs(codeSystemDao, resourceTableDao, myTermCodeSystemStorageSvc, mySrd); } private void createExternalCsAndLocalVs() { @@ -214,7 +189,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandById() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -246,7 +221,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testExpandByIdWithPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); Parameters respParam = ourClient @@ -277,7 +252,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandByIdWithFilter() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -298,7 +273,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testExpandByIdWithFilterWithPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); Parameters respParam = ourClient @@ -318,7 +293,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandByUrl() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -338,7 +313,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandByUrlWithBogusUrl() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); try { ourClient @@ -357,7 +332,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testExpandByUrlWithPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); Parameters respParam = ourClient @@ -380,11 +355,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testExpandByUrlWithPreExpansionAndBogusUrl() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); try { - Parameters respParam = ourClient + ourClient .operation() .onType(ValueSet.class) .named("expand") @@ -398,7 +373,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandByValueSet() throws IOException { - loadAndPersistCodeSystem(HttpVerb.POST); + loadAndPersistCodeSystem(); ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); @@ -422,7 +397,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testExpandByValueSetWithPreExpansion() throws IOException { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystem(HttpVerb.POST); + loadAndPersistCodeSystem(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); @@ -487,7 +462,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testExpandInvalidParams() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); try { ourClient @@ -653,7 +628,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { } @Test - public void testExpandValueSetBasedOnCodeSystemWithChangedUrl() throws IOException { + public void testExpandValueSetBasedOnCodeSystemWithChangedUrl() { CodeSystem cs = new CodeSystem(); cs.setId("CodeSystem/CS"); @@ -731,7 +706,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testUpdateValueSetTriggersAnotherPreExpansion() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSetWithDesignations(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSetWithDesignations(); CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); @@ -760,7 +735,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { public void testUpdateValueSetTriggersAnotherPreExpansionUsingTransactionBundle() throws Exception { myDaoConfig.setPreExpandValueSets(true); - loadAndPersistCodeSystemAndValueSetWithDesignations(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSetWithDesignations(); CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); @@ -891,7 +866,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testValidateCodeOperationByCodeAndSystemInstance() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -952,7 +927,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { @Test public void testValidateCodeOperationByCodeAndSystemType() throws Exception { - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); Parameters respParam = ourClient .operation() @@ -1049,7 +1024,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { TestUtil.clearAllStaticFieldsForUnitTest(); } - public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, IHapiTerminologySvc theTermSvc, ServletRequestDetails theRequestDetails) { + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails) { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -1078,7 +1053,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java index 350fffae75f..6076a5ec44d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/TerminologyUploaderProviderR4Test.java @@ -2,11 +2,11 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; -import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3Test; -import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; @@ -21,9 +21,9 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.*; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Test { @@ -41,8 +41,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes zos.write(IOUtils.toByteArray(getClass().getResourceAsStream("/sct/" + nextName))); } zos.close(); - byte[] packageBytes = bos.toByteArray(); - return packageBytes; + return bos.toByteArray(); } @Test @@ -54,25 +53,25 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI + "FOO")) - .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI + "FOO")) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, 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()); + assertThat(e.getMessage(), containsString("Did not find file matching concepts.csv")); } } @Test public void testUploadLoinc() throws Exception { - byte[] packageBytes = TerminologyUploaderProviderDstu3Test.createLoincZip(); + byte[] packageBytes = createLoincZip(); Parameters respParam = ourClient .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI)) - .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.LOINC_URI)) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, new Attachment().setUrl("file.zip").setData(packageBytes)) .execute(); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); @@ -89,8 +88,8 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.LOINC_URI)) - .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.LOINC_URI)) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, new Attachment().setUrl("file.zip").setData(packageBytes)) .execute(); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); @@ -100,13 +99,12 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes @Test public void testUploadMissingPackage() { - //@formatter:off try { ourClient .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI)) .execute(); fail(); } catch (InvalidRequestException e) { @@ -123,11 +121,11 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "package", new Attachment().setUrl("file.zip").setData(packageBytes)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_FILE, new Attachment().setUrl("file.zip").setData(packageBytes)) .execute(); fail(); } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Unknown URL: ", e.getMessage()); + assertThat(e.getMessage(), containsString("Missing mandatory parameter: system")); } } @@ -140,8 +138,8 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI)) - .andParameter("package", new Attachment().setUrl("file.zip").setData(packageBytes)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI)) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, new Attachment().setUrl("file.zip").setData(packageBytes)) .execute(); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); @@ -164,7 +162,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named("upload-external-code-system") - .withParameter(Parameters.class, "url", new UriType(IHapiTerminologyLoaderSvc.SCT_URI)) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI)) .andParameter("localfile", new StringType(tempFile.getAbsolutePath())) .execute(); @@ -175,34 +173,17 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes } @Test - public void testApplyDeltaAdd() { - - CodeSystem delta = new CodeSystem(); - delta.setUrl("http://example.com/labCodes"); - delta.setName("Example Hospital Lab Codes"); - delta.setStatus(Enumerations.PublicationStatus.ACTIVE); - delta.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - delta.setUrl("http://foo"); - CodeSystem.ConceptDefinitionComponent chem = delta - .addConcept() - .setCode("CHEM") - .setDisplay("Chemistry Tests"); - chem - .addConcept() - .setCode("HB") - .setDisplay("Hemoglobin"); - chem - .addConcept() - .setCode("NEUT") - .setDisplay("Neutrophil"); - CodeSystem.ConceptDefinitionComponent micro = delta - .addConcept() - .setCode("MICRO") - .setDisplay("Microbiology Tests"); - micro - .addConcept() - .setCode("C&S") - .setDisplay("Culture & Sensitivity"); + public void testApplyDeltaAdd() throws IOException { + String conceptsCsv = loadResource("/custom_term/concepts.csv"); + Attachment conceptsAttachment = new Attachment() + .setData(conceptsCsv.getBytes(Charsets.UTF_8)) + .setContentType("text/csv") + .setUrl("file:/foo/concepts.csv"); + String hierarchyCsv = loadResource("/custom_term/hierarchy.csv"); + Attachment hierarchyAttachment = new Attachment() + .setData(hierarchyCsv.getBytes(Charsets.UTF_8)) + .setContentType("text/csv") + .setUrl("file:/foo/hierarchy.csv"); LoggingInterceptor interceptor = new LoggingInterceptor(true); ourClient.registerInterceptor(interceptor); @@ -210,7 +191,104 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes .operation() .onType(CodeSystem.class) .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) - .withParameter(Parameters.class, TerminologyUploaderProvider.VALUE, delta) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType("http://foo/cs")) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, conceptsAttachment) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, hierarchyAttachment) + .prettyPrint() + .execute(); + ourClient.unregisterInterceptor(interceptor); + + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); + ourLog.info(encoded); + assertThat(encoded, stringContainsInOrder( + "\"name\": \"conceptCount\"", + "\"valueInteger\": 5", + "\"name\": \"target\"", + "\"reference\": \"CodeSystem/" + )); + } + + @Test + public void testApplyDeltaAdd_MissingSystem() throws IOException { + String conceptsCsv = loadResource("/custom_term/concepts.csv"); + Attachment conceptsAttachment = new Attachment() + .setData(conceptsCsv.getBytes(Charsets.UTF_8)) + .setContentType("text/csv") + .setUrl("file:/foo/concepts.csv"); + + LoggingInterceptor interceptor = new LoggingInterceptor(true); + ourClient.registerInterceptor(interceptor); + + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_FILE, conceptsAttachment) + .prettyPrint() + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Missing mandatory parameter: system")); + } + ourClient.unregisterInterceptor(interceptor); + + } + + @Test + public void testApplyDeltaAdd_MissingFile() { + LoggingInterceptor interceptor = new LoggingInterceptor(true); + ourClient.registerInterceptor(interceptor); + + try { + ourClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType("http://foo/cs")) + .prettyPrint() + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Missing mandatory parameter: file")); + } + ourClient.unregisterInterceptor(interceptor); + } + + @Test + public void testApplyDeltaRemove() throws IOException { + String conceptsCsv = loadResource("/custom_term/concepts.csv"); + Attachment conceptsAttachment = new Attachment() + .setData(conceptsCsv.getBytes(Charsets.UTF_8)) + .setContentType("text/csv") + .setUrl("file:/foo/concepts.csv"); + String hierarchyCsv = loadResource("/custom_term/hierarchy.csv"); + Attachment hierarchyAttachment = new Attachment() + .setData(hierarchyCsv.getBytes(Charsets.UTF_8)) + .setContentType("text/csv") + .setUrl("file:/foo/hierarchy.csv"); + + // Add the codes + ourClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType("http://foo/cs")) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, conceptsAttachment) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, hierarchyAttachment) + .prettyPrint() + .execute(); + + // And remove them + LoggingInterceptor interceptor = new LoggingInterceptor(true); + ourClient.registerInterceptor(interceptor); + Parameters outcome = ourClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE) + .withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType("http://foo/cs")) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, conceptsAttachment) + .andParameter(TerminologyUploaderProvider.PARAM_FILE, hierarchyAttachment) .prettyPrint() .execute(); ourClient.unregisterInterceptor(interceptor); @@ -220,50 +298,42 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes assertThat(encoded, containsString("\"valueInteger\": 5")); } - @Test - public void testApplyDeltaRemove() { - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - ourClient.create().resource(cs).execute(); - - CodeSystem delta = new CodeSystem(); - delta.setUrl("http://foo"); - delta - .addConcept() - .setCode("codeA") - .setDisplay("displayA"); - - // Add - ourClient - .operation() - .onType(CodeSystem.class) - .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD) - .withParameter(Parameters.class, TerminologyUploaderProvider.VALUE, delta) - .prettyPrint() - .execute(); - - // Remove - LoggingInterceptor interceptor = new LoggingInterceptor(true); - ourClient.registerInterceptor(interceptor); - Parameters outcome = ourClient - .operation() - .onType(CodeSystem.class) - .named(JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE) - .withParameter(Parameters.class, TerminologyUploaderProvider.VALUE, delta) - .prettyPrint() - .execute(); - ourClient.unregisterInterceptor(interceptor); - - String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); - ourLog.info(encoded); - assertThat(encoded, containsString("\"valueInteger\": 1")); - } - @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } + private static void addFile(ZipOutputStream theZos, String theFileName) throws IOException { + theZos.putNextEntry(new ZipEntry(theFileName)); + theZos.write(IOUtils.toByteArray(TerminologyUploaderProviderR4Test.class.getResourceAsStream("/loinc/" + theFileName))); + } + + public static byte[] createLoincZip() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(bos); + + addFile(zos, LOINC_UPLOAD_PROPERTIES_FILE.getCode()); + addFile(zos, LOINC_PART_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_HIERARCHY_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_ANSWERLIST_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_ANSWERLIST_LINK_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_GROUP_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_GROUP_TERMS_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_PART_LINK_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_PART_RELATED_CODE_MAPPING_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_DOCUMENT_ONTOLOGY_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_RSNA_PLAYBOOK_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_UNIVERSAL_LAB_ORDER_VALUESET_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_IEEE_MEDICAL_DEVICE_CODE_MAPPING_TABLE_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_IMAGING_DOCUMENT_CODES_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_TOP2000_COMMON_LAB_RESULTS_SI_FILE_DEFAULT.getCode()); + addFile(zos, LOINC_TOP2000_COMMON_LAB_RESULTS_US_FILE_DEFAULT.getCode()); + + zos.close(); + + + return bos.toByteArray(); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index efcb90c94fe..1e4f77dab77 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -6,8 +6,8 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.r5.BaseResourceProviderR5Test; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -80,6 +80,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { persistCodeSystem(codeSystem, theVerb); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistCodeSystem(CodeSystem theCodeSystem, HttpVerb theVerb) { switch (theVerb) { case POST: @@ -116,6 +117,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { persistValueSet(valueSet, theVerb); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistValueSet(ValueSet theValueSet, HttpVerb theVerb) { switch (theVerb) { case POST: @@ -143,9 +145,9 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { private CodeSystem createExternalCs() { IFhirResourceDao codeSystemDao = myCodeSystemDao; IResourceTableDao resourceTableDao = myResourceTableDao; - IHapiTerminologySvc termSvc = myTermSvc; + ITermReadSvc termSvc = myTermSvc; - return createExternalCs(codeSystemDao, resourceTableDao, termSvc, mySrd); + return createExternalCs(codeSystemDao, resourceTableDao, myTermCodeSystemStorageSvc, mySrd); } private void createExternalCsAndLocalVs() { @@ -1032,7 +1034,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { TestUtil.clearAllStaticFieldsForUnitTest(); } - public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, IHapiTerminologySvc theTermSvc, ServletRequestDetails theRequestDetails) { + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails) { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -1061,7 +1063,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java index 8b8353a262f..be87d4f48d8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; 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.ResourceGoneException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -39,6 +40,8 @@ import org.springframework.transaction.TransactionStatus; import javax.persistence.EntityManager; import java.util.*; +import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -97,14 +100,14 @@ public class SearchCoordinatorSvcImplTest { when(myTxManager.getTransaction(any())).thenReturn(mock(TransactionStatus.class)); doAnswer(theInvocation -> { - PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) theInvocation.getArguments()[0]; - provider.setSearchCoordinatorSvc(mySvc); - provider.setPlatformTransactionManager(myTxManager); - provider.setSearchCacheSvc(mySearchCacheSvc); - provider.setEntityManager(myEntityManager); - provider.setContext(ourCtx); - provider.setInterceptorBroadcaster(myInterceptorBroadcaster); - return null; + PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) theInvocation.getArguments()[0]; + provider.setSearchCoordinatorSvc(mySvc); + provider.setPlatformTransactionManager(myTxManager); + provider.setSearchCacheSvc(mySearchCacheSvc); + provider.setEntityManager(myEntityManager); + provider.setContext(ourCtx); + provider.setInterceptorBroadcaster(myInterceptorBroadcaster); + return null; }).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class)); } @@ -118,14 +121,14 @@ public class SearchCoordinatorSvcImplTest { private Answer loadPids() { return theInvocation -> { - List pids = (List) theInvocation.getArguments()[0]; + List pids = (List) theInvocation.getArguments()[0]; List resources = (List) theInvocation.getArguments()[2]; - for (Long nextPid : pids) { - Patient pt = new Patient(); - pt.setId(nextPid.toString()); - resources.add(pt); - } - return null; + for (Long nextPid : pids) { + Patient pt = new Patient(); + pt.setId(nextPid.toString()); + resources.add(pt); + } + return null; }; } @@ -153,14 +156,14 @@ public class SearchCoordinatorSvcImplTest { @Test public void testAsyncSearchLargeResultSetBigCountSameCoordinator() { List allResults = new ArrayList<>(); - doAnswer(t->{ + doAnswer(t -> { List oldResults = t.getArgument(1, List.class); List newResults = t.getArgument(2, List.class); ourLog.info("Saving {} new results - have {} old results", newResults.size(), oldResults.size()); assertEquals(allResults.size(), oldResults.size()); allResults.addAll(newResults); return null; - }).when(mySearchResultCacheSvc).storeResults(any(),anyList(),anyList()); + }).when(mySearchResultCacheSvc).storeResults(any(), anyList(), anyList()); SearchParameterMap params = new SearchParameterMap(); @@ -169,7 +172,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(10, 800); SlowIterator iter = new SlowIterator(pids.iterator(), 1); when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt())).thenAnswer(t -> { List returnedValues = iter.getReturnedValues(); @@ -224,6 +227,51 @@ public class SearchCoordinatorSvcImplTest { myExpectedNumberOfSearchBuildersCreated = 4; } + + @Test + public void testFetchResourcesWhereSearchIsDeletedPartWayThroughProcessing() { + + myCurrentSearch = new Search(); + myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET); + myCurrentSearch.setNumFound(10); + + when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> Optional.ofNullable(myCurrentSearch)); + + when(mySearchCacheSvc.tryToMarkSearchAsInProgress(any())).thenAnswer(t -> { + when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t2 -> Optional.empty()); + return Optional.empty(); + }); + + try { + mySvc.getResources("1234-5678", 0, 100, null); + fail(); + } catch (ResourceGoneException e) { + assertEquals("Search ID \"1234-5678\" does not exist and may have expired", e.getMessage()); + } + } + + @Test + public void testFetchResourcesTimesOut() { + + mySvc.setMaxMillisToWaitForRemoteResultsForUnitTest(10); + + myCurrentSearch = new Search(); + myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET); + myCurrentSearch.setNumFound(10); + + when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> { + sleepAtLeast(100); + return Optional.ofNullable(myCurrentSearch); + }); + + try { + mySvc.getResources("1234-5678", 0, 100, null); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), containsString("Request timed out")); + } + } + @Test public void testAsyncSearchLargeResultSetSameCoordinator() { SearchParameterMap params = new SearchParameterMap(); @@ -233,7 +281,7 @@ public class SearchCoordinatorSvcImplTest { SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); @@ -261,7 +309,7 @@ public class SearchCoordinatorSvcImplTest { IResultIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchCacheSvc.save(any())).thenAnswer(t -> t.getArguments()[0]); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); @@ -306,7 +354,7 @@ public class SearchCoordinatorSvcImplTest { SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); @@ -336,17 +384,17 @@ public class SearchCoordinatorSvcImplTest { search.setResourceType("Patient"); when(mySearchCacheSvc.fetchByUuid(eq(uuid))).thenReturn(Optional.of(search)); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); PersistedJpaBundleProvider provider; List resources; new Thread(() -> { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // ignore - } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } when(mySearchResultCacheSvc.fetchResultPids(any(Search.class), anyInt(), anyInt())).thenAnswer(theInvocation -> { ArrayList results = new ArrayList<>(); @@ -355,8 +403,8 @@ public class SearchCoordinatorSvcImplTest { } return results; - }); - search.setStatus(SearchStatusEnum.FINISHED); + }); + search.setStatus(SearchStatusEnum.FINISHED); }).start(); /* @@ -387,7 +435,7 @@ public class SearchCoordinatorSvcImplTest { List pids = createPidSequence(10, 800); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator())); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNull(result.getUuid()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImplTest.java new file mode 100644 index 00000000000..425d59701be --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchCacheSvcImplTest.java @@ -0,0 +1,78 @@ +package ca.uhn.fhir.jpa.search.cache; + +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import org.hibernate.HibernateException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.transaction.PlatformTransactionManager; + +import java.util.Optional; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class DatabaseSearchCacheSvcImplTest { + + private DatabaseSearchCacheSvcImpl mySvc; + + @Mock + private ISearchDao mySearchDao; + + @Mock + private PlatformTransactionManager myTxManager; + + @Before + public void before() { + mySvc = new DatabaseSearchCacheSvcImpl(); + mySvc.setSearchDaoForUnitTest(mySearchDao); + mySvc.setTxManagerForUnitTest(myTxManager); + } + + @Test + public void tryToMarkSearchAsInProgressSuccess() { + Search updated = new Search(); + updated.setStatus(SearchStatusEnum.PASSCMPLET); + when(mySearchDao.findById(any())).thenReturn(Optional.of(updated)); + when(mySearchDao.save(any())).thenReturn(updated); + + Search search = new Search(); + Optional outcome = mySvc.tryToMarkSearchAsInProgress(search); + assertTrue(outcome.isPresent()); + + verify(mySearchDao, times(1)).save(any()); + assertEquals(SearchStatusEnum.LOADING, updated.getStatus()); + } + + @Test + public void tryToMarkSearchAsInProgressFail() { + Search updated = new Search(); + updated.setStatus(SearchStatusEnum.PASSCMPLET); + when(mySearchDao.findById(any())).thenReturn(Optional.of(updated)); + when(mySearchDao.save(any())).thenThrow(new HibernateException("FOO")); + + Search search = new Search(); + Optional outcome = mySvc.tryToMarkSearchAsInProgress(search); + assertFalse(outcome.isPresent()); + verify(mySearchDao, times(1)).save(any()); + } + + @Test + public void tryToMarkSearchAsInProgressAlreadyLoading() { + Search updated = new Search(); + updated.setStatus(SearchStatusEnum.LOADING); + when(mySearchDao.findById(any())).thenReturn(Optional.of(updated)); + + Search search = new Search(); + Optional outcome = mySvc.tryToMarkSearchAsInProgress(search); + assertFalse(outcome.isPresent()); + verify(mySearchDao, never()).save(any()); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseLoaderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseLoaderTest.java index 114048fc269..99d41a1f3c7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseLoaderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseLoaderTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.jpa.BaseTest; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -17,7 +18,7 @@ import java.util.List; import java.util.Map; @RunWith(MockitoJUnitRunner.class) -abstract class BaseLoaderTest { +abstract class BaseLoaderTest extends BaseTest { @Mock protected RequestDetails mySrd; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcCustomTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcCustomTest.java index 9724fb57b1a..2dbedd188dd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcCustomTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcCustomTest.java @@ -1,51 +1,62 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.IdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.io.IOException; import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class TerminologyLoaderSvcCustomTest extends BaseLoaderTest { - private TerminologyLoaderSvcImpl mySvc; + private TermLoaderSvcImpl mySvc; @Mock - private IHapiTerminologySvc myTermSvc; - + private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; private ZipCollectionBuilder myFiles; - + @Captor + private ArgumentCaptor myCustomTerminologySetCaptor; + @Mock + private ITermDeferredStorageSvc myTermDeferredStorageSvc; @Before public void before() { - mySvc = new TerminologyLoaderSvcImpl(); - mySvc.setTermSvcForUnitTests(myTermSvc); + mySvc = new TermLoaderSvcImpl(); + mySvc.setTermCodeSystemStorageSvcForUnitTests(myTermCodeSystemStorageSvc); + mySvc.setTermDeferredStorageSvc(myTermDeferredStorageSvc); myFiles = new ZipCollectionBuilder(); } @Test public void testLoadComplete() throws Exception { - myFiles.addFileZip("/custom_term/", TerminologyLoaderSvcImpl.CUSTOM_CODESYSTEM_JSON); - myFiles.addFileZip("/custom_term/", TerminologyLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); - myFiles.addFileZip("/custom_term/", TerminologyLoaderSvcImpl.CUSTOM_HIERARCHY_FILE); + myFiles.addFileZip("/custom_term/", TermLoaderSvcImpl.CUSTOM_CODESYSTEM_JSON); + myFiles.addFileZip("/custom_term/", TermLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); + myFiles.addFileZip("/custom_term/", TermLoaderSvcImpl.CUSTOM_HIERARCHY_FILE); // Actually do the load mySvc.loadCustom("http://example.com/labCodes", myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); // Verify codesystem @@ -70,12 +81,12 @@ public class TerminologyLoaderSvcCustomTest extends BaseLoaderTest { @Test public void testLoadWithNoCodeSystem() throws Exception { - myFiles.addFileZip("/custom_term/", TerminologyLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); + myFiles.addFileZip("/custom_term/", TermLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); // Actually do the load mySvc.loadCustom("http://example.com/labCodes", myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); // Verify codesystem @@ -89,12 +100,12 @@ public class TerminologyLoaderSvcCustomTest extends BaseLoaderTest { */ @Test public void testLoadCodesOnly() throws Exception { - myFiles.addFileZip("/custom_term/", TerminologyLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); + myFiles.addFileZip("/custom_term/", TermLoaderSvcImpl.CUSTOM_CONCEPTS_FILE); // Actually do the load mySvc.loadCustom("http://example.com/labCodes", myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); TermConcept code; @@ -107,6 +118,67 @@ public class TerminologyLoaderSvcCustomTest extends BaseLoaderTest { } + @Test + public void testDeltaAdd() throws IOException { + + myFiles.addFileText(loadResource("/custom_term/concepts.csv"), "concepts.csv"); + myFiles.addFileText(loadResource("/custom_term/hierarchy.csv"), "hierarchy.csv"); + + UploadStatistics stats = new UploadStatistics(100, new IdType("CodeSystem/100")); + when(myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd(eq("http://foo/system"), any())).thenReturn(stats); + + UploadStatistics outcome = mySvc.loadDeltaAdd("http://foo/system", myFiles.getFiles(), mySrd); + assertSame(stats, outcome); + + verify(myTermCodeSystemStorageSvc, times(1)).applyDeltaCodeSystemsAdd(eq("http://foo/system"), myCustomTerminologySetCaptor.capture()); + CustomTerminologySet set = myCustomTerminologySetCaptor.getValue(); + + // Root concepts + assertEquals(2, set.getRootConcepts().size()); + assertEquals("CHEM", set.getRootConcepts().get(0).getCode()); + assertEquals("Chemistry", set.getRootConcepts().get(0).getDisplay()); + assertEquals("MICRO", set.getRootConcepts().get(1).getCode()); + assertEquals("Microbiology", set.getRootConcepts().get(1).getDisplay()); + + // Child concepts + assertEquals(2, set.getRootConcepts().get(0).getChildren().size()); + assertEquals("HB", set.getRootConcepts().get(0).getChildren().get(0).getChild().getCode()); + assertEquals("Hemoglobin", set.getRootConcepts().get(0).getChildren().get(0).getChild().getDisplay()); + assertEquals(null, set.getRootConcepts().get(0).getChildren().get(0).getChild().getSequence()); + assertEquals("NEUT", set.getRootConcepts().get(0).getChildren().get(1).getChild().getCode()); + assertEquals("Neutrophils", set.getRootConcepts().get(0).getChildren().get(1).getChild().getDisplay()); + + } + + @Test + public void testDeltaRemove() throws IOException { + + myFiles.addFileText(loadResource("/custom_term/concepts.csv"), "concepts.csv"); + + // Hierarchy should be ignored for remove, but we'll add one just + // to make sure it's ignored.. + myFiles.addFileText(loadResource("/custom_term/hierarchy.csv"), "hierarchy.csv"); + + UploadStatistics stats = new UploadStatistics(100, new IdType("CodeSystem/100")); + when(myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove(eq("http://foo/system"), any())).thenReturn(stats); + + UploadStatistics outcome = mySvc.loadDeltaRemove("http://foo/system", myFiles.getFiles(), mySrd); + assertSame(stats, outcome); + + verify(myTermCodeSystemStorageSvc, times(1)).applyDeltaCodeSystemsRemove(eq("http://foo/system"), myCustomTerminologySetCaptor.capture()); + CustomTerminologySet set = myCustomTerminologySetCaptor.getValue(); + + // Root concepts + assertEquals(5, set.getRootConcepts().size()); + assertEquals("CHEM", set.getRootConcepts().get(0).getCode()); + assertEquals("Chemistry", set.getRootConcepts().get(0).getDisplay()); + assertEquals("HB", set.getRootConcepts().get(1).getCode()); + assertEquals("Hemoglobin", set.getRootConcepts().get(1).getDisplay()); + assertEquals("NEUT", set.getRootConcepts().get(2).getCode()); + assertEquals("Neutrophils", set.getRootConcepts().get(2).getDisplay()); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImgthlaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImgthlaTest.java index 81d8f16661c..21e61ab0686 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImgthlaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImgthlaTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -16,18 +17,18 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class TerminologyLoaderSvcImgthlaTest extends BaseLoaderTest { - private TerminologyLoaderSvcImpl mySvc; + private TermLoaderSvcImpl mySvc; @Mock - private IHapiTerminologySvc myTermSvc; + private ITermCodeSystemStorageSvc myTermStorageSvc; private ZipCollectionBuilder myFiles; @Before public void before() { - mySvc = new TerminologyLoaderSvcImpl(); - mySvc.setTermSvcForUnitTests(myTermSvc); + mySvc = new TermLoaderSvcImpl(); + mySvc.setTermCodeSystemStorageSvcForUnitTests(myTermStorageSvc); myFiles = new ZipCollectionBuilder(); } @@ -78,8 +79,8 @@ public class TerminologyLoaderSvcImgthlaTest extends BaseLoaderTest { public static void addImgthlaMandatoryFilesToZip(ZipCollectionBuilder theFiles) throws IOException { - theFiles.addFileZip("/imgthla/", TerminologyLoaderSvcImpl.IMGTHLA_HLA_NOM_TXT); - theFiles.addFileZip("/imgthla/", TerminologyLoaderSvcImpl.IMGTHLA_HLA_XML); + theFiles.addFileZip("/imgthla/", TermLoaderSvcImpl.IMGTHLA_HLA_NOM_TXT); + theFiles.addFileZip("/imgthla/", TermLoaderSvcImpl.IMGTHLA_HLA_XML); } @AfterClass diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java index f3c0bcb8e1d..6ef7afe5c59 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Lists; import org.hl7.fhir.dstu3.model.*; @@ -31,7 +32,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { private static final Logger ourLog = LoggerFactory.getLogger(TerminologyLoaderSvcIntegrationDstu3Test.class); @Autowired - private IHapiTerminologyLoaderSvc myLoader; + private ITermLoaderSvc myLoader; @After public void after() { @@ -43,6 +44,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { myDaoConfig.setDeferIndexingForCodesystemsOfSize(20000); } + @SuppressWarnings("unchecked") private Optional findProperty(Parameters theParameters, String thePropertyName) { return theParameters .getParameter() @@ -64,7 +66,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { input .getCompose() .addInclude() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addFilter() .setProperty("SCALE_TYP") .setOp(ValueSet.FilterOperator.EQUAL) @@ -80,7 +82,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { input .getCompose() .addInclude() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addFilter() .setProperty("SCALE_TYP") .setOp(ValueSet.FilterOperator.EQUAL) @@ -95,7 +97,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { input .getCompose() .addInclude() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addFilter() .setProperty("SCALE_TYP") .setOp(ValueSet.FilterOperator.EQUAL) @@ -112,7 +114,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); runInTransaction(() -> { await().until(() -> myTermConceptMapDao.count(), greaterThan(0L)); @@ -129,7 +131,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { input .getCompose() .addInclude() - .setSystem(IHapiTerminologyLoaderSvc.LOINC_URI) + .setSystem(ITermLoaderSvc.LOINC_URI) .addFilter() .setProperty("CLASS") .setOp(ValueSet.FilterOperator.EQUAL) @@ -147,14 +149,14 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, mySrd); + IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); Optional propertyValue = findProperty(parameters, "SCALE_TYP"); assertTrue(propertyValue.isPresent()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, propertyValue.get().getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, propertyValue.get().getSystem()); assertEquals("LP7753-9", propertyValue.get().getCode()); assertEquals("Qn", propertyValue.get().getDisplay()); @@ -177,14 +179,14 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("17788-1"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, mySrd); + IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("17788-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, null); ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters)); Optional propertyValue = findProperty(parameters, "COMPONENT"); assertTrue(propertyValue.isPresent()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, propertyValue.get().getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, propertyValue.get().getSystem()); assertEquals("LP19258-0", propertyValue.get().getCode()); assertEquals("Large unstained cells/100 leukocytes", propertyValue.get().getDisplay()); } @@ -195,7 +197,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, mySrd); + IContextValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, mySrd); List> properties = Lists.newArrayList(new CodeType("SCALE_TYP")); Parameters parameters = (Parameters) result.toParameters(myFhirCtx, properties); @@ -203,7 +205,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { Optional propertyValueCoding = findProperty(parameters, "SCALE_TYP"); assertTrue(propertyValueCoding.isPresent()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, propertyValueCoding.get().getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, propertyValueCoding.get().getSystem()); assertEquals("LP7753-9", propertyValueCoding.get().getCode()); assertEquals("Qn", propertyValueCoding.get().getDisplay()); @@ -218,7 +220,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, null, null, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); assertTrue(result.isResult()); assertEquals("Found code", result.getMessage()); @@ -230,7 +232,7 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); myLoader.loadLoinc(files.getFiles(), mySrd); - IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1-9999999999"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, null, null, mySrd); + IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); assertFalse(result.isResult()); assertEquals("Code not found", result.getMessage()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java index 523c770757d..1cef209f864 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java @@ -3,6 +3,9 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptProperty; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.loinc.*; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; @@ -33,20 +36,22 @@ import static org.mockito.Mockito.verify; public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcLoincTest.class); - private TerminologyLoaderSvcImpl mySvc; + private TermLoaderSvcImpl mySvc; @Mock - private IHapiTerminologySvc myTermSvc; + private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Captor private ArgumentCaptor mySystemCaptor; - private ZipCollectionBuilder myFiles; + @Mock + private ITermDeferredStorageSvc myTermDeferredStorageSvc; @Before public void before() { - mySvc = new TerminologyLoaderSvcImpl(); - mySvc.setTermSvcForUnitTests(myTermSvc); + mySvc = new TermLoaderSvcImpl(); + mySvc.setTermCodeSystemStorageSvcForUnitTests(myTermCodeSystemStorageSvc); + mySvc.setTermDeferredStorageSvc(myTermDeferredStorageSvc); myFiles = new ZipCollectionBuilder(); } @@ -58,7 +63,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Actually do the load mySvc.loadLoinc(myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); Map valueSets = extractValueSets(); Map conceptMaps = extractConceptMaps(); @@ -71,7 +76,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Normal LOINC code code = concepts.get("10013-1"); assertEquals("10013-1", code.getCode()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode()); assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay()); assertEquals("EKG.MEAS", code.getStringProperty("CLASS")); @@ -117,7 +122,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals("http://loinc.org/vs/LL1001-8", vs.getUrl()); assertEquals(1, vs.getCompose().getInclude().size()); assertEquals(7, vs.getCompose().getInclude().get(0).getConcept().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals("LA6270-8", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("Never", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -139,8 +144,8 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals("Beta.1", conceptMap.getVersion()); assertEquals(1, conceptMap.getGroup().size()); group = conceptMap.getGroup().get(0); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, group.getSource()); - assertEquals(IHapiTerminologyLoaderSvc.SCT_URI, group.getTarget()); + assertEquals(ITermLoaderSvc.LOINC_URI, group.getSource()); + assertEquals(ITermLoaderSvc.SCT_URI, group.getTarget()); assertEquals("http://snomed.info/sct/900000000000207008/version/20170731", group.getTargetVersion()); assertEquals("LP18172-4", group.getElement().get(0).getCode()); assertEquals("Interferon.beta", group.getElement().get(0).getDisplay()); @@ -153,7 +158,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(LoincDocumentOntologyHandler.DOCUMENT_ONTOLOGY_CODES_VS_NAME, vs.getName()); assertEquals(LoincDocumentOntologyHandler.DOCUMENT_ONTOLOGY_CODES_VS_URI, vs.getUrl()); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals(3, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals("11488-4", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("Consult note", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -161,7 +166,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Document ontology parts code = concepts.get("11488-4"); assertEquals(1, code.getCodingProperties("document-kind").size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties("document-kind").get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("document-kind").get(0).getSystem()); assertEquals("LP173418-7", code.getCodingProperties("document-kind").get(0).getCode()); assertEquals("Note", code.getCodingProperties("document-kind").get(0).getDisplay()); @@ -171,7 +176,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(LoincRsnaPlaybookHandler.RSNA_CODES_VS_URI, vs.getUrl()); assertEquals(1, vs.getCompose().getInclude().size()); assertEquals(3, vs.getCompose().getInclude().get(0).getConcept().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals("17787-3", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("NM Thyroid gland Study report", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -179,21 +184,21 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { code = concepts.get("17787-3"); String propertyName = "rad-anatomic-location-region-imaged"; assertEquals(1, code.getCodingProperties(propertyName).size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); assertEquals("LP199995-4", code.getCodingProperties(propertyName).get(0).getCode()); assertEquals("Neck", code.getCodingProperties(propertyName).get(0).getDisplay()); // RSNA Playbook Code Parts - Imaging Focus code = concepts.get("17787-3"); propertyName = "rad-anatomic-location-imaging-focus"; assertEquals(1, code.getCodingProperties(propertyName).size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); assertEquals("LP206648-0", code.getCodingProperties(propertyName).get(0).getCode()); assertEquals("Thyroid gland", code.getCodingProperties(propertyName).get(0).getDisplay()); // RSNA Playbook Code Parts - Modality Type code = concepts.get("17787-3"); propertyName = "rad-modality-modality-type"; assertEquals(1, code.getCodingProperties(propertyName).size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties(propertyName).get(0).getSystem()); assertEquals("LP208891-4", code.getCodingProperties(propertyName).get(0).getCode()); assertEquals("NM", code.getCodingProperties(propertyName).get(0).getDisplay()); @@ -204,7 +209,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(1, conceptMap.getGroup().size()); group = conceptMap.getGroupFirstRep(); // all entries have the same source and target so these should be null - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, group.getSource()); + assertEquals(ITermLoaderSvc.LOINC_URI, group.getSource()); assertEquals(LoincRsnaPlaybookHandler.RID_CS_URI, group.getTarget()); assertEquals("LP199995-4", group.getElement().get(0).getCode()); assertEquals("Neck", group.getElement().get(0).getDisplay()); @@ -220,7 +225,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(1, conceptMap.getGroup().size()); group = conceptMap.getGroupFirstRep(); // all entries have the same source and target so these should be null - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, group.getSource()); + assertEquals(ITermLoaderSvc.LOINC_URI, group.getSource()); assertEquals(LoincRsnaPlaybookHandler.RPID_CS_URI, group.getTarget()); assertEquals("24531-6", group.getElement().get(0).getCode()); assertEquals("US Retroperitoneum", group.getElement().get(0).getDisplay()); @@ -234,7 +239,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(vs.getName(), LoincTop2000LabResultsUsHandler.TOP_2000_US_VS_NAME); assertEquals(vs.getUrl(), LoincTop2000LabResultsUsHandler.TOP_2000_US_VS_URI); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals(9, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals("2160-0", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("Creatinine [Mass/volume] in Serum or Plasma", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -246,7 +251,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(vs.getName(), LoincTop2000LabResultsSiHandler.TOP_2000_SI_VS_NAME); assertEquals(vs.getUrl(), LoincTop2000LabResultsSiHandler.TOP_2000_SI_VS_URI); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals(9, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals("14682-9", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("Creatinine [Moles/volume] in Serum or Plasma", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -256,7 +261,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Universal lab order VS vs = valueSets.get(LoincUniversalOrderSetHandler.VS_ID); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals(9, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals("42176-8", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("1,3 beta glucan [Mass/volume] in Serum", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -275,7 +280,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertTrue(vs.hasCompose()); assertTrue(vs.getCompose().hasInclude()); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); // IEEE Medical Device Codes conceptMap = conceptMaps.get(LoincIeeeMedicalDeviceCodeHandler.LOINC_IEEE_CM_ID); @@ -283,8 +288,8 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(LoincIeeeMedicalDeviceCodeHandler.LOINC_IEEE_CM_NAME, conceptMap.getName()); assertEquals(LoincIeeeMedicalDeviceCodeHandler.LOINC_IEEE_CM_URI, conceptMap.getUrl()); assertEquals(1, conceptMap.getGroup().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, conceptMap.getGroup().get(0).getSource()); - assertEquals(IHapiTerminologyLoaderSvc.IEEE_11073_10101_URI, conceptMap.getGroup().get(0).getTarget()); + assertEquals(ITermLoaderSvc.LOINC_URI, conceptMap.getGroup().get(0).getSource()); + assertEquals(ITermLoaderSvc.IEEE_11073_10101_URI, conceptMap.getGroup().get(0).getTarget()); assertEquals(7, conceptMap.getGroup().get(0).getElement().size()); assertEquals("14749-6", conceptMap.getGroup().get(0).getElement().get(4).getCode()); assertEquals("Glucose [Moles/volume] in Serum or Plasma", conceptMap.getGroup().get(0).getElement().get(4).getDisplay()); @@ -297,7 +302,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals(LoincImagingDocumentCodeHandler.VS_URI, vs.getUrl()); assertEquals(LoincImagingDocumentCodeHandler.VS_NAME, vs.getName()); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, vs.getCompose().getInclude().get(0).getSystem()); assertEquals(9, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals("11525-3", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("US Pelvis Fetus for pregnancy", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); @@ -330,7 +335,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Actually do the load mySvc.loadLoinc(myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); Map valueSets = extractValueSets(); Map conceptMaps = extractConceptMaps(); @@ -392,7 +397,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Actually do the load mySvc.loadLoinc(myFiles.getFiles(), mySrd); - verify(myTermSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); Map concepts = extractConcepts(); Map valueSets = extractValueSets(); Map conceptMaps = extractConceptMaps(); @@ -405,7 +410,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { // Normal LOINC code code = concepts.get("10013-1"); assertEquals("10013-1", code.getCode()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, code.getCodingProperties("PROPERTY").get(0).getSystem()); assertEquals("LP6802-5", code.getCodingProperties("PROPERTY").get(0).getCode()); assertEquals("Elpot", code.getCodingProperties("PROPERTY").get(0).getDisplay()); assertEquals("EKG.MEAS", code.getStringProperty("CLASS")); @@ -417,7 +422,7 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { List properties = new ArrayList<>(code.getProperties()); assertEquals(1, properties.size()); assertEquals("child", properties.get(0).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); assertEquals("LP14559-6", properties.get(0).getValue()); assertEquals("Microorganism", properties.get(0).getDisplay()); assertEquals(0, code.getParents().size()); @@ -430,11 +435,11 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { properties = new ArrayList<>(childCode.getProperties()); assertEquals(2, properties.size()); assertEquals("parent", properties.get(0).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); assertEquals(code.getCode(), properties.get(0).getValue()); assertEquals(code.getDisplay(), properties.get(0).getDisplay()); assertEquals("child", properties.get(1).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); assertEquals("LP98185-9", properties.get(1).getValue()); assertEquals("Bacteria", properties.get(1).getDisplay()); assertEquals(1, childCode.getParents().size()); @@ -448,11 +453,11 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { properties = new ArrayList<>(nestedChildCode.getProperties()); assertEquals(2, properties.size()); assertEquals("parent", properties.get(0).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); assertEquals(childCode.getCode(), properties.get(0).getValue()); assertEquals(childCode.getDisplay(), properties.get(0).getDisplay()); assertEquals("child", properties.get(1).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); assertEquals("LP14082-9", properties.get(1).getValue()); assertEquals("Bacteria", properties.get(1).getDisplay()); assertEquals(1, nestedChildCode.getParents().size()); @@ -466,19 +471,19 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { properties = new ArrayList<>(doublyNestedChildCode.getProperties()); assertEquals(4, properties.size()); assertEquals("parent", properties.get(0).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(0).getCodeSystem()); assertEquals(nestedChildCode.getCode(), properties.get(0).getValue()); assertEquals(nestedChildCode.getDisplay(), properties.get(0).getDisplay()); assertEquals("child", properties.get(1).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(1).getCodeSystem()); assertEquals("LP52258-8", properties.get(1).getValue()); assertEquals("Bacteria | Body Fluid", properties.get(1).getDisplay()); assertEquals("child", properties.get(2).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(2).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(2).getCodeSystem()); assertEquals("LP52260-4", properties.get(2).getValue()); assertEquals("Bacteria | Cerebral spinal fluid", properties.get(2).getDisplay()); assertEquals("child", properties.get(3).getKey()); - assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, properties.get(3).getCodeSystem()); + assertEquals(ITermLoaderSvc.LOINC_URI, properties.get(3).getCodeSystem()); assertEquals("LP52960-9", properties.get(3).getValue()); assertEquals("Bacteria | Cervix", properties.get(3).getDisplay()); assertEquals(1, doublyNestedChildCode.getParents().size()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java index fc3a3dea363..056b796a2a3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java @@ -3,6 +3,9 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -35,26 +38,29 @@ import static org.mockito.Mockito.verify; public class TerminologyLoaderSvcSnomedCtTest extends BaseLoaderTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcSnomedCtTest.class); - private TerminologyLoaderSvcImpl mySvc; + private TermLoaderSvcImpl mySvc; @Mock - private IHapiTerminologySvc myTermSvc; + private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Captor private ArgumentCaptor myCsvCaptor; private ZipCollectionBuilder myFiles; + @Mock + private ITermDeferredStorageSvc myTermDeferredStorageSvc; @Before public void before() { - mySvc = new TerminologyLoaderSvcImpl(); - mySvc.setTermSvcForUnitTests(myTermSvc); + mySvc = new TermLoaderSvcImpl(); + mySvc.setTermCodeSystemStorageSvcForUnitTests(myTermCodeSystemStorageSvc); + mySvc.setTermDeferredStorageSvc(myTermDeferredStorageSvc); myFiles = new ZipCollectionBuilder(); } - private ArrayList list(byte[]... theByteArray) { - ArrayList retVal = new ArrayList<>(); + private ArrayList list(byte[]... theByteArray) { + ArrayList retVal = new ArrayList<>(); for (byte[] next : theByteArray) { - retVal.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { + retVal.add(new ITermLoaderSvc.FileDescriptor() { @Override public String getFilename() { return "aaa.zip"; } @@ -80,7 +86,7 @@ public class TerminologyLoaderSvcSnomedCtTest extends BaseLoaderTest { mySvc.loadSnomedCt(myFiles.getFiles(), mySrd); - verify(myTermSvc).storeNewCodeSystemVersion(any(CodeSystem.class), myCsvCaptor.capture(), any(RequestDetails.class), anyList(), anyListOf(ConceptMap.class)); + verify(myTermCodeSystemStorageSvc).storeNewCodeSystemVersion(any(CodeSystem.class), myCsvCaptor.capture(), any(RequestDetails.class), anyList(), anyListOf(ConceptMap.class)); TermCodeSystemVersion csv = myCsvCaptor.getValue(); TreeSet allCodes = toCodes(csv, true); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java new file mode 100644 index 00000000000..62778b88eb9 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java @@ -0,0 +1,490 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.r4.model.*; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.test.context.TestPropertySource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.*; + +@TestPropertySource(properties = { + "scheduling_disabled=true" +}) +public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcDeltaR4Test.class); + + + @Test + public void testAddRootConcepts() { + createNotPresentCodeSystem(); + ValueSet vs; + vs = expandNotPresentCodeSystem(); + assertEquals(0, vs.getExpansion().getContains().size()); + + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("RootA", "Root A"); + delta.addRootConcept("RootB", "Root B"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertHierarchyContains( + "RootA seq=1", + "RootB seq=2" + ); + + delta = new CustomTerminologySet(); + delta.addRootConcept("RootC", "Root C"); + delta.addRootConcept("RootD", "Root D"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertHierarchyContains( + "RootA seq=1", + "RootB seq=2", + "RootC seq=3", + "RootD seq=4" + ); + } + + @Test + public void testAddRootConceptsWithCycle() { + createNotPresentCodeSystem(); + ValueSet vs; + vs = expandNotPresentCodeSystem(); + assertEquals(0, vs.getExpansion().getContains().size()); + + CustomTerminologySet delta = new CustomTerminologySet(); + + TermConcept root = delta.addRootConcept("Root", "Root"); + TermConcept child = root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("Child").setDisplay("Child"); + child.addChild(root, TermConceptParentChildLink.RelationshipTypeEnum.ISA); + + try { + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Cycle detected around code Root", e.getMessage()); + } + } + + @Test + public void testAddHierarchyConcepts() { + ourLog.info("Starting testAddHierarchyConcepts"); + + createNotPresentCodeSystem(); + assertHierarchyContains(); + + ourLog.info("Have created code system"); + runInTransaction(() -> { + ourLog.info("All code systems: {}", myTermCodeSystemDao.findAll()); + ourLog.info("All code system versions: {}", myTermCodeSystemVersionDao.findAll()); + ourLog.info("All concepts: {}", myTermConceptDao.findAll()); + }); + + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("RootA", "Root A"); + delta.addRootConcept("RootB", "Root B"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertHierarchyContains( + "RootA seq=1", + "RootB seq=2" + ); + + ourLog.info("Have performed add"); + runInTransaction(() -> { + ourLog.info("All code systems: {}", myTermCodeSystemDao.findAll()); + ourLog.info("All code system versions: {}", myTermCodeSystemVersionDao.findAll()); + ourLog.info("All concepts: {}", myTermConceptDao.findAll()); + }); + + delta = new CustomTerminologySet(); + delta.addUnanchoredChildConcept("RootA", "ChildAA", "Child AA"); + delta.addUnanchoredChildConcept("RootA", "ChildAB", "Child AB"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertHierarchyContains( + "RootA seq=1", + " ChildAA seq=1", + " ChildAB seq=2", + "RootB seq=2" + ); + } + + @Test + public void testAddMoveConceptFromOneParentToAnother() { + createNotPresentCodeSystem(); + assertHierarchyContains(); + + UploadStatistics outcome; + CustomTerminologySet delta; + + delta = new CustomTerminologySet(); + delta.addRootConcept("RootA", "Root A") + .addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA"); + delta.addRootConcept("RootB", "Root B"); + outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertHierarchyContains( + "RootA seq=1", + " ChildAA seq=1", + "RootB seq=2" + ); + assertEquals(3, outcome.getUpdatedConceptCount()); + + delta = new CustomTerminologySet(); + delta.addUnanchoredChildConcept("RootB", "ChildAA", "Child AA"); + outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertEquals(1, outcome.getUpdatedConceptCount()); + assertHierarchyContains( + "RootA seq=1", + "RootB seq=2", + " ChildAA seq=1" + ); + + } + + @Test + public void testAddNotPermittedForNonExternalCodeSystem() { + + // Create not-present + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://foo"); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + myCodeSystemDao.create(cs); + + CodeSystem delta = new CodeSystem(); + delta + .addConcept() + .setCode("codeA") + .setDisplay("displayA"); + try { + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", new CustomTerminologySet()); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("can not apply a delta - wrong content mode")); + } + + } + + @Test + public void testAddWithoutPreExistingCodeSystem() { + + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("CBC", "Complete Blood Count"); + delta.addRootConcept("URNL", "Routine Urinalysis"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); + + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronous(true); + params.add(CodeSystem.SP_URL, new UriParam("http://foo")); + IBundleProvider searchResult = myCodeSystemDao.search(params, mySrd); + assertEquals(1, Objects.requireNonNull(searchResult.size()).intValue()); + CodeSystem outcome = (CodeSystem) searchResult.getResources(0, 1).get(0); + + assertEquals("http://foo", outcome.getUrl()); + assertEquals(CodeSystem.CodeSystemContentMode.NOTPRESENT, outcome.getContent()); + + IContextValidationSupport.LookupCodeResult lookup = myTermSvc.lookupCode(myFhirCtx, "http://foo", "CBC"); + assertEquals("Complete Blood Count", lookup.getCodeDisplay()); + } + + + @Test + public void testAddModifiesExistingCodesInPlace() { + + // Add codes + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("codea", "CODEA0"); + delta.addRootConcept("codeb", "CODEB0"); + + UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); + assertEquals(2, outcome.getUpdatedConceptCount()); + assertEquals("CODEA0", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + + // Add codes again with different display + delta = new CustomTerminologySet(); + delta.addRootConcept("codea", "CODEA1"); + delta.addRootConcept("codeb", "CODEB1"); + outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); + assertEquals(2, outcome.getUpdatedConceptCount()); + assertEquals("CODEA1", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + + // Add codes again with no changes + outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); + assertEquals(2, outcome.getUpdatedConceptCount()); + assertEquals("CODEA1", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay()); + } + + @Test + public void testAddUnanchoredWithUnknownParent() { + createNotPresentCodeSystem(); + + // Add root code + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("CodeA", "Code A"); + UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); + assertEquals(1, outcome.getUpdatedConceptCount()); + + // Try to add child to nonexistent root code + delta = new CustomTerminologySet(); + delta.addUnanchoredChildConcept("CodeB", "CodeBB", "Code BB"); + try { + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Unable to add code \"CodeBB\" to unknown parent: CodeB")); + } + } + + @Test + public void testAddRelocateHierarchy() { + createNotPresentCodeSystem(); + + // Add code hierarchy + CustomTerminologySet delta = new CustomTerminologySet(); + TermConcept codeA = delta.addRootConcept("CodeA", "Code A"); + TermConcept codeAA = codeA.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeAA").setDisplay("Code AA"); + codeAA.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeAAA").setDisplay("Code AAA"); + codeAA.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeAAB").setDisplay("Code AAB"); + TermConcept codeB = delta.addRootConcept("CodeB", "Code B"); + TermConcept codeBA = codeB.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeBA").setDisplay("Code BA"); + codeBA.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeBAA").setDisplay("Code BAA"); + codeBA.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeBAB").setDisplay("Code BAB"); + UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertEquals(8, outcome.getUpdatedConceptCount()); + assertHierarchyContains( + "CodeA seq=1", + " CodeAA seq=1", + " CodeAAA seq=1", + " CodeAAB seq=2", + "CodeB seq=2", + " CodeBA seq=1", + " CodeBAA seq=1", + " CodeBAB seq=2" + ); + + // Move a single child code to a new spot and make sure the hierarchy comes along + // for the ride.. + delta = new CustomTerminologySet(); + delta.addUnanchoredChildConcept("CodeB", "CodeAA", "Code AA"); + outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + assertEquals(3, outcome.getUpdatedConceptCount()); + assertHierarchyContains( + "CodeA seq=1", + "CodeB seq=2", + " CodeBA seq=1", + " CodeBAA seq=1", + " CodeBAB seq=2", + " CodeAA seq=2", // <-- CodeAA got added here so it comes second + " CodeAAA seq=1", + " CodeAAB seq=2" + ); + + } + + @Test + @Ignore + public void testAddWithPropertiesAndDesignations() { + + // Create not-present + CodeSystem cs = new CodeSystem(); + cs.setName("Description of my life"); + cs.setUrl("http://foo/cs"); + cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); + cs.setVersion("1.2.3"); + myCodeSystemDao.create(cs); + + CodeSystem delta = new CodeSystem(); + CodeSystem.ConceptDefinitionComponent concept = delta + .addConcept() + .setCode("lunch") + .setDisplay("I'm having dog food"); + concept + .addDesignation() + .setLanguage("fr") + .setUse(new Coding("http://sys", "code", "display")) + .setValue("Je mange une pomme"); + concept + .addDesignation() + .setLanguage("es") + .setUse(new Coding("http://sys", "code", "display")) + .setValue("Como una pera"); + concept.addProperty() + .setCode("flavour") + .setValue(new StringType("Hints of lime")); + concept.addProperty() + .setCode("useless_sct_code") + .setValue(new Coding("http://snomed.info", "1234567", "Choked on large meal (finding)")); + + IContextValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(myFhirCtx, "http://foo/cs", "lunch"); + assertEquals(true, result.isFound()); + assertEquals("lunch", result.getSearchedForCode()); + assertEquals("http://foo/cs", result.getSearchedForSystem()); + + Parameters output = (Parameters) result.toParameters(myFhirCtx, null); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + + assertEquals("Description of my life", ((StringType) output.getParameter("name")).getValue()); + assertEquals("1.2.3", ((StringType) output.getParameter("version")).getValue()); + assertEquals(false, output.getParameterBool("abstract")); + + List designations = output.getParameter().stream().filter(t -> t.getName().equals("designation")).collect(Collectors.toList()); + assertEquals("language", designations.get(0).getPart().get(0).getName()); + assertEquals("fr", ((CodeType) designations.get(0).getPart().get(0).getValue()).getValueAsString()); + assertEquals("use", designations.get(0).getPart().get(1).getName()); + assertEquals("http://sys", ((Coding) designations.get(0).getPart().get(1).getValue()).getSystem()); + assertEquals("code", ((Coding) designations.get(0).getPart().get(1).getValue()).getCode()); + assertEquals("display", ((Coding) designations.get(0).getPart().get(1).getValue()).getDisplay()); + assertEquals("value", designations.get(0).getPart().get(2).getName()); + assertEquals("Je mange une pomme", ((StringType) designations.get(0).getPart().get(2).getValue()).getValueAsString()); + + List properties = output.getParameter().stream().filter(t -> t.getName().equals("property")).collect(Collectors.toList()); + assertEquals("code", properties.get(0).getPart().get(0).getName()); + assertEquals("flavour", ((CodeType) properties.get(0).getPart().get(0).getValue()).getValueAsString()); + assertEquals("value", properties.get(0).getPart().get(1).getName()); + assertEquals("Hints of lime", ((StringType) properties.get(0).getPart().get(1).getValue()).getValueAsString()); + + assertEquals("code", properties.get(1).getPart().get(0).getName()); + assertEquals("useless_sct_code", ((CodeType) properties.get(1).getPart().get(0).getValue()).getValueAsString()); + assertEquals("value", properties.get(1).getPart().get(1).getName()); + assertEquals("http://snomed.info", ((Coding) properties.get(1).getPart().get(1).getValue()).getSystem()); + assertEquals("1234567", ((Coding) properties.get(1).getPart().get(1).getValue()).getCode()); + assertEquals("Choked on large meal (finding)", ((Coding) properties.get(1).getPart().get(1).getValue()).getDisplay()); + + } + + @Test + public void testRemove() { + + // Create not-present + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://foo/cs"); + cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); + myCodeSystemDao.create(cs); + + CustomTerminologySet delta = new CustomTerminologySet(); + TermConcept codeA = delta.addRootConcept("codeA", "displayA"); + TermConcept codeAA = codeA + .addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA) + .setCode("codeAA") + .setDisplay("displayAA"); + codeAA + .addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA) + .setCode("codeAAA") + .setDisplay("displayAAA"); + delta.addRootConcept("codeB", "displayB"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta); + + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeB").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeA").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAA").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAAA").isPresent())); + + // Remove CodeB + delta = new CustomTerminologySet(); + delta.addRootConcept("codeB", "displayB"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta); + + assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeB").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeA").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAA").isPresent())); + assertEquals(true, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAAA").isPresent())); + + // Remove CodeA + delta = new CustomTerminologySet(); + delta.addRootConcept("codeA"); + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta); + + assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeB").isPresent())); + assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeA").isPresent())); + assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAA").isPresent())); + assertEquals(false, runInTransaction(() -> myTermSvc.findCode("http://foo/cs", "codeAAA").isPresent())); + + } + + + @Test + public void testRemove_UnknownSystem() { + + CustomTerminologySet delta = new CustomTerminologySet(); + delta.addRootConcept("codeA", "displayA"); + try { + myTermCodeSystemStorageSvc.applyDeltaCodeSystemsRemove("http://foo/cs", delta); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Unknown code system: http://foo")); + } + + } + + + private void assertHierarchyContains(String... theStrings) { + List hierarchy = runInTransaction(() -> { + List hierarchyHolder = new ArrayList<>(); + TermCodeSystem codeSystem = myTermCodeSystemDao.findAll().iterator().next(); + TermCodeSystemVersion csv = codeSystem.getCurrentVersion(); + List codes = myTermConceptDao.findByCodeSystemVersion(csv); + List rootCodes = codes.stream().filter(t -> t.getParents().isEmpty()).collect(Collectors.toList()); + flattenExpansionHierarchy(hierarchyHolder, rootCodes, ""); + return hierarchyHolder; + }); + if (theStrings.length == 0) { + assertThat("\n" + String.join("\n", hierarchy), hierarchy, empty()); + } else { + assertThat("\n" + String.join("\n", hierarchy), hierarchy, contains(theStrings)); + } + } + + private void flattenExpansionHierarchy(List theFlattenedHierarchy, List theCodes, String thePrefix) { + theCodes.sort((o1, o2) -> { + int s1 = o1.getSequence() != null ? o1.getSequence() : o1.getCode().hashCode(); + int s2 = o2.getSequence() != null ? o2.getSequence() : o2.getCode().hashCode(); + return s1 - s2; + }); + + for (TermConcept nextCode : theCodes) { + String hierarchyEntry = thePrefix + nextCode.getCode() + " seq=" + nextCode.getSequence(); + theFlattenedHierarchy.add(hierarchyEntry); + + List children = nextCode.getChildCodes(); + flattenExpansionHierarchy(theFlattenedHierarchy, children, thePrefix + " "); + } + } + + private ValueSet expandNotPresentCodeSystem() { + ValueSet vs = new ValueSet(); + vs.setUrl("http://foo/vs"); + vs.getCompose().addInclude().setSystem("http://foo/cs"); + vs = myValueSetDao.expand(vs, null); + ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(vs)); + return vs; + } + + private void createNotPresentCodeSystem() { + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://foo/cs"); + cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); + myCodeSystemDao.create(cs); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index 9d2d785b5ee..2963ebaa26d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -26,12 +26,13 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import static ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc.LOINC_URI; +import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -44,13 +45,10 @@ 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"; - private IIdType myExtensionalCsId; - private IIdType myExtensionalVsId; - @After public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } private IIdType createCodeSystem() { @@ -101,12 +99,12 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermConcept parentB = new TermConcept(cs, "ParentB"); cs.getConcepts().add(parentB); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return id; } - private IIdType createCodeSystem2() { + private void createCodeSystem2() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(CS_URL_2); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); @@ -120,9 +118,8 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermConcept parentA = new TermConcept(cs, "CS2"); cs.getConcepts().add(parentA); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); - return id; } public void createLoincSystemWithSomeCodes() { @@ -190,7 +187,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { code2.getDisplay()); cs.getConcepts().add(code4); - myTermSvc.storeNewCodeSystemVersion(table.getId(), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); }); } @@ -206,7 +203,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermCodeSystemVersion cs = new TermCodeSystemVersion(); cs.setResource(table); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); // Update cs = new TermCodeSystemVersion(); @@ -215,7 +212,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { id = myCodeSystemDao.update(codeSystem, null, true, true, mySrd).getId().toUnqualified(); table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalArgumentException::new); cs.setResource(table); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); // Try to update to a different resource codeSystem = new CodeSystem(); @@ -233,17 +230,17 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { @Test public void testCreatePropertiesAndDesignationsWithDeferredConcepts() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(1); - BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); createCodeSystem(); Validate.notNull(myTermSvc); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); - myTermSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); ValueSet vs = new ValueSet(); ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); @@ -1785,7 +1782,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { child.addChild(parent, RelationshipTypeEnum.ISA); try { - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION" , cs, table); fail(); } catch (InvalidRequestException e) { assertEquals("CodeSystem contains circular reference around code parent", e.getMessage()); @@ -1800,7 +1797,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { ResourceTable resourceTable = (ResourceTable) myCodeSystemDao.readEntity(codeSystemResource.getIdElement(), null); Long codeSystemResourcePid = resourceTable.getId(); TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(codeSystemResourcePid); @@ -1910,7 +1907,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { runInTransaction(()->{ ResourceTable resTable = myEntityManager.find(ResourceTable.class, csId.getIdPartAsLong()); version.setResource(resTable); - myTermSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", "SYSTEM VERSION" , version, resTable); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", "SYSTEM VERSION" , version, resTable); }); org.hl7.fhir.dstu3.model.ValueSet vs = new org.hl7.fhir.dstu3.model.ValueSet(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index 32c412f5a0d..774304c5a39 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -1,15 +1,10 @@ package ca.uhn.fhir.jpa.term; -import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.UriParam; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Lists; @@ -17,7 +12,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport.CodeValidationResult; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; -import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.junit.*; import org.junit.rules.ExpectedException; @@ -33,8 +27,6 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.util.List; import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.empty; @@ -117,23 +109,24 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { TermConcept parentB = new TermConcept(cs, "ParentB"); cs.getConcepts().add(parentB); - myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return id; } - private void createAndPersistConceptMap(HttpVerb theVerb) { + private void createAndPersistConceptMap() { ConceptMap conceptMap = createConceptMap(); conceptMap.setId("ConceptMap/cm"); persistConceptMap(conceptMap, HttpVerb.POST); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistConceptMap(ConceptMap theConceptMap, HttpVerb theVerb) { switch (theVerb) { case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myConceptMapId = myConceptMapDao.create(theConceptMap, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -141,7 +134,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { case PUT: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myConceptMapId = myConceptMapDao.update(theConceptMap, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -151,9 +144,9 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { } } - private void loadAndPersistCodeSystemAndValueSet(HttpVerb theVerb) throws IOException { - loadAndPersistCodeSystem(theVerb); - loadAndPersistValueSet(theVerb); + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(HttpVerb.POST); } private void loadAndPersistCodeSystemAndValueSetWithDesignations(HttpVerb theVerb) throws IOException { @@ -166,10 +159,10 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { loadAndPersistValueSetWithExclude(theVerb); } - private void loadAndPersistCodeSystem(HttpVerb theVerb) throws IOException { + private void loadAndPersistCodeSystem() throws IOException { CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); codeSystem.setId("CodeSystem/cs"); - persistCodeSystem(codeSystem, theVerb); + persistCodeSystem(codeSystem, HttpVerb.POST); } private void loadAndPersistCodeSystemWithDesignations(HttpVerb theVerb) throws IOException { @@ -178,12 +171,13 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { persistCodeSystem(codeSystem, theVerb); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistCodeSystem(CodeSystem theCodeSystem, HttpVerb theVerb) { switch (theVerb) { case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -191,7 +185,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { case PUT: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalCsId = myCodeSystemDao.update(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -214,12 +208,13 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { persistValueSet(valueSet, theVerb); } + @SuppressWarnings("EnumSwitchStatementWhichMissesCases") private void persistValueSet(ValueSet theValueSet, HttpVerb theVerb) { switch (theVerb) { case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -227,7 +222,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { case PUT: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myExtensionalVsId = myValueSetDao.update(theValueSet, mySrd).getId().toUnqualifiedVersionless(); } }); @@ -238,304 +233,6 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myExtensionalVsIdOnResourceTable = myValueSetDao.readEntity(myExtensionalVsId, null).getId(); } - @Test - public void testApplyCodeSystemDeltaAdd() { - - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - myCodeSystemDao.create(cs); - - CodeSystem delta = new CodeSystem(); - delta - .addConcept() - .setCode("codeA") - .setDisplay("displayA"); - delta - .addConcept() - .setCode("codeB") - .setDisplay("displayB"); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - - assertEquals(true, runInTransaction(()->myTermSvc.findCode("http://foo", "codeA").isPresent())); - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeZZZ").isPresent())); - - } - - /** - * This would be a good check, but there is no easy eay to do it... - */ - @Test - @Ignore - public void testApplyCodeSystemDeltaAddNotPermittedForNonExternalCodeSystem() { - - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); - myCodeSystemDao.create(cs); - - CodeSystem delta = new CodeSystem(); - delta - .addConcept() - .setCode("codeA") - .setDisplay("displayA"); - try { - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - fail(); - } catch (InvalidRequestException e) { - assertEquals("", e.getMessage()); - } - - } - - @Test - public void testApplyCodeSystemDeltaAddWithoutPreExistingCodeSystem() { - - CodeSystem delta = new CodeSystem(); - delta.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - delta.setUrl("http://foo"); - delta.setName("Acme Lab Codes"); - delta - .addConcept() - .setCode("CBC") - .setDisplay("Complete Blood Count"); - delta - .addConcept() - .setCode("URNL") - .setDisplay("Routine Urinalysis"); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronous(true); - params.add(CodeSystem.SP_URL, new UriParam("http://foo")); - IBundleProvider searchResult = myCodeSystemDao.search(params, mySrd); - assertEquals(1, searchResult.size().intValue()); - CodeSystem outcome = (CodeSystem) searchResult.getResources(0,1).get(0); - - assertEquals("http://foo", outcome.getUrl()); - assertEquals("Acme Lab Codes", outcome.getName()); - } - - - @Test - public void testApplyCodeSystemDeltaAddDuplicatesIgnored() { - - // Add codes - CodeSystem delta = new CodeSystem(); - delta.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - delta.setUrl("http://foo"); - delta.setName("Acme Lab Codes"); - delta - .addConcept() - .setCode("codea") - .setDisplay("CODEA0"); - delta - .addConcept() - .setCode("codeb") - .setDisplay("CODEB0"); - AtomicInteger outcome = myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - assertEquals(2, outcome.get()); - - // Add codes again with different display - delta = new CodeSystem(); - delta.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - delta.setUrl("http://foo"); - delta.setName("Acme Lab Codes"); - delta - .addConcept() - .setCode("codea") - .setDisplay("CODEA1"); - delta - .addConcept() - .setCode("codeb") - .setDisplay("CODEB1"); - outcome = myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - assertEquals(2, outcome.get()); - - // Add codes again with no changes - outcome = myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - assertEquals(0, outcome.get()); - } - - - @Test - public void testApplyCodeSystemDeltaAddAsChild() { - - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - myCodeSystemDao.create(cs); - - CodeSystem delta = new CodeSystem(); - delta - .addConcept() - .setCode("codeA") - .setDisplay("displayA"); - delta - .addConcept() - .setCode("codeB") - .setDisplay("displayB"); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - - delta = new CodeSystem(); - CodeSystem.ConceptDefinitionComponent codeAA = delta - .addConcept() - .setCode("codeAA") - .setDisplay("displayAA"); - codeAA - .addConcept() - .setCode("codeAAA") - .setDisplay("displayAAA"); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", "codeA", delta); - - assertEquals(true, runInTransaction(()->myTermSvc.findCode("http://foo", "codeAA").isPresent())); - assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY, myTermSvc.subsumes(toString("codeA"), toString("codeAA"), toString("http://foo"), null, null).getOutcome()); - assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY, myTermSvc.subsumes(toString("codeA"), toString("codeAAA"), toString("http://foo"), null, null).getOutcome()); - assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY, myTermSvc.subsumes(toString("codeAA"), toString("codeAAA"), toString("http://foo"), null, null).getOutcome()); - assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED, myTermSvc.subsumes(toString("codeB"), toString("codeAA"), toString("http://foo"), null, null).getOutcome()); - - runInTransaction(() -> { - List allChildren = myTermConceptParentChildLinkDao.findAll(); - assertEquals(2, allChildren.size()); - }); - } - - @Test - public void testApplyCodeSystemDeltaAddWithPropertiesAndDesignations() { - - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setName("Description of my life"); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - cs.setVersion("1.2.3"); - myCodeSystemDao.create(cs); - - CodeSystem delta = new CodeSystem(); - CodeSystem.ConceptDefinitionComponent concept = delta - .addConcept() - .setCode("lunch") - .setDisplay("I'm having dog food"); - concept - .addDesignation() - .setLanguage("fr") - .setUse(new Coding("http://sys", "code", "display")) - .setValue("Je mange une pomme"); - concept - .addDesignation() - .setLanguage("es") - .setUse(new Coding("http://sys", "code", "display")) - .setValue("Como una pera"); - concept.addProperty() - .setCode("flavour") - .setValue(new StringType("Hints of lime")); - concept.addProperty() - .setCode("useless_sct_code") - .setValue(new Coding("http://snomed.info", "1234567", "Choked on large meal (finding)")); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - - IContextValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(myFhirCtx, "http://foo", "lunch"); - assertEquals(true, result.isFound()); - assertEquals("lunch", result.getSearchedForCode()); - assertEquals("http://foo", result.getSearchedForSystem()); - - Parameters output = (Parameters) result.toParameters(myFhirCtx, null); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); - - assertEquals("Description of my life", ((StringType) output.getParameter("name")).getValue()); - assertEquals("1.2.3", ((StringType) output.getParameter("version")).getValue()); - assertEquals(false, output.getParameterBool("abstract")); - - List designations = output.getParameter().stream().filter(t -> t.getName().equals("designation")).collect(Collectors.toList()); - assertEquals("language", designations.get(0).getPart().get(0).getName()); - assertEquals("fr", ((CodeType) designations.get(0).getPart().get(0).getValue()).getValueAsString()); - assertEquals("use", designations.get(0).getPart().get(1).getName()); - assertEquals("http://sys", ((Coding) designations.get(0).getPart().get(1).getValue()).getSystem()); - assertEquals("code", ((Coding) designations.get(0).getPart().get(1).getValue()).getCode()); - assertEquals("display", ((Coding) designations.get(0).getPart().get(1).getValue()).getDisplay()); - assertEquals("value", designations.get(0).getPart().get(2).getName()); - assertEquals("Je mange une pomme", ((StringType) designations.get(0).getPart().get(2).getValue()).getValueAsString()); - - List properties = output.getParameter().stream().filter(t -> t.getName().equals("property")).collect(Collectors.toList()); - assertEquals("code", properties.get(0).getPart().get(0).getName()); - assertEquals("flavour", ((CodeType) properties.get(0).getPart().get(0).getValue()).getValueAsString()); - assertEquals("value", properties.get(0).getPart().get(1).getName()); - assertEquals("Hints of lime", ((StringType) properties.get(0).getPart().get(1).getValue()).getValueAsString()); - - assertEquals("code", properties.get(1).getPart().get(0).getName()); - assertEquals("useless_sct_code", ((CodeType) properties.get(1).getPart().get(0).getValue()).getValueAsString()); - assertEquals("value", properties.get(1).getPart().get(1).getName()); - assertEquals("http://snomed.info", ((Coding) properties.get(1).getPart().get(1).getValue()).getSystem()); - assertEquals("1234567", ((Coding) properties.get(1).getPart().get(1).getValue()).getCode()); - assertEquals("Choked on large meal (finding)", ((Coding) properties.get(1).getPart().get(1).getValue()).getDisplay()); - - } - - @Test - public void testApplyCodeSystemDeltaRemove() { - - // Create not-present - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://foo"); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - myCodeSystemDao.create(cs); - - CodeSystem delta = new CodeSystem(); - CodeSystem.ConceptDefinitionComponent codeA = delta - .addConcept() - .setCode("codeA") - .setDisplay("displayA"); - delta - .addConcept() - .setCode("codeB") - .setDisplay("displayB"); - CodeSystem.ConceptDefinitionComponent codeAA = codeA - .addConcept() - .setCode("codeAA") - .setDisplay("displayAA"); - codeAA - .addConcept() - .setCode("codeAAA") - .setDisplay("displayAAA"); - myTermSvc.applyDeltaCodesystemsAdd("http://foo", null, delta); - - // Remove CodeB - delta = new CodeSystem(); - delta - .addConcept() - .setCode("codeB") - .setDisplay("displayB"); - myTermSvc.applyDeltaCodesystemsRemove("http://foo", delta); - - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeB").isPresent())); - assertEquals(true, runInTransaction(()->myTermSvc.findCode("http://foo", "codeA").isPresent())); - assertEquals(true, runInTransaction(()->myTermSvc.findCode("http://foo", "codeAA").isPresent())); - assertEquals(true, runInTransaction(()->myTermSvc.findCode("http://foo", "codeAAA").isPresent())); - - // Remove CodeA - delta = new CodeSystem(); - delta - .addConcept() - .setCode("codeA"); - myTermSvc.applyDeltaCodesystemsRemove("http://foo", delta); - - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeB").isPresent())); - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeA").isPresent())); - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeAA").isPresent())); - assertEquals(false, runInTransaction(()->myTermSvc.findCode("http://foo", "codeAAA").isPresent())); - - } - - - @Nonnull - private StringType toString(String theString) { - return new StringType(theString); - } - - @Test public void testCreateConceptMapWithMissingSourceSystem() { ConceptMap conceptMap = new ConceptMap(); @@ -654,7 +351,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myTermValueSetConceptDesignationDao.deleteByTermValueSetId(termValueSetId); assertEquals(0, myTermValueSetConceptDesignationDao.countByTermValueSetId(termValueSetId).intValue()); myTermValueSetConceptDao.deleteByTermValueSetId(termValueSetId); @@ -691,7 +388,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { myTermValueSetConceptDesignationDao.deleteByTermValueSetId(termValueSetId); assertEquals(0, myTermValueSetConceptDesignationDao.countByTermValueSetId(termValueSetId).intValue()); myTermValueSetConceptDao.deleteByTermValueSetId(termValueSetId); @@ -704,22 +401,22 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testDuplicateCodeSystemUrls() throws Exception { - loadAndPersistCodeSystem(HttpVerb.POST); + loadAndPersistCodeSystem(); expectedException.expect(UnprocessableEntityException.class); expectedException.expectMessage("Can not create multiple CodeSystem resources with CodeSystem.url \"http://acme.org\", already have one with resource ID: CodeSystem/" + myExtensionalCsId.getIdPart()); - loadAndPersistCodeSystem(HttpVerb.POST); + loadAndPersistCodeSystem(); } @Test public void testDuplicateConceptMapUrls() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); expectedException.expect(UnprocessableEntityException.class); expectedException.expectMessage("Can not create multiple ConceptMap resources with ConceptMap.url \"http://example.com/my_concept_map\", already have one with resource ID: ConceptMap/" + myConceptMapId.getIdPart()); - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); } @Test @@ -727,7 +424,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myDaoConfig.setPreExpandValueSets(true); // DM 2019-03-05 - We pre-load our custom CodeSystem otherwise pre-expansion of the ValueSet will fail. - loadAndPersistCodeSystemAndValueSet(HttpVerb.POST); + loadAndPersistCodeSystemAndValueSet(); expectedException.expect(UnprocessableEntityException.class); expectedException.expectMessage("Can not create multiple ValueSet resources with ValueSet.url \"http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2\", already have one with resource ID: ValueSet/" + myExtensionalVsId.getIdPart()); @@ -735,6 +432,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { loadAndPersistValueSet(HttpVerb.POST); } + @SuppressWarnings("SpellCheckingInspection") @Test public void testExpandTermValueSetAndChildren() throws Exception { myDaoConfig.setPreExpandValueSets(true); @@ -1239,7 +937,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size()); - ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); + ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); assertEquals("http://acme.org", containsComponent.getSystem()); assertEquals("11378-7", containsComponent.getCode()); assertEquals("Systolic blood pressure at First encounter", containsComponent.getDisplay()); @@ -1300,7 +998,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size()); - ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); + ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); assertEquals("http://acme.org", containsComponent.getSystem()); assertEquals("11378-7", containsComponent.getCode()); assertEquals("Systolic blood pressure at First encounter", containsComponent.getDisplay()); @@ -1361,7 +1059,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { assertEquals(22, expandedValueSet.getExpansion().getContains().size()); - ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); + ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); assertEquals("http://acme.org", containsComponent.getSystem()); assertEquals("11378-7", containsComponent.getCode()); assertEquals("Systolic blood pressure at First encounter", containsComponent.getDisplay()); @@ -1416,7 +1114,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { assertEquals(22, expandedValueSet.getExpansion().getContains().size()); - ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); + ValueSet.ValueSetExpansionContainsComponent containsComponent = expandedValueSet.getExpansion().getContains().get(0); assertEquals("http://acme.org", containsComponent.getSystem()); assertEquals("11378-7", containsComponent.getCode()); assertEquals("Systolic blood pressure at First encounter", containsComponent.getDisplay()); @@ -1465,7 +1163,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(myExtensionalCsIdOnResourceTable); assertEquals("http://acme.org", codeSystem.getCodeSystemUri()); assertNull(codeSystem.getName()); @@ -1533,7 +1231,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(myExtensionalCsIdOnResourceTable); assertEquals("http://acme.org", codeSystem.getCodeSystemUri()); assertNull(codeSystem.getName()); @@ -1599,7 +1297,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { ResourceTable resourceTable = (ResourceTable) myCodeSystemDao.readEntity(codeSystemResource.getIdElement(), null); Long codeSystemResourcePid = resourceTable.getId(); TermCodeSystem codeSystem = myTermCodeSystemDao.findByResourcePid(codeSystemResourcePid); @@ -1692,14 +1390,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testStoreTermConceptMapAndChildren() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { Optional optionalConceptMap = myTermConceptMapDao.findTermConceptMapByUrl(CM_URL); assertTrue(optionalConceptMap.isPresent()); @@ -1870,14 +1568,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testStoreTermConceptMapAndChildrenWithClientAssignedId() { - createAndPersistConceptMap(HttpVerb.PUT); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { Optional optionalConceptMap = myTermConceptMapDao.findTermConceptMapByUrl(CM_URL); assertTrue(optionalConceptMap.isPresent()); @@ -2058,7 +1756,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2076,7 +1774,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2160,7 +1858,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2178,7 +1876,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2262,7 +1960,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2280,7 +1978,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2364,7 +2062,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2382,7 +2080,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); - runInTransaction(()->{ + runInTransaction(() -> { Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); assertTrue(optionalValueSetByResourcePid.isPresent()); @@ -2456,14 +2154,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testTranslateByCodeSystemsAndSourceCodeOneToMany() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TranslationRequest translationRequest = new TranslationRequest(); translationRequest.getCodeableConcept().addCoding() .setSystem(CS_URL) @@ -2473,7 +2171,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2503,21 +2201,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateByCodeSystemsAndSourceCodeOneToOne() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TranslationRequest translationRequest = new TranslationRequest(); translationRequest.getCodeableConcept().addCoding() .setSystem(CS_URL) @@ -2527,7 +2225,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2577,21 +2275,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateByCodeSystemsAndSourceCodeUnmapped() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TranslationRequest translationRequest = new TranslationRequest(); translationRequest.getCodeableConcept().addCoding() .setSystem(CS_URL) @@ -2637,14 +2335,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testTranslateUsingPredicatesWithCodeOnly() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2656,7 +2354,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2698,21 +2396,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceAndTargetSystem2() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2728,7 +2426,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2746,21 +2444,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceAndTargetSystem3() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2776,7 +2474,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2806,21 +2504,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceSystem() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2834,7 +2532,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2876,21 +2574,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceSystemAndVersion1() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2906,7 +2604,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2924,21 +2622,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(1, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceSystemAndVersion3() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -2954,7 +2652,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -2984,21 +2682,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(2, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithSourceValueSet() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3012,7 +2710,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -3054,21 +2752,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateUsingPredicatesWithTargetValueSet() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3082,7 +2780,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); TermConceptMapGroupElementTarget target = targets.get(0); @@ -3124,21 +2822,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { targets = myTermSvc.translate(translationRequest); assertNotNull(targets); assertEquals(3, targets.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationCache()); } }); } @Test public void testTranslateWithReverse() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3156,7 +2854,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3173,21 +2871,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseByCodeSystemsAndSourceCodeUnmapped() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { TranslationRequest translationRequest = new TranslationRequest(); translationRequest.getCodeableConcept().addCoding() .setSystem(CS_URL_3) @@ -3203,14 +2901,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Test public void testTranslateWithReverseUsingPredicatesWithCodeOnly() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3224,7 +2922,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3252,21 +2950,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithSourceAndTargetSystem1() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3284,7 +2982,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3301,21 +2999,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithSourceAndTargetSystem4() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3333,7 +3031,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3350,21 +3048,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(1, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithSourceSystem() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3380,7 +3078,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3408,21 +3106,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithSourceSystemAndVersion() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3440,7 +3138,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3468,21 +3166,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithSourceValueSet() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3498,7 +3196,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3526,21 +3224,21 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } @Test public void testTranslateWithReverseUsingPredicatesWithTargetValueSet() { - createAndPersistConceptMap(HttpVerb.POST); + createAndPersistConceptMap(); ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId); ourLog.info("ConceptMap:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { /* * Provided: * source code @@ -3556,7 +3254,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { List elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertFalse(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertFalse(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); TermConceptMapGroupElement element = elements.get(0); @@ -3584,7 +3282,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { elements = myTermSvc.translateWithReverse(translationRequest); assertNotNull(elements); assertEquals(2, elements.size()); - assertTrue(BaseHapiTerminologySvcImpl.isOurLastResultsFromTranslationWithReverseCache()); + assertTrue(BaseTermReadSvcImpl.isOurLastResultsFromTranslationWithReverseCache()); } }); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java index 9df85caef70..026bd396e7d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; @@ -17,7 +19,7 @@ import java.util.zip.ZipOutputStream; public class ZipCollectionBuilder { private static final Logger ourLog = LoggerFactory.getLogger(ZipCollectionBuilder.class); - private final ArrayList myFiles; + private final ArrayList myFiles; /** * Constructor @@ -31,7 +33,7 @@ public class ZipCollectionBuilder { */ public void addFilePlain(String theClasspathPrefix, String theClasspathFileName) throws IOException { byte[] file = readFile(theClasspathPrefix, theClasspathFileName); - myFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { + myFiles.add(new ITermLoaderSvc.FileDescriptor() { @Override public String getFilename() { return theClasspathFileName; @@ -61,7 +63,7 @@ public class ZipCollectionBuilder { zos.closeEntry(); zos.close(); ourLog.info("ZIP file has {} bytes", bos.toByteArray().length); - myFiles.add(new IHapiTerminologyLoaderSvc.FileDescriptor() { + myFiles.add(new ITermLoaderSvc.FileDescriptor() { @Override public String getFilename() { return "AAA.zip"; @@ -83,8 +85,21 @@ public class ZipCollectionBuilder { return byteArray; } - public List getFiles() { + public List getFiles() { return myFiles; } + public void addFileText(String theText, String theFilename) { + myFiles.add(new ITermLoaderSvc.FileDescriptor() { + @Override + public String getFilename() { + return theFilename; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(theText.getBytes(Charsets.UTF_8)); + } + }); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java new file mode 100644 index 00000000000..944ca3395f0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java @@ -0,0 +1,56 @@ +package ca.uhn.fhir.validator; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.util.AttachmentUtil; +import org.hl7.fhir.instance.model.api.ICompositeType; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AttachmentUtilTest { + + @Test + public void testCreateAttachmentDstu3() { + FhirContext ctx = FhirContext.forDstu3(); + ICompositeType attachment = AttachmentUtil.newInstance(ctx); + AttachmentUtil.setData(ctx, attachment, new byte[]{0, 1, 2, 3}); + AttachmentUtil.setUrl(ctx, attachment, "http://foo"); + AttachmentUtil.setContentType(ctx, attachment, "text/plain"); + AttachmentUtil.setSize(ctx, attachment, 123); + + org.hl7.fhir.dstu3.model.Observation obs = new org.hl7.fhir.dstu3.model.Observation(); + obs.setValue((org.hl7.fhir.dstu3.model.Type) attachment); + String encoded = ctx.newJsonParser().encodeResourceToString(obs); + assertEquals("{\"resourceType\":\"Observation\",\"valueAttachment\":{\"contentType\":\"text/plain\",\"data\":\"AAECAw==\",\"url\":\"http://foo\",\"size\":123}}", encoded); + } + + @Test + public void testCreateAttachmentR4() { + FhirContext ctx = FhirContext.forR4(); + ICompositeType attachment = AttachmentUtil.newInstance(ctx); + AttachmentUtil.setData(ctx, attachment, new byte[]{0, 1, 2, 3}); + AttachmentUtil.setUrl(ctx, attachment, "http://foo"); + AttachmentUtil.setContentType(ctx, attachment, "text/plain"); + AttachmentUtil.setSize(ctx, attachment, 123); + + org.hl7.fhir.r4.model.Communication communication = new org.hl7.fhir.r4.model.Communication(); + communication.addPayload().setContent((org.hl7.fhir.r4.model.Type) attachment); + String encoded = ctx.newJsonParser().encodeResourceToString(communication); + assertEquals("{\"resourceType\":\"Communication\",\"payload\":[{\"contentAttachment\":{\"contentType\":\"text/plain\",\"data\":\"AAECAw==\",\"url\":\"http://foo\",\"size\":123}}]}", encoded); + } + + @Test + public void testCreateAttachmentR5() { + FhirContext ctx = FhirContext.forR5(); + ICompositeType attachment = AttachmentUtil.newInstance(ctx); + AttachmentUtil.setData(ctx, attachment, new byte[]{0, 1, 2, 3}); + AttachmentUtil.setUrl(ctx, attachment, "http://foo"); + AttachmentUtil.setContentType(ctx, attachment, "text/plain"); + AttachmentUtil.setSize(ctx, attachment, 123); + + org.hl7.fhir.r5.model.Communication communication = new org.hl7.fhir.r5.model.Communication(); + communication.addPayload().setContent((org.hl7.fhir.r5.model.Type) attachment); + String encoded = ctx.newJsonParser().encodeResourceToString(communication); + assertEquals("{\"resourceType\":\"Communication\",\"payload\":[{\"contentAttachment\":{\"contentType\":\"text/plain\",\"data\":\"AAECAw==\",\"url\":\"http://foo\",\"size\":123}}]}", encoded); + } +} diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 8c5b13a4e26..56835cface9 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -60,6 +60,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { protected void init410() { Builder version = forVersion(VersionEnum.V4_1_0); + // TRM_CONCEPT_PC_LINK + version.onTable("TRM_CONCEPT_PC_LINK") + .addIndex("IDX_TRMCONCPCLNK_CSV").unique(false).withColumns("CODESYSTEM_PID"); + // HFJ_SEARCH version.onTable("HFJ_SEARCH").addColumn("EXPIRY_OR_NULL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP); version.onTable("HFJ_SEARCH").addColumn("NUM_BLOCKED").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index e36ddbd785e..e4c7c52672c 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy; import ca.uhn.fhir.util.*; import com.google.common.collect.Lists; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -1126,13 +1127,22 @@ public class RestfulServer extends HttpServlet implements IRestfulServer methodParamsTemp = new HashSet<>(); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java index b070cd7de51..cdf53000b2b 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/TransactionMethodBinding.java @@ -120,15 +120,6 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding return response; } - // Grab the IDs of all of the resources in the transaction - List resources; - resources = (List) theMethodParams[myTransactionParamIndex]; - - IdentityHashMap oldIds = new IdentityHashMap(); - for (IResource next : resources) { - oldIds.put(next, next.getId()); - } - // Call the server implementation method Object response = invokeServerMethod(theServer, theRequest, theMethodParams); IBundleProvider retVal = toResourceList(response); @@ -142,19 +133,12 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding List retResources = retVal.getResources(0, retVal.size()); for (int i = 0; i < retResources.size(); i++) { - IdDt oldId = oldIds.get(retResources.get(i)); IBaseResource newRes = retResources.get(i); if (newRes.getIdElement() == null || newRes.getIdElement().isEmpty()) { if (!(newRes instanceof BaseOperationOutcome)) { throw new InternalErrorException("Transaction method returned resource at index " + i + " with no id specified - IResource#setId(IdDt)"); } } - - if (oldId != null && !oldId.isEmpty()) { - if (!oldId.equals(newRes.getIdElement()) && newRes instanceof IResource) { - ((IResource) newRes).getResourceMetadata().put(ResourceMetadataKeyEnum.PREVIOUS_ID, oldId); - } - } } return retVal; diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java index 4cb94a109b2..e5f04c63226 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java @@ -1965,7 +1965,7 @@ public class JsonParserDstu2Test { ProcedureRequest p = parser.parseResource(ProcedureRequest.class, input); ArgumentCaptor capt = ArgumentCaptor.forClass(String.class); - verify(peh, Mockito.never()).unknownElement(Mockito.isNull(IParseLocation.class), capt.capture()); + verify(peh, Mockito.never()).unknownElement(nullable(IParseLocation.class), capt.capture()); assertParsedResourcesExtensionMetadata(p); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java index 1ece9388c92..daafc55a686 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java @@ -28,6 +28,13 @@ public class FhirContextDstu3Test { assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion()); } + + @Test + public void testToString() { + assertEquals("FhirContext[DSTU3]", FhirContext.forDstu3().toString()); + } + + @Test public void testRuntimeSearchParamToString() { String val = ourCtx.getResourceDefinition("Patient").getSearchParam("gender").toString(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index cf9a68e5f4a..65f1a096eb7 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -165,6 +165,17 @@ public class JsonParserR4Test { assertEquals("
Copy © 1999
", p.getText().getDivAsString()); } + @Test + public void testEncodeBinary() { + Binary b = new Binary(); + b.setContent(new byte[]{0,1,2,3,4}); + b.setContentType("application/octet-stream"); + + IParser parser = ourCtx.newJsonParser().setPrettyPrint(false); + String output = parser.encodeResourceToString(b); + assertEquals("{\"resourceType\":\"Binary\",\"contentType\":\"application/octet-stream\",\"data\":\"AAECAwQ=\"}", output); + } + @Test public void testEncodeWithInvalidExtensionMissingUrl() { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java index e17f7c75393..e1fb597f3b4 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.rest.annotation.IncludeParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -83,6 +83,22 @@ public class SearchR4Test { return bundle; } + /** + * A paging request that incorrectly executes at the type level shouldn't be grabbed by the search method binding + */ + @Test + public void tesPageRequestCantTriggerSearchAccidentally() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Constants.PARAM_PAGINGACTION + "=12345"); + Bundle bundle; + try (CloseableHttpResponse status = ourClient.execute(httpGet)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(400, status.getStatusLine().getStatusCode()); + assertThat(responseContent, containsString("not know how to handle GET operation[Patient] with parameters [[_getpages]]")); + } + } + + @Test public void testPagingPreservesElements() throws Exception { HttpGet httpGet; @@ -368,7 +384,7 @@ public class SearchR4Test { try (CloseableHttpResponse status = ourClient.execute(httpGet)) { assertEquals(200, status.getStatusLine().getStatusCode()); String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue(); - assertThat(requestId, matchesPattern("[a-z0-9]{16}")); + assertThat(requestId, matchesPattern("[a-zA-Z0-9]{16}")); } } @@ -390,7 +406,7 @@ public class SearchR4Test { try (CloseableHttpResponse status = ourClient.execute(httpGet)) { assertEquals(200, status.getStatusLine().getStatusCode()); String requestId = status.getFirstHeader(Constants.HEADER_REQUEST_ID).getValue(); - assertThat(requestId, matchesPattern("[a-z0-9]{16}")); + assertThat(requestId, matchesPattern("[a-zA-Z0-9]{16}")); } } @@ -461,7 +477,7 @@ public class SearchR4Test { @SuppressWarnings("rawtypes") @Search() public List search( - @RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { + @OptionalParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { ourLastMethod = "search"; ourIdentifiers = theIdentifiers; ArrayList retVal = new ArrayList<>(); diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java index f6e98a8fdc4..3d855284db4 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java @@ -36,6 +36,7 @@ import org.thymeleaf.ITemplateEngine; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.*; @@ -649,7 +650,9 @@ public class BaseController { if (lastResponse != null) { resultStatus = "HTTP " + lastResponse.getStatus() + " " + lastResponse.getStatusInfo(); lastResponse.bufferEntity(); - resultBody = IOUtils.toString(lastResponse.readEntity(), Constants.CHARSET_UTF8); + try (InputStream input = lastResponse.readEntity()) { + resultBody = IOUtils.toString(input, Constants.CHARSET_UTF8); + } List ctStrings = lastResponse.getHeaders(Constants.HEADER_CONTENT_TYPE); if (ctStrings != null && ctStrings.isEmpty() == false) { diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java new file mode 100644 index 00000000000..9b81cbdf8ac --- /dev/null +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/SchemaBaseValidatorTest.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.junit.Test; + +import javax.xml.transform.Source; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.*; + +public class SchemaBaseValidatorTest { + + @Test + public void testLoadXmlSuccess() { + SchemaBaseValidator validator = new SchemaBaseValidator(FhirContext.forR4()); + Source schema = validator.loadXml("fhir-single.xsd"); + assertNotNull(schema); + } + + + @Test + public void testLoadXmlFail() { + SchemaBaseValidator validator = new SchemaBaseValidator(FhirContext.forR4()); + try { + validator.loadXml("foo.xsd"); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), containsString("Schema not found")); + } + } +} diff --git a/pom.xml b/pom.xml index c4887d24589..44e6bd7cd74 100755 --- a/pom.xml +++ b/pom.xml @@ -784,7 +784,12 @@ org.apache.commons commons-compress - 1.18 + 1.19 + + + org.apache.commons + commons-csv + 1.7 org.jetbrains diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 62ee9e53e9c..95cb1550e2c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -350,6 +350,15 @@ A potential XXE vulnerability in the validator was corrected. The XML parser used for validating XML payloads (i.e. FHIR resources) will no longer read from DTD declarations. + + Auto generated transaction IDs will now use both upper- and lowercase letters for more uniqueness in + the same amount of space. + + + Paging requests that are incorrectly executed at the type level were interpreted by the plain server + as search requests with no search parameters, leading to confusing search results. These will now + result in an HTTP 400 error with a meaningful error message. +