Merge remote-tracking branch 'remotes/origin/master' into expunge-hooks

# Conflicts:
#	hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java
This commit is contained in:
Ken Stevens 2019-07-12 13:27:11 -04:00
commit 8d817b364c
55 changed files with 1810 additions and 979 deletions

View File

@ -1401,6 +1401,41 @@ public enum Pointcut {
"ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails" "ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails"
), ),
/**
* Invoked when the storage engine is about to reuse the results of
* a previously cached search.
* <p>
* Hooks may accept the following parameters:
* </p>
* <ul>
* <li>
* ca.uhn.fhir.jpa.searchparam.SearchParameterMap - Contains the details of the search being checked
* </li>
* <li>
* 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. <b>Note that this parameter may be null in contexts where the request is not
* known, such as while processing searches</b>
* </li>
* <li>
* 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.
* </li>
* </ul>
* <p>
* Hooks should return <code>void</code>.
* </p>
*/
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 * Note that this is a performance tracing hook. Use with caution in production
* systems, since calling it may (or may not) carry a cost. * systems, since calling it may (or may not) carry a cost.

View File

@ -34,6 +34,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
@ -65,6 +66,7 @@ public abstract class BaseParser implements IParser {
private boolean mySummaryMode; private boolean mySummaryMode;
private boolean mySuppressNarratives; private boolean mySuppressNarratives;
private Set<String> myDontStripVersionsFromReferencesAtPaths; private Set<String> myDontStripVersionsFromReferencesAtPaths;
/** /**
* Constructor * Constructor
*/ */
@ -165,8 +167,6 @@ public abstract class BaseParser implements IParser {
myNext = null; myNext = null;
} else if (!myNext.shouldBeEncoded(theContainedResource)) { } else if (!myNext.shouldBeEncoded(theContainedResource)) {
myNext = null; myNext = null;
} else if (isSummaryMode() && !myNext.getDef().isSummary()) {
myNext = null;
} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) { } else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
if (isSuppressNarratives() || isSummaryMode()) { if (isSuppressNarratives() || isSummaryMode()) {
myNext = null; myNext = null;
@ -1004,7 +1004,7 @@ public abstract class BaseParser implements IParser {
private final RuntimeResourceDefinition myResDef; private final RuntimeResourceDefinition myResDef;
private final EncodeContext myEncodeContext; private final EncodeContext myEncodeContext;
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) { public CompositeChildElement(CompositeChildElement theParent, @Nullable BaseRuntimeChildDefinition theDef, EncodeContext theEncodeContext) {
myDef = theDef; myDef = theDef;
myParent = theParent; myParent = theParent;
myResDef = null; myResDef = null;
@ -1015,7 +1015,9 @@ public abstract class BaseParser implements IParser {
StringBuilder path = theParent.buildPath(); StringBuilder path = theParent.buildPath();
if (path != null) { if (path != null) {
path.append('.'); path.append('.');
path.append(myDef.getElementName()); if (myDef != null) {
path.append(myDef.getElementName());
}
ourLog.trace(" * Next path: {}", path.toString()); ourLog.trace(" * Next path: {}", path.toString());
} }
} }
@ -1165,6 +1167,21 @@ public abstract class BaseParser implements IParser {
if (theContainedResource) { if (theContainedResource) {
retVal = !notEncodeForContainedResource.contains(myDef.getElementName()); 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; return retVal;
} }
@ -1183,7 +1200,7 @@ public abstract class BaseParser implements IParser {
@Override @Override
public String toString() { 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<EncodeContextPathElement> getPath() { protected List<EncodeContextPathElement> getPath() {
@ -1251,6 +1268,14 @@ public abstract class BaseParser implements IParser {
return myResourcePath; 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() { public String getLeafResourcePathFirstField() {
String retVal = null; String retVal = null;
for (int i = getPath().size() - 1; i >= 0; i--) { for (int i = getPath().size() - 1; i >= 0; i--) {

View File

@ -80,7 +80,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theListToAddTo.add(null); theListToAddTo.add(null);
} }
if (theListToAddTo.get(valueIdx) == null) { if (theListToAddTo.get(valueIdx) == null) {
theListToAddTo.set(valueIdx, new ArrayList<String>()); theListToAddTo.set(valueIdx, new ArrayList<>());
} }
theListToAddTo.get(valueIdx).addAll(theCommentsToAdd); theListToAddTo.get(valueIdx).addAll(theCommentsToAdd);
return true; return true;
@ -89,21 +89,34 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem, private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem,
CompositeChildElement theParent) { CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) {
boolean retVal = false;
if (ext.size() > 0) { if (ext.size() > 0) {
list.ensureCapacity(valueIdx); Boolean encodeExtension = null;
while (list.size() <= valueIdx) {
list.add(null);
}
if (list.get(valueIdx) == null) {
list.set(valueIdx, new ArrayList<JsonParser.HeldExtension>());
}
for (IBaseExtension<?, ?> next : ext) { 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<HeldExtension> 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<String> theListToAddTo, String theId) { private void addToHeldIds(int theValueIdx, ArrayList<String> 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") if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) { || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
if (!haveWrittenExtensions) { 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; haveWrittenExtensions = true;
} }
continue; continue;
@ -433,20 +446,20 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (primitive) { if (primitive) {
if (nextValue instanceof ISupportsUndeclaredExtensions) { if (nextValue instanceof ISupportsUndeclaredExtensions) {
List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions(); List<ExtensionDt> 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(); ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent); force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent, theEncodeContext, theContainedResource, theElement);
} else { } else {
if (nextValue instanceof IBaseHasExtensions) { if (nextValue instanceof IBaseHasExtensions) {
IBaseHasExtensions element = (IBaseHasExtensions) nextValue; IBaseHasExtensions element = (IBaseHasExtensions) nextValue;
List<? extends IBaseExtension<?, ?>> ext = element.getExtension(); List<? extends IBaseExtension<?, ?>> 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) { if (nextValue instanceof IBaseHasModifierExtensions) {
IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue; IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension(); List<? extends IBaseExtension<?, ?>> 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()) { if (nextValue.hasFormatComment()) {
@ -486,6 +499,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endArray(); theEventWriter.endArray();
} }
if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) { if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) {
if (inArray) { if (inArray) {
// If this is a repeatable field, the extensions go in an array too // 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(); theEventWriter.endArray();
} }
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext, theContainedResource);
if (inArray) { if (inArray) {
theEventWriter.endObject(); theEventWriter.endObject();
} }
@ -626,7 +640,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
final List<HeldExtension> extensions = new ArrayList<>(0); final List<HeldExtension> extensions = new ArrayList<>(0);
final List<HeldExtension> modifierExtensions = new ArrayList<>(0); final List<HeldExtension> modifierExtensions = new ArrayList<>(0);
// Undeclared extensions // Undeclared extensions
extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null); extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null, theEncodeContext, theContainedResource);
boolean haveExtension = false; boolean haveExtension = false;
if (!extensions.isEmpty()) { if (!extensions.isEmpty()) {
haveExtension = true; haveExtension = true;
@ -638,7 +652,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
writeCommentsPreAndPost(theResourceId, theEventWriter); writeCommentsPreAndPost(theResourceId, theEventWriter);
} }
if (haveExtension) { if (haveExtension) {
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource);
} }
theEventWriter.endObject(); theEventWriter.endObject();
} }
@ -741,12 +755,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
* called _name): resource extensions, and extension extensions * called _name): resource extensions, and extension extensions
*/ */
private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, 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<HeldExtension> extensions = new ArrayList<>(0); List<HeldExtension> extensions = new ArrayList<>(0);
List<HeldExtension> modifierExtensions = new ArrayList<>(0); List<HeldExtension> modifierExtensions = new ArrayList<>(0);
// Undeclared extensions // Undeclared extensions
extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent); extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent, theEncodeContext, theContainedResource);
// Declared extensions // Declared extensions
if (theElementDef != null) { if (theElementDef != null) {
@ -754,7 +768,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
// Write the extensions // 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<HeldExtension> extensions, List<HeldExtension> modifierExtensions, private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
@ -782,7 +796,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem, private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem,
CompositeChildElement theParent) { CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource) {
if (theElement instanceof ISupportsUndeclaredExtensions) { if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement; ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
List<ExtensionDt> ext = element.getUndeclaredExtensions(); List<ExtensionDt> ext = element.getUndeclaredExtensions();
@ -804,11 +818,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (theElement instanceof IBaseHasExtensions) { if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions element = (IBaseHasExtensions) theElement; IBaseHasExtensions element = (IBaseHasExtensions) theElement;
List<? extends IBaseExtension<?, ?>> ext = element.getExtension(); List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
Boolean encodeExtension = null;
for (IBaseExtension<?, ?> next : ext) { for (IBaseExtension<?, ?> next : ext) {
if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
continue; 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) { if (theElement instanceof IBaseHasModifierExtensions) {
@ -818,12 +842,28 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (next == null || next.isEmpty()) { if (next == null || next.isEmpty()) {
continue; 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);
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;
}
@Override @Override
public EncodingEnum getEncoding() { public EncodingEnum getEncoding() {
return EncodingEnum.JSON; return EncodingEnum.JSON;
@ -1275,21 +1315,24 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext) throws IOException { List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext, boolean theContainedResource) throws IOException {
// Write Extensions
if (extensions.isEmpty() == false) { if (extensions.isEmpty() == false) {
theEncodeContext.pushPath("extension", false); theEncodeContext.pushPath("extension", false);
beginArray(theEventWriter, "extension"); beginArray(theEventWriter, "extension");
for (HeldExtension next : extensions) { for (HeldExtension next : extensions) {
next.write(resDef, theResource, theEventWriter, theEncodeContext); next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
} }
theEventWriter.endArray(); theEventWriter.endArray();
theEncodeContext.popPath(); theEncodeContext.popPath();
} }
// Write ModifierExtensions
if (modifierExtensions.isEmpty() == false) { if (modifierExtensions.isEmpty() == false) {
theEncodeContext.pushPath("modifierExtension", false); theEncodeContext.pushPath("modifierExtension", false);
beginArray(theEventWriter, "modifierExtension"); beginArray(theEventWriter, "modifierExtension");
for (HeldExtension next : modifierExtensions) { for (HeldExtension next : modifierExtensions) {
next.write(resDef, theResource, theEventWriter, theEncodeContext); next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
} }
theEventWriter.endArray(); theEventWriter.endArray();
theEncodeContext.popPath(); theEncodeContext.popPath();
@ -1353,12 +1396,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
return url1.compareTo(url2); 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)) { if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0); final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0); final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
// Undeclared extensions // Undeclared extensions
extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null); extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null, theEncodeContext, theContainedResource);
// Declared extensions // Declared extensions
if (def != null) { if (def != null) {
extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent); extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
@ -1369,15 +1412,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
if (haveContent) { if (haveContent) {
beginObject(theEventWriter, '_' + childName); beginObject(theEventWriter, '_' + childName);
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext); writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext, theContainedResource);
theEventWriter.endObject(); 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) { if (myUndeclaredExtension != null) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext); writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext, theContainedResource);
} else { } else {
theEventWriter.beginObject(); theEventWriter.beginObject();
@ -1410,18 +1453,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass()); BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { 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 { } else {
String childName = myDef.getChildNameByDatatype(myValue.getClass()); String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false, theEncodeContext); 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(); 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(); IBase value = ext.getValue();
final String extensionUrl = getExtensionUrl(ext.getUrl()); final String extensionUrl = getExtensionUrl(ext.getUrl());
@ -1465,7 +1508,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} }
for (Object next : ext.getExtension()) { for (Object next : ext.getExtension()) {
writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext); writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext, theContainedResource);
} }
theEventWriter.endArray(); theEventWriter.endArray();
@ -1473,6 +1516,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
// Write value // Write value
if (!noValue) { if (!noValue) {
theEncodeContext.pushPath("value", false);
/* /*
* Pre-process value - This is called in case the value is a reference * Pre-process value - This is called in case the value is a reference
@ -1490,8 +1534,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); 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); 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);
theEncodeContext.popPath();
} }
} }

View File

@ -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 * 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. * {@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 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 org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
// private static final Set<String> RESOURCE_NAMESPACES; // private static final Set<String> RESOURCE_NAMESPACES;

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.*;
public class ValidateUtil { 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) { public static void isGreaterThanOrEqualTo(long theValue, long theMinimum, String theMessage) {
if (theValue < theMinimum) { 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) { public static void isNotBlankOrThrowInvalidRequest(String theString, String theMessage) {
if (isBlank(theString)) { if (isBlank(theString)) {
throw new InvalidRequestException(theMessage); 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) { public static void isTrueOrThrowInvalidRequest(boolean theSuccess, String theMessage) {
if (theSuccess == false) { if (theSuccess == false) {
throw new InvalidRequestException(theMessage); throw new InvalidRequestException(theMessage);

View File

@ -22,7 +22,7 @@ package org.hl7.fhir.instance.model.api;
import java.util.List; import java.util.List;
public interface IBaseHasExtensions { public interface IBaseHasExtensions extends IBase {
IBaseExtension<?, ?> addExtension(); IBaseExtension<?, ?> addExtension();

View File

@ -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.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.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.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} ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}

View File

@ -145,6 +145,10 @@ public class DaoConfig {
private boolean myEnableInMemorySubscriptionMatching = true; private boolean myEnableInMemorySubscriptionMatching = true;
private boolean myEnforceReferenceTargetTypes = true; private boolean myEnforceReferenceTargetTypes = true;
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC; private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
/**
* EXPERIMENTAL - Do not use in production! Do not change default of {@code false}!
*/
private boolean myPreExpandValueSetsExperimental = false;
/** /**
* Constructor * Constructor
@ -1600,6 +1604,34 @@ public class DaoConfig {
myModelConfig.setWebsocketContextPath(theWebsocketContextPath); myModelConfig.setWebsocketContextPath(theWebsocketContextPath);
} }
/**
* EXPERIMENTAL - Do not use in production!
* <p>
* 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.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*/
public boolean isPreExpandValueSetsExperimental() {
return myPreExpandValueSetsExperimental;
}
/**
* EXPERIMENTAL - Do not use in production!
* <p>
* 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.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*/
public void setPreExpandValueSetsExperimental(boolean thePreExpandValueSetsExperimental) {
myPreExpandValueSetsExperimental = thePreExpandValueSetsExperimental;
}
public enum IndexEnabledEnum { public enum IndexEnabledEnum {
ENABLED, ENABLED,
DISABLED DISABLED

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; 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.model.util.StringNormalizer;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService; 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.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; 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 final int MAXIMUM_PAGE_SIZE = 800;
private static Long NO_MORE = -1L; 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 boolean myDontUseHashesForSearch;
private final DaoConfig myDaoConfig; private final DaoConfig myDaoConfig;
@Autowired @Autowired
@ -1546,12 +1542,6 @@ public class SearchBuilder implements ISearchBuilder {
myBuilder = myEntityManager.getCriteriaBuilder(); myBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchRuntimeDetails.getSearchUuid(); mySearchUuid = theSearchRuntimeDetails.getSearchUuid();
if (ourTrackHandlersForUnitTest) {
ourLastHandlerParamsForUnitTest = theParams;
ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY;
ourLastHandlerThreadForUnitTest = Thread.currentThread().getName();
}
if (myPidSet == null) { if (myPidSet == null) {
myPidSet = new HashSet<>(); myPidSet = new HashSet<>();
} }
@ -2214,9 +2204,16 @@ public class SearchBuilder implements ISearchBuilder {
if (sb != null) { if (sb != null) {
String indexString = sb.toString(); String indexString = sb.toString();
ourLog.debug("Checking for unique index for query: {}", indexString); 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); addPredicateCompositeStringUnique(theParams, indexString);
} }
} }
@ -2788,24 +2785,6 @@ public class SearchBuilder implements ISearchBuilder {
return query.getResultList(); 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<Predicate> thePredicates) { private static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[0]); return thePredicates.toArray(new Predicate[0]);
} }

View File

@ -34,7 +34,7 @@ public interface ITermCodeSystemDao extends JpaRepository<TermCodeSystem, Long>
TermCodeSystem findByCodeSystemUri(@Param("code_system_uri") String theCodeSystemUri); TermCodeSystem findByCodeSystemUri(@Param("code_system_uri") String theCodeSystemUri);
@Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myResourcePid = :resource_pid") @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") @Query("SELECT cs FROM TermCodeSystem cs WHERE cs.myCurrentVersion.myId = :csv_pid")
Optional<TermCodeSystem> findWithCodeSystemVersionAsCurrentVersion(@Param("csv_pid") Long theCodeSystemVersionPid); Optional<TermCodeSystem> findWithCodeSystemVersionAsCurrentVersion(@Param("csv_pid") Long theCodeSystemVersionPid);

View File

@ -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<TermValueSetCode, Long> {
@Query("DELETE FROM TermValueSetCode vsc WHERE vsc.myValueSet.myId = :pid")
@Modifying
void deleteTermValueSetCodesByValueSetId(@Param("pid") Long theValueSetId);
}

View File

@ -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<TermValueSet, Long> {
@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<TermValueSet> findByResourcePid(@Param("resource_pid") Long theResourcePid);
@Query("SELECT vs FROM TermValueSet vs WHERE vs.myUrl = :url")
Optional<TermValueSet> findByUrl(@Param("url") String theUrl);
}

View File

@ -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;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; 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.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import org.apache.commons.codec.binary.StringUtils; 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.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.*; 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.FilterOperator;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 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.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -45,14 +51,18 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> { public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class); private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class);
@Autowired
private IHapiTerminologySvc myHapiTerminologySvc;
@Autowired @Autowired
@Qualifier("myJpaValidationSupportChainDstu3") @Qualifier("myJpaValidationSupportChainDstu3")
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -295,4 +305,26 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
// nothing // 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;
}
} }

View File

@ -96,6 +96,8 @@ public class ExpungeEverythingService {
counter.addAndGet(expungeEverythingByType(ResourceLink.class)); counter.addAndGet(expungeEverythingByType(ResourceLink.class));
counter.addAndGet(expungeEverythingByType(SearchResult.class)); counter.addAndGet(expungeEverythingByType(SearchResult.class));
counter.addAndGet(expungeEverythingByType(SearchInclude.class)); counter.addAndGet(expungeEverythingByType(SearchInclude.class));
counter.addAndGet(expungeEverythingByType(TermValueSetCode.class));
counter.addAndGet(expungeEverythingByType(TermValueSet.class));
counter.addAndGet(expungeEverythingByType(TermConceptParentChildLink.class)); counter.addAndGet(expungeEverythingByType(TermConceptParentChildLink.class));
counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElementTarget.class)); counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElementTarget.class));
counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElement.class)); counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElement.class));

