From 9b938262e6081f536541f003e0589c93d5c6e61f Mon Sep 17 00:00:00 2001 From: Patrick Werner Date: Wed, 10 Jul 2019 16:31:22 +0200 Subject: [PATCH 01/10] fixed nostore -> no-store --- src/site/xdoc/doc_jpa.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/site/xdoc/doc_jpa.xml b/src/site/xdoc/doc_jpa.xml index dfee8e9ab03..6628949f4e1 100644 --- a/src/site/xdoc/doc_jpa.xml +++ b/src/site/xdoc/doc_jpa.xml @@ -276,7 +276,7 @@ public DaoConfig daoConfig() { If the client knows that they will only want a small number of results (for example, a UI containing 20 results is being shown and the client knows that they will never load the next page of results) the client - may also use the nostore directive along with a HAPI FHIR + may also use the no-store directive along with a HAPI FHIR extension called max-results in order to specify that only the given number of results should be fetched. This directive disabled paging entirely for the request and causes the request to @@ -285,7 +285,7 @@ public DaoConfig daoConfig() {

-						Cache-Control: nostore, max-results=20
+						Cache-Control: no-store, max-results=20
 					
From 32a8e1aeaddb82708c7bb4b9f2de760939d32e33 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 11 Jul 2019 16:28:50 -0400 Subject: [PATCH 02/10] Omit extensions when encoding in summary mode (except for CapabilityStatement) --- .../java/ca/uhn/fhir/parser/BaseParser.java | 35 +++++- .../java/ca/uhn/fhir/parser/JsonParser.java | 116 ++++++++++++------ .../java/ca/uhn/fhir/parser/XmlParser.java | 8 +- .../model/api/IBaseHasExtensions.java | 2 +- .../uhn/fhir/parser/JsonParserDstu3Test.java | 66 +++++++++- .../uhn/fhir/parser/XmlParserDstu3Test.java | 30 +++++ src/changes/changes.xml | 4 + 7 files changed, 208 insertions(+), 53 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 939f9d2ceeb..ba8b7754c2f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -34,6 +34,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hl7.fhir.instance.model.api.*; +import javax.annotation.Nullable; import java.io.*; import java.lang.reflect.Modifier; import java.util.*; @@ -65,6 +66,7 @@ public abstract class BaseParser implements IParser { private boolean mySummaryMode; private boolean mySuppressNarratives; private Set myDontStripVersionsFromReferencesAtPaths; + /** * Constructor */ @@ -165,8 +167,6 @@ public abstract class BaseParser implements IParser { myNext = null; } else if (!myNext.shouldBeEncoded(theContainedResource)) { myNext = null; - } else if (isSummaryMode() && !myNext.getDef().isSummary()) { - myNext = null; } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) { if (isSuppressNarratives() || isSummaryMode()) { myNext = null; @@ -1004,7 +1004,7 @@ public abstract class BaseParser implements IParser { private final RuntimeResourceDefinition myResDef; private final EncodeContext myEncodeContext; - public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) { + public CompositeChildElement(CompositeChildElement theParent, @Nullable BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) { myDef = theDef; myParent = theParent; myResDef = null; @@ -1015,7 +1015,9 @@ public abstract class BaseParser implements IParser { StringBuilder path = theParent.buildPath(); if (path != null) { path.append('.'); - path.append(myDef.getElementName()); + if (myDef != null) { + path.append(myDef.getElementName()); + } ourLog.trace(" * Next path: {}", path.toString()); } } @@ -1165,6 +1167,21 @@ public abstract class BaseParser implements IParser { if (theContainedResource) { retVal = !notEncodeForContainedResource.contains(myDef.getElementName()); } + if (retVal && isSummaryMode() && (getDef() == null || !getDef().isSummary())) { + String resourceName = myEncodeContext.getLeafResourceName(); + // Technically the spec says we shouldn't include extensions in CapabilityStatement + // but we will do so because there are people who depend on this behaviour, at least + // as of 2019-07. See + // https://github.com/smart-on-fhir/Swift-FHIR/issues/26 + // for example. + if (("Conformance".equals(resourceName) || "CapabilityStatement".equals(resourceName)) && + ("extension".equals(myDef.getElementName()) || "extension".equals(myEncodeContext.getLeafElementName()) + )) { + // skip + } else { + retVal = false; + } + } return retVal; } @@ -1183,7 +1200,7 @@ public abstract class BaseParser implements IParser { @Override public String toString() { - return myPath.stream().map(t->t.toString()).collect(Collectors.joining(".")); + return myPath.stream().map(t -> t.toString()).collect(Collectors.joining(".")); } protected List getPath() { @@ -1251,6 +1268,14 @@ public abstract class BaseParser implements IParser { return myResourcePath; } + public String getLeafElementName() { + return getPath().get(getPath().size() - 1).getName(); + } + + public String getLeafResourceName() { + return myResourcePath.get(myResourcePath.size() - 1).getName(); + } + public String getLeafResourcePathFirstField() { String retVal = null; for (int i = getPath().size() - 1; i >= 0; i--) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 92d109798b6..76fd4347839 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -80,7 +80,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theListToAddTo.add(null); } if (theListToAddTo.get(valueIdx) == null) { - theListToAddTo.set(valueIdx, new ArrayList()); + theListToAddTo.set(valueIdx, new ArrayList<>()); } theListToAddTo.get(valueIdx).addAll(theCommentsToAdd); return true; @@ -89,21 +89,34 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private boolean addToHeldExtensions(int valueIdx, List> ext, ArrayList> list, boolean theIsModifier, CompositeChildElement theChildElem, - CompositeChildElement theParent) { + CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) { + boolean retVal = false; if (ext.size() > 0) { - list.ensureCapacity(valueIdx); - while (list.size() <= valueIdx) { - list.add(null); - } - if (list.get(valueIdx) == null) { - list.set(valueIdx, new ArrayList()); - } + Boolean encodeExtension = null; for (IBaseExtension next : ext) { - list.get(valueIdx).add(new HeldExtension(next, theIsModifier, theChildElem, theParent)); + + // Make sure we respect _summary and _elements + if (encodeExtension == null) { + encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, theContainingElement); + } + + if (encodeExtension) { + HeldExtension extension = new HeldExtension(next, theIsModifier, theChildElem, theParent); + list.ensureCapacity(valueIdx); + while (list.size() <= valueIdx) { + list.add(null); + } + ArrayList extensionList = list.get(valueIdx); + if (extensionList == null) { + extensionList = new ArrayList<>(); + list.set(valueIdx, extensionList); + } + extensionList.add(extension); + retVal = true; + } } - return true; } - return false; + return retVal; } private void addToHeldIds(int theValueIdx, ArrayList theListToAddTo, String theId) { @@ -342,7 +355,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension") || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) { if (!haveWrittenExtensions) { - extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext); + extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext, theContainedResource); haveWrittenExtensions = true; } continue; @@ -433,20 +446,20 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (primitive) { if (nextValue instanceof ISupportsUndeclaredExtensions) { List ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions(); - force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent); + force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions(); - force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent); + force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); } else { if (nextValue instanceof IBaseHasExtensions) { IBaseHasExtensions element = (IBaseHasExtensions) nextValue; List> ext = element.getExtension(); - force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent); + force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); } if (nextValue instanceof IBaseHasModifierExtensions) { IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue; List> ext = element.getModifierExtension(); - force |= addToHeldExtensions(valueIdx, ext, extensions, true, nextChildElem, theParent); + force |= addToHeldExtensions(valueIdx, ext, extensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement); } } if (nextValue.hasFormatComment()) { @@ -486,6 +499,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endArray(); } + if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) { if (inArray) { // If this is a repeatable field, the extensions go in an array too @@ -541,7 +555,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } theEventWriter.endArray(); } - writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext); + writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext, theContainedResource); if (inArray) { theEventWriter.endObject(); } @@ -626,7 +640,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { final List extensions = new ArrayList<>(0); final List modifierExtensions = new ArrayList<>(0); // Undeclared extensions - extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null); + extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null, theEncodeContext, theContainedResource); boolean haveExtension = false; if (!extensions.isEmpty()) { haveExtension = true; @@ -638,7 +652,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { writeCommentsPreAndPost(theResourceId, theEventWriter); } if (haveExtension) { - writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext); + writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); } theEventWriter.endObject(); } @@ -741,12 +755,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { * called _name): resource extensions, and extension extensions */ private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition theElementDef, RuntimeResourceDefinition theResDef, - IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException { + IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { List extensions = new ArrayList<>(0); List modifierExtensions = new ArrayList<>(0); // Undeclared extensions - extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent); + extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent, theEncodeContext, theContainedResource); // Declared extensions if (theElementDef != null) { @@ -754,7 +768,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } // Write the extensions - writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext); + writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); } private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition resDef, List extensions, List modifierExtensions, @@ -782,7 +796,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private void extractUndeclaredExtensions(IBase theElement, List extensions, List modifierExtensions, CompositeChildElement theChildElem, - CompositeChildElement theParent) { + CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) { if (theElement instanceof ISupportsUndeclaredExtensions) { ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement; List ext = element.getUndeclaredExtensions(); @@ -804,11 +818,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (theElement instanceof IBaseHasExtensions) { IBaseHasExtensions element = (IBaseHasExtensions) theElement; List> ext = element.getExtension(); + Boolean encodeExtension = null; for (IBaseExtension next : ext) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { continue; } - extensions.add(new HeldExtension(next, false, theChildElem, theParent)); + + // Make sure we respect _elements and _summary + if (encodeExtension == null) { + encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, element); + } + if (encodeExtension) { + HeldExtension extension = new HeldExtension(next, false, theChildElem, theParent); + extensions.add(extension); + } + } } if (theElement instanceof IBaseHasModifierExtensions) { @@ -818,12 +842,23 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (next == null || next.isEmpty()) { continue; } - modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent)); + + HeldExtension extension = new HeldExtension(next, true, theChildElem, theParent); + modifierExtensions.add(extension); } } } } + private boolean isEncodeExtension(CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theElement) { + theEncodeContext.pushPath("extension", false); + BaseRuntimeChildDefinition childDef = ((BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theElement.getClass())).getChildByName("extension"); + CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext); + boolean retVal = c.shouldBeEncoded(theContainedResource); + theEncodeContext.popPath(); + return retVal; + } + @Override public EncodingEnum getEncoding() { return EncodingEnum.JSON; @@ -1275,21 +1310,24 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List extensions, - List modifierExtensions, EncodeContext theEncodeContext) throws IOException { + List modifierExtensions, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { + // Write Extensions if (extensions.isEmpty() == false) { theEncodeContext.pushPath("extension", false); beginArray(theEventWriter, "extension"); for (HeldExtension next : extensions) { - next.write(resDef, theResource, theEventWriter, theEncodeContext); + next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource); } theEventWriter.endArray(); theEncodeContext.popPath(); } + + // Write ModifierExtensions if (modifierExtensions.isEmpty() == false) { theEncodeContext.pushPath("modifierExtension", false); beginArray(theEventWriter, "modifierExtension"); for (HeldExtension next : modifierExtensions) { - next.write(resDef, theResource, theEventWriter, theEncodeContext); + next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource); } theEventWriter.endArray(); theEncodeContext.popPath(); @@ -1353,12 +1391,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { return url1.compareTo(url2); } - private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition def, final String childName, EncodeContext theEncodeContext) throws IOException { + private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition def, final String childName, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) { final List extensions = new ArrayList(0); final List modifierExtensions = new ArrayList(0); // Undeclared extensions - extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null); + extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null, theEncodeContext, theContainedResource); // Declared extensions if (def != null) { extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent); @@ -1369,15 +1407,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } if (haveContent) { beginObject(theEventWriter, '_' + childName); - writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext); + writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource); theEventWriter.endObject(); } } } - public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException { + public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { if (myUndeclaredExtension != null) { - writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext); + writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext, theContainedResource); } else { theEventWriter.beginObject(); @@ -1410,18 +1448,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { BaseRuntimeElementDefinition def = myDef.getChildElementDefinitionByDatatype(myValue.getClass()); if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { - extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext); + extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext, theContainedResource); } else { String childName = myDef.getChildNameByDatatype(myValue.getClass()); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext); - managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext); + managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext, theContainedResource); } theEventWriter.endObject(); } } - private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension ext, EncodeContext theEncodeContext) throws IOException { + private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension ext, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException { IBase value = ext.getValue(); final String extensionUrl = getExtensionUrl(ext.getUrl()); @@ -1465,7 +1503,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } for (Object next : ext.getExtension()) { - writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension) next, theEncodeContext); + writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension) next, theEncodeContext, theContainedResource); } theEventWriter.endArray(); @@ -1490,7 +1528,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); } encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent,false, theEncodeContext); - managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext); + managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext, theContainedResource); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index a0cf79b2df3..ebd305e4a47 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -51,15 +51,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use * {@link FhirContext#newXmlParser()} to get an instance. */ -public class XmlParser extends BaseParser /* implements IParser */ { +public class XmlParser extends BaseParser { - static final String ATOM_NS = "http://www.w3.org/2005/Atom"; static final String FHIR_NS = "http://hl7.org/fhir"; - static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/"; - static final String RESREF_DISPLAY = "display"; - static final String RESREF_REFERENCE = "reference"; - static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0"; - static final String XHTML_NS = "http://www.w3.org/1999/xhtml"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class); // private static final Set RESOURCE_NAMESPACES; diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java index 4a1cd1e1361..46ecf64a4c0 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java @@ -22,7 +22,7 @@ package org.hl7.fhir.instance.model.api; import java.util.List; -public interface IBaseHasExtensions { +public interface IBaseHasExtensions extends IBase { IBaseExtension addExtension(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 808bd102918..9256172561d 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -715,13 +715,26 @@ public class JsonParserDstu3Test { .setValue(new Reference("Practitioner/A")); IParser parser = ourCtx.newJsonParser().setPrettyPrint(true); - parser.setDontEncodeElements(new HashSet(Arrays.asList("*.id", "*.meta"))); + parser.setDontEncodeElements(new HashSet<>(Arrays.asList("*.id", "*.meta"))); String encoded = parser.encodeResourceToString(p); ourLog.info(encoded); assertThat(encoded, containsString("http://foo")); assertThat(encoded, containsString("Practitioner/A")); + + // Now with exclude + + parser = ourCtx.newJsonParser().setPrettyPrint(true); + parser.setDontEncodeElements(new HashSet<>(Arrays.asList("*.id", "*.meta", "*.extension"))); + + encoded = parser.encodeResourceToString(p); + ourLog.info(encoded); + + assertThat(encoded, (containsString("http://foo"))); + assertThat(encoded, (containsString("Practitioner/A"))); + + } @Test @@ -1064,6 +1077,57 @@ public class JsonParserDstu3Test { assertThat(encoded, not(containsString("maritalStatus"))); } + /** + * We specifically include extensions on CapabilityStatment even in + * summary mode, since this is behaviour that people depend on + */ + @Test + public void testEncodeSummaryCapabilityStatementExtensions() { + + CapabilityStatement cs = new CapabilityStatement(); + CapabilityStatement.CapabilityStatementRestComponent rest = cs.addRest(); + rest.setMode(CapabilityStatement.RestfulCapabilityMode.CLIENT); + rest.getSecurity() + .addExtension() + .setUrl("http://foo") + .setValue(new StringType("bar")); + + cs.getVersionElement().addExtension() + .setUrl("http://goo") + .setValue(new StringType("ber")); + + String encoded = ourCtx.newJsonParser().setSummaryMode(true).setPrettyPrint(true).setPrettyPrint(true).encodeResourceToString(cs); + ourLog.info(encoded); + + assertThat(encoded, (containsString("http://foo"))); + assertThat(encoded, (containsString("bar"))); + assertThat(encoded, (containsString("http://goo"))); + assertThat(encoded, (containsString("ber"))); + } + + @Test + public void testEncodeSummaryPatientExtensions() { + + Patient cs = new Patient(); + Address address = cs.addAddress(); + address.setCity("CITY"); + address + .addExtension() + .setUrl("http://foo") + .setValue(new StringType("bar")); + address.getCityElement().addExtension() + .setUrl("http://goo") + .setValue(new StringType("ber")); + + String encoded = ourCtx.newJsonParser().setSummaryMode(true).setPrettyPrint(true).setPrettyPrint(true).encodeResourceToString(cs); + ourLog.info(encoded); + + assertThat(encoded, not(containsString("http://foo"))); + assertThat(encoded, not(containsString("bar"))); + assertThat(encoded, not(containsString("http://goo"))); + assertThat(encoded, not(containsString("ber"))); + } + @Test public void testEncodeSummary2() { Patient patient = new Patient(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 15c77074645..d44704e9365 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -12,6 +12,7 @@ import com.google.common.collect.Sets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.hamcrest.Matchers; import org.hamcrest.collection.IsEmptyCollection; import org.hamcrest.core.StringContains; import org.hamcrest.text.StringContainsInOrder; @@ -69,6 +70,35 @@ public class XmlParserDstu3Test { ourCtx.setNarrativeGenerator(null); } + /** + * We specifically include extensions on CapabilityStatment even in + * summary mode, since this is behaviour that people depend on + */ + @Test + public void testEncodeSummaryCapabilityStatementExtensions() { + + CapabilityStatement cs = new CapabilityStatement(); + CapabilityStatement.CapabilityStatementRestComponent rest = cs.addRest(); + rest.setMode(CapabilityStatement.RestfulCapabilityMode.CLIENT); + rest.getSecurity() + .addExtension() + .setUrl("http://foo") + .setValue(new StringType("bar")); + + cs.getVersionElement().addExtension() + .setUrl("http://goo") + .setValue(new StringType("ber")); + + String encoded = ourCtx.newXmlParser().setSummaryMode(true).setPrettyPrint(true).setPrettyPrint(true).encodeResourceToString(cs); + ourLog.info(encoded); + + assertThat(encoded, Matchers.containsString("http://foo")); + assertThat(encoded, Matchers.containsString("bar")); + assertThat(encoded, Matchers.containsString("http://goo")); + assertThat(encoded, Matchers.containsString("ber")); + } + + @Test public void testEncodeInvalidMetaTime() { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 24ebecacdde..58ab5ee9b26 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -266,6 +266,10 @@ A new server interceptor hook called PROCESSING_COMPLETED has been added. This hook is called by the server at the end of processing every request (success and failure). + + The _summary]]> element was not always respected when encoding + JSON resources. + From 0729e38e6ec5e472a0e3c87062623358d323dcc6 Mon Sep 17 00:00:00 2001 From: Diederik Muylwyk Date: Thu, 11 Jul 2019 16:52:19 -0400 Subject: [PATCH 03/10] Resolve "experimental implementation for storing expanded ValueSets in terminology tables" (#1369) * Added experimental implementation for storing expanded ValueSets in terminology tables. * Minor tweak to log message for consistency. * Another minor tweak to log message for consistency. * Renamed test. * Addressing review comments. * Added migration tasks. --- .../java/ca/uhn/fhir/util/ValidateUtil.java | 16 +- .../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +- .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 32 ++++ .../fhir/jpa/dao/data/ITermCodeSystemDao.java | 2 +- .../jpa/dao/data/ITermValueSetCodeDao.java | 35 ++++ .../fhir/jpa/dao/data/ITermValueSetDao.java | 43 +++++ .../dstu3/FhirResourceDaoValueSetDstu3.java | 34 +++- .../dao/expunge/ExpungeEverythingService.java | 2 + .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 62 +++++-- .../uhn/fhir/jpa/entity/TermCodeSystem.java | 28 ++- .../jpa/entity/TermCodeSystemVersion.java | 19 +- .../ca/uhn/fhir/jpa/entity/TermConcept.java | 77 ++++---- .../jpa/entity/TermConceptDesignation.java | 34 +++- .../uhn/fhir/jpa/entity/TermConceptMap.java | 47 +++-- .../fhir/jpa/entity/TermConceptMapGroup.java | 47 +++-- .../entity/TermConceptMapGroupElement.java | 35 ++-- .../TermConceptMapGroupElementTarget.java | 39 ++-- .../entity/TermConceptParentChildLink.java | 26 +-- .../fhir/jpa/entity/TermConceptProperty.java | 43 +++-- .../ca/uhn/fhir/jpa/entity/TermValueSet.java | 144 +++++++++++++++ .../uhn/fhir/jpa/entity/TermValueSetCode.java | 172 ++++++++++++++++++ .../jpa/term/BaseHapiTerminologySvcImpl.java | 102 ++++++++++- .../fhir/jpa/term/IHapiTerminologySvc.java | 4 + .../FhirResourceDaoDstu3TerminologyTest.java | 49 ++--- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 8 + .../r4/FhirResourceDaoR4TerminologyTest.java | 2 +- .../dao/r4/FhirResourceDaoR4ValueSetTest.java | 30 +-- .../jpa/term/TerminologySvcImplDstu3Test.java | 2 +- .../jpa/term/TerminologySvcImplR4Test.java | 126 ++++++++++++- .../tasks/HapiFhirJpaMigrationTasks.java | 35 ++++ 30 files changed, 1080 insertions(+), 218 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java index 6b5df86e696..8d234d690bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java @@ -23,7 +23,7 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.*; public class ValidateUtil { @@ -37,7 +37,7 @@ public class ValidateUtil { } /** - * Throws {@link IllegalArgumentException} if theValue is <= theMinimum + * Throws {@link IllegalArgumentException} if theValue is < theMinimum */ public static void isGreaterThanOrEqualTo(long theValue, long theMinimum, String theMessage) { if (theValue < theMinimum) { @@ -45,6 +45,12 @@ public class ValidateUtil { } } + public static void isNotBlankOrThrowIllegalArgument(String theString, String theMessage) { + if (isBlank(theString)) { + throw new IllegalArgumentException(theMessage); + } + } + public static void isNotBlankOrThrowInvalidRequest(String theString, String theMessage) { if (isBlank(theString)) { throw new InvalidRequestException(theMessage); @@ -57,6 +63,12 @@ public class ValidateUtil { } } + public static void isNotTooLongOrThrowIllegalArgument(String theString, int theMaxLength, String theMessage) { + if (length(theString) > theMaxLength) { + throw new IllegalArgumentException(theMessage); + } + } + public static void isTrueOrThrowInvalidRequest(boolean theSuccess, String theMessage) { if (theSuccess == false) { throw new InvalidRequestException(theMessage); 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 5838eb9baf1..45d5426bda2 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 @@ -115,8 +115,9 @@ ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascadi ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation +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.cannotCreateDuplicateCodeSystemUri=Can not create multiple code systems with URI "{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.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 1a149a9134e..d8df260f116 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -145,6 +145,10 @@ public class DaoConfig { private boolean myEnableInMemorySubscriptionMatching = true; private boolean myEnforceReferenceTargetTypes = true; private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC; + /** + * EXPERIMENTAL - Do not use in production! Do not change default of {@code false}! + */ + private boolean myPreExpandValueSetsExperimental = false; /** * Constructor @@ -1600,6 +1604,34 @@ public class DaoConfig { myModelConfig.setWebsocketContextPath(theWebsocketContextPath); } + /** + * EXPERIMENTAL - Do not use in production! + *

+ * If set to {@code true}, ValueSets and expansions are stored in terminology tables. This is to facilitate + * future optimization of the $expand operation on large ValueSets. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ */ + public boolean isPreExpandValueSetsExperimental() { + return myPreExpandValueSetsExperimental; + } + + /** + * EXPERIMENTAL - Do not use in production! + *

+ * If set to {@code true}, ValueSets and expansions are stored in terminology tables. This is to facilitate + * future optimization of the $expand operation on large ValueSets. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ */ + public void setPreExpandValueSetsExperimental(boolean thePreExpandValueSetsExperimental) { + myPreExpandValueSetsExperimental = thePreExpandValueSetsExperimental; + } + public enum IndexEnabledEnum { ENABLED, DISABLED diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java index 5fe695bc06c..3a672f8a312 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemDao.java @@ -34,7 +34,7 @@ public interface ITermCodeSystemDao extends JpaRepository TermCodeSystem findByCodeSystemUri(@Param("code_system_uri") String theCodeSystemUri); @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myResourcePid = :resource_pid") - TermCodeSystem findByResourcePid(@Param("resource_pid") Long theReourcePid); + TermCodeSystem findByResourcePid(@Param("resource_pid") Long theResourcePid); @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myCurrentVersion.myId = :csv_pid") Optional findWithCodeSystemVersionAsCurrentVersion(@Param("csv_pid") Long theCodeSystemVersionPid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java new file mode 100644 index 00000000000..5702aceb846 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.entity.TermValueSetCode; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ITermValueSetCodeDao extends JpaRepository { + + @Query("DELETE FROM TermValueSetCode vsc WHERE vsc.myValueSet.myId = :pid") + @Modifying + void deleteTermValueSetCodesByValueSetId(@Param("pid") Long theValueSetId); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java new file mode 100644 index 00000000000..087dcefe70e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.dao.data; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.entity.TermValueSet; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface ITermValueSetDao extends JpaRepository { + + @Query("DELETE FROM TermValueSet vs WHERE vs.myId = :pid") + @Modifying + void deleteTermValueSetById(@Param("pid") Long theId); + + @Query("SELECT vs FROM TermValueSet vs WHERE vs.myResourcePid = :resource_pid") + Optional findByResourcePid(@Param("resource_pid") Long theResourcePid); + + @Query("SELECT vs FROM TermValueSet vs WHERE vs.myUrl = :url") + Optional findByUrl(@Param("url") String theUrl); + +} 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 93116549405..2f83a634c66 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 @@ -23,11 +23,15 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; 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.util.LogicUtil; 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.util.ElementUtil; import org.apache.commons.codec.binary.StringUtils; +import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; @@ -37,6 +41,8 @@ import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.exceptions.FHIRException; +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.slf4j.Logger; @@ -45,14 +51,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import java.util.Collections; +import java.util.Date; import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoValueSet { - private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class); + + @Autowired + private IHapiTerminologySvc myHapiTerminologySvc; + @Autowired @Qualifier("myJpaValidationSupportChainDstu3") private IValidationSupport myValidationSupport; @@ -295,4 +305,26 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3 // nothing } + @Override + protected ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, + boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { + ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); + + if (myDaoConfig.isPreExpandValueSetsExperimental()) { + if (retVal.getDeleted() == null) { + try { + ValueSet valueSet = (ValueSet) theResource; + org.hl7.fhir.r4.model.ValueSet converted = VersionConvertor_30_40.convertValueSet(valueSet); + myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, converted); + } catch (FHIRException fe) { + throw new InternalErrorException(fe); + } + } else { + myHapiTerminologySvc.deleteValueSetAndChildren(retVal); + } + } + + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java index b704bb0668e..feaf1a820a9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java @@ -74,6 +74,8 @@ public class ExpungeEverythingService { counter.addAndGet(doExpungeEverythingQuery(txTemplate, ResourceLink.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, SearchResult.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, SearchInclude.class)); + counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermValueSetCode.class)); + counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermValueSet.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptParentChildLink.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptMapGroupElementTarget.class)); counter.addAndGet(doExpungeEverythingQuery(txTemplate, TermConceptMapGroupElement.class)); 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 c1c685ceb00..68e78e778db 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 @@ -20,34 +20,43 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.Collections; -import java.util.List; - -import org.apache.commons.codec.binary.StringUtils; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; -import org.hl7.fhir.r4.model.ValueSet.*; -import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; 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.util.LogicUtil; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ElementUtil; +import org.apache.commons.codec.binary.StringUtils; +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.hapi.ctx.HapiWorkerContext; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; +import org.hl7.fhir.r4.model.ValueSet.FilterOperator; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 implements IFhirResourceDaoValueSet { + @Autowired + private IHapiTerminologySvc myHapiTerminologySvc; + @Autowired @Qualifier("myJpaValidationSupportChainR4") private IValidationSupport myValidationSupport; @@ -296,4 +305,21 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4 imple // nothing } + @Override + protected ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, + boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { + ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); + + if (myDaoConfig.isPreExpandValueSetsExperimental()) { + if (retVal.getDeleted() == null) { + ValueSet valueSet = (ValueSet) theResource; + myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, valueSet); + } else { + myHapiTerminologySvc.deleteValueSetAndChildren(retVal); + } + } + + return retVal; + } + } 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 fd23f3f4065..6d468b49ae6 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 @@ -21,13 +21,16 @@ 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.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; //@formatter:off @Table(name = "TRM_CODESYSTEM", uniqueConstraints = { @@ -37,9 +40,11 @@ import static org.apache.commons.lang3.StringUtils.left; //@formatter:on public class TermCodeSystem implements Serializable { private static final long serialVersionUID = 1L; - public static final int CS_NAME_LENGTH = 200; - @Column(name = "CODE_SYSTEM_URI", nullable = false) + private static final int MAX_NAME_LENGTH = 200; + static final int MAX_URL_LENGTH = 200; + + @Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH) private String myCodeSystemUri; @OneToOne() @@ -55,7 +60,7 @@ public class TermCodeSystem implements Serializable { private ResourceTable myResource; @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourcePid; - @Column(name = "CS_NAME", nullable = true) + @Column(name = "CS_NAME", nullable = true, length = MAX_NAME_LENGTH) private String myName; public String getCodeSystemUri() { @@ -66,16 +71,21 @@ public class TermCodeSystem implements Serializable { return myName; } - public void setCodeSystemUri(String theCodeSystemUri) { + public TermCodeSystem setCodeSystemUri(@Nonnull String theCodeSystemUri) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCodeSystemUri, "theCodeSystemUri must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystemUri, MAX_URL_LENGTH, + "URI exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theCodeSystemUri)); myCodeSystemUri = theCodeSystemUri; + return this; } public TermCodeSystemVersion getCurrentVersion() { return myCurrentVersion; } - public void setCurrentVersion(TermCodeSystemVersion theCurrentVersion) { + public TermCodeSystem setCurrentVersion(TermCodeSystemVersion theCurrentVersion) { myCurrentVersion = theCurrentVersion; + return this; } public Long getPid() { @@ -86,12 +96,14 @@ public class TermCodeSystem implements Serializable { return myResource; } - public void setName(String theName) { - myName = left(theName, CS_NAME_LENGTH); + public TermCodeSystem setName(String theName) { + myName = left(theName, MAX_NAME_LENGTH); + return this; } - public void setResource(ResourceTable theResource) { + public TermCodeSystem setResource(ResourceTable theResource) { myResource = theResource; + return this; } @Override 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 2bd272cb6a5..969710901ea 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 @@ -22,12 +22,15 @@ 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 javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import static org.apache.commons.lang3.StringUtils.length; + //@formatter:off @Table(name = "TRM_CODESYSTEM_VER" // Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this) @@ -37,6 +40,8 @@ import java.util.Collection; public class TermCodeSystemVersion implements Serializable { private static final long serialVersionUID = 1L; + static final int MAX_VERSION_LENGTH = 200; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") private Collection myConcepts; @@ -50,7 +55,7 @@ public class TermCodeSystemVersion implements Serializable { @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CODESYSVER_RES_ID")) private ResourceTable myResource; - @Column(name = "CS_VERSION_ID", nullable = true, updatable = false) + @Column(name = "CS_VERSION_ID", nullable = true, updatable = false, length = MAX_VERSION_LENGTH) private String myCodeSystemVersionId; /** * This was added in HAPI FHIR 3.3.0 and is nullable just to avoid migration @@ -104,16 +109,21 @@ public class TermCodeSystemVersion implements Serializable { return myCodeSystem; } - public void setCodeSystem(TermCodeSystem theCodeSystem) { + public TermCodeSystemVersion setCodeSystem(TermCodeSystem theCodeSystem) { myCodeSystem = theCodeSystem; + return this; } public String getCodeSystemVersionId() { return myCodeSystemVersionId; } - public void setCodeSystemVersionId(String theCodeSystemVersionId) { + public TermCodeSystemVersion setCodeSystemVersionId(String theCodeSystemVersionId) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument( + theCodeSystemVersionId, MAX_VERSION_LENGTH, + "Version ID exceeds maximum length (" + MAX_VERSION_LENGTH + "): " + length(theCodeSystemVersionId)); myCodeSystemVersionId = theCodeSystemVersionId; + return this; } public Collection getConcepts() { @@ -131,8 +141,9 @@ public class TermCodeSystemVersion implements Serializable { return myResource; } - public void setResource(ResourceTable theResource) { + public TermCodeSystemVersion setResource(ResourceTable theResource) { myResource = theResource; + return this; } @Override 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 bfe8ee61f53..3a3158cc4bc 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 @@ -1,25 +1,5 @@ package ca.uhn.fhir.jpa.entity; -import ca.uhn.fhir.context.support.IContextValidationSupport; -import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; -import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; -import ca.uhn.fhir.util.ValidateUtil; -import org.apache.commons.lang3.Validate; -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 org.hibernate.search.annotations.*; -import org.hl7.fhir.r4.model.Coding; - -import javax.annotation.Nonnull; -import javax.persistence.*; -import javax.persistence.Index; -import java.io.Serializable; -import java.util.*; - -import static org.apache.commons.lang3.StringUtils.isNotBlank; - /* * #%L * HAPI FHIR JPA Server @@ -40,6 +20,27 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ +import ca.uhn.fhir.context.support.IContextValidationSupport; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.Validate; +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 org.hibernate.search.annotations.*; +import org.hl7.fhir.r4.model.Coding; + +import javax.annotation.Nonnull; +import javax.persistence.Index; +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Indexed(interceptor = DeferConceptIndexingInterceptor.class) @Table(name = "TRM_CONCEPT", uniqueConstraints = { @@ -49,16 +50,17 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED") }) public class TermConcept implements Serializable { - public static final int CODE_LENGTH = 500; - protected static final int MAX_DESC_LENGTH = 400; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class); private static final long serialVersionUID = 1L; + static final int MAX_CODE_LENGTH = 500; + static final int MAX_DESC_LENGTH = 400; + @OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {}) private Collection myChildren; - @Column(name = "CODE", length = CODE_LENGTH, nullable = false) + @Column(name = "CODE", nullable = false, length = MAX_CODE_LENGTH) @Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),}) private String myCode; @Temporal(TemporalType.TIMESTAMP) @@ -70,7 +72,7 @@ public class TermConcept implements Serializable { @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false) @Fields({@Field(name = "myCodeSystemVersionPid")}) private long myCodeSystemVersionPid; - @Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true) + @Column(name = "DISPLAY", nullable = true, length = MAX_DESC_LENGTH) @Fields({ @Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")), @@ -187,20 +189,24 @@ public class TermConcept implements Serializable { return myCode; } - public void setCode(String theCode) { - ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "Code must not be null or empty"); + public TermConcept setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, MAX_CODE_LENGTH, + "Code exceeds maximum length (" + MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermCodeSystemVersion getCodeSystemVersion() { return myCodeSystem; } - public void setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { + public TermConcept setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { myCodeSystem = theCodeSystemVersion; if (theCodeSystemVersion.getPid() != null) { myCodeSystemVersionPid = theCodeSystemVersion.getPid(); } + return this; } public List getCodingProperties(String thePropertyName) { @@ -231,10 +237,7 @@ public class TermConcept implements Serializable { } public TermConcept setDisplay(String theDisplay) { - myDisplay = theDisplay; - if (isNotBlank(theDisplay) && theDisplay.length() > MAX_DESC_LENGTH) { - myDisplay = myDisplay.substring(0, MAX_DESC_LENGTH); - } + myDisplay = left(theDisplay, MAX_DESC_LENGTH); return this; } @@ -246,8 +249,9 @@ public class TermConcept implements Serializable { return myIndexStatus; } - public void setIndexStatus(Long theIndexStatus) { + public TermConcept setIndexStatus(Long theIndexStatus) { myIndexStatus = theIndexStatus; + return this; } public String getParentPidsAsString() { @@ -272,8 +276,9 @@ public class TermConcept implements Serializable { return mySequence; } - public void setSequence(Integer theSequence) { + public TermConcept setSequence(Integer theSequence) { mySequence = theSequence; + return this; } public List getStringProperties(String thePropertyName) { @@ -300,8 +305,9 @@ public class TermConcept implements Serializable { return myUpdated; } - public void setUpdated(Date theUpdated) { + public TermConcept setUpdated(Date theUpdated) { myUpdated = theUpdated; + return this; } @Override @@ -355,8 +361,9 @@ public class TermConcept implements Serializable { myParentPids = b.toString(); } - public void setParentPids(String theParentPids) { + public TermConcept setParentPids(String theParentPids) { myParentPids = theParentPids; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java index 9741d4377b4..4aeea98ccae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptDesignation.java @@ -20,16 +20,24 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; + +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_DESIG", uniqueConstraints = { }, indexes = { }) public class TermConceptDesignation implements Serializable { - private static final long serialVersionUID = 1L; + + private static final int MAX_LENGTH = 500; + @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT")) private TermConcept myConcept; @@ -38,15 +46,15 @@ public class TermConceptDesignation implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_DESIG_PID") @Column(name = "PID") private Long myId; - @Column(name = "LANG", length = 500, nullable = true) + @Column(name = "LANG", nullable = true, length = MAX_LENGTH) private String myLanguage; - @Column(name = "USE_SYSTEM", length = 500, nullable = true) + @Column(name = "USE_SYSTEM", nullable = true, length = MAX_LENGTH) private String myUseSystem; - @Column(name = "USE_CODE", length = 500, nullable = true) + @Column(name = "USE_CODE", nullable = true, length = MAX_LENGTH) private String myUseCode; - @Column(name = "USE_DISPLAY", length = 500, nullable = true) + @Column(name = "USE_DISPLAY", nullable = true, length = MAX_LENGTH) private String myUseDisplay; - @Column(name = "VAL", length = 500, nullable = false) + @Column(name = "VAL", nullable = false, length = MAX_LENGTH) private String myValue; /** * TODO: Make this non-null @@ -62,6 +70,8 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setLanguage(String theLanguage) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theLanguage, MAX_LENGTH, + "Language exceeds maximum length (" + MAX_LENGTH + "): " + length(theLanguage)); myLanguage = theLanguage; return this; } @@ -71,6 +81,8 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseCode(String theUseCode) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseCode, MAX_LENGTH, + "Use code exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseCode)); myUseCode = theUseCode; return this; } @@ -80,7 +92,7 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseDisplay(String theUseDisplay) { - myUseDisplay = theUseDisplay; + myUseDisplay = left(theUseDisplay, MAX_LENGTH); return this; } @@ -89,6 +101,9 @@ public class TermConceptDesignation implements Serializable { } public TermConceptDesignation setUseSystem(String theUseSystem) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseSystem, MAX_LENGTH, + "Use system exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseSystem)); + myUseSystem = theUseSystem; return this; } @@ -97,7 +112,10 @@ public class TermConceptDesignation implements Serializable { return myValue; } - public TermConceptDesignation setValue(String theValue) { + public TermConceptDesignation setValue(@Nonnull String theValue) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theValue, "theValue must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theValue, MAX_LENGTH, + "Value exceeds maximum length (" + MAX_LENGTH + "): " + length(theValue)); myValue = theValue; return this; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java index fd67f26716c..24e1dda2797 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMap.java @@ -21,19 +21,27 @@ 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.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP", uniqueConstraints = { @UniqueConstraint(name = "IDX_CONCEPT_MAP_URL", columnNames = {"URL"}) }) public class TermConceptMap implements Serializable { + private static final long serialVersionUID = 1L; + + static final int MAX_URL_LENGTH = 200; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_PID", sequenceName = "SEQ_CONCEPT_MAP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_PID") @@ -47,13 +55,13 @@ public class TermConceptMap implements Serializable { @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourcePid; - @Column(name = "SOURCE_URL", nullable = true, length = 200) + @Column(name = "SOURCE_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String mySource; - @Column(name = "TARGET_URL", nullable = true, length = 200) + @Column(name = "TARGET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myTarget; - @Column(name = "URL", length = 200, nullable = false) + @Column(name = "URL", nullable = false, length = MAX_URL_LENGTH) private String myUrl; @OneToMany(mappedBy = "myConceptMap") @@ -75,47 +83,58 @@ public class TermConceptMap implements Serializable { return myResource; } - public void setResource(ResourceTable resource) { - myResource = resource; + public TermConceptMap setResource(ResourceTable theResource) { + myResource = theResource; + return this; } public Long getResourcePid() { return myResourcePid; } - public void setResourcePid(Long resourcePid) { - myResourcePid = resourcePid; + public TermConceptMap setResourcePid(Long theResourcePid) { + myResourcePid = theResourcePid; + return this; } public String getSource() { return mySource; } - public void setSource(String source) { - mySource = source; + public TermConceptMap setSource(String theSource) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSource, TermValueSet.MAX_URL_LENGTH, + "Source exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theSource)); + mySource = theSource; + return this; } public String getTarget() { return myTarget; } - public void setTarget(String target) { - myTarget = target; + public TermConceptMap setTarget(String theTarget) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTarget, TermValueSet.MAX_URL_LENGTH, + "Target exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theTarget)); + myTarget = theTarget; + return this; } public String getUrl() { return myUrl; } - public void setUrl(String theUrl) { + public TermConceptMap setUrl(@Nonnull String theUrl) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theUrl, "theUrl must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUrl, MAX_URL_LENGTH, + "URL exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theUrl)); myUrl = theUrl; + return this; } @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) - .append("myResource", myResource.toString()) .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) .append("myResourcePid", myResourcePid) .append("mySource", mySource) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java index 72d23359a0b..d11ae915f46 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroup.java @@ -20,17 +20,23 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GROUP") public class TermConceptMapGroup implements Serializable { + private static final long serialVersionUID = 1L; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_GROUP_PID", sequenceName = "SEQ_CONCEPT_MAP_GROUP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_GROUP_PID") @@ -41,36 +47,37 @@ public class TermConceptMapGroup implements Serializable { @JoinColumn(name = "CONCEPT_MAP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGROUP_CONCEPTMAP")) private TermConceptMap myConceptMap; - @Column(name = "SOURCE_URL", nullable = false, length = 200) + @Column(name = "SOURCE_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) private String mySource; - @Column(name = "SOURCE_VERSION", length = 100) + @Column(name = "SOURCE_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySourceVersion; - @Column(name = "TARGET_URL", nullable = false, length = 200) + @Column(name = "TARGET_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) private String myTarget; - @Column(name = "TARGET_VERSION", length = 100) + @Column(name = "TARGET_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String myTargetVersion; @OneToMany(mappedBy = "myConceptMapGroup") private List myConceptMapGroupElements; - @Column(name= "CONCEPT_MAP_URL", length = 200, nullable = true) + @Column(name= "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name= "SOURCE_VS", length = 200, nullable = true) + @Column(name= "SOURCE_VS", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String mySourceValueSet; - @Column(name= "TARGET_VS", length = 200, nullable = true) + @Column(name= "TARGET_VS", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myTargetValueSet; public TermConceptMap getConceptMap() { return myConceptMap; } - public void setConceptMap(TermConceptMap theTermConceptMap) { + public TermConceptMapGroup setConceptMap(TermConceptMap theTermConceptMap) { myConceptMap = theTermConceptMap; + return this; } public List getConceptMapGroupElements() { @@ -96,8 +103,12 @@ public class TermConceptMapGroup implements Serializable { return mySource; } - public void setSource(String theSource) { + public TermConceptMapGroup setSource(@Nonnull String theSource) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theSource, "theSource must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSource, TermCodeSystem.MAX_URL_LENGTH, + "Source exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSource)); this.mySource = theSource; + return this; } public String getSourceValueSet() { @@ -111,16 +122,23 @@ public class TermConceptMapGroup implements Serializable { return mySourceVersion; } - public void setSourceVersion(String theSourceVersion) { + public TermConceptMapGroup setSourceVersion(String theSourceVersion) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSourceVersion, TermCodeSystemVersion.MAX_VERSION_LENGTH, + "Source version ID exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): " + length(theSourceVersion)); mySourceVersion = theSourceVersion; + return this; } public String getTarget() { return myTarget; } - public void setTarget(String theTarget) { + public TermConceptMapGroup setTarget(@Nonnull String theTarget) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theTarget, "theTarget must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTarget, TermCodeSystem.MAX_URL_LENGTH, + "Target exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theTarget)); this.myTarget = theTarget; + return this; } public String getTargetValueSet() { @@ -134,13 +152,16 @@ public class TermConceptMapGroup implements Serializable { return myTargetVersion; } - public void setTargetVersion(String theTargetVersion) { + public TermConceptMapGroup setTargetVersion(String theTargetVersion) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTargetVersion, TermCodeSystemVersion.MAX_VERSION_LENGTH, + "Target version ID exceeds maximum length (" + TermCodeSystemVersion.MAX_VERSION_LENGTH + "): " + length(theTargetVersion)); myTargetVersion = theTargetVersion; + return this; } @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)")) .append("mySource", mySource) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java index d36689129ae..f182b16165e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElement.java @@ -20,22 +20,28 @@ package ca.uhn.fhir.jpa.entity; * #L% */ -import org.apache.commons.lang3.Validate; +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.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GRP_ELEMENT", indexes = { @Index(name = "IDX_CNCPT_MAP_GRP_CD", columnList = "SOURCE_CODE") }) public class TermConceptMapGroupElement implements Serializable { + private static final long serialVersionUID = 1L; + @Id() @SequenceGenerator(name = "SEQ_CONCEPT_MAP_GRP_ELM_PID", sequenceName = "SEQ_CONCEPT_MAP_GRP_ELM_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_MAP_GRP_ELM_PID") @@ -46,7 +52,7 @@ public class TermConceptMapGroupElement implements Serializable { @JoinColumn(name = "CONCEPT_MAP_GROUP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGELEMENT_GROUP")) private TermConceptMapGroup myConceptMapGroup; - @Column(name = "SOURCE_CODE", nullable = false, length = TermConcept.CODE_LENGTH) + @Column(name = "SOURCE_CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) private String myCode; @Column(name = "SOURCE_DISPLAY", length = TermConcept.MAX_DESC_LENGTH) @@ -55,33 +61,37 @@ public class TermConceptMapGroupElement implements Serializable { @OneToMany(mappedBy = "myConceptMapGroupElement") private List myConceptMapGroupElementTargets; - @Column(name = "CONCEPT_MAP_URL", length = 200) + @Column(name = "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name = "SYSTEM_URL", length = 200) + @Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH) private String mySystem; - @Column(name = "SYSTEM_VERSION", length = 200) + @Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySystemVersion; - @Column(name = "VALUESET_URL", length = 200) + @Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myValueSet; public String getCode() { return myCode; } - public void setCode(String theCode) { - Validate.notBlank(theCode, "theCode must not be blank"); + public TermConceptMapGroupElement setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermConceptMapGroup getConceptMapGroup() { return myConceptMapGroup; } - public void setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) { + public TermConceptMapGroupElement setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) { myConceptMapGroup = theTermConceptMapGroup; + return this; } public List getConceptMapGroupElementTargets() { @@ -103,8 +113,9 @@ public class TermConceptMapGroupElement implements Serializable { return myDisplay; } - public void setDisplay(String theDisplay) { - myDisplay = theDisplay; + public TermConceptMapGroupElement setDisplay(String theDisplay) { + myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); + return this; } public Long getId() { @@ -158,7 +169,7 @@ public class TermConceptMapGroupElement implements Serializable { @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)")) .append("myCode", myCode) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java index 754161ca0a9..d463f7dddfb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptMapGroupElementTarget.java @@ -20,20 +20,28 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +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 org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_MAP_GRP_ELM_TGT", indexes = { @Index(name = "IDX_CNCPT_MP_GRP_ELM_TGT_CD", columnList = "TARGET_CODE") }) public class TermConceptMapGroupElementTarget implements Serializable { + private static final long serialVersionUID = 1L; + + static final int MAX_EQUIVALENCE_LENGTH = 50; + @Id() @SequenceGenerator(name = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID", sequenceName = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID") @@ -44,31 +52,38 @@ public class TermConceptMapGroupElementTarget implements Serializable { @JoinColumn(name = "CONCEPT_MAP_GRP_ELM_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGETARGET_ELEMENT")) private TermConceptMapGroupElement myConceptMapGroupElement; - @Column(name = "TARGET_CODE", nullable = false, length = TermConcept.CODE_LENGTH) + @Column(name = "TARGET_CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) private String myCode; - @Column(name = "TARGET_DISPLAY", length = TermConcept.MAX_DESC_LENGTH) + @Column(name = "TARGET_DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) private String myDisplay; @Enumerated(EnumType.STRING) - @Column(name = "TARGET_EQUIVALENCE", length = 50) + @Column(name = "TARGET_EQUIVALENCE", nullable = true, length = MAX_EQUIVALENCE_LENGTH) private ConceptMapEquivalence myEquivalence; - @Column(name = "CONCEPT_MAP_URL", length = 200) + @Column(name = "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH) private String myConceptMapUrl; - @Column(name = "SYSTEM_URL", length = 200) + + @Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH) private String mySystem; - @Column(name = "SYSTEM_VERSION", length = 200) + + @Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH) private String mySystemVersion; - @Column(name = "VALUESET_URL", length = 200) + + @Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH) private String myValueSet; public String getCode() { return myCode; } - public void setCode(String theCode) { + public TermConceptMapGroupElementTarget setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); myCode = theCode; + return this; } public TermConceptMapGroupElement getConceptMapGroupElement() { @@ -90,16 +105,18 @@ public class TermConceptMapGroupElementTarget implements Serializable { return myDisplay; } - public void setDisplay(String theDisplay) { + public TermConceptMapGroupElementTarget setDisplay(String theDisplay) { myDisplay = theDisplay; + return this; } public ConceptMapEquivalence getEquivalence() { return myEquivalence; } - public void setEquivalence(ConceptMapEquivalence theEquivalence) { + public TermConceptMapGroupElementTarget setEquivalence(ConceptMapEquivalence theEquivalence) { myEquivalence = theEquivalence; + return this; } public Long getId() { @@ -155,7 +172,7 @@ public class TermConceptMapGroupElementTarget implements Serializable { @Override public String toString() { - return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("myId", myId) .append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)")) .append("myCode", myCode) 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 dfb380d605e..c7d38eed131 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,21 +20,9 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import javax.persistence.*; import java.io.Serializable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; - @Entity @Table(name = "TRM_CONCEPT_PC_LINK") public class TermConceptParentChildLink implements Serializable { @@ -136,20 +124,24 @@ public class TermConceptParentChildLink implements Serializable { return result; } - public void setChild(TermConcept theChild) { + public TermConceptParentChildLink setChild(TermConcept theChild) { myChild = theChild; + return this; } - public void setCodeSystem(TermCodeSystemVersion theCodeSystem) { + public TermConceptParentChildLink setCodeSystem(TermCodeSystemVersion theCodeSystem) { myCodeSystem = theCodeSystem; + return this; } - public void setParent(TermConcept theParent) { + public TermConceptParentChildLink setParent(TermConcept theParent) { myParent = theParent; + return this; } - public void setRelationshipType(RelationshipTypeEnum theRelationshipType) { + public TermConceptParentChildLink setRelationshipType(RelationshipTypeEnum theRelationshipType) { myRelationshipType = theRelationshipType; + return this; } public enum RelationshipTypeEnum { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java index 517dc1b70f1..144fc1ae632 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java @@ -20,21 +20,29 @@ package ca.uhn.fhir.jpa.entity; * #L% */ +import ca.uhn.fhir.util.ValidateUtil; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hibernate.validator.constraints.NotBlank; +import javax.annotation.Nonnull; import javax.persistence.*; import java.io.Serializable; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + @Entity @Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = { }, indexes = { }) public class TermConceptProperty implements Serializable { - - static final int MAX_PROPTYPE_ENUM_LENGTH = 6; private static final long serialVersionUID = 1L; + + private static final int MAX_LENGTH = 500; + static final int MAX_PROPTYPE_ENUM_LENGTH = 6; + @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) private TermConcept myConcept; @@ -51,22 +59,22 @@ public class TermConceptProperty implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID") @Column(name = "PID") private Long myId; - @Column(name = "PROP_KEY", length = 500, nullable = false) + @Column(name = "PROP_KEY", nullable = false, length = MAX_LENGTH) @NotBlank private String myKey; - @Column(name = "PROP_VAL", length = 500, nullable = true) + @Column(name = "PROP_VAL", nullable = true, length = MAX_LENGTH) private String myValue; - @Column(name = "PROP_TYPE", length = MAX_PROPTYPE_ENUM_LENGTH, nullable = false) + @Column(name = "PROP_TYPE", nullable = false, length = MAX_PROPTYPE_ENUM_LENGTH) private TermConceptPropertyTypeEnum myType; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_CODESYSTEM", length = 500, nullable = true) + @Column(name = "PROP_CODESYSTEM", length = MAX_LENGTH, nullable = true) private String myCodeSystem; /** * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ - @Column(name = "PROP_DISPLAY", length = 500, nullable = true) + @Column(name = "PROP_DISPLAY", length = MAX_LENGTH, nullable = true) private String myDisplay; /** @@ -80,6 +88,8 @@ public class TermConceptProperty implements Serializable { * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ public TermConceptProperty setCodeSystem(String theCodeSystem) { + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystem, MAX_LENGTH, + "Property code system exceeds maximum length (" + MAX_LENGTH + "): " + length(theCodeSystem)); myCodeSystem = theCodeSystem; return this; } @@ -95,7 +105,7 @@ public class TermConceptProperty implements Serializable { * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} */ public TermConceptProperty setDisplay(String theDisplay) { - myDisplay = theDisplay; + myDisplay = left(theDisplay, MAX_LENGTH); return this; } @@ -103,15 +113,20 @@ public class TermConceptProperty implements Serializable { return myKey; } - public void setKey(String theKey) { + public TermConceptProperty setKey(@Nonnull String theKey) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theKey, "theKey must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theKey, MAX_LENGTH, + "Code exceeds maximum length (" + MAX_LENGTH + "): " + length(theKey)); myKey = theKey; + return this; } public TermConceptPropertyTypeEnum getType() { return myType; } - public TermConceptProperty setType(TermConceptPropertyTypeEnum theType) { + public TermConceptProperty setType(@Nonnull TermConceptPropertyTypeEnum theType) { + Validate.notNull(theType); myType = theType; return this; } @@ -128,8 +143,9 @@ public class TermConceptProperty implements Serializable { * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string} * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property. */ - public void setValue(String theValue) { - myValue = theValue; + public TermConceptProperty setValue(String theValue) { + myValue = left(theValue, MAX_LENGTH); + return this; } public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { @@ -137,8 +153,9 @@ public class TermConceptProperty implements Serializable { return this; } - public void setConcept(TermConcept theConcept) { + public TermConceptProperty setConcept(TermConcept theConcept) { myConcept = theConcept; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java new file mode 100644 index 00000000000..d53d6d07191 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSet.java @@ -0,0 +1,144 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.jpa.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; + +import javax.annotation.Nonnull; +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + +@Table(name = "TRM_VALUESET", uniqueConstraints = { + @UniqueConstraint(name = "IDX_VALUESET_URL", columnNames = {"URL"}) +}) +@Entity() +public class TermValueSet implements Serializable { + private static final long serialVersionUID = 1L; + + private static final int MAX_NAME_LENGTH = 200; + static final int MAX_URL_LENGTH = 200; + + @Id() + @SequenceGenerator(name = "SEQ_VALUESET_PID", sequenceName = "SEQ_VALUESET_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_PID") + @Column(name = "PID") + private Long myId; + + @Column(name = "URL", nullable = false, length = MAX_URL_LENGTH) + private String myUrl; + + @OneToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_TRMVALUESET_RES")) + private ResourceTable myResource; + + @Column(name = "RES_ID", insertable = false, updatable = false) + private Long myResourcePid; + + @Column(name = "NAME", nullable = true, length = MAX_NAME_LENGTH) + private String myName; + + @OneToMany(mappedBy = "myValueSet") + private List myCodes; + + public Long getId() { + return myId; + } + + public String getUrl() { + return myUrl; + } + + public TermValueSet setUrl(@Nonnull String theUrl) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theUrl, "theUrl must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUrl, MAX_URL_LENGTH, + "URL exceeds maximum length (" + MAX_URL_LENGTH + "): " + length(theUrl)); + myUrl = theUrl; + return this; + } + + public ResourceTable getResource() { + return myResource; + } + + public TermValueSet setResource(ResourceTable theResource) { + myResource = theResource; + return this; + } + + public String getName() { + return myName; + } + + public TermValueSet setName(String theName) { + myName = left(theName, MAX_NAME_LENGTH); + return this; + } + + public List getCodes() { + if (myCodes == null) { + myCodes = new ArrayList<>(); + } + + return myCodes; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (!(theO instanceof TermValueSet)) return false; + + TermValueSet that = (TermValueSet) theO; + + return new EqualsBuilder() + .append(getUrl(), that.getUrl()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getUrl()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append("myUrl", myUrl) + .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) + .append("myResourcePid", myResourcePid) + .append("myName", myName) + .append(myCodes != null ? ("myCodes - size=" + myCodes.size()) : ("myCodes=(null)")) + .toString(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java new file mode 100644 index 00000000000..bbe0f518c35 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java @@ -0,0 +1,172 @@ +package ca.uhn.fhir.jpa.entity; + +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.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.annotation.Nonnull; +import javax.persistence.*; +import java.io.Serializable; + +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.length; + +@Table(name = "TRM_VALUESET_CODE", indexes = { + @Index(name = "IDX_VALUESET_CODE_CS_CD", columnList = "SYSTEM, CODE") +}) +@Entity() +public class TermValueSetCode implements Serializable { + private static final long serialVersionUID = 1L; + + @Id() + @SequenceGenerator(name = "SEQ_VALUESET_CODE_PID", sequenceName = "SEQ_VALUESET_CODE_PID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_CODE_PID") + @Column(name = "PID") + private Long myId; + + @ManyToOne() + @JoinColumn(name = "VALUESET_PID", referencedColumnName = "PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TRM_VALUESET_PID")) + private TermValueSet myValueSet; + + @Transient + private String myValueSetUrl; + + @Transient + private String myValueSetName; + + @Column(name = "SYSTEM", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH) + private String mySystem; + + @Column(name = "CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH) + private String myCode; + + @Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH) + private String myDisplay; + + public Long getId() { + return myId; + } + + public TermValueSet getValueSet() { + return myValueSet; + } + + public TermValueSetCode setValueSet(TermValueSet theValueSet) { + myValueSet = theValueSet; + return this; + } + + public String getValueSetUrl() { + if (myValueSetUrl == null) { + myValueSetUrl = getValueSet().getUrl(); + } + + return myValueSetUrl; + } + + public String getValueSetName() { + if (myValueSetName == null) { + myValueSetName = getValueSet().getName(); + } + + return myValueSetName; + } + + public String getSystem() { + return mySystem; + } + + public TermValueSetCode setSystem(@Nonnull String theSystem) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theSystem, "theSystem must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSystem, TermCodeSystem.MAX_URL_LENGTH, + "System exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSystem)); + + ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "theSystem must not be null or empty"); + if (theSystem.length() > TermCodeSystem.MAX_URL_LENGTH) { + throw new IllegalArgumentException(); + } + mySystem = theSystem; + return this; + } + + public String getCode() { + return myCode; + } + + public TermValueSetCode setCode(@Nonnull String theCode) { + ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); + ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, + "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); + + myCode = theCode; + return this; + } + + public String getDisplay() { + return myDisplay; + } + + public TermValueSetCode setDisplay(String theDisplay) { + myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH); + return this; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (!(theO instanceof TermValueSetCode)) return false; + + TermValueSetCode that = (TermValueSetCode) theO; + + return new EqualsBuilder() + .append(getValueSetUrl(), that.getValueSetUrl()) + .append(getSystem(), that.getSystem()) + .append(getCode(), that.getCode()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getValueSetUrl()) + .append(getSystem()) + .append(getCode()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append(myValueSet != null ? ("myValueSet - id=" + myValueSet.getId()) : ("myValueSet=(null)")) + .append("myValueSetUrl", this.getValueSetUrl()) + .append("myValueSetName", this.getValueSetName()) + .append("mySystem", mySystem) + .append("myCode", myCode) + .append("myDisplay", myDisplay) + .toString(); + } +} 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/BaseHapiTerminologySvcImpl.java index ba55a3712e9..165f1918d60 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/BaseHapiTerminologySvcImpl.java @@ -119,6 +119,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, @Autowired protected ITermConceptDesignationDao myConceptDesignationDao; @Autowired + protected ITermValueSetDao myValueSetDao; + @Autowired + protected ITermValueSetCodeDao myValueSetCodeDao; + @Autowired protected FhirContext myContext; @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; @@ -388,6 +392,31 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, deleteConceptMap(theResourceTable); } + public void deleteValueSet(ResourceTable theResourceTable) { + // Get existing entity so it can be deleted. + Optional optionalExistingTermValueSetById = myValueSetDao.findByResourcePid(theResourceTable.getId()); + + if (optionalExistingTermValueSetById.isPresent()) { + TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get(); + + ourLog.info("Deleting existing TermValueSet {} and its children...", existingTermValueSet.getId()); + myValueSetCodeDao.deleteTermValueSetCodesByValueSetId(existingTermValueSet.getId()); + myValueSetDao.deleteTermValueSetById(existingTermValueSet.getId()); + ourLog.info("Done deleting existing TermValueSet {} and its children.", existingTermValueSet.getId()); + + ourLog.info("Flushing..."); + myValueSetCodeDao.flush(); + myValueSetDao.flush(); + ourLog.info("Done flushing."); + } + } + + @Override + @Transactional + public void deleteValueSetAndChildren(ResourceTable theResourceTable) { + deleteValueSet(theResourceTable); + } + private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { int count; ourLog.info(" * Deleting {}", theDescriptor); @@ -690,7 +719,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private void expandWithoutHibernateSearch(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) { ourLog.trace("Hibernate search is not enabled"); - Validate.isTrue(theExpansionComponent.getParameter().isEmpty(), "Can not exapnd ValueSet with parameters - Hibernate Search is not enabled on this server."); + Validate.isTrue(theExpansionComponent.getParameter().isEmpty(), "Can not expand ValueSet with parameters - Hibernate Search is not enabled on this server."); Validate.isTrue(theInclude.getFilter().isEmpty(), "Can not expand ValueSet with filters - Hibernate Search is not enabled on this server."); Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server."); @@ -1144,7 +1173,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myCodeSystemDao.save(codeSystem); } else { if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemVersion.getResource().getId())) { - String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, + String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); throw new UnprocessableEntityException(msg); } @@ -1310,7 +1339,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget); if (codesSaved++ % 250 == 0) { - ourLog.info("Have saved {} codes in conceptmap", codesSaved); + ourLog.info("Have saved {} codes in ConceptMap", codesSaved); myConceptMapGroupElementTargetDao.flush(); } } @@ -1334,6 +1363,69 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ourLog.info("Done storing TermConceptMap."); } + @Override + @Transactional + public void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet) { + ourLog.info("Storing TermValueSet {}", theValueSet.getIdElement().getValue()); + + ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied"); + ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theValueSet.getUrl(), "ValueSet has no value for ValueSet.url"); + + TermValueSet termValueSet = new TermValueSet(); + termValueSet.setResource(theResourceTable); + termValueSet.setUrl(theValueSet.getUrl()); + termValueSet.setName(theValueSet.hasName() ? theValueSet.getName() : null); + + // We delete old versions; we don't support versioned ValueSets. + deleteValueSet(theResourceTable); + + /* + * Do the upload. + */ + String url = termValueSet.getUrl(); + Optional optionalExistingTermValueSetByUrl = myValueSetDao.findByUrl(url); + if (!optionalExistingTermValueSetByUrl.isPresent()) { + myValueSetDao.save(termValueSet); + int codesSaved = 0; + + ValueSet expandedValueSet = expandValueSet(theValueSet); + if (expandedValueSet.hasExpansion()) { + if (expandedValueSet.getExpansion().hasTotal() && expandedValueSet.getExpansion().getTotal() > 0) { + TermValueSetCode code; + for (ValueSet.ValueSetExpansionContainsComponent contains : expandedValueSet.getExpansion().getContains()) { + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getSystem(), "ValueSet contains a code with no system value"); + ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getCode(), "ValueSet contains a code with no code value"); + + code = new TermValueSetCode(); + code.setValueSet(termValueSet); + code.setSystem(contains.getSystem()); + code.setCode(contains.getCode()); + code.setDisplay(contains.hasDisplay() ? contains.getDisplay() : null); + myValueSetCodeDao.save(code); + + if (codesSaved++ % 250 == 0) { + ourLog.info("Have pre-expanded {} codes in ValueSet", codesSaved); + myValueSetCodeDao.flush(); + } + } + } + } + + } else { + TermValueSet existingTermValueSet = optionalExistingTermValueSetByUrl.get(); + + String msg = myContext.getLocalizer().getMessage( + BaseHapiTerminologySvcImpl.class, + "cannotCreateDuplicateValueSetUrl", + url, + existingTermValueSet.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + + throw new UnprocessableEntityException(msg); + } + + ourLog.info("Done storing TermValueSet."); + } + @Override public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); @@ -1581,9 +1673,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList theConceptsStack, IdentityHashMap theAllConcepts) { - ValidateUtil.isTrueOrThrowInvalidRequest(theConcept.getCodeSystemVersion() != null, "CodesystemValue is null"); + 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"); + 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()); 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/IHapiTerminologySvc.java index cc861e27f7f..4d50f75afb5 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/IHapiTerminologySvc.java @@ -81,8 +81,12 @@ public interface IHapiTerminologySvc { void deleteConceptMapAndChildren(ResourceTable theResourceTable); + void deleteValueSetAndChildren(ResourceTable theResourceTable); + void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap); + void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet); + boolean supportsSystem(String theCodeSystem); List translate(TranslationRequest theTranslationRequest); 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 b03e0b09148..64390e9a671 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 @@ -1,31 +1,13 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsStringIgnoringCase; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.util.*; - -import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; -import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; -import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu3.model.ValueSet.*; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; -import org.springframework.beans.factory.annotation.Autowired; - import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.model.entity.*; -import ca.uhn.fhir.jpa.entity.*; +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.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.param.TokenParam; @@ -35,6 +17,25 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; +import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; +import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @@ -226,7 +227,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myCodeSystemDao.create(codeSystem, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); + assertEquals("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); } } 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 e3d27011253..877d0601247 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 @@ -118,6 +118,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myCodeSystemDaoR4") protected IFhirResourceDaoCodeSystem myCodeSystemDao; @Autowired + protected ITermCodeSystemDao myTermCodeSystemDao; + @Autowired + protected ITermCodeSystemVersionDao myTermCodeSystemVersionDao; + @Autowired @Qualifier("myCompartmentDefinitionDaoR4") protected IFhirResourceDao myCompartmentDefinitionDao; @Autowired @@ -287,6 +291,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myValueSetDaoR4") protected IFhirResourceDaoValueSet myValueSetDao; @Autowired + protected ITermValueSetDao myTermValueSetDao; + @Autowired + protected ITermValueSetCodeDao myTermValueSetCodeDao; + @Autowired protected ITermConceptMapDao myTermConceptMapDao; @Autowired protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; 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 589f593c373..e362d462251 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 @@ -222,7 +222,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { myCodeSystemDao.create(codeSystem, mySrd); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); + assertEquals("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/" + id.getIdPart(), e.getMessage()); } } 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 2a10ef2f9c6..cf3d7ce0bd6 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 @@ -1,34 +1,20 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; - -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.CodeType; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.UriType; -import org.hl7.fhir.r4.model.ValueSet; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; -import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; -import ca.uhn.fhir.util.TestUtil; +import java.io.IOException; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { @@ -224,7 +210,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { @Test - public void testValiedateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { + public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { IPrimitiveType display = null; Coding coding = null; CodeableConcept codeableConcept = null; 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 e7006342458..3089835220d 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 @@ -177,7 +177,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs); fail(); } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); + assertThat(e.getMessage(), containsString("Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/")); } } 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 785de25822c..e1ebe208e36 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 @@ -2,17 +2,12 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.entity.TermConceptMap; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; -import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; -import org.hl7.fhir.r4.model.UriType; import org.junit.*; import org.junit.rules.ExpectedException; import org.slf4j.Logger; @@ -21,6 +16,7 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import java.io.IOException; import java.util.List; import java.util.Optional; @@ -31,6 +27,8 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @Rule public final ExpectedException expectedException = ExpectedException.none(); private IIdType myConceptMapId; + private IIdType myExtensionalCsId; + private IIdType myExtensionalVsId; @Before public void before() { @@ -40,6 +38,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { @After public void after() { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); + myDaoConfig.setPreExpandValueSetsExperimental(new DaoConfig().isPreExpandValueSetsExperimental()); } private void createAndPersistConceptMap() { @@ -56,6 +55,39 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { }); } + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(); + } + + private void loadAndPersistCodeSystem() throws IOException { + CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + persistCodeSystem(codeSystem); + } + + private void persistCodeSystem(CodeSystem theCodeSystem) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); + } + + private void loadAndPersistValueSet() throws IOException { + ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + persistValueSet(valueSet); + } + + private void persistValueSet(ValueSet theValueSet) { + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + }); + } + @Test public void testCreateConceptMapWithMissingSourceSystem() { ConceptMap conceptMap = new ConceptMap(); @@ -137,6 +169,16 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { } + @Test + public void testDuplicateCodeSystemUrls() throws Exception { + 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(); + } + @Test public void testDuplicateConceptMapUrls() { createAndPersistConceptMap(); @@ -147,6 +189,19 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { createAndPersistConceptMap(); } + @Test + public void testDuplicateValueSetUrls() throws Exception { + myDaoConfig.setPreExpandValueSetsExperimental(true); + + // DM 2019-03-05 - We pre-load our custom CodeSystem otherwise pre-expansion of the ValueSet will fail. + 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()); + + loadAndPersistValueSet(); + } + @Test public void testStoreTermConceptMapAndChildren() { createAndPersistConceptMap(); @@ -325,6 +380,63 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { }); } + @Test + public void testStoreTermValueSetAndChildren() throws Exception { + myDaoConfig.setPreExpandValueSetsExperimental(true); + + loadAndPersistCodeSystemAndValueSet(); + + CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem)); + + ValueSet valueSet = myValueSetDao.read(myExtensionalVsId); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet)); + + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong()); + assertTrue(optionalValueSetByResourcePid.isPresent()); + + Optional optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); + assertTrue(optionalValueSetByUrl.isPresent()); + + TermValueSet valueSet = optionalValueSetByUrl.get(); + assertSame(optionalValueSetByResourcePid.get(), valueSet); + ourLog.info("ValueSet:\n" + valueSet.toString()); + assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", valueSet.getUrl()); + assertEquals("Terminology Services Connectation #1 Extensional case #2", valueSet.getName()); + assertEquals(codeSystem.getConcept().size(), valueSet.getCodes().size()); + + TermValueSetCode code = valueSet.getCodes().get(0); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8450-9", code.getCode()); + assertEquals("Systolic blood pressure--expiration", code.getDisplay()); + + code = valueSet.getCodes().get(1); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("11378-7", code.getCode()); + assertEquals("Systolic blood pressure at First encounter", code.getDisplay()); + + // ... + + code = valueSet.getCodes().get(22); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8491-3", code.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum", code.getDisplay()); + + code = valueSet.getCodes().get(23); + ourLog.info("Code:\n" + code.toString()); + assertEquals("http://acme.org", code.getSystem()); + assertEquals("8492-1", code.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum", code.getDisplay()); + } + }); + } + @Test public void testTranslateByCodeSystemsAndSourceCodeOneToMany() { createAndPersistConceptMap(); 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 852e9204c22..163c11a0626 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 @@ -75,6 +75,41 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { .renameColumn("mySystem", "SYSTEM_URL") .renameColumn("mySystemVersion", "SYSTEM_VERSION") .renameColumn("myValueSet", "VALUESET_URL"); + + // TermValueSet + version.startSectionWithMessage("Processing table: TRM_VALUESET"); + version.addIdGenerator("SEQ_VALUESET_PID"); + Builder.BuilderAddTableByColumns termValueSetTable = version.addTableByColumns("TRM_VALUESET", "PID"); + termValueSetTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetTable.addColumn("URL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetTable + .addIndex("IDX_VALUESET_URL") + .unique(true) + .withColumns("URL"); + termValueSetTable.addColumn("RES_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetTable + .addForeignKey("FK_TRMVALUESET_RES") + .toColumn("RES_ID") + .references("HFJ_RESOURCE", "RES_ID"); + termValueSetTable.addColumn("NAME").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + + // TermValueSetCode + version.startSectionWithMessage("Processing table: TRM_VALUESET_CODE"); + version.addIdGenerator("SEQ_VALUESET_CODE_PID"); + Builder.BuilderAddTableByColumns termValueSetCodeTable = version.addTableByColumns("TRM_VALUESET_CODE", "PID"); + termValueSetCodeTable.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetCodeTable.addColumn("VALUESET_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + termValueSetCodeTable + .addForeignKey("FK_TRM_VALUESET_PID") + .toColumn("VALUESET_PID") + .references("TRM_VALUESET", "PID"); + termValueSetCodeTable.addColumn("SYSTEM").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetCodeTable.addColumn("CODE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); + termValueSetCodeTable + .addIndex("IDX_VALUESET_CODE_CS_CD") + .unique(false) + .withColumns("SYSTEM", "CODE"); + termValueSetCodeTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING); } From 62e1b7e9eaaea087377a21d186c999186c1531d6 Mon Sep 17 00:00:00 2001 From: Diederik Muylwyk Date: Thu, 11 Jul 2019 17:07:26 -0400 Subject: [PATCH 04/10] Fixing minor overlooked change from recent pull request. --- .../main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java index bbe0f518c35..88fc16f5b1f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermValueSetCode.java @@ -102,11 +102,6 @@ public class TermValueSetCode implements Serializable { ValidateUtil.isNotBlankOrThrowIllegalArgument(theSystem, "theSystem must not be null or empty"); ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSystem, TermCodeSystem.MAX_URL_LENGTH, "System exceeds maximum length (" + TermCodeSystem.MAX_URL_LENGTH + "): " + length(theSystem)); - - ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "theSystem must not be null or empty"); - if (theSystem.length() > TermCodeSystem.MAX_URL_LENGTH) { - throw new IllegalArgumentException(); - } mySystem = theSystem; return this; } @@ -119,7 +114,6 @@ public class TermValueSetCode implements Serializable { ValidateUtil.isNotBlankOrThrowIllegalArgument(theCode, "theCode must not be null or empty"); ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCode, TermConcept.MAX_CODE_LENGTH, "Code exceeds maximum length (" + TermConcept.MAX_CODE_LENGTH + "): " + length(theCode)); - myCode = theCode; return this; } From 642e62a56383d6009fbdc9ccc9b6cf9f8137be4b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 11 Jul 2019 18:17:13 -0400 Subject: [PATCH 05/10] Improve toString method --- .../java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java index 62a8e14de6b..7d91f3dbc23 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java @@ -446,7 +446,7 @@ public class SearchParameterMap implements Serializable { public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); if (isEmpty() == false) { - b.append("params", super.toString()); + b.append("params", mySearchParameterMap); } if (getIncludes().isEmpty() == false) { b.append("includes", getIncludes()); From 1db019b6df951cf1bb49382a8238f4c3e4f0577f Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 11 Jul 2019 20:20:27 -0400 Subject: [PATCH 06/10] Test fix and try to get Travis building again --- .../ca/uhn/fhir/interceptor/api/Pointcut.java | 35 ++ .../java/ca/uhn/fhir/parser/JsonParser.java | 13 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 43 +-- .../jpa/search/SearchCoordinatorSvcImpl.java | 8 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 3 +- ...ResourceDaoDstu3UniqueSearchParamTest.java | 345 ------------------ ...hirResourceDaoR4UniqueSearchParamTest.java | 210 +++++++++-- pom.xml | 41 ++- 8 files changed, 255 insertions(+), 443 deletions(-) delete mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 2de3cf3cb54..e53be5e6696 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -1392,6 +1392,41 @@ public enum Pointcut { "ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails" ), + /** + * Invoked when the storage engine is about to reuse the results of + * a previously cached search. + *

+ * Hooks may accept the following parameters: + *

+ *
    + *
  • + * ca.uhn.fhir.jpa.searchparam.SearchParameterMap - Contains the details of the search being checked + *
  • + *
  • + * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. Note that the bean + * properties are not all guaranteed to be populated, depending on how early during processing the + * exception occurred. Note that this parameter may be null in contexts where the request is not + * known, such as while processing searches + *
  • + *
  • + * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the + * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been + * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will + * only be populated when operating in a RestfulServer implementation. It is provided as a convenience. + *
  • + *
+ *

+ * Hooks should return void. + *

+ */ + JPA_PERFTRACE_SEARCH_REUSING_CACHED(boolean.class, + "ca.uhn.fhir.jpa.searchparam.SearchParameterMap", + "ca.uhn.fhir.rest.api.server.RequestDetails", + "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" + ), + /** * Note that this is a performance tracing hook. Use with caution in production * systems, since calling it may (or may not) carry a cost. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 76fd4347839..8fbb7f4bb5e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -852,10 +852,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { private boolean isEncodeExtension(CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theElement) { theEncodeContext.pushPath("extension", false); - BaseRuntimeChildDefinition childDef = ((BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theElement.getClass())).getChildByName("extension"); - CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext); - boolean retVal = c.shouldBeEncoded(theContainedResource); - theEncodeContext.popPath(); + BaseRuntimeElementDefinition runtimeElementDefinition = myContext.getElementDefinition(theElement.getClass()); + boolean retVal = true; + if (runtimeElementDefinition instanceof BaseRuntimeElementCompositeDefinition) { + BaseRuntimeElementCompositeDefinition definition = (BaseRuntimeElementCompositeDefinition) runtimeElementDefinition; + BaseRuntimeChildDefinition childDef = definition.getChildByName("extension"); + CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext); + retVal = c.shouldBeEncoded(theContainedResource); + theEncodeContext.popPath(); + } return retVal; } 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 8fe6bac3d4f..575f379983c 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 @@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; +import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; @@ -60,7 +61,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -113,10 +113,6 @@ public class SearchBuilder implements ISearchBuilder { */ private static final int MAXIMUM_PAGE_SIZE = 800; private static Long NO_MORE = -1L; - private static HandlerTypeEnum ourLastHandlerMechanismForUnitTest; - private static SearchParameterMap ourLastHandlerParamsForUnitTest; - private static String ourLastHandlerThreadForUnitTest; - private static boolean ourTrackHandlersForUnitTest; private final boolean myDontUseHashesForSearch; private final DaoConfig myDaoConfig; @Autowired @@ -1546,12 +1542,6 @@ public class SearchBuilder implements ISearchBuilder { myBuilder = myEntityManager.getCriteriaBuilder(); mySearchUuid = theSearchRuntimeDetails.getSearchUuid(); - if (ourTrackHandlersForUnitTest) { - ourLastHandlerParamsForUnitTest = theParams; - ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY; - ourLastHandlerThreadForUnitTest = Thread.currentThread().getName(); - } - if (myPidSet == null) { myPidSet = new HashSet<>(); } @@ -2214,9 +2204,16 @@ public class SearchBuilder implements ISearchBuilder { if (sb != null) { String indexString = sb.toString(); ourLog.debug("Checking for unique index for query: {}", indexString); - if (ourTrackHandlersForUnitTest) { - ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX; - } + + // Interceptor broadcast: JPA_PERFTRACE_INFO + StorageProcessingMessage msg = new StorageProcessingMessage() + .setMessage("Using unique index for query for search: " + indexString); + HookParams params = new HookParams() + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest) + .add(StorageProcessingMessage.class, msg); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params); + addPredicateCompositeStringUnique(theParams, indexString); } } @@ -2788,24 +2785,6 @@ public class SearchBuilder implements ISearchBuilder { return query.getResultList(); } - @VisibleForTesting - public static HandlerTypeEnum getLastHandlerMechanismForUnitTest() { - return ourLastHandlerMechanismForUnitTest; - } - - @VisibleForTesting - public static String getLastHandlerParamsForUnitTest() { - return ourLastHandlerParamsForUnitTest.toString() + " on thread [" + ourLastHandlerThreadForUnitTest + "]"; - } - - @VisibleForTesting - public static void resetLastHandlerMechanismForUnitTest() { - ourLastHandlerMechanismForUnitTest = null; - ourLastHandlerParamsForUnitTest = null; - ourLastHandlerThreadForUnitTest = null; - ourTrackHandlersForUnitTest = true; - } - private static Predicate[] toArray(List thePredicates) { return thePredicates.toArray(new Predicate[0]); } 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 91228809799..a6402100d4d 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 @@ -410,6 +410,14 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { PersistedJpaBundleProvider retVal = null; if (searchToUse != null) { ourLog.debug("Reusing search {} from cache", searchToUse.getUuid()); + + // Interceptor call: JPA_PERFTRACE_SEARCH_REUSING_CACHED + params = new HookParams() + .add(SearchParameterMap.class, theParams) + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED, params); + searchToUse.setSearchLastReturned(new Date()); mySearchDao.updateSearchLastReturned(searchToUse.getId(), new Date()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 54be5d46320..c33c0e8f50b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -5,7 +5,6 @@ import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; -import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; @@ -104,7 +103,7 @@ public class TestR4Config extends BaseJavaConfigR4 { DataSource dataSource = ProxyDataSourceBuilder .create(retVal) - .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") +// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") // .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .countQuery(new ThreadQueryCountHolder()) .beforeQuery(new BlockLargeNumbersOfParamsListener()) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java deleted file mode 100644 index e648d839c7a..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UniqueSearchParamTest.java +++ /dev/null @@ -1,345 +0,0 @@ -package ca.uhn.fhir.jpa.dao.dstu3; - -import ca.uhn.fhir.jpa.dao.SearchBuilder; -import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; -import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.util.TestUtil; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; -import org.springframework.test.context.TestPropertySource; - -import java.util.List; - -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.*; - -@SuppressWarnings({"unchecked", "deprecation"}) -@TestPropertySource(properties = { - // Since scheduled tasks can cause searches, which messes up the - // value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() - "scheduling_disabled=true" -}) -public class FhirResourceDaoDstu3UniqueSearchParamTest extends BaseJpaDstu3Test { - - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3UniqueSearchParamTest.class); - - @After - public void after() { - myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden()); - } - - @Before - public void before() { - myModelConfig.setDefaultSearchParamsCanBeOverridden(true); - } - - private void createUniqueBirthdateAndGenderSps() { - SearchParameter sp = new SearchParameter(); - sp.setId("SearchParameter/patient-gender"); - sp.setType(Enumerations.SearchParamType.TOKEN); - sp.setCode("gender"); - sp.setExpression("Patient.gender"); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/patient-birthdate"); - sp.setType(Enumerations.SearchParamType.DATE); - sp.setCode("birthdate"); - sp.setExpression("Patient.birthDate"); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/patient-gender-birthdate"); - sp.setType(Enumerations.SearchParamType.COMPOSITE); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - sp.addComponent() - .setExpression("Patient") - .setDefinition(new Reference("SearchParameter/patient-gender")); - sp.addComponent() - .setExpression("Patient") - .setDefinition(new Reference("SearchParameter/patient-birthdate")); - sp.addExtension() - .setUrl(SearchParamConstants.EXT_SP_UNIQUE) - .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); - - mySearchParamRegistry.forceRefresh(); - } - - private void createUniqueIndexCoverageBeneficiary() { - SearchParameter sp = new SearchParameter(); - sp.setId("SearchParameter/coverage-beneficiary"); - sp.setCode("beneficiary"); - sp.setExpression("Coverage.beneficiary"); - sp.setType(Enumerations.SearchParamType.REFERENCE); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Coverage"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/coverage-identifier"); - sp.setCode("identifier"); - sp.setExpression("Coverage.identifier"); - sp.setType(Enumerations.SearchParamType.TOKEN); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Coverage"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/coverage-beneficiary-identifier"); - sp.setCode("coverage-beneficiary-identifier"); - sp.setExpression("Coverage.beneficiary"); - sp.setType(Enumerations.SearchParamType.COMPOSITE); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Coverage"); - sp.addComponent() - .setExpression("Coverage") - .setDefinition(new Reference("/SearchParameter/coverage-beneficiary")); - sp.addComponent() - .setExpression("Coverage") - .setDefinition(new Reference("/SearchParameter/coverage-identifier")); - sp.addExtension() - .setUrl(SearchParamConstants.EXT_SP_UNIQUE) - .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); - mySearchParamRegistry.forceRefresh(); - } - - private void createUniqueNameAndManagingOrganizationSps() { - SearchParameter sp = new SearchParameter(); - sp.setId("SearchParameter/patient-name"); - sp.setType(Enumerations.SearchParamType.STRING); - sp.setCode("name"); - sp.setExpression("Patient.name"); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/patient-organization"); - sp.setType(Enumerations.SearchParamType.REFERENCE); - sp.setCode("organization"); - sp.setExpression("Patient.managingOrganization"); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - mySearchParameterDao.update(sp); - - sp = new SearchParameter(); - sp.setId("SearchParameter/patient-name-organization"); - sp.setType(Enumerations.SearchParamType.COMPOSITE); - sp.setStatus(PublicationStatus.ACTIVE); - sp.addBase("Patient"); - sp.addComponent() - .setExpression("Patient") - .setDefinition(new Reference("SearchParameter/patient-name")); - sp.addComponent() - .setExpression("Patient") - .setDefinition(new Reference("SearchParameter/patient-organization")); - sp.addExtension() - .setUrl(SearchParamConstants.EXT_SP_UNIQUE) - .setValue(new BooleanType(true)); - mySearchParameterDao.update(sp); - - mySearchParamRegistry.forceRefresh(); - } - - @Test - public void testDetectUniqueSearchParams() { - createUniqueBirthdateAndGenderSps(); - List params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient"); - - assertEquals(1, params.size()); - assertEquals(params.get(0).isUnique(), true); - assertEquals(2, params.get(0).getCompositeOf().size()); - // Should be alphabetical order - assertEquals("birthdate", params.get(0).getCompositeOf().get(0).getName()); - assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); - } - - @Test - public void testDuplicateUniqueValuesAreRejected() { - createUniqueBirthdateAndGenderSps(); - - Patient pt1 = new Patient(); - pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - - try { - myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - fail(); - } catch (PreconditionFailedException e) { - // good - } - - Patient pt2 = new Patient(); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - - pt2 = new Patient(); - pt2.setId(id2); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-01")); - try { - myPatientDao.update(pt2); - fail(); - } catch (PreconditionFailedException e) { - // good - } - - } - - @Test - public void testReplaceOneWithAnother() { - createUniqueBirthdateAndGenderSps(); - - Patient pt1 = new Patient(); - pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualified(); - assertNotNull(id1); - - ourLog.info("** Replacing"); - - pt1 = new Patient(); - pt1.setId(id1); - pt1.setGender(Enumerations.AdministrativeGender.FEMALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - id1 = myPatientDao.update(pt1).getId().toUnqualified(); - assertNotNull(id1); - assertEquals("2", id1.getVersionIdPart()); - - Patient pt2 = new Patient(); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-01")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - SearchParameterMap params = new SearchParameterMap(); - params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); - IBundleProvider results = myPatientDao.search(params); - String searchId = results.getUuid(); - assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id2.getValue())); - assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); - - } - - @Test - public void testSearchSynchronousUsingUniqueComposite() { - createUniqueBirthdateAndGenderSps(); - - Patient pt1 = new Patient(); - pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - - Patient pt2 = new Patient(); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-02")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - SearchParameterMap params = new SearchParameterMap(); - params.setLoadSynchronousUpTo(100); - params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); - IBundleProvider results = myPatientDao.search(params); - assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); - assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); - } - - @Test - public void testSearchUsingUniqueComposite() { - createUniqueBirthdateAndGenderSps(); - - Patient pt1 = new Patient(); - pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - assertNotNull(id1); - - Patient pt2 = new Patient(); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-02")); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - SearchParameterMap params = new SearchParameterMap(); - params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); - IBundleProvider results = myPatientDao.search(params); - String searchId = results.getUuid(); - assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); - assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); - - // Other order - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - params = new SearchParameterMap(); - params.add("birthdate", new DateParam("2011-01-01")); - params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - results = myPatientDao.search(params); - assertEquals(searchId, results.getUuid()); - String id1Value = id1.getValue(); - List actualValues = toUnqualifiedVersionlessIdValues(results); - assertThat(actualValues, containsInAnyOrder(id1Value)); - // Null because we just reuse the last search - assertEquals(null, SearchBuilder.getLastHandlerMechanismForUnitTest()); - - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - params = new SearchParameterMap(); - params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-03")); - results = myPatientDao.search(params); - assertThat(toUnqualifiedVersionlessIdValues(results), empty()); - assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); - - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - params = new SearchParameterMap(); - params.add("birthdate", new DateParam("2011-01-03")); - results = myPatientDao.search(params); - assertThat(toUnqualifiedVersionlessIdValues(results), empty()); - assertEquals(SearchBuilder.HandlerTypeEnum.STANDARD_QUERY, SearchBuilder.getLastHandlerMechanismForUnitTest()); - - } - - @Test - public void testUniqueValuesAreIndexed_DateAndToken() { - createUniqueBirthdateAndGenderSps(); - - Patient pt1 = new Patient(); - pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); - IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); - assertEquals(1, uniques.size()); - assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); - assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString()); - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index def1b97383d..8e621e7f504 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -1,15 +1,18 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.ReferenceParam; @@ -17,6 +20,7 @@ import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; @@ -25,6 +29,7 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.TransactionStatus; @@ -32,6 +37,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -40,8 +46,10 @@ import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -@SuppressWarnings({"unchecked", "deprecation"}) @TestPropertySource(properties = { // Since scheduled tasks can cause searches, which messes up the // value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() @@ -52,6 +60,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class); @Autowired private ISearchParamRegistry mySearchParamRegistry; + private IInterceptorBroadcaster myInterceptorBroadcaster; + private List myMessages = new ArrayList<>(); @After public void after() { @@ -66,9 +76,31 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { myModelConfig.setDefaultSearchParamsCanBeOverridden(true); myDaoConfig.setSchedulingDisabled(true); myDaoConfig.setUniqueIndexesEnabled(true); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + + myInterceptorBroadcaster = mock(IInterceptorBroadcaster.class); + when(mySrd.getInterceptorBroadcaster()).thenReturn(myInterceptorBroadcaster); + when(mySrd.getServer().getPagingProvider()).thenReturn(new DatabaseBackedPagingProvider()); + + when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_WARNING))).thenReturn(true); + when(myInterceptorBroadcaster.hasHooks(eq(Pointcut.JPA_PERFTRACE_INFO))).thenReturn(true); + when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_INFO), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> { + HookParams params = t.getArgument(1, HookParams.class); + myMessages.add("INFO " + params.get(StorageProcessingMessage.class).getMessage()); + return null; + }); + when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> { + HookParams params = t.getArgument(1, HookParams.class); + myMessages.add("WARN " + params.get(StorageProcessingMessage.class).getMessage()); + return null; + }); + when(myInterceptorBroadcaster.callHooks(eq(Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> { + HookParams params = t.getArgument(1, HookParams.class); + myMessages.add("REUSING CACHED SEARCH"); + return null; + }); } + private void createUniqueBirthdateAndGenderSps() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/patient-gender"); @@ -106,7 +138,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegistry.forceRefresh(); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); } private void createUniqueIndexCoverageBeneficiary() { @@ -322,19 +354,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegistry.forceRefresh(); } - @Test - public void testDetectUniqueSearchParams() { - createUniqueBirthdateAndGenderSps(); - List params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient"); - - assertEquals(1, params.size()); - assertTrue(params.get(0).isUnique()); - assertEquals(2, params.get(0).getCompositeOf().size()); - // Should be alphabetical order - assertEquals("birthdate", params.get(0).getCompositeOf().get(0).getName()); - assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); - } - @Test public void testDoubleMatchingOnAnd_Search() { createUniqueIndexPatientIdentifier(); @@ -981,15 +1000,19 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); myCaptureQueriesListener.clear(); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(100); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("birthdate", new DateParam("2011-01-01")); - IBundleProvider results = myPatientDao.search(params); + IBundleProvider results = myPatientDao.search(params, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); + + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale")); + myMessages.clear(); + } @@ -1007,40 +1030,50 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { pt2.setBirthDateElement(new DateType("2011-01-02")); myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("birthdate", new DateParam("2011-01-01")); - IBundleProvider results = myPatientDao.search(params); + IBundleProvider results = myPatientDao.search(params, mySrd); String searchId = results.getUuid(); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1)); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale")); + myMessages.clear(); // Other order - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); params = new SearchParameterMap(); params.add("birthdate", new DateParam("2011-01-01")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - results = myPatientDao.search(params); + results = myPatientDao.search(params, mySrd); assertEquals(searchId, results.getUuid()); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1)); - // Null because we just reuse the last search - assertNull(SearchBuilder.getLastHandlerMechanismForUnitTest()); + // Because we just reuse the last search + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("REUSING")); + assertThat(myMessages.toString(), not(containsString("Using unique index"))); + myMessages.clear(); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); params = new SearchParameterMap(); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("birthdate", new DateParam("2011-01-03")); - results = myPatientDao.search(params); + results = myPatientDao.search(params, mySrd); assertThat(toUnqualifiedVersionlessIdValues(results), empty()); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-03&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale")); + myMessages.clear(); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); + myMessages.clear(); params = new SearchParameterMap(); params.add("birthdate", new DateParam("2011-01-03")); - results = myPatientDao.search(params); + results = myPatientDao.search(params, mySrd); assertThat(toUnqualifiedVersionlessIdValues(results), empty()); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.STANDARD_QUERY, SearchBuilder.getLastHandlerMechanismForUnitTest()); + // STANDARD QUERY + logCapturedMessages(); + assertThat(myMessages.toString(), not(containsString("unique index"))); + myMessages.clear(); } @@ -1104,9 +1137,12 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { pt1.addName().setFamily("FAMILY1"); pt1.setManagingOrganization(new Reference("Organization/ORG")); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG").getId().toUnqualifiedVersionless(); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); + IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless(); + + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG")); + myMessages.clear(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); @@ -1118,9 +1154,12 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { pt1.addName().setFamily("FAMILY1"); pt1.setManagingOrganization(new Reference("Organization/ORG")); - SearchBuilder.resetLastHandlerMechanismForUnitTest(); - id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG").getId().toUnqualifiedVersionless(); - assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); + id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless(); + + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG")); + myMessages.clear(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(1, uniques.size()); assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); @@ -1128,6 +1167,10 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { } + private void logCapturedMessages() { + ourLog.info("Messages:\n {}", String.join("\n ", myMessages)); + } + @Test public void testUniqueValuesAreIndexed_StringAndReference() { createUniqueNameAndManagingOrganizationSps(); @@ -1375,6 +1418,93 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { } + + @Test + public void testDetectUniqueSearchParams() { + createUniqueBirthdateAndGenderSps(); + List params = mySearchParamRegistry.getActiveUniqueSearchParams("Patient"); + + assertEquals(1, params.size()); + assertEquals(params.get(0).isUnique(), true); + assertEquals(2, params.get(0).getCompositeOf().size()); + // Should be alphabetical order + assertEquals("birthdate", params.get(0).getCompositeOf().get(0).getName()); + assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); + } + + @Test + public void testDuplicateUniqueValuesAreRejected() { + createUniqueBirthdateAndGenderSps(); + + Patient pt1 = new Patient(); + pt1.setGender(Enumerations.AdministrativeGender.MALE); + pt1.setBirthDateElement(new DateType("2011-01-01")); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + try { + myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + fail(); + } catch (PreconditionFailedException e) { + // good + } + + Patient pt2 = new Patient(); + pt2.setGender(Enumerations.AdministrativeGender.MALE); + IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + + pt2 = new Patient(); + pt2.setId(id2); + pt2.setGender(Enumerations.AdministrativeGender.MALE); + pt2.setBirthDateElement(new DateType("2011-01-01")); + try { + myPatientDao.update(pt2); + fail(); + } catch (PreconditionFailedException e) { + // good + } + + } + + @Test + public void testReplaceOneWithAnother() { + createUniqueBirthdateAndGenderSps(); + + Patient pt1 = new Patient(); + pt1.setGender(Enumerations.AdministrativeGender.MALE); + pt1.setBirthDateElement(new DateType("2011-01-01")); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualified(); + assertNotNull(id1); + + ourLog.info("** Replacing"); + + pt1 = new Patient(); + pt1.setId(id1); + pt1.setGender(Enumerations.AdministrativeGender.FEMALE); + pt1.setBirthDateElement(new DateType("2011-01-01")); + id1 = myPatientDao.update(pt1).getId().toUnqualified(); + assertNotNull(id1); + assertEquals("2", id1.getVersionIdPart()); + + Patient pt2 = new Patient(); + pt2.setGender(Enumerations.AdministrativeGender.MALE); + pt2.setBirthDateElement(new DateType("2011-01-01")); + IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + + myMessages.clear(); + SearchParameterMap params = new SearchParameterMap(); + params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); + params.add("birthdate", new DateParam("2011-01-01")); + IBundleProvider results = myPatientDao.search(params, mySrd); + String searchId = results.getUuid(); + assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id2.getValue())); + + logCapturedMessages(); + assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale")); + myMessages.clear(); + + } + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/pom.xml b/pom.xml index 217eefd7495..0f48b01b040 100755 --- a/pom.xml +++ b/pom.xml @@ -1792,23 +1792,6 @@ - - org.codehaus.mojo - license-maven-plugin - false - - - update-project-license - package - - update-project-license - - - apache_v2 - - - - maven-antrun-plugin false @@ -2196,9 +2179,27 @@ DIST - - - + + + + org.codehaus.mojo + license-maven-plugin + false + + + update-project-license + package + + update-project-license + + + apache_v2 + + + + + + ROOT From a352137938a901df721d3c82812d4231dff619f4 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 11 Jul 2019 21:10:30 -0400 Subject: [PATCH 07/10] Test fixes --- .../java/ca/uhn/fhir/parser/JsonParser.java | 6 +- .../uhn/fhir/parser/JsonParserDstu3Test.java | 126 ++++++------------ .../fhir/rest/server/ElementsParamR4Test.java | 10 ++ 3 files changed, 52 insertions(+), 90 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 8fbb7f4bb5e..d391be8c3df 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -851,7 +851,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private boolean isEncodeExtension(CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theElement) { - theEncodeContext.pushPath("extension", false); +// theEncodeContext.pushPath("extension", false); BaseRuntimeElementDefinition runtimeElementDefinition = myContext.getElementDefinition(theElement.getClass()); boolean retVal = true; if (runtimeElementDefinition instanceof BaseRuntimeElementCompositeDefinition) { @@ -859,8 +859,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { BaseRuntimeChildDefinition childDef = definition.getChildByName("extension"); CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext); retVal = c.shouldBeEncoded(theContainedResource); - theEncodeContext.popPath(); } +// theEncodeContext.popPath(); return retVal; } @@ -1516,6 +1516,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { // Write value if (!noValue) { + theEncodeContext.pushPath("value", false); /* * Pre-process value - This is called in case the value is a reference @@ -1535,6 +1536,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent,false, theEncodeContext); managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext, theContainedResource); + theEncodeContext.popPath(); } } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index 9256172561d..2a9e8f42798 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -18,9 +18,9 @@ import net.sf.json.JsonConfig; import org.apache.commons.io.IOUtils; import org.hamcrest.Matchers; import org.hamcrest.core.StringContains; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Address.AddressUse; import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory; -import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.CapabilityStatement.UnknownContentCode; @@ -33,7 +33,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.*; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import java.io.IOException; import java.io.StringReader; @@ -45,9 +44,8 @@ import static org.apache.commons.lang3.StringUtils.countMatches; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.*; @@ -110,16 +108,16 @@ public class JsonParserDstu3Test { // Deserialize then check that valueReference value is still correct fhirPat = parser.parseResource(Patient.class, output); - List extlst = fhirPat.getExtensionsByUrl("x1"); - Assert.assertEquals(1, extlst.size()); - Assert.assertEquals(refVal, ((Reference) extlst.get(0).getValue()).getReference()); + List extensions = fhirPat.getExtensionsByUrl("x1"); + Assert.assertEquals(1, extensions.size()); + Assert.assertEquals(refVal, ((Reference) extensions.get(0).getValue()).getReference()); } /** * See #544 */ @Test - public void testBundleStitchReferencesByUuid() throws Exception { + public void testBundleStitchReferencesByUuid() { Bundle bundle = new Bundle(); DocumentManifest dm = new DocumentManifest(); @@ -175,7 +173,7 @@ public class JsonParserDstu3Test { } @Test - public void testCustomUrlExtensioninBundle() { + public void testCustomUrlExtensionInBundle() { final String expected = "{\"resourceType\":\"Bundle\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://www.example.com/petname\",\"valueString\":\"myName\"}]}}]}"; final MyPatientWithCustomUrlExtension patient = new MyPatientWithCustomUrlExtension(); @@ -213,7 +211,7 @@ public class JsonParserDstu3Test { * See #276 */ @Test - public void testDoubleEncodingContainedResources() throws Exception { + public void testDoubleEncodingContainedResources() { Patient patient = new Patient(); patient.setId("#patient-1"); patient.setActive(true); @@ -236,7 +234,7 @@ public class JsonParserDstu3Test { } @Test - public void testEncodeAndParseExtensions() throws Exception { + public void testEncodeAndParseExtensions() { Patient patient = new Patient(); patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135"); @@ -402,6 +400,7 @@ public class JsonParserDstu3Test { /** * See #336 */ + @SuppressWarnings("SpellCheckingInspection") @Test public void testEncodeAndParseNullPrimitiveWithExtensions() { @@ -458,7 +457,7 @@ public class JsonParserDstu3Test { Patient p = new Patient(); p.addName().setFamily("FAMILY"); - List labels = new ArrayList(); + List labels = new ArrayList<>(); labels.add(new Coding().setSystem("SYSTEM1").setCode("CODE1").setDisplay("DISPLAY1").setVersion("VERSION1")); labels.add(new Coding().setSystem("SYSTEM2").setCode("CODE2").setDisplay("DISPLAY2").setVersion("VERSION2")); p.getMeta().getSecurity().addAll(labels); @@ -602,7 +601,7 @@ public class JsonParserDstu3Test { */ @Test public void testEncodeEmptyTag() { - ArrayList tagList = new ArrayList(); + ArrayList tagList = new ArrayList<>(); tagList.add(new Coding()); tagList.add(new Coding().setDisplay("Label")); @@ -618,7 +617,7 @@ public class JsonParserDstu3Test { */ @Test public void testEncodeEmptyTag2() { - ArrayList tagList = new ArrayList(); + ArrayList tagList = new ArrayList<>(); tagList.add(new Coding().setSystem("scheme").setCode("code")); tagList.add(new Coding().setDisplay("Label")); @@ -731,8 +730,8 @@ public class JsonParserDstu3Test { encoded = parser.encodeResourceToString(p); ourLog.info(encoded); - assertThat(encoded, (containsString("http://foo"))); - assertThat(encoded, (containsString("Practitioner/A"))); + assertThat(encoded, not(containsString("http://foo"))); + assertThat(encoded, not(containsString("Practitioner/A"))); } @@ -831,7 +830,7 @@ public class JsonParserDstu3Test { TestPatientFor327 patient = new TestPatientFor327(); patient.setBirthDateElement(new DateType("2016-04-14")); - List conditions = new ArrayList(); + List conditions = new ArrayList<>(); Condition condition = new Condition(); condition.addBodySite().setText("BODY SITE"); conditions.add(new Reference(condition)); @@ -909,7 +908,7 @@ public class JsonParserDstu3Test { ourLog.info(enc); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); - parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList()); + parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList<>()); enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent); ourLog.info(enc); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2\"")); @@ -943,12 +942,12 @@ public class JsonParserDstu3Test { ourLog.info(enc); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); - ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(Arrays.asList("Patient.managingOrganization")); + ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(Collections.singletonList("Patient.managingOrganization")); enc = parser.setPrettyPrint(true).encodeResourceToString(p); ourLog.info(enc); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); - ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(new HashSet(Arrays.asList("Patient.managingOrganization"))); + ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(new HashSet<>(Collections.singletonList("Patient.managingOrganization"))); enc = parser.setPrettyPrint(true).encodeResourceToString(p); ourLog.info(enc); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); @@ -1026,7 +1025,7 @@ public class JsonParserDstu3Test { } @Test - public void testEncodeNarrativeSuppressed() throws Exception { + public void testEncodeNarrativeSuppressed() { Patient patient = new Patient(); patient.setId("Patient/1/_history/1"); patient.getText().setDivAsString("
THE DIV
"); @@ -1171,7 +1170,7 @@ public class JsonParserDstu3Test { * See #241 */ @Test - public void testEncodeThenParseShouldNotAddSpuriousId() throws Exception { + public void testEncodeThenParseShouldNotAddSpuriousId() { Condition condition = new Condition().setVerificationStatus(ConditionVerificationStatus.CONFIRMED); Bundle bundle = new Bundle(); BundleEntryComponent entry = new Bundle.BundleEntryComponent(); @@ -1190,7 +1189,7 @@ public class JsonParserDstu3Test { } @Test - public void testEncodeUndeclaredBlock() throws Exception { + public void testEncodeUndeclaredBlock() { FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent(); source.getMessageHeaderApplicationId().setValue("APPID"); source.setName("NAME"); @@ -1217,7 +1216,7 @@ public class JsonParserDstu3Test { Patient patient = new Patient(); patient.addAddress().setUse(AddressUse.HOME); EnumFactory fact = new AddressUseEnumFactory(); - PrimitiveType enumeration = new Enumeration(fact).setValue(AddressUse.HOME); + PrimitiveType enumeration = new Enumeration<>(fact).setValue(AddressUse.HOME); patient.addExtension().setUrl("urn:foo").setValue(enumeration); String val = parser.encodeResourceToString(patient); @@ -1232,7 +1231,7 @@ public class JsonParserDstu3Test { } @Test - public void testEncodeWithDontEncodeElements() throws Exception { + public void testEncodeWithDontEncodeElements() { Patient patient = new Patient(); patient.setId("123"); @@ -1288,7 +1287,7 @@ public class JsonParserDstu3Test { { IParser p = ourCtx.newJsonParser(); p.setDontEncodeElements(Sets.newHashSet("Patient.meta")); - p.setEncodeElements(new HashSet(Arrays.asList("Patient.name"))); + p.setEncodeElements(new HashSet<>(Collections.singletonList("Patient.name"))); p.setPrettyPrint(true); String out = p.encodeResourceToString(patient); ourLog.info(out); @@ -1502,7 +1501,7 @@ public class JsonParserDstu3Test { String res = "{ \"resourceType\": \"ValueSet\", \"url\": \"http://sample/ValueSet/education-levels\", \"version\": \"1\", \"name\": \"Education Levels\", \"status\": \"draft\", \"compose\": { \"include\": [ { \"filter\": [ { \"property\": \"n\", \"op\": \"n\", \"value\": \"365460000\" } ], \"system\": \"http://snomed.info/sct\" } ], \"exclude\": [ { \"concept\": [ { \"code\": \"224298008\" }, { \"code\": \"365460000\" }, { \"code\": \"473462005\" }, { \"code\": \"424587006\" } ], \"system\": \"http://snomed.info/sct\" } ] }, \"description\": \"A selection of Education Levels\", \"text\": { \"status\": \"generated\", \"div\": \"

Education Levels

http://csiro.au/ValueSet/education-levels

A selection of Education Levels

\" }, \"experimental\": true, \"date\": \"2016-07-26\" }"; IParser parser = ourCtx.newJsonParser(); parser.setParserErrorHandler(new StrictErrorHandler()); - ValueSet parsed = parser.parseResource(ValueSet.class, res); + parser.parseResource(ValueSet.class, res); fail("DataFormat Invalid attribute exception should be thrown"); } @@ -1735,53 +1734,6 @@ public class JsonParserDstu3Test { assertThat(reencoded, containsString("contained")); } - @Test - @Ignore - public void testParseAndEncodeBundleNewStyle() throws Exception { - String content = IOUtils.toString(JsonParserDstu3Test.class.getResourceAsStream("/bundle-example.json"), StandardCharsets.UTF_8); - - Bundle parsed = ourCtx.newJsonParser().parseResource(Bundle.class, content); - assertEquals("Bundle/example/_history/1", parsed.getIdElement().getValue()); - assertEquals("1", parsed.getIdElement().getVersionIdPart()); - assertEquals("2014-08-18T01:43:30Z", parsed.getMeta().getLastUpdatedElement().getValueAsString()); - assertEquals("searchset", parsed.getType()); - assertEquals(3, parsed.getTotal()); - assertEquals("https://example.com/base/MedicationRequest?patient=347&searchId=ff15fd40-ff71-4b48-b366-09c706bed9d0&page=2", parsed.getLink().get(0).getUrlElement().getValueAsString()); - assertEquals("https://example.com/base/MedicationRequest?patient=347&_include=MedicationRequest.medication", parsed.getLink().get(1).getUrlElement().getValueAsString()); - - assertEquals(2, parsed.getEntry().size()); - assertEquals("alternate", parsed.getEntry().get(0).getLink().get(0).getRelation()); - assertEquals("http://example.com/base/MedicationRequest/3123/_history/1", parsed.getEntry().get(0).getLink().get(0).getUrl()); - assertEquals("http://foo?search", parsed.getEntry().get(0).getRequest().getUrlElement().getValueAsString()); - - MedicationRequest p = (MedicationRequest) parsed.getEntry().get(0).getResource(); - assertEquals("Patient/347", p.getSubject().getReference()); - assertEquals("2014-08-16T05:31:17Z", p.getMeta().getLastUpdatedElement().getValueAsString()); - assertEquals("http://example.com/base/MedicationRequest/3123/_history/1", p.getId()); - // assertEquals("3123", p.getId()); - - Medication m = (Medication) parsed.getEntry().get(1).getResource(); - assertEquals("http://example.com/base/Medication/example", m.getId()); - assertSame(((Reference) p.getMedication()).getResource(), m); - - String reencoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parsed); - ourLog.info(reencoded); - - JsonConfig cfg = new JsonConfig(); - - JSON expected = JSONSerializer.toJSON(content.trim(), cfg); - JSON actual = JSONSerializer.toJSON(reencoded.trim(), cfg); - - String exp = expected.toString().replace("\\r\\n", "\\n"); // .replace("§", "ยง"); - String act = actual.toString().replace("\\r\\n", "\\n"); - - ourLog.info("Expected: {}", exp); - ourLog.info("Actual : {}", act); - - assertEquals(exp, act); - - } - @Test public void testParseAndEncodeBundleWithUuidBase() { //@formatter:off @@ -2285,20 +2237,18 @@ public class JsonParserDstu3Test { @Test(expected = DataFormatException.class) public void testParseWithTrailingContent() { - //@formatter:off String bundle = "{\n" + " \"resourceType\" : \"Bundle\",\n" + " \"total\" : 1\n" + "}}"; - //@formatter:on - Bundle b = ourCtx.newJsonParser().parseResource(Bundle.class, bundle); + ourCtx.newJsonParser().parseResource(Bundle.class, bundle); } @Test @Ignore public void testParseWithWrongTypeObjectShouldBeArray() throws Exception { - String input = IOUtils.toString(getClass().getResourceAsStream("/invalid_metadata.json")); + String input = IOUtils.toString(getClass().getResourceAsStream("/invalid_metadata.json"), Charsets.UTF_8); try { ourCtx.newJsonParser().parseResource(CapabilityStatement.class, input); fail(); @@ -2410,14 +2360,14 @@ public class JsonParserDstu3Test { ArgumentCaptor actual = ArgumentCaptor.forClass(ValueType.class); ArgumentCaptor expectedScalar = ArgumentCaptor.forClass(ScalarType.class); ArgumentCaptor actualScalar = ArgumentCaptor.forClass(ScalarType.class); - verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.nullable(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalar.capture(), actual.capture(), + verify(errorHandler, atLeastOnce()).incorrectJsonType(nullable(IParseLocation.class), elementName.capture(), expected.capture(), expectedScalar.capture(), actual.capture(), actualScalar.capture()); - verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.nullable(IParseLocation.class), Mockito.eq("_id"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR), + verify(errorHandler, atLeastOnce()).incorrectJsonType(nullable(IParseLocation.class), eq("_id"), eq(ValueType.OBJECT), expectedScalar.capture(), eq(ValueType.SCALAR), actualScalar.capture()); - verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.nullable(IParseLocation.class), Mockito.eq("__v"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), Mockito.eq(ValueType.SCALAR), + verify(errorHandler, atLeastOnce()).incorrectJsonType(nullable(IParseLocation.class), eq("__v"), eq(ValueType.OBJECT), expectedScalar.capture(), eq(ValueType.SCALAR), actualScalar.capture()); - verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.nullable(IParseLocation.class), Mockito.eq("_status"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), - Mockito.eq(ValueType.SCALAR), actualScalar.capture()); + verify(errorHandler, atLeastOnce()).incorrectJsonType(nullable(IParseLocation.class), eq("_status"), eq(ValueType.OBJECT), expectedScalar.capture(), + eq(ValueType.SCALAR), actualScalar.capture()); assertEquals("_id", elementName.getAllValues().get(0)); assertEquals(ValueType.OBJECT, expected.getAllValues().get(0)); @@ -2427,7 +2377,7 @@ public class JsonParserDstu3Test { } @Test - public void testValidateCustomStructure() throws Exception { + public void testValidateCustomStructure() { FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent(); source.getMessageHeaderApplicationId().setValue("APPID"); @@ -2456,13 +2406,13 @@ public class JsonParserDstu3Test { containedOrganization.getMeta().addProfile(UUID.randomUUID().toString()); containedOrganization.getMeta().setLastUpdated(new Date()); containedOrganization.getMeta().setVersionId(UUID.randomUUID().toString()); - containedOrganization.getMeta().setSecurity(Arrays.asList(new Coding(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()))); - containedOrganization.getMeta().setTag(Arrays.asList(new Coding(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()))); + containedOrganization.getMeta().setSecurity(Collections.singletonList(new Coding(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()))); + containedOrganization.getMeta().setTag(Collections.singletonList(new Coding(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()))); Patient patient = new Patient(); patient.setId(UUID.randomUUID().toString()); patient.getMeta().addProfile(UUID.randomUUID().toString()); - patient.setGeneralPractitioner(Arrays.asList(new Reference(containedOrganization))); + patient.setGeneralPractitioner(Collections.singletonList(new Reference(containedOrganization))); HashSet excludeElementsInEncoded = new HashSet<>(); // ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED excludeElementsInEncoded.add("id"); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ElementsParamR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ElementsParamR4Test.java index 41c31be82d6..55c7ac6f6ac 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ElementsParamR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ElementsParamR4Test.java @@ -264,6 +264,16 @@ public class ElementsParamR4Test { assertEquals("mg", ((Quantity) procedure.getExtension().get(0).getValue()).getCode()); }); + verifyXmlAndJson( + "http://localhost:" + ourPort + "/Procedure?_elements=Procedure.extension.value", + bundle -> { + Procedure procedure = (Procedure) bundle.getEntry().get(0).getResource(); + assertEquals("SUBSETTED", procedure.getMeta().getTag().get(0).getCode()); + assertEquals(0, procedure.getReasonCode().size()); + assertEquals("1.1", ((Quantity) procedure.getExtension().get(0).getValue()).getValueElement().getValueAsString()); + assertEquals("mg", ((Quantity) procedure.getExtension().get(0).getValue()).getCode()); + }); + verifyXmlAndJson( "http://localhost:" + ourPort + "/Procedure?_elements=Procedure.extension.value.value", bundle -> { From 75f0dc993c910f7f9dde2ae4d541db46eb75c46f Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 11 Jul 2019 21:39:41 -0400 Subject: [PATCH 08/10] Try to prevent an intermittent test fix --- hapi-fhir-structures-dstu2/pom.xml | 6 + .../ca/uhn/fhir/rest/server/PreferTest.java | 41 ++- .../InterceptorUserDataMapDstu2Test.java | 308 +++++++++--------- .../fhir/rest/server/SummaryParamR4Test.java | 5 +- hapi-fhir-test-utilities/pom.xml | 5 + pom.xml | 19 ++ 6 files changed, 206 insertions(+), 178 deletions(-) diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index c787fd6d0e7..0d164e14ebb 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -210,6 +210,12 @@ spring-web test + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + ${project.version} + test + diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java index 72781c267f2..57bade29e4a 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java @@ -1,14 +1,18 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.emptyOrNullString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; - -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; @@ -26,19 +30,12 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Update; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.junit.Assert.*; public class PreferTest { private static CloseableHttpClient ourClient; diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java index 3ce63c22b01..4bf1dd72d99 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java @@ -1,19 +1,27 @@ package ca.uhn.fhir.rest.server.interceptor; -import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - -import java.util.*; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; +import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; +import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.UriDt; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -24,37 +32,30 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; -import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; -import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.*; public class InterceptorUserDataMapDstu2Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InterceptorUserDataMapDstu2Test.class); private static CloseableHttpClient ourClient; private static FhirContext ourCtx = FhirContext.forDstu2(); - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InterceptorUserDataMapDstu2Test.class); private static int ourPort; private static Server ourServer; private static RestfulServer servlet; private final Object myKey = "KEY"; + private final Object myValue = "VALUE"; private Map myMap; private Set myMapCheckMethods; - private final Object myValue = "VALUE"; @Before public void before() { @@ -66,10 +67,10 @@ public class InterceptorUserDataMapDstu2Test { @Before public void beforePurgeMap() { myMap = null; - myMapCheckMethods= new LinkedHashSet<>(); + myMapCheckMethods = new LinkedHashSet<>(); } - + @Test public void testException() throws Exception { @@ -78,7 +79,7 @@ public class InterceptorUserDataMapDstu2Test { IOUtils.closeQuietly(status.getEntity().getContent()); ourLog.info(myMapCheckMethods.toString()); - assertThat(myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "preProcessOutgoingException", "handleException", "processingCompleted")); + await().until(() -> myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "preProcessOutgoingException", "handleException", "processingCompleted")); } @Test @@ -94,9 +95,8 @@ public class InterceptorUserDataMapDstu2Test { } } - - ourLog.info(myMapCheckMethods.toString()); - assertThat(myMapCheckMethods.toString(), myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "outgoingResponse", "processingCompletedNormally", "processingCompleted")); + + await().until(() -> myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "outgoingResponse", "processingCompletedNormally", "processingCompleted")); } private void updateMapUsing(Map theUserData, String theMethod) { @@ -111,125 +111,6 @@ public class InterceptorUserDataMapDstu2Test { myMapCheckMethods.add(theMethod); } - @AfterClass - public static void afterClassClearContext() throws Exception { - JettyUtil.closeServer(ourServer); - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void beforeClass() throws Exception { - ourServer = new Server(0); - - ServletHandler proxyHandler = new ServletHandler(); - servlet = new RestfulServer(ourCtx); - - servlet.setResourceProviders(new DummyPatientResourceProvider()); - servlet.setPlainProviders(new PlainProvider()); - - ServletHolder servletHolder = new ServletHolder(servlet); - proxyHandler.addServletWithMapping(servletHolder, "/*"); - ourServer.setHandler(proxyHandler); - JettyUtil.startServer(ourServer); - ourPort = JettyUtil.getPortForStartedServer(ourServer); - - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); - HttpClientBuilder builder = HttpClientBuilder.create(); - builder.setConnectionManager(connectionManager); - ourClient = builder.build(); - - } - - public static class DummyPatientResourceProvider implements IResourceProvider { - - private Patient createPatient1() { - Patient patient = new Patient(); - patient.addIdentifier(); - patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00001"); - patient.addName(); - patient.getName().get(0).addFamily("Test"); - patient.getName().get(0).addGiven("PatientOne"); - patient.setGender(AdministrativeGenderEnum.MALE); - patient.getId().setValue("1"); - return patient; - } - - public Map getIdToPatient() { - Map idToPatient = new HashMap(); - { - Patient patient = createPatient1(); - idToPatient.put("1", patient); - } - { - Patient patient = new Patient(); - patient.getIdentifier().add(new IdentifierDt()); - patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); - patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); - patient.getIdentifier().get(0).setValue("00002"); - patient.getName().add(new HumanNameDt()); - patient.getName().get(0).addFamily("Test"); - patient.getName().get(0).addGiven("PatientTwo"); - patient.setGender(AdministrativeGenderEnum.FEMALE); - patient.getId().setValue("2"); - idToPatient.put("2", patient); - } - return idToPatient; - } - - /** - * Retrieve the resource by its identifier - * - * @param theId - * The resource identity - * @return The resource - */ - @Read() - public Patient getResourceById(@IdParam IdDt theId) { - if (theId.getIdPart().equals("EX")) { - throw new InvalidRequestException("FOO"); - } - String key = theId.getIdPart(); - Patient retVal = getIdToPatient().get(key); - return retVal; - } - - /** - * Retrieve the resource by its identifier - * - * @param theId - * The resource identity - * @return The resource - */ - @Search() - public List getResourceById(@RequiredParam(name = "_id") String theId) { - throw new InvalidRequestException("FOO"); - } - - @Override - public Class getResourceType() { - return Patient.class; - } - - @Operation(name = "$everything", idempotent = true) - public Bundle patientTypeOperation(@OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) { - - Bundle retVal = new Bundle(); - // Populate bundle with matching resources - return retVal; - } - - @Operation(name = "$everything", idempotent = true) - public Bundle patientTypeOperation(@IdParam IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) { - - Bundle retVal = new Bundle(); - // Populate bundle with matching resources - return retVal; - } - - } - @Interceptor public class MyInterceptor { @@ -276,6 +157,94 @@ public class InterceptorUserDataMapDstu2Test { } + public static class DummyPatientResourceProvider implements IResourceProvider { + + private Patient createPatient1() { + Patient patient = new Patient(); + patient.addIdentifier(); + patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); + patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); + patient.getIdentifier().get(0).setValue("00001"); + patient.addName(); + patient.getName().get(0).addFamily("Test"); + patient.getName().get(0).addGiven("PatientOne"); + patient.setGender(AdministrativeGenderEnum.MALE); + patient.getId().setValue("1"); + return patient; + } + + public Map getIdToPatient() { + Map idToPatient = new HashMap(); + { + Patient patient = createPatient1(); + idToPatient.put("1", patient); + } + { + Patient patient = new Patient(); + patient.getIdentifier().add(new IdentifierDt()); + patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL); + patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); + patient.getIdentifier().get(0).setValue("00002"); + patient.getName().add(new HumanNameDt()); + patient.getName().get(0).addFamily("Test"); + patient.getName().get(0).addGiven("PatientTwo"); + patient.setGender(AdministrativeGenderEnum.FEMALE); + patient.getId().setValue("2"); + idToPatient.put("2", patient); + } + return idToPatient; + } + + /** + * Retrieve the resource by its identifier + * + * @param theId The resource identity + * @return The resource + */ + @Read() + public Patient getResourceById(@IdParam IdDt theId) { + if (theId.getIdPart().equals("EX")) { + throw new InvalidRequestException("FOO"); + } + String key = theId.getIdPart(); + Patient retVal = getIdToPatient().get(key); + return retVal; + } + + /** + * Retrieve the resource by its identifier + * + * @param theId The resource identity + * @return The resource + */ + @Search() + public List getResourceById(@RequiredParam(name = "_id") String theId) { + throw new InvalidRequestException("FOO"); + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Operation(name = "$everything", idempotent = true) + public Bundle patientTypeOperation(@OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) { + + Bundle retVal = new Bundle(); + // Populate bundle with matching resources + return retVal; + } + + @Operation(name = "$everything", idempotent = true) + public Bundle patientTypeOperation(@IdParam IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) { + + Bundle retVal = new Bundle(); + // Populate bundle with matching resources + return retVal; + } + + } + public static class PlainProvider { @Operation(name = "$everything", idempotent = true) @@ -287,5 +256,34 @@ public class InterceptorUserDataMapDstu2Test { } } - + + @AfterClass + public static void afterClassClearContext() throws Exception { + JettyUtil.closeServer(ourServer); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourServer = new Server(0); + + ServletHandler proxyHandler = new ServletHandler(); + servlet = new RestfulServer(ourCtx); + + servlet.setResourceProviders(new DummyPatientResourceProvider()); + servlet.setPlainProviders(new PlainProvider()); + + ServletHolder servletHolder = new ServletHolder(servlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + JettyUtil.startServer(ourServer); + ourPort = JettyUtil.getPortForStartedServer(ourServer); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + + } + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java index 2e85e6508a6..16c3506a56c 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SummaryParamR4Test.java @@ -18,6 +18,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.hamcrest.CoreMatchers; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; import org.junit.AfterClass; @@ -31,6 +32,8 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; +import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -220,7 +223,7 @@ public class SummaryParamR4Test { assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString(">TEXT<"))); assertThat(responseContent, (containsString("Medication/123"))); - assertThat(responseContent, not(containsStringIgnoringCase("note"))); + assertThat(responseContent, not(CoreMatchers.containsStringIgnoringCase("note"))); } ); diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index f68d07c606f..4336272c1d5 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -46,6 +46,11 @@ junit junit + + + org.awaitility + awaitility + diff --git a/pom.xml b/pom.xml index 0f48b01b040..d780efa1aa5 100755 --- a/pom.xml +++ b/pom.xml @@ -1022,6 +1022,25 @@ velocity-tools 2.0 + + org.awaitility + awaitility + 3.1.6 + + + org.hamcrest + java-hamcrest + + + org.hamcrest + hamcrest-core + + + org.hamcrest + hamcrest-library + + + org.codehaus.plexus plexus-compiler-api From afd2b35ff6445983a52b0806a6351670b361c342 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 12 Jul 2019 06:32:18 -0400 Subject: [PATCH 09/10] Fix an intermittent test failure --- .../jpa/subscription/resthook/RestHookTestDstu3Test.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java index 9ffe6661eab..38dca6bae5b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java @@ -36,6 +36,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; @@ -471,6 +472,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode()); assertEquals("In-memory", tag.getDisplay()); + // Wait for subscription to be moved to active + await().until(()-> Subscription.SubscriptionStatus.ACTIVE.equals(ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute().getStatus())); + Subscription subscriptionActivated = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute(); assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscriptionActivated.getStatus()); tags = subscriptionActivated.getMeta().getTag(); @@ -495,6 +499,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode()); assertEquals("Database", tag.getDisplay()); + // Wait for subscription to be moved to active + await().until(()-> Subscription.SubscriptionStatus.ACTIVE.equals(ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute().getStatus())); + Subscription subscription = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute(); assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus()); tags = subscription.getMeta().getTag(); From a5a9950e7778ca84e51d9b2fe8b340d2f4792cb1 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Fri, 12 Jul 2019 08:52:47 -0400 Subject: [PATCH 10/10] License header updates --- .../java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java | 4 ++-- .../main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java index 5702aceb846..9c2be4bfb47 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetCodeDao.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.data; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java index 087dcefe70e..c7273a1f733 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetDao.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.data; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.