View File

@ -57,6 +57,14 @@ public class IdHelperService {
myForcedIdDao.delete(forcedId); myForcedIdDao.delete(forcedId);
} }
/**
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
public Long translateForcedIdToPid(IIdType theId, RequestDetails theRequestDetails) {
return translateForcedIdToPid(theId.getResourceType(), theId.getIdPart(), theRequestDetails);
}
/** /**
* @throws ResourceNotFoundException If the ID can not be found * @throws ResourceNotFoundException If the ID can not be found
*/ */

View File

@ -20,34 +20,43 @@ package ca.uhn.fhir.jpa.dao.r4;
* #L% * #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;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet; 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.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil; 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<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> { public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
@Autowired
private IHapiTerminologySvc myHapiTerminologySvc;
@Autowired @Autowired
@Qualifier("myJpaValidationSupportChainR4") @Qualifier("myJpaValidationSupportChainR4")
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -296,4 +305,21 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
// nothing // 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;
}
} }

View File

@ -21,13 +21,16 @@ package ca.uhn.fhir.jpa.entity;
*/ */
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import static org.apache.commons.lang3.StringUtils.left; import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
//@formatter:off //@formatter:off
@Table(name = "TRM_CODESYSTEM", uniqueConstraints = { @Table(name = "TRM_CODESYSTEM", uniqueConstraints = {
@ -37,9 +40,11 @@ import static org.apache.commons.lang3.StringUtils.left;
//@formatter:on //@formatter:on
public class TermCodeSystem implements Serializable { public class TermCodeSystem implements Serializable {
private static final long serialVersionUID = 1L; 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;
public static final int MAX_URL_LENGTH = 200;
@Column(name = "CODE_SYSTEM_URI", nullable = false, length = MAX_URL_LENGTH)
private String myCodeSystemUri; private String myCodeSystemUri;
@OneToOne() @OneToOne()
@ -55,7 +60,7 @@ public class TermCodeSystem implements Serializable {
private ResourceTable myResource; private ResourceTable myResource;
@Column(name = "RES_ID", insertable = false, updatable = false) @Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourcePid; private Long myResourcePid;
@Column(name = "CS_NAME", nullable = true) @Column(name = "CS_NAME", nullable = true, length = MAX_NAME_LENGTH)
private String myName; private String myName;
public String getCodeSystemUri() { public String getCodeSystemUri() {
@ -66,16 +71,21 @@ public class TermCodeSystem implements Serializable {
return myName; 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; myCodeSystemUri = theCodeSystemUri;
return this;
} }
public TermCodeSystemVersion getCurrentVersion() { public TermCodeSystemVersion getCurrentVersion() {
return myCurrentVersion; return myCurrentVersion;
} }
public void setCurrentVersion(TermCodeSystemVersion theCurrentVersion) { public TermCodeSystem setCurrentVersion(TermCodeSystemVersion theCurrentVersion) {
myCurrentVersion = theCurrentVersion; myCurrentVersion = theCurrentVersion;
return this;
} }
public Long getPid() { public Long getPid() {
@ -86,12 +96,14 @@ public class TermCodeSystem implements Serializable {
return myResource; return myResource;
} }
public void setName(String theName) { public TermCodeSystem setName(String theName) {
myName = left(theName, CS_NAME_LENGTH); myName = left(theName, MAX_NAME_LENGTH);
return this;
} }
public void setResource(ResourceTable theResource) { public TermCodeSystem setResource(ResourceTable theResource) {
myResource = theResource; myResource = theResource;
return this;
} }
@Override @Override

View File

@ -22,12 +22,15 @@ package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ValidateUtil;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.length;
//@formatter:off //@formatter:off
@Table(name = "TRM_CODESYSTEM_VER" @Table(name = "TRM_CODESYSTEM_VER"
// Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this) // 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 { public class TermCodeSystemVersion implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
static final int MAX_VERSION_LENGTH = 200;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem")
private Collection<TermConcept> myConcepts; private Collection<TermConcept> 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")) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CODESYSVER_RES_ID"))
private ResourceTable myResource; 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; private String myCodeSystemVersionId;
/** /**
* This was added in HAPI FHIR 3.3.0 and is nullable just to avoid migration * 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; return myCodeSystem;
} }
public void setCodeSystem(TermCodeSystem theCodeSystem) { public TermCodeSystemVersion setCodeSystem(TermCodeSystem theCodeSystem) {
myCodeSystem = theCodeSystem; myCodeSystem = theCodeSystem;
return this;
} }
public String getCodeSystemVersionId() { public String getCodeSystemVersionId() {
return myCodeSystemVersionId; 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; myCodeSystemVersionId = theCodeSystemVersionId;
return this;
} }
public Collection<TermConcept> getConcepts() { public Collection<TermConcept> getConcepts() {
@ -131,8 +141,9 @@ public class TermCodeSystemVersion implements Serializable {
return myResource; return myResource;
} }
public void setResource(ResourceTable theResource) { public TermCodeSystemVersion setResource(ResourceTable theResource) {
myResource = theResource; myResource = theResource;
return this;
} }
@Override @Override

View File

@ -1,25 +1,5 @@
package ca.uhn.fhir.jpa.entity; 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 * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -40,6 +20,27 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L% * #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 @Entity
@Indexed(interceptor = DeferConceptIndexingInterceptor.class) @Indexed(interceptor = DeferConceptIndexingInterceptor.class)
@Table(name = "TRM_CONCEPT", uniqueConstraints = { @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") @Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
}) })
public class TermConcept implements Serializable { 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 org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class);
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public static final int MAX_CODE_LENGTH = 500;
public static final int MAX_DESC_LENGTH = 400;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {}) @OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
private Collection<TermConceptParentChildLink> myChildren; private Collection<TermConceptParentChildLink> 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")),}) @Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
private String myCode; private String myCode;
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@ -70,7 +72,7 @@ public class TermConcept implements Serializable {
@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false) @Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
@Fields({@Field(name = "myCodeSystemVersionPid")}) @Fields({@Field(name = "myCodeSystemVersionPid")})
private long myCodeSystemVersionPid; private long myCodeSystemVersionPid;
@Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true) @Column(name = "DISPLAY", nullable = true, length = MAX_DESC_LENGTH)
@Fields({ @Fields({
@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @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")), @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; return myCode;
} }
public void setCode(String theCode) { public TermConcept setCode(@Nonnull String theCode) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "Code must not be null or empty"); 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; myCode = theCode;
return this;
} }
public TermCodeSystemVersion getCodeSystemVersion() { public TermCodeSystemVersion getCodeSystemVersion() {
return myCodeSystem; return myCodeSystem;
} }
public void setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { public TermConcept setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
myCodeSystem = theCodeSystemVersion; myCodeSystem = theCodeSystemVersion;
if (theCodeSystemVersion.getPid() != null) { if (theCodeSystemVersion.getPid() != null) {
myCodeSystemVersionPid = theCodeSystemVersion.getPid(); myCodeSystemVersionPid = theCodeSystemVersion.getPid();
} }
return this;
} }
public List<Coding> getCodingProperties(String thePropertyName) { public List<Coding> getCodingProperties(String thePropertyName) {
@ -231,10 +237,7 @@ public class TermConcept implements Serializable {
} }
public TermConcept setDisplay(String theDisplay) { public TermConcept setDisplay(String theDisplay) {
myDisplay = theDisplay; myDisplay = left(theDisplay, MAX_DESC_LENGTH);
if (isNotBlank(theDisplay) && theDisplay.length() > MAX_DESC_LENGTH) {
myDisplay = myDisplay.substring(0, MAX_DESC_LENGTH);
}
return this; return this;
} }
@ -246,8 +249,9 @@ public class TermConcept implements Serializable {
return myIndexStatus; return myIndexStatus;
} }
public void setIndexStatus(Long theIndexStatus) { public TermConcept setIndexStatus(Long theIndexStatus) {
myIndexStatus = theIndexStatus; myIndexStatus = theIndexStatus;
return this;
} }
public String getParentPidsAsString() { public String getParentPidsAsString() {
@ -272,8 +276,9 @@ public class TermConcept implements Serializable {
return mySequence; return mySequence;
} }
public void setSequence(Integer theSequence) { public TermConcept setSequence(Integer theSequence) {
mySequence = theSequence; mySequence = theSequence;
return this;
} }
public List<String> getStringProperties(String thePropertyName) { public List<String> getStringProperties(String thePropertyName) {
@ -300,8 +305,9 @@ public class TermConcept implements Serializable {
return myUpdated; return myUpdated;
} }
public void setUpdated(Date theUpdated) { public TermConcept setUpdated(Date theUpdated) {
myUpdated = theUpdated; myUpdated = theUpdated;
return this;
} }
@Override @Override
@ -355,8 +361,9 @@ public class TermConcept implements Serializable {
myParentPids = b.toString(); myParentPids = b.toString();
} }
public void setParentPids(String theParentPids) { public TermConcept setParentPids(String theParentPids) {
myParentPids = theParentPids; myParentPids = theParentPids;
return this;
} }
@Override @Override

View File

@ -20,16 +20,24 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.util.ValidateUtil;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_DESIG", uniqueConstraints = { @Table(name = "TRM_CONCEPT_DESIG", uniqueConstraints = {
}, indexes = { }, indexes = {
}) })
public class TermConceptDesignation implements Serializable { public class TermConceptDesignation implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
@ManyToOne @ManyToOne
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT")) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT"))
private TermConcept myConcept; private TermConcept myConcept;
@ -38,15 +46,15 @@ public class TermConceptDesignation implements Serializable {
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_DESIG_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_DESIG_PID")
@Column(name = "PID") @Column(name = "PID")
private Long myId; private Long myId;
@Column(name = "LANG", length = 500, nullable = true) @Column(name = "LANG", nullable = true, length = MAX_LENGTH)
private String myLanguage; private String myLanguage;
@Column(name = "USE_SYSTEM", length = 500, nullable = true) @Column(name = "USE_SYSTEM", nullable = true, length = MAX_LENGTH)
private String myUseSystem; private String myUseSystem;
@Column(name = "USE_CODE", length = 500, nullable = true) @Column(name = "USE_CODE", nullable = true, length = MAX_LENGTH)
private String myUseCode; private String myUseCode;
@Column(name = "USE_DISPLAY", length = 500, nullable = true) @Column(name = "USE_DISPLAY", nullable = true, length = MAX_LENGTH)
private String myUseDisplay; private String myUseDisplay;
@Column(name = "VAL", length = 500, nullable = false) @Column(name = "VAL", nullable = false, length = MAX_LENGTH)
private String myValue; private String myValue;
/** /**
* TODO: Make this non-null * TODO: Make this non-null
@ -62,6 +70,8 @@ public class TermConceptDesignation implements Serializable {
} }
public TermConceptDesignation setLanguage(String theLanguage) { public TermConceptDesignation setLanguage(String theLanguage) {
ValidateUtil.isNotTooLongOrThrowIllegalArgument(theLanguage, MAX_LENGTH,
"Language exceeds maximum length (" + MAX_LENGTH + "): " + length(theLanguage));
myLanguage = theLanguage; myLanguage = theLanguage;
return this; return this;
} }
@ -71,6 +81,8 @@ public class TermConceptDesignation implements Serializable {
} }
public TermConceptDesignation setUseCode(String theUseCode) { public TermConceptDesignation setUseCode(String theUseCode) {
ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseCode, MAX_LENGTH,
"Use code exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseCode));
myUseCode = theUseCode; myUseCode = theUseCode;
return this; return this;
} }
@ -80,7 +92,7 @@ public class TermConceptDesignation implements Serializable {
} }
public TermConceptDesignation setUseDisplay(String theUseDisplay) { public TermConceptDesignation setUseDisplay(String theUseDisplay) {
myUseDisplay = theUseDisplay; myUseDisplay = left(theUseDisplay, MAX_LENGTH);
return this; return this;
} }
@ -89,6 +101,9 @@ public class TermConceptDesignation implements Serializable {
} }
public TermConceptDesignation setUseSystem(String theUseSystem) { public TermConceptDesignation setUseSystem(String theUseSystem) {
ValidateUtil.isNotTooLongOrThrowIllegalArgument(theUseSystem, MAX_LENGTH,
"Use system exceeds maximum length (" + MAX_LENGTH + "): " + length(theUseSystem));
myUseSystem = theUseSystem; myUseSystem = theUseSystem;
return this; return this;
} }
@ -97,7 +112,10 @@ public class TermConceptDesignation implements Serializable {
return myValue; 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; myValue = theValue;
return this; return this;
} }

View File

@ -21,19 +21,27 @@ package ca.uhn.fhir.jpa.entity;
*/ */
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_MAP", uniqueConstraints = { @Table(name = "TRM_CONCEPT_MAP", uniqueConstraints = {
@UniqueConstraint(name = "IDX_CONCEPT_MAP_URL", columnNames = {"URL"}) @UniqueConstraint(name = "IDX_CONCEPT_MAP_URL", columnNames = {"URL"})
}) })
public class TermConceptMap implements Serializable { public class TermConceptMap implements Serializable {
private static final long serialVersionUID = 1L;
static final int MAX_URL_LENGTH = 200;
@Id() @Id()
@SequenceGenerator(name = "SEQ_CONCEPT_MAP_PID", sequenceName = "SEQ_CONCEPT_MAP_PID") @SequenceGenerator(name = "SEQ_CONCEPT_MAP_PID", sequenceName = "SEQ_CONCEPT_MAP_PID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "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) @Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourcePid; 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; 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; private String myTarget;
@Column(name = "URL", length = 200, nullable = false) @Column(name = "URL", nullable = false, length = MAX_URL_LENGTH)
private String myUrl; private String myUrl;
@OneToMany(mappedBy = "myConceptMap") @OneToMany(mappedBy = "myConceptMap")
@ -75,47 +83,58 @@ public class TermConceptMap implements Serializable {
return myResource; return myResource;
} }
public void setResource(ResourceTable resource) { public TermConceptMap setResource(ResourceTable theResource) {
myResource = resource; myResource = theResource;
return this;
} }
public Long getResourcePid() { public Long getResourcePid() {
return myResourcePid; return myResourcePid;
} }
public void setResourcePid(Long resourcePid) { public TermConceptMap setResourcePid(Long theResourcePid) {
myResourcePid = resourcePid; myResourcePid = theResourcePid;
return this;
} }
public String getSource() { public String getSource() {
return mySource; return mySource;
} }
public void setSource(String source) { public TermConceptMap setSource(String theSource) {
mySource = source; ValidateUtil.isNotTooLongOrThrowIllegalArgument(theSource, TermValueSet.MAX_URL_LENGTH,
"Source exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theSource));
mySource = theSource;
return this;
} }
public String getTarget() { public String getTarget() {
return myTarget; return myTarget;
} }
public void setTarget(String target) { public TermConceptMap setTarget(String theTarget) {
myTarget = target; ValidateUtil.isNotTooLongOrThrowIllegalArgument(theTarget, TermValueSet.MAX_URL_LENGTH,
"Target exceeds maximum length (" + TermValueSet.MAX_URL_LENGTH + "): " + length(theTarget));
myTarget = theTarget;
return this;
} }
public String getUrl() { public String getUrl() {
return myUrl; 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; myUrl = theUrl;
return this;
} }
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myId", myId) .append("myId", myId)
.append("myResource", myResource.toString())
.append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)")) .append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)"))
.append("myResourcePid", myResourcePid) .append("myResourcePid", myResourcePid)
.append("mySource", mySource) .append("mySource", mySource)

View File

@ -20,17 +20,23 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_MAP_GROUP") @Table(name = "TRM_CONCEPT_MAP_GROUP")
public class TermConceptMapGroup implements Serializable { public class TermConceptMapGroup implements Serializable {
private static final long serialVersionUID = 1L;
@Id() @Id()
@SequenceGenerator(name = "SEQ_CONCEPT_MAP_GROUP_PID", sequenceName = "SEQ_CONCEPT_MAP_GROUP_PID") @SequenceGenerator(name = "SEQ_CONCEPT_MAP_GROUP_PID", sequenceName = "SEQ_CONCEPT_MAP_GROUP_PID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "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")) @JoinColumn(name = "CONCEPT_MAP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGROUP_CONCEPTMAP"))
private TermConceptMap myConceptMap; 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; private String mySource;
@Column(name = "SOURCE_VERSION", length = 100) @Column(name = "SOURCE_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String mySourceVersion; 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; private String myTarget;
@Column(name = "TARGET_VERSION", length = 100) @Column(name = "TARGET_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String myTargetVersion; private String myTargetVersion;
@OneToMany(mappedBy = "myConceptMapGroup") @OneToMany(mappedBy = "myConceptMapGroup")
private List<TermConceptMapGroupElement> myConceptMapGroupElements; private List<TermConceptMapGroupElement> 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; 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; 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; private String myTargetValueSet;
public TermConceptMap getConceptMap() { public TermConceptMap getConceptMap() {
return myConceptMap; return myConceptMap;
} }
public void setConceptMap(TermConceptMap theTermConceptMap) { public TermConceptMapGroup setConceptMap(TermConceptMap theTermConceptMap) {
myConceptMap = theTermConceptMap; myConceptMap = theTermConceptMap;
return this;
} }
public List<TermConceptMapGroupElement> getConceptMapGroupElements() { public List<TermConceptMapGroupElement> getConceptMapGroupElements() {
@ -96,8 +103,12 @@ public class TermConceptMapGroup implements Serializable {
return mySource; 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; this.mySource = theSource;
return this;
} }
public String getSourceValueSet() { public String getSourceValueSet() {
@ -111,16 +122,23 @@ public class TermConceptMapGroup implements Serializable {
return mySourceVersion; 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; mySourceVersion = theSourceVersion;
return this;
} }
public String getTarget() { public String getTarget() {
return myTarget; 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; this.myTarget = theTarget;
return this;
} }
public String getTargetValueSet() { public String getTargetValueSet() {
@ -134,13 +152,16 @@ public class TermConceptMapGroup implements Serializable {
return myTargetVersion; 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; myTargetVersion = theTargetVersion;
return this;
} }
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myId", myId) .append("myId", myId)
.append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)")) .append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)"))
.append("mySource", mySource) .append("mySource", mySource)

View File

@ -20,22 +20,28 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #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.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_MAP_GRP_ELEMENT", indexes = { @Table(name = "TRM_CONCEPT_MAP_GRP_ELEMENT", indexes = {
@Index(name = "IDX_CNCPT_MAP_GRP_CD", columnList = "SOURCE_CODE") @Index(name = "IDX_CNCPT_MAP_GRP_CD", columnList = "SOURCE_CODE")
}) })
public class TermConceptMapGroupElement implements Serializable { public class TermConceptMapGroupElement implements Serializable {
private static final long serialVersionUID = 1L;
@Id() @Id()
@SequenceGenerator(name = "SEQ_CONCEPT_MAP_GRP_ELM_PID", sequenceName = "SEQ_CONCEPT_MAP_GRP_ELM_PID") @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") @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")) @JoinColumn(name = "CONCEPT_MAP_GROUP_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGELEMENT_GROUP"))
private TermConceptMapGroup myConceptMapGroup; 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; private String myCode;
@Column(name = "SOURCE_DISPLAY", length = TermConcept.MAX_DESC_LENGTH) @Column(name = "SOURCE_DISPLAY", length = TermConcept.MAX_DESC_LENGTH)
@ -55,33 +61,37 @@ public class TermConceptMapGroupElement implements Serializable {
@OneToMany(mappedBy = "myConceptMapGroupElement") @OneToMany(mappedBy = "myConceptMapGroupElement")
private List<TermConceptMapGroupElementTarget> myConceptMapGroupElementTargets; private List<TermConceptMapGroupElementTarget> myConceptMapGroupElementTargets;
@Column(name = "CONCEPT_MAP_URL", length = 200) @Column(name = "CONCEPT_MAP_URL", nullable = true, length = TermConceptMap.MAX_URL_LENGTH)
private String myConceptMapUrl; private String myConceptMapUrl;
@Column(name = "SYSTEM_URL", length = 200) @Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH)
private String mySystem; private String mySystem;
@Column(name = "SYSTEM_VERSION", length = 200) @Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String mySystemVersion; private String mySystemVersion;
@Column(name = "VALUESET_URL", length = 200) @Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH)
private String myValueSet; private String myValueSet;
public String getCode() { public String getCode() {
return myCode; return myCode;
} }
public void setCode(String theCode) { public TermConceptMapGroupElement setCode(@Nonnull String theCode) {
Validate.notBlank(theCode, "theCode must not be blank"); 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; myCode = theCode;
return this;
} }
public TermConceptMapGroup getConceptMapGroup() { public TermConceptMapGroup getConceptMapGroup() {
return myConceptMapGroup; return myConceptMapGroup;
} }
public void setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) { public TermConceptMapGroupElement setConceptMapGroup(TermConceptMapGroup theTermConceptMapGroup) {
myConceptMapGroup = theTermConceptMapGroup; myConceptMapGroup = theTermConceptMapGroup;
return this;
} }
public List<TermConceptMapGroupElementTarget> getConceptMapGroupElementTargets() { public List<TermConceptMapGroupElementTarget> getConceptMapGroupElementTargets() {
@ -103,8 +113,9 @@ public class TermConceptMapGroupElement implements Serializable {
return myDisplay; return myDisplay;
} }
public void setDisplay(String theDisplay) { public TermConceptMapGroupElement setDisplay(String theDisplay) {
myDisplay = theDisplay; myDisplay = left(theDisplay, TermConcept.MAX_DESC_LENGTH);
return this;
} }
public Long getId() { public Long getId() {
@ -158,7 +169,7 @@ public class TermConceptMapGroupElement implements Serializable {
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myId", myId) .append("myId", myId)
.append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)")) .append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)"))
.append("myCode", myCode) .append("myCode", myCode)

View File

@ -20,20 +20,28 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_MAP_GRP_ELM_TGT", indexes = { @Table(name = "TRM_CONCEPT_MAP_GRP_ELM_TGT", indexes = {
@Index(name = "IDX_CNCPT_MP_GRP_ELM_TGT_CD", columnList = "TARGET_CODE") @Index(name = "IDX_CNCPT_MP_GRP_ELM_TGT_CD", columnList = "TARGET_CODE")
}) })
public class TermConceptMapGroupElementTarget implements Serializable { public class TermConceptMapGroupElementTarget implements Serializable {
private static final long serialVersionUID = 1L;
static final int MAX_EQUIVALENCE_LENGTH = 50;
@Id() @Id()
@SequenceGenerator(name = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID", sequenceName = "SEQ_CNCPT_MAP_GRP_ELM_TGT_PID") @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") @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")) @JoinColumn(name = "CONCEPT_MAP_GRP_ELM_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TCMGETARGET_ELEMENT"))
private TermConceptMapGroupElement myConceptMapGroupElement; 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; 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; private String myDisplay;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "TARGET_EQUIVALENCE", length = 50) @Column(name = "TARGET_EQUIVALENCE", nullable = true, length = MAX_EQUIVALENCE_LENGTH)
private ConceptMapEquivalence myEquivalence; 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; private String myConceptMapUrl;
@Column(name = "SYSTEM_URL", length = 200)
@Column(name = "SYSTEM_URL", nullable = true, length = TermCodeSystem.MAX_URL_LENGTH)
private String mySystem; private String mySystem;
@Column(name = "SYSTEM_VERSION", length = 200)
@Column(name = "SYSTEM_VERSION", nullable = true, length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String mySystemVersion; private String mySystemVersion;
@Column(name = "VALUESET_URL", length = 200)
@Column(name = "VALUESET_URL", nullable = true, length = TermValueSet.MAX_URL_LENGTH)
private String myValueSet; private String myValueSet;
public String getCode() { public String getCode() {
return myCode; 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; myCode = theCode;
return this;
} }
public TermConceptMapGroupElement getConceptMapGroupElement() { public TermConceptMapGroupElement getConceptMapGroupElement() {
@ -90,16 +105,18 @@ public class TermConceptMapGroupElementTarget implements Serializable {
return myDisplay; return myDisplay;
} }
public void setDisplay(String theDisplay) { public TermConceptMapGroupElementTarget setDisplay(String theDisplay) {
myDisplay = theDisplay; myDisplay = theDisplay;
return this;
} }
public ConceptMapEquivalence getEquivalence() { public ConceptMapEquivalence getEquivalence() {
return myEquivalence; return myEquivalence;
} }
public void setEquivalence(ConceptMapEquivalence theEquivalence) { public TermConceptMapGroupElementTarget setEquivalence(ConceptMapEquivalence theEquivalence) {
myEquivalence = theEquivalence; myEquivalence = theEquivalence;
return this;
} }
public Long getId() { public Long getId() {
@ -155,7 +172,7 @@ public class TermConceptMapGroupElementTarget implements Serializable {
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myId", myId) .append("myId", myId)
.append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)")) .append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)"))
.append("myCode", myCode) .append("myCode", myCode)

View File

@ -20,21 +20,9 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #L%
*/ */
import javax.persistence.*;
import java.io.Serializable; 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 @Entity
@Table(name = "TRM_CONCEPT_PC_LINK") @Table(name = "TRM_CONCEPT_PC_LINK")
public class TermConceptParentChildLink implements Serializable { public class TermConceptParentChildLink implements Serializable {
@ -136,20 +124,24 @@ public class TermConceptParentChildLink implements Serializable {
return result; return result;
} }
public void setChild(TermConcept theChild) { public TermConceptParentChildLink setChild(TermConcept theChild) {
myChild = theChild; myChild = theChild;
return this;
} }
public void setCodeSystem(TermCodeSystemVersion theCodeSystem) { public TermConceptParentChildLink setCodeSystem(TermCodeSystemVersion theCodeSystem) {
myCodeSystem = theCodeSystem; myCodeSystem = theCodeSystem;
return this;
} }
public void setParent(TermConcept theParent) { public TermConceptParentChildLink setParent(TermConcept theParent) {
myParent = theParent; myParent = theParent;
return this;
} }
public void setRelationshipType(RelationshipTypeEnum theRelationshipType) { public TermConceptParentChildLink setRelationshipType(RelationshipTypeEnum theRelationshipType) {
myRelationshipType = theRelationshipType; myRelationshipType = theRelationshipType;
return this;
} }
public enum RelationshipTypeEnum { public enum RelationshipTypeEnum {

View File

@ -20,21 +20,29 @@ package ca.uhn.fhir.jpa.entity;
* #L% * #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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import javax.annotation.Nonnull;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Entity @Entity
@Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = { @Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = {
}, indexes = { }, indexes = {
}) })
public class TermConceptProperty implements Serializable { public class TermConceptProperty implements Serializable {
static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
@ManyToOne @ManyToOne
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
private TermConcept myConcept; private TermConcept myConcept;
@ -51,22 +59,22 @@ public class TermConceptProperty implements Serializable {
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID")
@Column(name = "PID") @Column(name = "PID")
private Long myId; private Long myId;
@Column(name = "PROP_KEY", length = 500, nullable = false) @Column(name = "PROP_KEY", nullable = false, length = MAX_LENGTH)
@NotBlank @NotBlank
private String myKey; private String myKey;
@Column(name = "PROP_VAL", length = 500, nullable = true) @Column(name = "PROP_VAL", nullable = true, length = MAX_LENGTH)
private String myValue; 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; private TermConceptPropertyTypeEnum myType;
/** /**
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * 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; private String myCodeSystem;
/** /**
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * 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; private String myDisplay;
/** /**
@ -80,6 +88,8 @@ public class TermConceptProperty implements Serializable {
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
*/ */
public TermConceptProperty setCodeSystem(String theCodeSystem) { public TermConceptProperty setCodeSystem(String theCodeSystem) {
ValidateUtil.isNotTooLongOrThrowIllegalArgument(theCodeSystem, MAX_LENGTH,
"Property code system exceeds maximum length (" + MAX_LENGTH + "): " + length(theCodeSystem));
myCodeSystem = theCodeSystem; myCodeSystem = theCodeSystem;
return this; return this;
} }
@ -95,7 +105,7 @@ public class TermConceptProperty implements Serializable {
* Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING}
*/ */
public TermConceptProperty setDisplay(String theDisplay) { public TermConceptProperty setDisplay(String theDisplay) {
myDisplay = theDisplay; myDisplay = left(theDisplay, MAX_LENGTH);
return this; return this;
} }
@ -103,15 +113,20 @@ public class TermConceptProperty implements Serializable {
return myKey; 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; myKey = theKey;
return this;
} }
public TermConceptPropertyTypeEnum getType() { public TermConceptPropertyTypeEnum getType() {
return myType; return myType;
} }
public TermConceptProperty setType(TermConceptPropertyTypeEnum theType) { public TermConceptProperty setType(@Nonnull TermConceptPropertyTypeEnum theType) {
Validate.notNull(theType);
myType = theType; myType = theType;
return this; return this;
} }
@ -128,8 +143,9 @@ public class TermConceptProperty implements Serializable {
* This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string} * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string}
* property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property. * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property.
*/ */
public void setValue(String theValue) { public TermConceptProperty setValue(String theValue) {
myValue = theValue; myValue = left(theValue, MAX_LENGTH);
return this;
} }
public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { public TermConceptProperty setCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
@ -137,8 +153,9 @@ public class TermConceptProperty implements Serializable {
return this; return this;
} }
public void setConcept(TermConcept theConcept) { public TermConceptProperty setConcept(TermConcept theConcept) {
myConcept = theConcept; myConcept = theConcept;
return this;
} }
@Override @Override

View File

@ -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;
public static final int MAX_NAME_LENGTH = 200;
public 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<TermValueSetCode> 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<TermValueSetCode> 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();
}
}

View File

@ -0,0 +1,166 @@
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));
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();
}
}

View File

@ -410,6 +410,14 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
PersistedJpaBundleProvider retVal = null; PersistedJpaBundleProvider retVal = null;
if (searchToUse != null) { if (searchToUse != null) {
ourLog.debug("Reusing search {} from cache", searchToUse.getUuid()); 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()); searchToUse.setSearchLastReturned(new Date());
mySearchDao.updateSearchLastReturned(searchToUse.getId(), new Date()); mySearchDao.updateSearchLastReturned(searchToUse.getId(), new Date());

View File

@ -119,6 +119,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
@Autowired @Autowired
protected ITermConceptDesignationDao myConceptDesignationDao; protected ITermConceptDesignationDao myConceptDesignationDao;
@Autowired @Autowired
protected ITermValueSetDao myValueSetDao;
@Autowired
protected ITermValueSetCodeDao myValueSetCodeDao;
@Autowired
protected FhirContext myContext; protected FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
@ -388,6 +392,31 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
deleteConceptMap(theResourceTable); deleteConceptMap(theResourceTable);
} }
public void deleteValueSet(ResourceTable theResourceTable) {
// Get existing entity so it can be deleted.
Optional<TermValueSet> 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 <T> void doDelete(String theDescriptor, Supplier<Slice<T>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, ?> theDao) { private <T> void doDelete(String theDescriptor, Supplier<Slice<T>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, ?> theDao) {
int count; int count;
ourLog.info(" * Deleting {}", theDescriptor); ourLog.info(" * Deleting {}", theDescriptor);
@ -690,7 +719,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
private void expandWithoutHibernateSearch(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) { private void expandWithoutHibernateSearch(ValueSet.ValueSetExpansionComponent theExpansionComponent, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) {
ourLog.trace("Hibernate search is not enabled"); 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(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."); 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); myCodeSystemDao.save(codeSystem);
} else { } else {
if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemVersion.getResource().getId())) { 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()); codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue());
throw new UnprocessableEntityException(msg); throw new UnprocessableEntityException(msg);
} }
@ -1310,7 +1339,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget); myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget);
if (codesSaved++ % 250 == 0) { if (codesSaved++ % 250 == 0) {
ourLog.info("Have saved {} codes in conceptmap", codesSaved); ourLog.info("Have saved {} codes in ConceptMap", codesSaved);
myConceptMapGroupElementTargetDao.flush(); myConceptMapGroupElementTargetDao.flush();
} }
} }
@ -1334,6 +1363,69 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
ourLog.info("Done storing TermConceptMap."); 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<TermValueSet> 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 @Override
public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) {
VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA);
@ -1581,9 +1673,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList<String> theConceptsStack, private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, ArrayList<String> theConceptsStack,
IdentityHashMap<TermConcept, Object> theAllConcepts) { IdentityHashMap<TermConcept, Object> 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.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())) { if (theConceptsStack.contains(theConcept.getCode())) {
throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode()); throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode());

View File

@ -81,8 +81,12 @@ public interface IHapiTerminologySvc {
void deleteConceptMapAndChildren(ResourceTable theResourceTable); void deleteConceptMapAndChildren(ResourceTable theResourceTable);
void deleteValueSetAndChildren(ResourceTable theResourceTable);
void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap); void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap);
void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet);
boolean supportsSystem(String theCodeSystem); boolean supportsSystem(String theCodeSystem);
List<TermConceptMapGroupElementTarget> translate(TranslationRequest theTranslationRequest); List<TermConceptMapGroupElementTarget> translate(TranslationRequest theTranslationRequest);

View File

@ -5,7 +5,6 @@ import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -104,7 +103,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
DataSource dataSource = ProxyDataSourceBuilder DataSource dataSource = ProxyDataSourceBuilder
.create(retVal) .create(retVal)
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") // .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
// .countQuery(new ThreadQueryCountHolder()) // .countQuery(new ThreadQueryCountHolder())
.beforeQuery(new BlockLargeNumbersOfParamsListener()) .beforeQuery(new BlockLargeNumbersOfParamsListener())

View File

@ -1,31 +1,13 @@
package ca.uhn.fhir.jpa.dao.dstu3; 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.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam; 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.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult; 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 { public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
@ -226,7 +227,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
myCodeSystemDao.create(codeSystem, mySrd); myCodeSystemDao.create(codeSystem, mySrd);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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());
} }
} }

View File

@ -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<JpaRuntimeSearchParam> 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<String> 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<ResourceIndexedCompositeStringUnique> 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();
}
}

View File

@ -118,6 +118,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("myCodeSystemDaoR4") @Qualifier("myCodeSystemDaoR4")
protected IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> myCodeSystemDao; protected IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> myCodeSystemDao;
@Autowired @Autowired
protected ITermCodeSystemDao myTermCodeSystemDao;
@Autowired
protected ITermCodeSystemVersionDao myTermCodeSystemVersionDao;
@Autowired
@Qualifier("myCompartmentDefinitionDaoR4") @Qualifier("myCompartmentDefinitionDaoR4")
protected IFhirResourceDao<CompartmentDefinition> myCompartmentDefinitionDao; protected IFhirResourceDao<CompartmentDefinition> myCompartmentDefinitionDao;
@Autowired @Autowired
@ -287,6 +291,10 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("myValueSetDaoR4") @Qualifier("myValueSetDaoR4")
protected IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> myValueSetDao; protected IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> myValueSetDao;
@Autowired @Autowired
protected ITermValueSetDao myTermValueSetDao;
@Autowired
protected ITermValueSetCodeDao myTermValueSetCodeDao;
@Autowired
protected ITermConceptMapDao myTermConceptMapDao; protected ITermConceptMapDao myTermConceptMapDao;
@Autowired @Autowired
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;

View File

@ -222,7 +222,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
myCodeSystemDao.create(codeSystem, mySrd); myCodeSystemDao.create(codeSystem, mySrd);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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());
} }
} }

View File

@ -1,15 +1,18 @@
package ca.uhn.fhir.jpa.dao.r4; 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.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; 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.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.ReferenceParam; 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.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
@ -25,6 +29,7 @@ import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
@ -32,6 +37,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; 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 ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; 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 = { @TestPropertySource(properties = {
// Since scheduled tasks can cause searches, which messes up the // Since scheduled tasks can cause searches, which messes up the
// value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() // 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); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class);
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
private IInterceptorBroadcaster myInterceptorBroadcaster;
private List<String> myMessages = new ArrayList<>();
@After @After
public void after() { public void after() {
@ -66,9 +76,31 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myModelConfig.setDefaultSearchParamsCanBeOverridden(true); myModelConfig.setDefaultSearchParamsCanBeOverridden(true);
myDaoConfig.setSchedulingDisabled(true); myDaoConfig.setSchedulingDisabled(true);
myDaoConfig.setUniqueIndexesEnabled(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() { private void createUniqueBirthdateAndGenderSps() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender"); sp.setId("SearchParameter/patient-gender");
@ -106,7 +138,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
SearchBuilder.resetLastHandlerMechanismForUnitTest(); myMessages.clear();
} }
private void createUniqueIndexCoverageBeneficiary() { private void createUniqueIndexCoverageBeneficiary() {
@ -322,19 +354,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> 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 @Test
public void testDoubleMatchingOnAnd_Search() { public void testDoubleMatchingOnAnd_Search() {
createUniqueIndexPatientIdentifier(); createUniqueIndexPatientIdentifier();
@ -981,15 +1000,19 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
SearchBuilder.resetLastHandlerMechanismForUnitTest(); myMessages.clear();
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100); params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params); IBundleProvider results = myPatientDao.search(params, mySrd);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); 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")); pt2.setBirthDateElement(new DateType("2011-01-02"));
myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
SearchBuilder.resetLastHandlerMechanismForUnitTest(); myMessages.clear();
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params); IBundleProvider results = myPatientDao.search(params, mySrd);
String searchId = results.getUuid(); String searchId = results.getUuid();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1)); 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 // Other order
SearchBuilder.resetLastHandlerMechanismForUnitTest(); myMessages.clear();
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-01")); params.add("birthdate", new DateParam("2011-01-01"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); 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()); assertEquals(searchId, results.getUuid());
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1)); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1));
// Null because we just reuse the last search // Because we just reuse the last search
assertNull(SearchBuilder.getLastHandlerMechanismForUnitTest()); logCapturedMessages();
assertThat(myMessages.toString(), containsString("REUSING"));
assertThat(myMessages.toString(), not(containsString("Using unique index")));
myMessages.clear();
SearchBuilder.resetLastHandlerMechanismForUnitTest(); myMessages.clear();
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-03")); params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params); results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results), empty()); 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 = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-03")); params.add("birthdate", new DateParam("2011-01-03"));
results = myPatientDao.search(params); results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results), empty()); 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.addName().setFamily("FAMILY1");
pt1.setManagingOrganization(new Reference("Organization/ORG")); pt1.setManagingOrganization(new Reference("Organization/ORG"));
SearchBuilder.resetLastHandlerMechanismForUnitTest(); IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless();
IIdType id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG").getId().toUnqualifiedVersionless();
assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
myMessages.clear();
uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size()); assertEquals(1, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); 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.addName().setFamily("FAMILY1");
pt1.setManagingOrganization(new Reference("Organization/ORG")); pt1.setManagingOrganization(new Reference("Organization/ORG"));
SearchBuilder.resetLastHandlerMechanismForUnitTest(); id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG", mySrd).getId().toUnqualifiedVersionless();
id1 = myPatientDao.update(pt1, "Patient?name=FAMILY1&organization:Organization=ORG").getId().toUnqualifiedVersionless();
assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); logCapturedMessages();
assertThat(myMessages.toString(), containsString("Using unique index for query for search: Patient?name=FAMILY1&organization=Organization%2FORG"));
myMessages.clear();
uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); uniques = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(1, uniques.size()); assertEquals(1, uniques.size());
assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); 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 @Test
public void testUniqueValuesAreIndexed_StringAndReference() { public void testUniqueValuesAreIndexed_StringAndReference() {
createUniqueNameAndManagingOrganizationSps(); createUniqueNameAndManagingOrganizationSps();
@ -1375,6 +1418,93 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
} }
@Test
public void testDetectUniqueSearchParams() {
createUniqueBirthdateAndGenderSps();
List<JpaRuntimeSearchParam> 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 @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -1,34 +1,20 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import static org.hamcrest.Matchers.containsString; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import static org.hamcrest.Matchers.not; import ca.uhn.fhir.util.TestUtil;
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 org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import java.io.IOException;
import ca.uhn.fhir.util.TestUtil;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test { public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
@ -224,7 +210,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
@Test @Test
public void testValiedateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() {
IPrimitiveType<String> display = null; IPrimitiveType<String> display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;

View File

@ -36,6 +36,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -471,6 +472,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode()); assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
assertEquals("In-memory", tag.getDisplay()); 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(); Subscription subscriptionActivated = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscriptionActivated.getStatus()); assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscriptionActivated.getStatus());
tags = subscriptionActivated.getMeta().getTag(); tags = subscriptionActivated.getMeta().getTag();
@ -495,6 +499,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode()); assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
assertEquals("Database", tag.getDisplay()); 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(); Subscription subscription = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus()); assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus());
tags = subscription.getMeta().getTag(); tags = subscription.getMeta().getTag();

View File

@ -177,7 +177,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs); myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", cs);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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/"));
} }
} }

View File

@ -2,17 +2,12 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.TermConceptMap; import ca.uhn.fhir.jpa.entity.*;
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.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.model.UriType;
import org.junit.*; import org.junit.*;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -21,6 +16,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -31,6 +27,8 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@Rule @Rule
public final ExpectedException expectedException = ExpectedException.none(); public final ExpectedException expectedException = ExpectedException.none();
private IIdType myConceptMapId; private IIdType myConceptMapId;
private IIdType myExtensionalCsId;
private IIdType myExtensionalVsId;
@Before @Before
public void before() { public void before() {
@ -40,6 +38,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
@After @After
public void after() { public void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setPreExpandValueSetsExperimental(new DaoConfig().isPreExpandValueSetsExperimental());
} }
private void createAndPersistConceptMap() { 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 @Test
public void testCreateConceptMapWithMissingSourceSystem() { public void testCreateConceptMapWithMissingSourceSystem() {
ConceptMap conceptMap = new ConceptMap(); 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 @Test
public void testDuplicateConceptMapUrls() { public void testDuplicateConceptMapUrls() {
createAndPersistConceptMap(); createAndPersistConceptMap();
@ -147,6 +189,19 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
createAndPersistConceptMap(); 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 @Test
public void testStoreTermConceptMapAndChildren() { public void testStoreTermConceptMapAndChildren() {
createAndPersistConceptMap(); 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<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
Optional<TermValueSet> 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 @Test
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() { public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
createAndPersistConceptMap(); createAndPersistConceptMap();

View File

@ -102,7 +102,7 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
Validate.notNull(myNullable); Validate.notNull(myNullable);
if (myColumnType == ColumnTypeEnum.STRING) { if (myColumnType == ColumnTypeEnum.STRING) {
Validate.notNull(myColumnLength); Validate.notNull(myColumnLength, "No length specified for " + ColumnTypeEnum.STRING + " column {}.", getColumnName());
} else { } else {
Validate.isTrue(myColumnLength == null); Validate.isTrue(myColumnLength == null);
} }

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.migrate.tasks;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask; import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask;
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask; import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
@ -56,7 +59,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
init400(); init400();
} }
private void init400() { protected void init400() {
Builder version = forVersion(VersionEnum.V4_0_0); Builder version = forVersion(VersionEnum.V4_0_0);
version.onTable("TRM_CONCEPT_MAP_GROUP") version.onTable("TRM_CONCEPT_MAP_GROUP")
@ -75,6 +78,41 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.renameColumn("mySystem", "SYSTEM_URL") .renameColumn("mySystem", "SYSTEM_URL")
.renameColumn("mySystemVersion", "SYSTEM_VERSION") .renameColumn("mySystemVersion", "SYSTEM_VERSION")
.renameColumn("myValueSet", "VALUESET_URL"); .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, TermValueSet.MAX_URL_LENGTH);
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, TermValueSet.MAX_NAME_LENGTH);
// 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, TermCodeSystem.MAX_URL_LENGTH);
termValueSetCodeTable.addColumn("CODE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_CODE_LENGTH);
termValueSetCodeTable
.addIndex("IDX_VALUESET_CODE_CS_CD")
.unique(false)
.withColumns("SYSTEM", "CODE");
termValueSetCodeTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_DESC_LENGTH);
} }

View File

@ -446,7 +446,7 @@ public class SearchParameterMap implements Serializable {
public String toString() { public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
if (isEmpty() == false) { if (isEmpty() == false) {
b.append("params", super.toString()); b.append("params", mySearchParameterMap);
} }
if (getIncludes().isEmpty() == false) { if (getIncludes().isEmpty() == false) {
b.append("includes", getIncludes()); b.append("includes", getIncludes());

View File

@ -210,6 +210,12 @@
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,14 +1,18 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString; import ca.uhn.fhir.context.FhirContext;
import static org.hamcrest.Matchers.emptyOrNullString; import ca.uhn.fhir.model.api.IResource;
import static org.hamcrest.Matchers.is; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import static org.junit.Assert.assertEquals; import ca.uhn.fhir.model.dstu2.resource.Patient;
import static org.junit.Assert.assertNull; import ca.uhn.fhir.model.primitive.IdDt;
import static org.junit.Assert.assertThat; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import java.util.concurrent.TimeUnit; 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.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
@ -26,19 +30,12 @@ import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import static org.hamcrest.CoreMatchers.is;
import ca.uhn.fhir.model.dstu2.resource.Patient; import static org.hamcrest.Matchers.containsString;
import ca.uhn.fhir.model.primitive.IdDt; import static org.hamcrest.Matchers.emptyOrNullString;
import ca.uhn.fhir.rest.annotation.Create; import static org.junit.Assert.*;
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;
public class PreferTest { public class PreferTest {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;

View File

@ -1,19 +1,27 @@
package ca.uhn.fhir.rest.server.interceptor; package ca.uhn.fhir.rest.server.interceptor;
import static org.hamcrest.Matchers.contains; import ca.uhn.fhir.context.FhirContext;
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.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut; 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.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.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse; 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.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; 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 java.util.*;
import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import static org.awaitility.Awaitility.await;
import ca.uhn.fhir.model.dstu2.resource.Patient; import static org.hamcrest.Matchers.contains;
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; import static org.junit.Assert.*;
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;
public class InterceptorUserDataMapDstu2Test { public class InterceptorUserDataMapDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InterceptorUserDataMapDstu2Test.class);
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2(); 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 int ourPort;
private static Server ourServer; private static Server ourServer;
private static RestfulServer servlet; private static RestfulServer servlet;
private final Object myKey = "KEY"; private final Object myKey = "KEY";
private final Object myValue = "VALUE";
private Map<Object, Object> myMap; private Map<Object, Object> myMap;
private Set<String> myMapCheckMethods; private Set<String> myMapCheckMethods;
private final Object myValue = "VALUE";
@Before @Before
public void before() { public void before() {
@ -66,10 +67,10 @@ public class InterceptorUserDataMapDstu2Test {
@Before @Before
public void beforePurgeMap() { public void beforePurgeMap() {
myMap = null; myMap = null;
myMapCheckMethods= new LinkedHashSet<>(); myMapCheckMethods = new LinkedHashSet<>();
} }
@Test @Test
public void testException() throws Exception { public void testException() throws Exception {
@ -78,7 +79,7 @@ public class InterceptorUserDataMapDstu2Test {
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(myMapCheckMethods.toString()); ourLog.info(myMapCheckMethods.toString());
assertThat(myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "preProcessOutgoingException", "handleException", "processingCompleted")); await().until(() -> myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "preProcessOutgoingException", "handleException", "processingCompleted"));
} }
@Test @Test
@ -94,9 +95,8 @@ public class InterceptorUserDataMapDstu2Test {
} }
} }
ourLog.info(myMapCheckMethods.toString()); await().until(() -> myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "outgoingResponse", "processingCompletedNormally", "processingCompleted"));
assertThat(myMapCheckMethods.toString(), myMapCheckMethods, contains("incomingRequestPostProcessed", "incomingRequestPreHandled", "outgoingResponse", "processingCompletedNormally", "processingCompleted"));
} }
private void updateMapUsing(Map<Object, Object> theUserData, String theMethod) { private void updateMapUsing(Map<Object, Object> theUserData, String theMethod) {
@ -111,125 +111,6 @@ public class InterceptorUserDataMapDstu2Test {
myMapCheckMethods.add(theMethod); 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<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
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<Patient> getResourceById(@RequiredParam(name = "_id") String theId) {
throw new InvalidRequestException("FOO");
}
@Override
public Class<Patient> 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 @Interceptor
public class MyInterceptor { 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<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
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<Patient> getResourceById(@RequiredParam(name = "_id") String theId) {
throw new InvalidRequestException("FOO");
}
@Override
public Class<Patient> 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 { public static class PlainProvider {
@Operation(name = "$everything", idempotent = true) @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();
}
} }

View File

@ -18,9 +18,9 @@ import net.sf.json.JsonConfig;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains; 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.AddressUse;
import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory; 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.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.CapabilityStatement.UnknownContentCode; 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.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.*; import org.junit.*;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; 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.contains;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Matchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -110,16 +108,16 @@ public class JsonParserDstu3Test {
// Deserialize then check that valueReference value is still correct // Deserialize then check that valueReference value is still correct
fhirPat = parser.parseResource(Patient.class, output); fhirPat = parser.parseResource(Patient.class, output);
List<Extension> extlst = fhirPat.getExtensionsByUrl("x1"); List<Extension> extensions = fhirPat.getExtensionsByUrl("x1");
Assert.assertEquals(1, extlst.size()); Assert.assertEquals(1, extensions.size());
Assert.assertEquals(refVal, ((Reference) extlst.get(0).getValue()).getReference()); Assert.assertEquals(refVal, ((Reference) extensions.get(0).getValue()).getReference());
} }
/** /**
* See #544 * See #544
*/ */
@Test @Test
public void testBundleStitchReferencesByUuid() throws Exception { public void testBundleStitchReferencesByUuid() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
DocumentManifest dm = new DocumentManifest(); DocumentManifest dm = new DocumentManifest();
@ -175,7 +173,7 @@ public class JsonParserDstu3Test {
} }
@Test @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 String expected = "{\"resourceType\":\"Bundle\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://www.example.com/petname\",\"valueString\":\"myName\"}]}}]}";
final MyPatientWithCustomUrlExtension patient = new MyPatientWithCustomUrlExtension(); final MyPatientWithCustomUrlExtension patient = new MyPatientWithCustomUrlExtension();
@ -213,7 +211,7 @@ public class JsonParserDstu3Test {
* See #276 * See #276
*/ */
@Test @Test
public void testDoubleEncodingContainedResources() throws Exception { public void testDoubleEncodingContainedResources() {
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("#patient-1"); patient.setId("#patient-1");
patient.setActive(true); patient.setActive(true);
@ -236,7 +234,7 @@ public class JsonParserDstu3Test {
} }
@Test @Test
public void testEncodeAndParseExtensions() throws Exception { public void testEncodeAndParseExtensions() {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135"); patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135");
@ -402,6 +400,7 @@ public class JsonParserDstu3Test {
/** /**
* See #336 * See #336
*/ */
@SuppressWarnings("SpellCheckingInspection")
@Test @Test
public void testEncodeAndParseNullPrimitiveWithExtensions() { public void testEncodeAndParseNullPrimitiveWithExtensions() {
@ -458,7 +457,7 @@ public class JsonParserDstu3Test {
Patient p = new Patient(); Patient p = new Patient();
p.addName().setFamily("FAMILY"); p.addName().setFamily("FAMILY");
List<Coding> labels = new ArrayList<Coding>(); List<Coding> labels = new ArrayList<>();
labels.add(new Coding().setSystem("SYSTEM1").setCode("CODE1").setDisplay("DISPLAY1").setVersion("VERSION1")); labels.add(new Coding().setSystem("SYSTEM1").setCode("CODE1").setDisplay("DISPLAY1").setVersion("VERSION1"));
labels.add(new Coding().setSystem("SYSTEM2").setCode("CODE2").setDisplay("DISPLAY2").setVersion("VERSION2")); labels.add(new Coding().setSystem("SYSTEM2").setCode("CODE2").setDisplay("DISPLAY2").setVersion("VERSION2"));
p.getMeta().getSecurity().addAll(labels); p.getMeta().getSecurity().addAll(labels);
@ -602,7 +601,7 @@ public class JsonParserDstu3Test {
*/ */
@Test @Test
public void testEncodeEmptyTag() { public void testEncodeEmptyTag() {
ArrayList<Coding> tagList = new ArrayList<Coding>(); ArrayList<Coding> tagList = new ArrayList<>();
tagList.add(new Coding()); tagList.add(new Coding());
tagList.add(new Coding().setDisplay("Label")); tagList.add(new Coding().setDisplay("Label"));
@ -618,7 +617,7 @@ public class JsonParserDstu3Test {
*/ */
@Test @Test
public void testEncodeEmptyTag2() { public void testEncodeEmptyTag2() {
ArrayList<Coding> tagList = new ArrayList<Coding>(); ArrayList<Coding> tagList = new ArrayList<>();
tagList.add(new Coding().setSystem("scheme").setCode("code")); tagList.add(new Coding().setSystem("scheme").setCode("code"));
tagList.add(new Coding().setDisplay("Label")); tagList.add(new Coding().setDisplay("Label"));
@ -715,13 +714,26 @@ public class JsonParserDstu3Test {
.setValue(new Reference("Practitioner/A")); .setValue(new Reference("Practitioner/A"));
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true); IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
parser.setDontEncodeElements(new HashSet<String>(Arrays.asList("*.id", "*.meta"))); parser.setDontEncodeElements(new HashSet<>(Arrays.asList("*.id", "*.meta")));
String encoded = parser.encodeResourceToString(p); String encoded = parser.encodeResourceToString(p);
ourLog.info(encoded); ourLog.info(encoded);
assertThat(encoded, containsString("http://foo")); assertThat(encoded, containsString("http://foo"));
assertThat(encoded, containsString("Practitioner/A")); 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, not(containsString("http://foo")));
assertThat(encoded, not(containsString("Practitioner/A")));
} }
@Test @Test
@ -818,7 +830,7 @@ public class JsonParserDstu3Test {
TestPatientFor327 patient = new TestPatientFor327(); TestPatientFor327 patient = new TestPatientFor327();
patient.setBirthDateElement(new DateType("2016-04-14")); patient.setBirthDateElement(new DateType("2016-04-14"));
List<Reference> conditions = new ArrayList<Reference>(); List<Reference> conditions = new ArrayList<>();
Condition condition = new Condition(); Condition condition = new Condition();
condition.addBodySite().setText("BODY SITE"); condition.addBodySite().setText("BODY SITE");
conditions.add(new Reference(condition)); conditions.add(new Reference(condition));
@ -896,7 +908,7 @@ public class JsonParserDstu3Test {
ourLog.info(enc); ourLog.info(enc);
assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\""));
parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList<String>()); parser.setDontStripVersionsFromReferencesAtPaths(new ArrayList<>());
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent); enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc); ourLog.info(enc);
assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2\"")); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2\""));
@ -930,12 +942,12 @@ public class JsonParserDstu3Test {
ourLog.info(enc); ourLog.info(enc);
assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); 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); enc = parser.setPrettyPrint(true).encodeResourceToString(p);
ourLog.info(enc); ourLog.info(enc);
assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\""));
ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(new HashSet<String>(Arrays.asList("Patient.managingOrganization"))); ourCtx.getParserOptions().setDontStripVersionsFromReferencesAtPaths(new HashSet<>(Collections.singletonList("Patient.managingOrganization")));
enc = parser.setPrettyPrint(true).encodeResourceToString(p); enc = parser.setPrettyPrint(true).encodeResourceToString(p);
ourLog.info(enc); ourLog.info(enc);
assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\"")); assertThat(enc, containsString("\"reference\": \"http://foo.com/Organization/2/_history/1\""));
@ -1013,7 +1025,7 @@ public class JsonParserDstu3Test {
} }
@Test @Test
public void testEncodeNarrativeSuppressed() throws Exception { public void testEncodeNarrativeSuppressed() {
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("Patient/1/_history/1"); patient.setId("Patient/1/_history/1");
patient.getText().setDivAsString("<div>THE DIV</div>"); patient.getText().setDivAsString("<div>THE DIV</div>");
@ -1064,6 +1076,57 @@ public class JsonParserDstu3Test {
assertThat(encoded, not(containsString("maritalStatus"))); 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 @Test
public void testEncodeSummary2() { public void testEncodeSummary2() {
Patient patient = new Patient(); Patient patient = new Patient();
@ -1107,7 +1170,7 @@ public class JsonParserDstu3Test {
* See #241 * See #241
*/ */
@Test @Test
public void testEncodeThenParseShouldNotAddSpuriousId() throws Exception { public void testEncodeThenParseShouldNotAddSpuriousId() {
Condition condition = new Condition().setVerificationStatus(ConditionVerificationStatus.CONFIRMED); Condition condition = new Condition().setVerificationStatus(ConditionVerificationStatus.CONFIRMED);
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
BundleEntryComponent entry = new Bundle.BundleEntryComponent(); BundleEntryComponent entry = new Bundle.BundleEntryComponent();
@ -1126,7 +1189,7 @@ public class JsonParserDstu3Test {
} }
@Test @Test
public void testEncodeUndeclaredBlock() throws Exception { public void testEncodeUndeclaredBlock() {
FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent(); FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent();
source.getMessageHeaderApplicationId().setValue("APPID"); source.getMessageHeaderApplicationId().setValue("APPID");
source.setName("NAME"); source.setName("NAME");
@ -1153,7 +1216,7 @@ public class JsonParserDstu3Test {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addAddress().setUse(AddressUse.HOME); patient.addAddress().setUse(AddressUse.HOME);
EnumFactory<AddressUse> fact = new AddressUseEnumFactory(); EnumFactory<AddressUse> fact = new AddressUseEnumFactory();
PrimitiveType<AddressUse> enumeration = new Enumeration<AddressUse>(fact).setValue(AddressUse.HOME); PrimitiveType<AddressUse> enumeration = new Enumeration<>(fact).setValue(AddressUse.HOME);
patient.addExtension().setUrl("urn:foo").setValue(enumeration); patient.addExtension().setUrl("urn:foo").setValue(enumeration);
String val = parser.encodeResourceToString(patient); String val = parser.encodeResourceToString(patient);
@ -1168,7 +1231,7 @@ public class JsonParserDstu3Test {
} }
@Test @Test
public void testEncodeWithDontEncodeElements() throws Exception { public void testEncodeWithDontEncodeElements() {
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("123"); patient.setId("123");
@ -1224,7 +1287,7 @@ public class JsonParserDstu3Test {
{ {
IParser p = ourCtx.newJsonParser(); IParser p = ourCtx.newJsonParser();
p.setDontEncodeElements(Sets.newHashSet("Patient.meta")); p.setDontEncodeElements(Sets.newHashSet("Patient.meta"));
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name"))); p.setEncodeElements(new HashSet<>(Collections.singletonList("Patient.name")));
p.setPrettyPrint(true); p.setPrettyPrint(true);
String out = p.encodeResourceToString(patient); String out = p.encodeResourceToString(patient);
ourLog.info(out); ourLog.info(out);
@ -1438,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\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><h2>Education Levels</h2><tt>http://csiro.au/ValueSet/education-levels</tt><p>A selection of Education Levels</p></div>\" }, \"experimental\": true, \"date\": \"2016-07-26\" }"; 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\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><h2>Education Levels</h2><tt>http://csiro.au/ValueSet/education-levels</tt><p>A selection of Education Levels</p></div>\" }, \"experimental\": true, \"date\": \"2016-07-26\" }";
IParser parser = ourCtx.newJsonParser(); IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler()); parser.setParserErrorHandler(new StrictErrorHandler());
ValueSet parsed = parser.parseResource(ValueSet.class, res); parser.parseResource(ValueSet.class, res);
fail("DataFormat Invalid attribute exception should be thrown"); fail("DataFormat Invalid attribute exception should be thrown");
} }
@ -1671,53 +1734,6 @@ public class JsonParserDstu3Test {
assertThat(reencoded, containsString("contained")); 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("&sect;", "§");
String act = actual.toString().replace("\\r\\n", "\\n");
ourLog.info("Expected: {}", exp);
ourLog.info("Actual : {}", act);
assertEquals(exp, act);
}
@Test @Test
public void testParseAndEncodeBundleWithUuidBase() { public void testParseAndEncodeBundleWithUuidBase() {
//@formatter:off //@formatter:off
@ -2221,20 +2237,18 @@ public class JsonParserDstu3Test {
@Test(expected = DataFormatException.class) @Test(expected = DataFormatException.class)
public void testParseWithTrailingContent() { public void testParseWithTrailingContent() {
//@formatter:off
String bundle = "{\n" + String bundle = "{\n" +
" \"resourceType\" : \"Bundle\",\n" + " \"resourceType\" : \"Bundle\",\n" +
" \"total\" : 1\n" + " \"total\" : 1\n" +
"}}"; "}}";
//@formatter:on
Bundle b = ourCtx.newJsonParser().parseResource(Bundle.class, bundle); ourCtx.newJsonParser().parseResource(Bundle.class, bundle);
} }
@Test @Test
@Ignore @Ignore
public void testParseWithWrongTypeObjectShouldBeArray() throws Exception { 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 { try {
ourCtx.newJsonParser().parseResource(CapabilityStatement.class, input); ourCtx.newJsonParser().parseResource(CapabilityStatement.class, input);
fail(); fail();
@ -2346,14 +2360,14 @@ public class JsonParserDstu3Test {
ArgumentCaptor<ValueType> actual = ArgumentCaptor.forClass(ValueType.class); ArgumentCaptor<ValueType> actual = ArgumentCaptor.forClass(ValueType.class);
ArgumentCaptor<ScalarType> expectedScalar = ArgumentCaptor.forClass(ScalarType.class); ArgumentCaptor<ScalarType> expectedScalar = ArgumentCaptor.forClass(ScalarType.class);
ArgumentCaptor<ScalarType> actualScalar = ArgumentCaptor.forClass(ScalarType.class); ArgumentCaptor<ScalarType> 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()); 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()); 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()); actualScalar.capture());
verify(errorHandler, atLeastOnce()).incorrectJsonType(Mockito.nullable(IParseLocation.class), Mockito.eq("_status"), Mockito.eq(ValueType.OBJECT), expectedScalar.capture(), verify(errorHandler, atLeastOnce()).incorrectJsonType(nullable(IParseLocation.class), eq("_status"), eq(ValueType.OBJECT), expectedScalar.capture(),
Mockito.eq(ValueType.SCALAR), actualScalar.capture()); eq(ValueType.SCALAR), actualScalar.capture());
assertEquals("_id", elementName.getAllValues().get(0)); assertEquals("_id", elementName.getAllValues().get(0));
assertEquals(ValueType.OBJECT, expected.getAllValues().get(0)); assertEquals(ValueType.OBJECT, expected.getAllValues().get(0));
@ -2363,7 +2377,7 @@ public class JsonParserDstu3Test {
} }
@Test @Test
public void testValidateCustomStructure() throws Exception { public void testValidateCustomStructure() {
FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent(); FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent();
source.getMessageHeaderApplicationId().setValue("APPID"); source.getMessageHeaderApplicationId().setValue("APPID");
@ -2392,13 +2406,13 @@ public class JsonParserDstu3Test {
containedOrganization.getMeta().addProfile(UUID.randomUUID().toString()); containedOrganization.getMeta().addProfile(UUID.randomUUID().toString());
containedOrganization.getMeta().setLastUpdated(new Date()); containedOrganization.getMeta().setLastUpdated(new Date());
containedOrganization.getMeta().setVersionId(UUID.randomUUID().toString()); containedOrganization.getMeta().setVersionId(UUID.randomUUID().toString());
containedOrganization.getMeta().setSecurity(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(Arrays.asList(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 patient = new Patient();
patient.setId(UUID.randomUUID().toString()); patient.setId(UUID.randomUUID().toString());
patient.getMeta().addProfile(UUID.randomUUID().toString()); patient.getMeta().addProfile(UUID.randomUUID().toString());
patient.setGeneralPractitioner(Arrays.asList(new Reference(containedOrganization))); patient.setGeneralPractitioner(Collections.singletonList(new Reference(containedOrganization)));
HashSet<String> excludeElementsInEncoded = new HashSet<>(); // ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED HashSet<String> excludeElementsInEncoded = new HashSet<>(); // ResourceMetaParams.EXCLUDE_ELEMENTS_IN_ENCODED
excludeElementsInEncoded.add("id"); excludeElementsInEncoded.add("id");

View File

@ -12,6 +12,7 @@ import com.google.common.collect.Sets;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsEmptyCollection; import org.hamcrest.collection.IsEmptyCollection;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
import org.hamcrest.text.StringContainsInOrder; import org.hamcrest.text.StringContainsInOrder;
@ -69,6 +70,35 @@ public class XmlParserDstu3Test {
ourCtx.setNarrativeGenerator(null); 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 @Test
public void testEncodeInvalidMetaTime() { public void testEncodeInvalidMetaTime() {

View File

@ -264,6 +264,16 @@ public class ElementsParamR4Test {
assertEquals("mg", ((Quantity) procedure.getExtension().get(0).getValue()).getCode()); 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( verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_elements=Procedure.extension.value.value", "http://localhost:" + ourPort + "/Procedure?_elements=Procedure.extension.value.value",
bundle -> { bundle -> {

View File

@ -18,6 +18,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.CoreMatchers;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -31,6 +32,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; 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.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -220,7 +223,7 @@ public class SummaryParamR4Test {
assertThat(responseContent, (containsString("entry"))); assertThat(responseContent, (containsString("entry")));
assertThat(responseContent, (containsString(">TEXT<"))); assertThat(responseContent, (containsString(">TEXT<")));
assertThat(responseContent, (containsString("Medication/123"))); assertThat(responseContent, (containsString("Medication/123")));
assertThat(responseContent, not(containsStringIgnoringCase("note"))); assertThat(responseContent, not(CoreMatchers.containsStringIgnoringCase("note")));
} }
); );

View File

@ -46,6 +46,11 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

60
pom.xml
View File

@ -1022,6 +1022,25 @@
<artifactId>velocity-tools</artifactId> <artifactId>velocity-tools</artifactId>
<version>2.0</version> <version>2.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>3.1.6</version>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-api</artifactId> <artifactId>plexus-compiler-api</artifactId>
@ -1792,23 +1811,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>update-project-license</id>
<phase>package</phase>
<goals>
<goal>update-project-license</goal>
</goals>
<configuration>
<licenseName>apache_v2</licenseName>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-antrun-plugin</artifactId>
<inherited>false</inherited> <inherited>false</inherited>
@ -2196,9 +2198,27 @@
<profiles> <profiles>
<profile> <profile>
<id>DIST</id> <id>DIST</id>
<modules> <build>
<!--<module>hapi-fhir-osgi-core</module>--> <plugins>
</modules> <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>update-project-license</id>
<phase>package</phase>
<goals>
<goal>update-project-license</goal>
</goals>
<configuration>
<licenseName>apache_v2</licenseName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile> </profile>
<profile> <profile>
<id>ROOT</id> <id>ROOT</id>

View File

@ -266,6 +266,10 @@
A new server interceptor hook called PROCESSING_COMPLETED has been added. This 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). hook is called by the server at the end of processing every request (success and failure).
</action> </action>
<action type="fix">
The <![CDATA[<code>_summary</code>]]> element was not always respected when encoding
JSON resources.
</action>
</release> </release>
<release version="3.8.0" date="2019-05-30" description="Hippo"> <release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix"> <action type="fix">

View File

@ -276,7 +276,7 @@ public DaoConfig daoConfig() {
If the client knows that they will only want a small number of results 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 (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 knows that they will never load the next page of results) the client
may also use the <code>nostore</code> directive along with a HAPI FHIR may also use the <code>no-store</code> directive along with a HAPI FHIR
extension called <code>max-results</code> in order to specify that extension called <code>max-results</code> in order to specify that
only the given number of results should be fetched. This directive only the given number of results should be fetched. This directive
disabled paging entirely for the request and causes the request to disabled paging entirely for the request and causes the request to
@ -285,7 +285,7 @@ public DaoConfig daoConfig() {
</p> </p>
<div class="source"> <div class="source">
<pre> <pre>
Cache-Control: nostore, max-results=20 Cache-Control: no-store, max-results=20
</pre> </pre>
</div> </div>