Refactor websockets to use queuing mechanism

This commit is contained in:
James Agnew 2017-09-14 21:29:57 -05:00
parent 97b44965ce
commit e94d639d29
66 changed files with 1474 additions and 7080 deletions

View File

@ -19,16 +19,6 @@ package ca.uhn.fhir.parser;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
@ -37,6 +27,16 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseParser implements IParser {
@ -71,7 +71,7 @@ public abstract class BaseParser implements IParser {
myErrorHandler = theParserErrorHandler;
}
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent) {
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) {
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
@ -87,8 +87,7 @@ public abstract class BaseParser implements IParser {
/**
* Constructor
*/
{
*/ {
myChildrenIter = children.iterator();
}
@ -105,7 +104,7 @@ public abstract class BaseParser implements IParser {
return false;
}
myNext = new CompositeChildElement(theParent, myChildrenIter.next());
myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource);
/*
* There are lots of reasons we might skip encoding a particular child
@ -282,40 +281,6 @@ public abstract class BaseParser implements IParser {
return ref.getValue();
}
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
if (stripVersionsFromReferences != null) {
return stripVersionsFromReferences;
}
if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
return false;
}
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
if (dontStripVersionsFromReferencesAtPaths != null) {
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
}
dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
return true;
}
private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
if (overrideResourceIdWithBundleEntryFullUrl != null) {
return overrideResourceIdWithBundleEntryFullUrl;
}
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
}
protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
@ -424,6 +389,11 @@ public abstract class BaseParser implements IParser {
return myContainedResources;
}
@Override
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
}
/**
* See {@link #setEncodeElements(Set)}
*/
@ -432,6 +402,21 @@ public abstract class BaseParser implements IParser {
return myEncodeElements;
}
@Override
public void setEncodeElements(Set<String> theEncodeElements) {
myEncodeElementsIncludesStars = false;
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
myEncodeElements = null;
} else {
myEncodeElements = theEncodeElements;
for (String next : theEncodeElements) {
if (next.startsWith("*.")) {
myEncodeElementsIncludesStars = true;
}
}
}
}
/**
* See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
*/
@ -440,15 +425,38 @@ public abstract class BaseParser implements IParser {
return myEncodeElementsAppliesToResourceTypes;
}
@Override
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
myEncodeElementsAppliesToResourceTypes = null;
} else {
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
}
}
@Override
public IIdType getEncodeForceResourceId() {
return myEncodeForceResourceId;
}
@Override
public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
myEncodeForceResourceId = theEncodeForceResourceId;
return this;
}
protected IParserErrorHandler getErrorHandler() {
return myErrorHandler;
}
protected String getExtensionUrl(final String extensionUrl) {
String url = extensionUrl;
if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
}
return url;
}
protected TagList getMetaTagsForEncoding(IResource theIResource) {
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
if (shouldAddSubsettedTag()) {
@ -459,11 +467,31 @@ public abstract class BaseParser implements IParser {
return tags;
}
@Override
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
return myOverrideResourceIdWithBundleEntryFullUrl;
}
@Override
public List<Class<? extends IBaseResource>> getPreferTypes() {
return myPreferTypes;
}
@Override
public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
if (thePreferTypes != null) {
ArrayList<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IBaseResource> next : thePreferTypes) {
if (Modifier.isAbstract(next.getModifiers()) == false) {
types.add(next);
}
}
myPreferTypes = Collections.unmodifiableList(types);
} else {
myPreferTypes = thePreferTypes;
}
}
@SuppressWarnings("deprecation")
protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
switch (myContext.getAddProfileTagWhenEncoding()) {
@ -503,6 +531,15 @@ public abstract class BaseParser implements IParser {
return theProfiles;
}
protected String getServerBaseUrl() {
return myServerBaseUrl;
}
@Override
public Boolean getStripVersionsFromReferences() {
return myStripVersionsFromReferences;
}
/**
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
* values.
@ -524,14 +561,38 @@ public abstract class BaseParser implements IParser {
return myOmitResourceId;
}
@Override
public Boolean getStripVersionsFromReferences() {
return myStripVersionsFromReferences;
private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
if (overrideResourceIdWithBundleEntryFullUrl != null) {
return overrideResourceIdWithBundleEntryFullUrl;
}
@Override
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
return myOverrideResourceIdWithBundleEntryFullUrl;
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
}
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
if (stripVersionsFromReferences != null) {
return stripVersionsFromReferences;
}
if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
return false;
}
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
if (dontStripVersionsFromReferencesAtPaths != null) {
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
}
dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
return true;
}
@Override
@ -623,7 +684,6 @@ public abstract class BaseParser implements IParser {
return parseResource(null, theMessageString);
}
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
CompositeChildElement theCompositeChildElement) {
if (myContext.getVersion().getVersion().isRi()) {
@ -727,82 +787,6 @@ public abstract class BaseParser implements IParser {
}
}
@Override
public void setEncodeElements(Set<String> theEncodeElements) {
myEncodeElementsIncludesStars = false;
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
myEncodeElements = null;
} else {
myEncodeElements = theEncodeElements;
for (String next : theEncodeElements) {
if (next.startsWith("*.")) {
myEncodeElementsIncludesStars = true;
}
}
}
}
@Override
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
myEncodeElementsAppliesToResourceTypes = null;
} else {
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
}
}
@Override
public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
myEncodeForceResourceId = theEncodeForceResourceId;
return this;
}
@Override
public IParser setOmitResourceId(boolean theOmitResourceId) {
myOmitResourceId = theOmitResourceId;
return this;
}
@Override
public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
myErrorHandler = theErrorHandler;
return this;
}
@Override
public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
if (thePreferTypes != null) {
ArrayList<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IBaseResource> next : thePreferTypes) {
if (Modifier.isAbstract(next.getModifiers()) == false) {
types.add(next);
}
}
myPreferTypes = Collections.unmodifiableList(types);
} else {
myPreferTypes = thePreferTypes;
}
}
@Override
public IParser setServerBaseUrl(String theUrl) {
myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
return this;
}
@Override
public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
myStripVersionsFromReferences = theStripVersionsFromReferences;
return this;
}
@Override
public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
return this;
}
@Override
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
if (thePaths == null) {
@ -827,8 +811,34 @@ public abstract class BaseParser implements IParser {
}
@Override
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
public IParser setOmitResourceId(boolean theOmitResourceId) {
myOmitResourceId = theOmitResourceId;
return this;
}
@Override
public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
return this;
}
@Override
public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
myErrorHandler = theErrorHandler;
return this;
}
@Override
public IParser setServerBaseUrl(String theUrl) {
myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
return this;
}
@Override
public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
myStripVersionsFromReferences = theStripVersionsFromReferences;
return this;
}
@Override
@ -847,7 +857,7 @@ public abstract class BaseParser implements IParser {
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
}
protected boolean shouldEncodeResourceId(IBaseResource theResource) {
protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) {
boolean retVal = true;
if (isOmitResourceId()) {
retVal = false;
@ -858,24 +868,14 @@ public abstract class BaseParser implements IParser {
retVal = false;
} else if (myDontEncodeElements.contains("*.id")) {
retVal = false;
} else if (theSubResource == false && myDontEncodeElements.contains("id")) {
retVal = false;
}
}
}
return retVal;
}
protected String getExtensionUrl(final String extensionUrl) {
String url = extensionUrl;
if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
}
return url;
}
protected String getServerBaseUrl() {
return myServerBaseUrl;
}
/**
* Used for DSTU2 only
*/
@ -965,11 +965,13 @@ public abstract class BaseParser implements IParser {
private final BaseRuntimeChildDefinition myDef;
private final CompositeChildElement myParent;
private final RuntimeResourceDefinition myResDef;
private final boolean mySubResource;
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) {
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) {
myDef = theDef;
myParent = theParent;
myResDef = null;
mySubResource = theSubResource;
if (ourLog.isTraceEnabled()) {
if (theParent != null) {
@ -984,12 +986,11 @@ public abstract class BaseParser implements IParser {
}
public boolean anyPathMatches(Set<String> thePaths) {
StringBuilder b = new StringBuilder();
addParent(this, b);
String path = b.toString();
return thePaths.contains(path);
public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) {
myResDef = theResDef;
myDef = null;
myParent = null;
mySubResource = theSubResource;
}
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
@ -1012,10 +1013,12 @@ public abstract class BaseParser implements IParser {
}
}
public CompositeChildElement(RuntimeResourceDefinition theResDef) {
myResDef = theResDef;
myDef = null;
myParent = null;
public boolean anyPathMatches(Set<String> thePaths) {
StringBuilder b = new StringBuilder();
addParent(this, b);
String path = b.toString();
return thePaths.contains(path);
}
private StringBuilder buildPath() {
@ -1079,7 +1082,17 @@ public abstract class BaseParser implements IParser {
thePathBuilder.append('.');
thePathBuilder.append(myDef.getElementName());
return theElements.contains(thePathBuilder.toString());
String currentPath = thePathBuilder.toString();
boolean retVal = theElements.contains(currentPath);
int dotIdx = currentPath.indexOf('.');
if (!retVal) {
if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) {
if (!myParent.isSubResource()) {
return true;
}
}
}
return retVal;
}
}
@ -1098,6 +1111,10 @@ public abstract class BaseParser implements IParser {
return myResDef;
}
private boolean isSubResource() {
return mySubResource;
}
public boolean shouldBeEncoded() {
boolean retVal = true;
if (myEncodeElements != null) {

View File

@ -191,7 +191,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem,
boolean theForceEmpty) throws IOException {
switch (theChildDef.getChildType()) {
@ -264,7 +264,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else {
theEventWriter.beginObject();
}
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem);
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
theEventWriter.endObject();
break;
}
@ -282,7 +282,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
for (IBaseResource next : containedResources) {
IIdType resourceId = getContainedResources().getResourceId(next);
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()));
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue()));
}
theEventWriter.endArray();
@ -320,7 +320,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
boolean theContainedResource, CompositeChildElement theParent) throws IOException {
boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException {
{
String elementId = getCompositeElementId(theElement);
@ -330,7 +330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
boolean haveWrittenExtensions = false;
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
@ -360,7 +360,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, theSubResource, nextChildElem, false);
continue;
}
}
@ -368,7 +368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
} else if (nextChild instanceof RuntimeChildContainedResources) {
String childName = nextChild.getValidChildNames().iterator().next();
BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, theSubResource, nextChildElem, false);
continue;
}
@ -451,15 +451,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
beginArray(theEventWriter, childName);
inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource,nextChildElem, force);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource,nextChildElem, false);
}
currentChildName = childName;
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource,theSubResource, nextChildElem, force);
}
valueIdx++;
@ -540,11 +540,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
}
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource,
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource,
CompositeChildElement theParent) throws IOException, DataFormatException {
writeCommentsPreAndPost(theNextValue, theEventWriter);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent);
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
}
@Override
@ -587,18 +587,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
if (!theContainedResource) {
if (super.shouldEncodeResourceId(theResource) == false) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
resourceId = null;
} else if (!theSubResource && getEncodeForceResourceId() != null) {
resourceId = getEncodeForceResourceId();
}
}
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId);
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, theSubResource, resourceId);
}
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
boolean theContainedResource, IIdType theResourceId) throws IOException {
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}
@ -670,7 +670,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
beginArray(theEventWriter, "security");
for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.beginObject();
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null);
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
theEventWriter.endObject();
}
theEventWriter.endArray();
@ -706,7 +706,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
write(theEventWriter, "content", contentAsBase64);
}
} else {
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
}
theEventWriter.endObject();
@ -1366,7 +1366,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
} else {
String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false);
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
}
@ -1421,7 +1421,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (childDef == null) {
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
}
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, myParent, false);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false);
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
}

View File

@ -214,7 +214,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
String theExtensionUrl, boolean theIncludedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
if (theElement == null || theElement.isEmpty()) {
if (isChildContained(childDef, theIncludedResource)) {
// We still want to go in..
@ -234,7 +234,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (StringUtils.isNotBlank(encodedValue)) {
theEventWriter.writeAttribute("value", encodedValue);
}
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource);
theEventWriter.writeEndElement();
}
break;
@ -251,7 +251,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (value != null) {
theEventWriter.writeAttribute("value", value);
}
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource);
theEventWriter.writeEndElement();
}
break;
@ -266,7 +266,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
if (isNotBlank(theExtensionUrl)) {
theEventWriter.writeAttribute("url", theExtensionUrl);
}
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent);
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent);
theEventWriter.writeEndElement();
break;
}
@ -280,7 +280,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue()));
theEventWriter.writeEndElement();
}
break;
@ -319,10 +319,10 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent)
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent)
throws XMLStreamException, DataFormatException {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
@ -352,13 +352,13 @@ public class XmlParser extends BaseParser /* implements IParser */ {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, theSubResource, nextChildElem);
continue;
}
}
if (nextChild instanceof RuntimeChildContainedResources) {
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem);
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, theSubResource, nextChildElem);
} else {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
@ -382,7 +382,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
if (extensionUrl != null && childName.equals("extension") == false) {
encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
encodeExtension(theResource, theEventWriter, theContainedResource, theSubResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
} else if (nextChild instanceof RuntimeChildExtension) {
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
if ((extension.getValue() == null || extension.getValue().isEmpty())) {
@ -390,18 +390,18 @@ public class XmlParser extends BaseParser /* implements IParser */ {
continue;
}
}
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, theSubResource, nextChildElem);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, theSubResource, nextChildElem);
}
}
}
}
}
private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
throws XMLStreamException {
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
if (extDef.isModifier()) {
@ -416,23 +416,23 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
theEventWriter.writeAttribute("url", extensionUrl);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, theSubResource, nextChildElem);
theEventWriter.writeEndElement();
}
private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
if (theElement instanceof ISupportsUndeclaredExtensions) {
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource);
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource);
}
if (theElement instanceof IBaseHasExtensions) {
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource);
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource);
}
if (theElement instanceof IBaseHasModifierExtensions) {
IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource);
encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource,theSubResource);
}
}
@ -447,17 +447,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
if (!theIncludedResource) {
if (super.shouldEncodeResourceId(theResource) == false) {
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
resourceId = null;
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
resourceId = getEncodeForceResourceId();
}
}
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, theSubResource, resourceId);
}
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId) throws XMLStreamException {
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws XMLStreamException {
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}
@ -476,12 +476,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPre(theEventWriter, theResourceId);
theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false);
theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId);
}
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
} else {
@ -494,7 +494,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeCommentsPost(theEventWriter, theResourceId);*/
theEventWriter.writeStartElement("id");
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false);
theEventWriter.writeEndElement();
writeCommentsPost(theEventWriter, theResourceId);
}
@ -525,7 +525,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
for (BaseCodingDt securityLabel : securityLabels) {
theEventWriter.writeStartElement("security");
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null);
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
theEventWriter.writeEndElement();
}
if (tags != null) {
@ -548,7 +548,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
} else {
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
}
}
@ -556,7 +556,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
theEventWriter.writeEndElement();
}
private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource)
private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, boolean theSubResource)
throws XMLStreamException, DataFormatException {
for (IBaseExtension<?, ?> next : theExtensions) {
if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
@ -592,11 +592,11 @@ public class XmlParser extends BaseParser /* implements IParser */ {
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
}
}
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null);
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null);
}
// child extensions
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource);
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource);
theEventWriter.writeEndElement();

View File

@ -20,20 +20,22 @@ package ca.uhn.fhir.jpa.config;
* #L%
*/
import javax.annotation.Resource;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import org.hl7.fhir.r4.utils.GraphQLEngine;
import ca.uhn.fhir.jpa.search.*;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@ -41,20 +43,17 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import ca.uhn.fhir.jpa.search.*;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import javax.annotation.Resource;
@Configuration
@EnableScheduling
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
public class BaseConfig implements SchedulingConfigurer {
@Resource
private ApplicationContext myAppCtx;
@Autowired
protected Environment myEnv;
@Resource
private ApplicationContext myAppCtx;
@Override
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) {
@ -67,6 +66,11 @@ public class BaseConfig implements SchedulingConfigurer {
return retVal;
}
@Bean
public IGraphQLStorageServices jpaStorageServices() {
return new JpaStorageServices();
}
@Bean()
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
@ -74,7 +78,7 @@ public class BaseConfig implements SchedulingConfigurer {
return b;
}
@Bean(autowire=Autowire.BY_TYPE)
@Bean(autowire = Autowire.BY_TYPE)
public ISearchCoordinatorSvc searchCoordinatorSvc() {
return new SearchCoordinatorSvcImpl();
}
@ -84,11 +88,32 @@ public class BaseConfig implements SchedulingConfigurer {
return new SearchParamPresenceSvcImpl();
}
@Bean(autowire=Autowire.BY_TYPE)
@Bean(autowire = Autowire.BY_TYPE)
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
return new StaleSearchDeletingSvcImpl();
}
// @PostConstruct
// public void wireResourceDaos() {
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
// List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class);
// for (IDao next : daoBeans.values()) {
// next.setResourceDaos(bean);
// }
// }
@Bean
@Lazy
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
return new SubscriptionRestHookInterceptor();
}
@Bean
@Lazy
public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
return new SubscriptionWebsocketInterceptor();
}
@Bean
public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
@ -100,15 +125,6 @@ public class BaseConfig implements SchedulingConfigurer {
// return retVal;
}
// @PostConstruct
// public void wireResourceDaos() {
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
// List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class);
// for (IDao next : daoBeans.values()) {
// next.setResourceDaos(bean);
// }
// }
/**
* This lets the "@Value" fields reference properties from the properties file
*/
@ -117,9 +133,5 @@ public class BaseConfig implements SchedulingConfigurer {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public IGraphQLStorageServices jpaStorageServices() {
return new JpaStorageServices();
}
}

View File

@ -1,9 +1,20 @@
package ca.uhn.fhir.jpa.config;
import org.hl7.fhir.instance.hapi.validation.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
@ -26,14 +37,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
@Configuration
@EnableTransactionManagement
public class BaseDstu2Config extends BaseConfig {
@ -65,12 +68,6 @@ public class BaseDstu2Config extends BaseConfig {
return ourFhirContextDstu2Hl7Org;
}
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(name = "myInstanceValidatorDstu2")
@Lazy
public IValidatorModule instanceValidatorDstu2() {
@ -80,6 +77,12 @@ public class BaseDstu2Config extends BaseConfig {
return retVal;
}
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDao() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
@ -115,10 +118,4 @@ public class BaseDstu2Config extends BaseConfig {
return new HapiTerminologySvcDstu2();
}
@Bean
@Lazy
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() {
return new RestHookSubscriptionDstu2Interceptor();
}
}

View File

@ -20,38 +20,32 @@ package ca.uhn.fhir.jpa.config;
* #L%
*/
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
@Configuration
@EnableWebSocket()
public class WebsocketDstu2Config implements WebSocketConfigurer {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketDstu2Config.class);
@Controller
public class WebsocketDispatcherConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2").setAllowedOrigins("*");
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket").setAllowedOrigins("*");
}
@Bean(autowire = Autowire.BY_TYPE)
public WebSocketHandler subscriptionWebSocketHandler() {
return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class);
}
@Bean
public TaskScheduler websocketTaskScheduler() {
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
retVal.setPoolSize(5);
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandler.class);
return retVal;
}

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
@Configuration
public class WebsocketDstu2DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerDstu2.setCtx(myCtx);
SubscriptionWebsocketHandlerDstu2.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

@ -1,8 +1,30 @@
package ca.uhn.fhir.jpa.config.dstu3;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
* #%L
@ -24,31 +46,6 @@ import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule;
@Configuration
@EnableTransactionManagement
public class BaseDstu3Config extends BaseConfig {
@ -133,10 +130,4 @@ public class BaseDstu3Config extends BaseConfig {
return new JpaValidationSupportChainDstu3();
}
@Bean
@Lazy
public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() {
return new RestHookSubscriptionDstu3Interceptor();
}
}

View File

@ -1,79 +0,0 @@
package ca.uhn.fhir.jpa.config.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
import ca.uhn.fhir.jpa.subscription.dstu3.WebSocketSubscriptionDstu3Interceptor;
import ca.uhn.fhir.jpa.subscription.dstu3.SubscriptionWebsocketHandlerDstu3;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@Configuration
@EnableWebSocket()
@Controller
public class WebsocketDstu3Config implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu3").setAllowedOrigins("*");
}
@Bean(autowire = Autowire.BY_TYPE)
public WebSocketHandler subscriptionWebSocketHandler() {
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu3.class);
return retVal;
}
@Bean(destroyMethod="destroy")
public TaskScheduler websocketTaskSchedulerDstu3() {
final ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler() {
private static final long serialVersionUID = 1L;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
getScheduledThreadPoolExecutor().setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
getScheduledThreadPoolExecutor().setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
}
};
retVal.setThreadNamePrefix("ws-dstu3-");
retVal.setPoolSize(5);
return retVal;
}
@Bean
public IServerInterceptor webSocketSubscriptionDstu3Interceptor(){
return new WebSocketSubscriptionDstu3Interceptor();
}
}

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 javax.annotation.PostConstruct;
import org.hl7.fhir.dstu3.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.dstu3.SubscriptionWebsocketHandlerDstu3;
@Configuration
public class WebsocketDstu3DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<org.hl7.fhir.dstu3.model.Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerDstu3.setCtx(myCtx);
SubscriptionWebsocketHandlerDstu3.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

@ -1,9 +1,31 @@
package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
* #%L
@ -25,23 +47,6 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
@Configuration
@EnableTransactionManagement
public class BaseR4Config extends BaseConfig {
@ -132,10 +137,4 @@ public class BaseR4Config extends BaseConfig {
return new JpaValidationSupportChainR4();
}
@Bean
@Lazy
public RestHookSubscriptionR4Interceptor restHookSubscriptionR4Interceptor() {
return new RestHookSubscriptionR4Interceptor();
}
}

View File

@ -1,77 +0,0 @@
package ca.uhn.fhir.jpa.config.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
import ca.uhn.fhir.jpa.subscription.r4.WebSocketSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@Configuration
@EnableWebSocket()
@Controller
public class WebsocketR4Config implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/r4").setAllowedOrigins("*");
}
@Bean(autowire = Autowire.BY_TYPE)
public WebSocketHandler subscriptionWebSocketHandler() {
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerR4.class);
return retVal;
}
@Bean(destroyMethod="destroy")
public TaskScheduler websocketTaskSchedulerR4() {
final ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler() {
private static final long serialVersionUID = 1L;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
getScheduledThreadPoolExecutor().setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
getScheduledThreadPoolExecutor().setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
}
};
retVal.setThreadNamePrefix("ws-r4-");
retVal.setPoolSize(5);
return retVal;
}
@Bean
public IServerInterceptor webSocketSubscriptionR4Interceptor(){
return new WebSocketSubscriptionR4Interceptor();
}
}

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 javax.annotation.PostConstruct;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4;
@Configuration
public class WebsocketR4DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerR4.setCtx(myCtx);
SubscriptionWebsocketHandlerR4.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

@ -123,8 +123,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
excludeElementsInEncoded.add("*.id");
excludeElementsInEncoded.add("*.meta");
excludeElementsInEncoded.add("id");
excludeElementsInEncoded.add("meta");
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
}

View File

@ -20,49 +20,27 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.util.*;
import javax.persistence.Query;
import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.*;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import java.util.Date;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@ -73,9 +51,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
SubscriptionTable subscriptionEntity = new SubscriptionTable();
subscriptionEntity.setCreated(new Date());
subscriptionEntity.setSubscriptionResource(theEntity);
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValue());
myEntityManager.persist(subscriptionEntity);
}
@ -89,145 +64,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
return table.getId();
}
@Override
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
retVal.add(toResource(nextFlaggedResource.getResource(), false));
}
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
mySubscriptionFlaggedResourceDataDao.flush();
mySubscriptionTableDao.updateLastClientPoll(new Date());
return retVal;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int pollForNewUndeliveredResources() {
return pollForNewUndeliveredResources((String) null);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
if (getConfig().isSubscriptionEnabled() == false) {
return 0;
}
ourLog.trace("Beginning pollForNewUndeliveredResources()");
// SubscriptionCandidateResource
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
@Override
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
}
});
int retVal = 0;
for (final Long nextSubscriptionTablePid : subscriptions) {
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
}
});
}
return retVal;
}
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
if (!subscription.getChannel().getType().equals(SubscriptionChannelTypeEnum.WEBSOCKET.getCode())){
ourLog.info("Skipping non web socket subscription");
return 0;
}
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
return 0;
}
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
long start = theSubscriptionTable.getMostRecentMatch().getTime();
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
if (end <= start) {
ourLog.trace("Skipping search for subscription");
return 0;
}
ourLog.debug("Subscription {} search from {} to {}", new Object[] { subscription.getId().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end)) });
DateRangeParam range = new DateRangeParam();
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
criteriaUrl.setLastUpdated(range);
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
IBundleProvider results = dao.search(criteriaUrl);
if (results.size() == 0) {
return 0;
}
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getId().getIdPart());
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
Date mostRecentMatch = null;
for (IBaseResource next : results.getResources(0, results.size())) {
Date updated = ResourceMetadataKeyEnum.UPDATED.get((IResource) next).getValue();
if (mostRecentMatch == null) {
mostRecentMatch = updated;
} else {
long mostRecentMatchTime = mostRecentMatch.getTime();
long updatedTime = updated.getTime();
if (mostRecentMatchTime < updatedTime) {
mostRecentMatch = updated;
}
}
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
Long pid = IDao.RESOURCE_PID.get((IResource) next);
ourLog.info("New resource for subscription: {}", pid);
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
nextFlag.setSubscription(theSubscriptionTable);
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
flags.add(nextFlag);
}
mySubscriptionFlaggedResourceDataDao.save(flags);
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getId().getIdPart(), new InstantDt(mostRecentMatch));
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
mySubscriptionTableDao.save(theSubscriptionTable);
return results.size();
}
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public synchronized void pollForNewUndeliveredResourcesScheduler() {
if (getConfig().isSchedulingDisabled()) {
return;
}
pollForNewUndeliveredResources();
}
@Override
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
@ -236,69 +72,14 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
createSubscriptionTable(theEntity, theSubscription);
}
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void purgeInactiveSubscriptions() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
return;
}
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
@Override
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
toPurge.size();
return toPurge;
}
});
for (SubscriptionTable subscriptionTable : toPurge) {
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
delete(subscriptionId, null);
return null;
}
});
}
}
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
Subscription resource = (Subscription) theResource;
Long resourceId = theEntity.getId();
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
if (subscriptionId != null) {
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
}
} else {
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
q.setParameter("res_id", resourceId);
q.setParameter("status", resource.getStatusElement().getValue());
if (q.executeUpdate() > 0) {
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatusElement().getValueAsEnum());
} else {
createSubscriptionTable(retVal, resource);
}
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
}
return retVal;
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
/*
* #%L
@ -22,20 +23,8 @@ import java.util.List;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
int pollForNewUndeliveredResources();
List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid);
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId);
void purgeInactiveSubscriptions();
void pollForNewUndeliveredResourcesScheduler();
int pollForNewUndeliveredResources(String resourceType);
}

View File

@ -1,41 +0,0 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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 ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
public interface ISubscriptionFlaggedResourceDataDao extends JpaRepository<SubscriptionFlaggedResource, Long> {
@Query("SELECT r FROM SubscriptionFlaggedResource r WHERE r.mySubscription.myId = :id ORDER BY r.myId ASC")
public Page<SubscriptionFlaggedResource> findAllBySubscriptionId(@Param("id") Long theId, Pageable thePage);
@Modifying
@Query("DELETE FROM SubscriptionFlaggedResource r WHERE r.mySubscription.myId = :id")
public void deleteAllForSubscription(@Param("id") Long theSubscriptionId);
}

View File

@ -20,32 +20,20 @@ package ca.uhn.fhir.jpa.dao.data;
* #L%
*/
import java.util.Collection;
import java.util.Date;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
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 ca.uhn.fhir.jpa.entity.SubscriptionTable;
public interface ISubscriptionTableDao extends JpaRepository<SubscriptionTable, Long> {
@Modifying
@Query("DELETE FROM SubscriptionTable t WHERE t.mySubscriptionResource = :subscription ")
void deleteAllForSubscription(@Param("subscription") ResourceTable theSubscription);
@Query("SELECT t FROM SubscriptionTable t WHERE t.myResId = :pid")
SubscriptionTable findOneByResourcePid(@Param("pid") Long theId);
@Modifying
@Query("DELETE FROM SubscriptionTable t WHERE t.myId = :id ")
void deleteAllForSubscription(@Param("id") Long theSubscriptionId);
@Modifying
@Query("UPDATE SubscriptionTable t SET t.myLastClientPoll = :last_client_poll")
int updateLastClientPoll(@Param("last_client_poll") Date theLastClientPoll);
@Query("SELECT t FROM SubscriptionTable t WHERE t.myLastClientPoll < :cutoff OR (t.myLastClientPoll IS NULL AND t.myCreated < :cutoff)")
Collection<SubscriptionTable> findInactiveBeforeCutoff(@Param("cutoff") Date theCutoff);
@Query("SELECT t.myId FROM SubscriptionTable t WHERE t.myStatus = :status AND t.myNextCheck <= :next_check")
Collection<Long> findSubscriptionsWhichNeedToBeChecked(@Param("status") String theStatus, @Param("next_check") Date theNextCheck);
}

View File

@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.dstu3;
*/
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu3.class);
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
SubscriptionTable subscriptionEntity = new SubscriptionTable();
subscriptionEntity.setCreated(new Date());
subscriptionEntity.setSubscriptionResource(theEntity);
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
myEntityManager.persist(subscriptionEntity);
}
@ -103,147 +68,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
return table.getId();
}
@Override
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
retVal.add(toResource(nextFlaggedResource.getResource(), false));
}
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
mySubscriptionFlaggedResourceDataDao.flush();
mySubscriptionTableDao.updateLastClientPoll(new Date());
return retVal;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int pollForNewUndeliveredResources() {
return pollForNewUndeliveredResources((String) null);
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
if (getConfig().isSubscriptionEnabled() == false) {
return 0;
}
ourLog.trace("Beginning pollForNewUndeliveredResources()");
// SubscriptionCandidateResource
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
@Override
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
}
});
int retVal = 0;
for (final Long nextSubscriptionTablePid : subscriptions) {
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
}
});
}
return retVal;
}
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET) {
ourLog.info("Skipping non web socket subscription");
return 0;
}
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
return 0;
}
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
criteriaUrl.setLoadSynchronousUpTo(1000);
long start = theSubscriptionTable.getMostRecentMatch().getTime();
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
if (end <= start) {
ourLog.trace("Skipping search for subscription");
return 0;
}
ourLog.info("Subscription {} search from {} to {}", new Object[]{subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end))});
DateRangeParam range = new DateRangeParam();
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
criteriaUrl.setLastUpdated(range);
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
IBundleProvider results = dao.search(criteriaUrl);
if (results.size() == 0) {
return 0;
}
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getIdElement().getIdPart());
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
Date mostRecentMatch = null;
for (IBaseResource nextBase : results.getResources(0, results.size())) {
IAnyResource next = (IAnyResource) nextBase;
Date updated = next.getMeta().getLastUpdated();
if (mostRecentMatch == null) {
mostRecentMatch = updated;
} else {
long mostRecentMatchTime = mostRecentMatch.getTime();
long updatedTime = updated.getTime();
if (mostRecentMatchTime < updatedTime) {
mostRecentMatch = updated;
}
}
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
Long pid = IDao.RESOURCE_PID.get(next);
ourLog.info("New resource for subscription: {}", pid);
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
nextFlag.setSubscription(theSubscriptionTable);
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
flags.add(nextFlag);
}
mySubscriptionFlaggedResourceDataDao.save(flags);
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch));
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
mySubscriptionTableDao.save(theSubscriptionTable);
return results.size();
}
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public synchronized void pollForNewUndeliveredResourcesScheduler() {
if (getConfig().isSchedulingDisabled()) {
return;
}
pollForNewUndeliveredResources();
}
@Override
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
@ -252,69 +78,15 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
createSubscriptionTable(theEntity, theSubscription);
}
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void purgeInactiveSubscriptions() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
return;
}
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
@Override
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
toPurge.size();
return toPurge;
}
});
for (SubscriptionTable subscriptionTable : toPurge) {
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
new Object[]{subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll()});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
delete(subscriptionId, null);
return null;
}
});
}
}
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
Subscription resource = (Subscription) theResource;
Long resourceId = theEntity.getId();
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
if (subscriptionId != null) {
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
}
} else {
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
q.setParameter("res_id", resourceId);
q.setParameter("status", resource.getStatusElement().getValueAsString());
if (q.executeUpdate() > 0) {
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus());
} else {
createSubscriptionTable(retVal, resource);
}
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
}
return retVal;
}

View File

@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.r4;
*/
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionR4.class);
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
SubscriptionTable subscriptionEntity = new SubscriptionTable();
subscriptionEntity.setCreated(new Date());
subscriptionEntity.setSubscriptionResource(theEntity);
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
myEntityManager.persist(subscriptionEntity);
}
@ -103,146 +68,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
return table.getId();
}
@Override
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
retVal.add(toResource(nextFlaggedResource.getResource(), false));
}
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
mySubscriptionFlaggedResourceDataDao.flush();
mySubscriptionTableDao.updateLastClientPoll(new Date());
return retVal;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int pollForNewUndeliveredResources() {
return pollForNewUndeliveredResources(null);
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
if (getConfig().isSubscriptionEnabled() == false) {
return 0;
}
ourLog.trace("Beginning pollForNewUndeliveredResources()");
// SubscriptionCandidateResource
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
@Override
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
}
});
int retVal = 0;
for (final Long nextSubscriptionTablePid : subscriptions) {
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
@Override
public Integer doInTransaction(TransactionStatus theStatus) {
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
}
});
}
return retVal;
}
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET) {
ourLog.info("Skipping non web socket subscription");
return 0;
}
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
return 0;
}
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
criteriaUrl.setLoadSynchronousUpTo(1000);
long start = theSubscriptionTable.getMostRecentMatch().getTime();
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
if (end <= start) {
ourLog.trace("Skipping search for subscription");
return 0;
}
ourLog.info("Subscription {} search from {} to {}", new Object[]{subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end))});
DateRangeParam range = new DateRangeParam();
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
criteriaUrl.setLastUpdated(range);
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
IBundleProvider results = dao.search(criteriaUrl);
if (results.size() == 0) {
return 0;
}
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getIdElement().getIdPart());
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
Date mostRecentMatch = null;
for (IBaseResource nextBase : results.getResources(0, results.size())) {
IAnyResource next = (IAnyResource) nextBase;
Date updated = next.getMeta().getLastUpdated();
if (mostRecentMatch == null) {
mostRecentMatch = updated;
} else {
long mostRecentMatchTime = mostRecentMatch.getTime();
long updatedTime = updated.getTime();
if (mostRecentMatchTime < updatedTime) {
mostRecentMatch = updated;
}
}
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
Long pid = IDao.RESOURCE_PID.get(next);
ourLog.info("New resource for subscription: {}", pid);
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
nextFlag.setSubscription(theSubscriptionTable);
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
flags.add(nextFlag);
}
mySubscriptionFlaggedResourceDataDao.save(flags);
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch));
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
mySubscriptionTableDao.save(theSubscriptionTable);
return results.size();
}
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public synchronized void pollForNewUndeliveredResourcesScheduler() {
if (getConfig().isSchedulingDisabled()) {
return;
}
pollForNewUndeliveredResources();
}
@Override
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
@ -251,70 +76,19 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
createSubscriptionTable(theEntity, theSubscription);
}
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void purgeInactiveSubscriptions() {
if (getConfig().isSchedulingDisabled()) {
return;
}
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
return;
}
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
@Override
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
toPurge.size();
return toPurge;
}
});
for (SubscriptionTable subscriptionTable : toPurge) {
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
new Object[]{subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll()});
txTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
delete(subscriptionId, null);
return null;
}
});
}
}
@Override
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
Subscription resource = (Subscription) theResource;
Long resourceId = theEntity.getId();
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
if (subscriptionId != null) {
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
}
} else {
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
q.setParameter("res_id", resourceId);
q.setParameter("status", resource.getStatusElement().getValueAsString());
if (q.executeUpdate() > 0) {
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus());
} else {
createSubscriptionTable(retVal, resource);
mySubscriptionTableDao.deleteAllForSubscription(retVal);
}
}
return retVal;
}

View File

@ -1,72 +0,0 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 javax.persistence.*;
@Entity
@Table(name = "HFJ_SUBSCRIPTION_FLAG_RES")
public class SubscriptionFlaggedResource {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SUBSCRIPTION_FLAG_ID")
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_FLAG_ID", sequenceName = "SEQ_SUBSCRIPTION_FLAG_ID")
@Column(name = "PID", insertable = false, updatable = false)
private Long myId;
@ManyToOne()
@JoinColumn(name = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_SUBSFLAGRES_RES"))
private ResourceTable myResource;
@ManyToOne()
@JoinColumn(name = "SUBSCRIPTION_ID",
foreignKey = @ForeignKey(name = "FK_SUBSFLAG_SUBS")
)
private SubscriptionTable mySubscription;
@Column(name = "RES_VERSION", nullable = false)
private Long myVersion;
public ResourceTable getResource() {
return myResource;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
public SubscriptionTable getSubscription() {
return mySubscription;
}
public void setSubscription(SubscriptionTable theSubscription) {
mySubscription = theSubscription;
}
public Long getVersion() {
return myVersion;
}
public void setVersion(Long theVersion) {
myVersion = theVersion;
}
}

View File

@ -20,140 +20,56 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import java.util.Collection;
import javax.persistence.*;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
//@formatter:off
@Entity
@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= {
@UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }),
@UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" })
@Table(name = "HFJ_SUBSCRIPTION_STATS", uniqueConstraints = {
@UniqueConstraint(name = "IDX_SUBSC_RESID", columnNames = {"RES_ID"}),
})
@NamedQueries({
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_SET_STATUS", query="UPDATE SubscriptionTable t SET t.myStatus = :status WHERE t.myResId = :res_id"),
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_GET_BY_RES", query="SELECT t FROM SubscriptionTable t WHERE t.myResId = :res_id")
})
//@formatter:on
public class SubscriptionTable {
@Column(name = "CHECK_INTERVAL", nullable = false)
private long myCheckInterval;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CREATED_TIME", nullable = false, insertable = true, updatable = false)
private Date myCreated;
@OneToMany(mappedBy = "mySubscription")
private Collection<SubscriptionFlaggedResource> myFlaggedResources;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SUBSCRIPTION_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SUBSCRIPTION_ID")
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID")
@Column(name = "PID", insertable = false, updatable = false)
private Long myId;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "LAST_CLIENT_POLL", nullable = true)
private Date myLastClientPoll;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "MOST_RECENT_MATCH", nullable = false)
private Date myMostRecentMatch;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "NEXT_CHECK", nullable = false)
private Date myNextCheck;
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResId;
@Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20)
private String myStatus;
@OneToOne()
@JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID",
foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID")
foreignKey = @ForeignKey(name = "FK_SUBSC_RESOURCE_ID")
)
private ResourceTable mySubscriptionResource;
/**
* Constructor
*/
public SubscriptionTable(){
public SubscriptionTable() {
super();
}
public long getCheckInterval() {
return myCheckInterval;
}
public Date getCreated() {
return myCreated;
}
public Long getId() {
return myId;
}
public Date getLastClientPoll() {
return myLastClientPoll;
}
public Date getMostRecentMatch() {
return myMostRecentMatch;
}
public Date getNextCheck() {
return myNextCheck;
}
public String getStatus() {
return myStatus;
}
public ResourceTable getSubscriptionResource() {
return mySubscriptionResource;
}
public void setCheckInterval(long theCheckInterval) {
myCheckInterval = theCheckInterval;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public void setLastClientPoll(Date theLastClientPoll) {
myLastClientPoll = theLastClientPoll;
public Long getId() {
return myId;
}
public void setMostRecentMatch(Date theMostRecentMatch) {
myMostRecentMatch = theMostRecentMatch;
}
public void setNextCheck(Date theNextCheck) {
myNextCheck = theNextCheck;
}
public void setStatus(String theStatus) {
myStatus = theStatus;
public ResourceTable getSubscriptionResource() {
return mySubscriptionResource;
}
public void setSubscriptionResource(ResourceTable theSubscriptionResource) {

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
@ -29,21 +32,25 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -70,6 +77,14 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
private ThreadPoolExecutor myDeliveryExecutor;
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
private LinkedBlockingQueue<Runnable> myDeliveryExecutorQueue;
private IFhirResourceDao<?> mySubscriptionDao;
@Autowired
private List<IFhirResourceDao<?>> myResourceDaos;
@Autowired
private FhirContext myCtx;
@Autowired(required = false)
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4;
/**
* Constructor
@ -79,7 +94,83 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
setExecutorThreadCount(5);
}
protected abstract CanonicalSubscription canonicalize(S theSubscription);
protected CanonicalSubscription canonicalize(S theSubscription) {
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
return canonicalizeDstu2(theSubscription);
case DSTU3:
return canonicalizeDstu3(theSubscription);
case R4:
return canonicalizeR4(theSubscription);
default:
throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion());
}
}
protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
retVal.setStatus(subscription.getStatus());
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(subscription.getChannel().getType());
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
List<org.hl7.fhir.r4.model.Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
if (topicExts.size() > 0) {
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
}
org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement());
retVal.addTrigger(def.getTrigger());
}
return retVal;
}
public abstract Subscription.SubscriptionChannelType getChannelType();
@ -116,7 +207,9 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
myProcessingChannel = theProcessingChannel;
}
protected abstract IFhirResourceDao<?> getSubscriptionDao();
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public List<CanonicalSubscription> getSubscriptions() {
return new ArrayList<>(myIdToSubscription.values());
@ -243,13 +336,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
initSubscriptions();
}
protected void registerSubscriptionCheckingSubscriber() {
if (mySubscriptionCheckingSubscriber == null) {
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
}
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
}
@SuppressWarnings("unused")
@PreDestroy
public void preDestroy() {
@ -268,6 +354,13 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
myIdToSubscription.put(theId.getIdPart(), canonicalize(theSubscription));
}
protected void registerSubscriptionCheckingSubscriber() {
if (mySubscriptionCheckingSubscriber == null) {
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
}
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
}
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
ResourceModifiedMessage msg = new ResourceModifiedMessage();
@ -294,6 +387,20 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
submitResourceModified(msg);
}
@PostConstruct
public void start() {
for (IFhirResourceDao<?> next : myResourceDaos) {
if (myCtx.getResourceDefinition(next.getResourceType()).getName().equals("Subscription")) {
mySubscriptionDao = next;
}
}
Validate.notNull(mySubscriptionDao);
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
Validate.notNull(myEventDefinitionDaoR4);
}
}
protected void submitResourceModified(final ResourceModifiedMessage theMsg) {
final GenericMessage<ResourceModifiedMessage> message = new GenericMessage<>(theMsg);
mySubscriptionActivatingSubscriber.handleMessage(message);
@ -308,4 +415,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
myIdToSubscription.remove(theId.getIdPart());
}
}

View File

@ -29,14 +29,14 @@ import org.springframework.messaging.MessageHandler;
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
private final IFhirResourceDao mySubscriptionDao;
private final IFhirResourceDao<?> mySubscriptionDao;
private final Subscription.SubscriptionChannelType myChannelType;
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
/**
* Constructor
*/
public BaseSubscriptionSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
public BaseSubscriptionSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
mySubscriptionDao = theSubscriptionDao;
myChannelType = theChannelType;
mySubscriptionInterceptor = theSubscriptionInterceptor;

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription;
* #L%
*/
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -54,6 +56,19 @@ public class CanonicalSubscription implements Serializable {
myTrigger = theTrigger;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
CanonicalSubscription that = (CanonicalSubscription) theO;
return new EqualsBuilder()
.append(getIdElement().getIdPart(), that.getIdElement().getIdPart())
.isEquals();
}
public IBaseResource getBackingSubscription() {
return myBackingSubscription;
}
@ -90,10 +105,12 @@ public class CanonicalSubscription implements Serializable {
return myHeaders;
}
public void setHeaders(String theHeaders) {
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
myHeaders = new ArrayList<>();
if (isNotBlank(theHeaders)) {
myHeaders.add(theHeaders);
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
}
}
}
@ -129,12 +146,17 @@ public class CanonicalSubscription implements Serializable {
return myTrigger;
}
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
myHeaders = new ArrayList<>();
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getIdElement().getIdPart())
.toHashCode();
}
public void setHeaders(String theHeaders) {
myHeaders = new ArrayList<>();
if (isNotBlank(theHeaders)) {
myHeaders.add(theHeaders);
}
}
}

View File

@ -1,157 +0,0 @@
package ca.uhn.fhir.jpa.subscription;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.gclient.IClientExecutable;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
public class SubscriptionDeliveringWebsocketSubscriber extends BaseSubscriptionSubscriber {
private final PlatformTransactionManager myTxManager;
private final ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDao;
private final ISubscriptionTableDao mySubscriptionTableDao;
private final IResourceTableDao myResourceTableDao;
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringWebsocketSubscriber.class);
public SubscriptionDeliveringWebsocketSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTxManager, ISubscriptionFlaggedResourceDataDao theSubscriptionFlaggedResourceDataDao, ISubscriptionTableDao theSubscriptionTableDao, IResourceTableDao theResourceTableDao) {
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
myTxManager = theTxManager;
mySubscriptionFlaggedResourceDao = theSubscriptionFlaggedResourceDataDao;
mySubscriptionTableDao = theSubscriptionTableDao;
myResourceTableDao = theResourceTableDao;
}
@Override
public void handleMessage(final Message<?> theMessage) throws MessagingException {
if (!(theMessage.getPayload() instanceof ResourceDeliveryMessage)) {
return;
}
final ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload();
if (!subscriptionTypeApplies(getContext(), msg.getSubscription().getBackingSubscription())) {
return;
}
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
IBaseResource payload = msg.getPayload();
Long payloadPid = extractResourcePid(payload);
ResourceTable payloadTable = myResourceTableDao.findOne(payloadPid);
IBaseResource subscription = msg.getSubscription().getBackingSubscription();
Long subscriptionPid = extractResourcePid(subscription);
SubscriptionTable subscriptionTable = mySubscriptionTableDao.findOneByResourcePid(subscriptionPid);
ourLog.info("Adding new resource {} for subscription: {}", payload.getIdElement().toUnqualified().getValue(), subscription.getIdElement().toUnqualifiedVersionless().getValue());
SubscriptionFlaggedResource candidate = new SubscriptionFlaggedResource();
candidate.setResource(payloadTable);
candidate.setSubscription(subscriptionTable);
candidate.setVersion(payload.getIdElement().getVersionIdPartAsLong());
mySubscriptionFlaggedResourceDao.save(candidate);
}
});
RestOperationTypeEnum operationType = msg.getOperationType();
CanonicalSubscription subscription = msg.getSubscription();
// Grab the endpoint from the subscription
String endpointUrl = subscription.getEndpointUrl();
// Grab the payload type (encoding mimetype ) from the subscription
String payloadString = subscription.getPayloadString();
if (payloadString.contains(";")) {
payloadString = payloadString.substring(0, payloadString.indexOf(';'));
}
payloadString = payloadString.trim();
EncodingEnum payloadType = EncodingEnum.forContentType(payloadString);
payloadType = ObjectUtils.defaultIfNull(payloadType, EncodingEnum.XML);
getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
IGenericClient client = getContext().newRestfulGenericClient(endpointUrl);
IBaseResource payloadResource = msg.getPayload();
IClientExecutable<?, ?> operation;
switch (operationType) {
case CREATE:
operation = client.create().resource(payloadResource);
break;
case UPDATE:
operation = client.update().resource(payloadResource);
break;
case DELETE:
operation = client.delete().resourceById(msg.getPayloadId());
break;
default:
ourLog.warn("Ignoring delivery message of type: {}", msg.getOperationType());
return;
}
operation.encoded(payloadType);
ourLog.info("Delivering {} rest-hook payload {} for {}", operationType, payloadResource.getIdElement().toUnqualified().getValue(), subscription.getIdElement().toUnqualifiedVersionless().getValue());
operation.execute();
}
private Long extractResourcePid(IBaseResource thePayoad) {
Long pid;
if (thePayoad instanceof IResource) {
pid = IDao.RESOURCE_PID.get((IResource) thePayoad);
} else {
pid = IDao.RESOURCE_PID.get((IAnyResource) thePayoad);
}
return pid;
}
}

View File

@ -1,75 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays;
public class RestHookSubscriptionDstu2Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,345 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketHandlerDstu2 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandlerDstu2.class);
private static IFhirResourceDaoSubscription<Subscription> ourSubscriptionDao;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskScheduler")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
public static void setCtx(FhirContext theCtx) {
ourCtx = theCtx;
}
public static void setSubscriptionDao(IFhirResourceDaoSubscription<Subscription> theSubscriptionDao) {
ourSubscriptionDao = theSubscriptionDao;
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
ourSubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(ourCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscipriptionState implements IState {
private WebSocketSession mySession;
public BoundStaticSubscipriptionState(WebSocketSession theSession) {
mySession = theSession;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
String payload = "ping " + mySubscriptionId.getIdPart();
ourLog.info("Sending WebSocket message: {}", payload);
mySession.sendMessage(new TextMessage(payload));
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdDt id = new IdDt(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = ourSubscriptionDao.read(id, null);
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscipriptionState(theSession);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bingSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = ourSubscriptionDao.create(subscription).getId();
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bingSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,380 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketReturnResourceHandlerDstu2 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerDstu2.class);
@Autowired
@Qualifier("myFhirContextDstu2")
private FhirContext myCtx;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
@Autowired
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskScheduler")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
mySubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdDt id = new IdDt(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = mySubscriptionDao.read(id, null);
EncodingEnum encoding = EncodingEnum.JSON;
String criteria = subscription.getCriteria();
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscriptionState(theSession, encoding);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bindSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = mySubscriptionDao.create(subscription).getId();
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bindSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,51 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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%
*/
public class WebSocketSubscriptionDstu2Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionDstu2Interceptor.doCanonicalize(theSubscription);
}
@Override
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,79 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays;
public class RestHookSubscriptionDstu3Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
}

View File

@ -1,348 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketHandlerDstu3 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandlerDstu3.class);
private static IFhirResourceDaoSubscription<Subscription> ourSubscriptionDao;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskSchedulerDstu3")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
public static void setCtx(FhirContext theCtx) {
ourCtx = theCtx;
}
public static void setSubscriptionDao(IFhirResourceDaoSubscription<Subscription> theSubscriptionDao) {
ourSubscriptionDao = theSubscriptionDao;
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
ourSubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(ourCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscipriptionState implements IState {
private WebSocketSession mySession;
public BoundStaticSubscipriptionState(WebSocketSession theSession) {
mySession = theSession;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
String payload = "ping " + mySubscriptionId.getIdPart();
ourLog.info("Sending WebSocket message: {}", payload);
mySession.sendMessage(new TextMessage(payload));
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdType id = new IdType(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = ourSubscriptionDao.read(id, null);
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscipriptionState(theSession);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bingSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subscription.setStatus(SubscriptionStatus.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = ourSubscriptionDao.create(subscription).getId();
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bingSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,382 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketReturnResourceHandlerDstu3 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerDstu3.class);
@Autowired
private FhirContext myCtx;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
@Autowired
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskScheduler")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
mySubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdType id = new IdType(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = mySubscriptionDao.read(id, null);
EncodingEnum encoding = EncodingEnum.JSON;
String criteria = subscription.getCriteria();
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscriptionState(theSession, encoding);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bindSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subscription.setStatus(SubscriptionStatus.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = mySubscriptionDao.create(subscription).getId();
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bindSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,64 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
public class WebSocketSubscriptionDstu3Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<org.hl7.fhir.dstu3.model.Subscription> mySubscriptionDao;
@Override
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionDstu3Interceptor.doCanonicalize(theSubscription);
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,97 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.EventDefinition;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.TriggerDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.List;
public class RestHookSubscriptionR4Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> mySubscriptionDao;
@Autowired
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDao;
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public void setSubscriptionDao(IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription, myEventDefinitionDao);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription, IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> theEventDefinitionDao) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
retVal.setStatus(subscription.getStatus());
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(subscription.getChannel().getType());
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
List<Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
if (topicExts.size() > 0) {
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
}
EventDefinition def = theEventDefinitionDao.read(ref.getReferenceElement());
retVal.addTrigger(def.getTrigger());
}
return retVal;
}
}

View File

@ -1,345 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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 java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketHandlerR4 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandlerR4.class);
private static IFhirResourceDaoSubscription<Subscription> ourSubscriptionDao;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskSchedulerR4")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
public static void setCtx(FhirContext theCtx) {
ourCtx = theCtx;
}
public static void setSubscriptionDao(IFhirResourceDaoSubscription<Subscription> theSubscriptionDao) {
ourSubscriptionDao = theSubscriptionDao;
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
ourSubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(ourCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscipriptionState implements IState {
private WebSocketSession mySession;
public BoundStaticSubscipriptionState(WebSocketSession theSession) {
mySession = theSession;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
String payload = "ping " + mySubscriptionId.getIdPart();
ourLog.info("Sending WebSocket message: {}", payload);
mySession.sendMessage(new TextMessage(payload));
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdType id = new IdType(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = ourSubscriptionDao.read(id, null);
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscipriptionState(theSession);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bingSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subscription.setStatus(SubscriptionStatus.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = ourSubscriptionDao.create(subscription).getId();
mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bingSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,361 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.ISubscriptionWebsocketHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class SubscriptionWebsocketReturnResourceHandlerR4 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerR4.class);
@Autowired
private FhirContext myCtx;
private ScheduledFuture<?> myScheduleFuture;
private IState myState = new InitialState();
@Autowired
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
private IIdType mySubscriptionId;
private Long mySubscriptionPid;
@Autowired
@Qualifier("websocketTaskScheduler")
private TaskScheduler myTaskScheduler;
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public void postConstruct() {
ourLog.info("Creating scheduled task for subscription websocket connection");
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
}
@PreDestroy
public void preDescroy() {
ourLog.info("Cancelling scheduled task for subscription websocket connection");
myScheduleFuture.cancel(true);
IState state = myState;
if (state != null) {
state.closing();
}
}
@Override
public void run() {
Long subscriptionPid = mySubscriptionPid;
if (subscriptionPid == null) {
return;
}
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
List<IBaseResource> results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
if (results.isEmpty() == false) {
myState.deliver(results);
}
}
private class BoundDynamicSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
ourLog.info("Deleting subscription {}", mySubscriptionId);
try {
mySubscriptionDao.delete(mySubscriptionId, null);
} catch (Exception e) {
handleFailure(e);
}
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class BoundStaticSubscriptionState implements IState {
private EncodingEnum myEncoding;
private WebSocketSession mySession;
public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
mySession = theSession;
myEncoding = theEncoding;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
try {
for (IBaseResource nextResource : theResults) {
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
mySession.sendMessage(new TextMessage(payload));
}
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdType id = new IdType(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Subscription subscription = mySubscriptionDao.read(id, null);
EncodingEnum encoding = EncodingEnum.JSON;
String criteria = subscription.getCriteria();
String params = criteria.substring(criteria.indexOf('?') + 1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundStaticSubscriptionState(theSession, encoding);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
private IIdType bindSearch(WebSocketSession theSession, String theRemaining) {
Subscription subscription = new Subscription();
subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subscription.setStatus(SubscriptionStatus.ACTIVE);
subscription.setCriteria(theRemaining);
try {
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
EncodingEnum encoding = EncodingEnum.JSON;
for (NameValuePair nameValuePair : paramValues) {
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
if (nextEncoding != null) {
encoding = nextEncoding;
}
}
}
IIdType id = mySubscriptionDao.create(subscription).getId();
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
mySubscriptionId = subscription.getIdElement();
myState = new BoundDynamicSubscriptionState(theSession, encoding);
return id;
} catch (UnprocessableEntityException e) {
ourLog.warn("Failed to bind subscription: " + e.getMessage());
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
} catch (IOException e2) {
handleFailure(e2);
}
} catch (Exception e) {
handleFailure(e);
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
} catch (IOException e2) {
handleFailure(e2);
}
}
return null;
}
@Override
public void closing() {
// nothing
}
@Override
public void deliver(List<IBaseResource> theResults) {
throw new IllegalStateException();
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
if (remaining.contains("?")) {
subscriptionId = bindSearch(theSession, remaining);
} else {
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
private interface IState {
void closing();
void deliver(List<IBaseResource> theResults);
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
}

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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%
*/
public class WebSocketSubscriptionR4Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> mySubscriptionDao;
@Autowired
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionR4Interceptor.doCanonicalize(theSubscription, myEventDefinitionDao);
}
@Override
public Subscription.SubscriptionChannelType getChannelType() {
return Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription;
package ca.uhn.fhir.jpa.subscription.resthook;
/*-
* #%L
@ -21,6 +21,10 @@ package ca.uhn.fhir.jpa.subscription;
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionSubscriber;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
@ -30,7 +34,6 @@ import ca.uhn.fhir.rest.gclient.IClientExecutable;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -38,7 +41,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription;
package ca.uhn.fhir.jpa.subscription.resthook;
/*-
* #%L
@ -20,7 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
* #L%
*/
public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
private SubscriptionDeliveringRestHookSubscriber mySubscriptionDeliverySubscriber;
@Override
@ -31,6 +33,10 @@ public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscripti
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
}
@Override
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected void unregisterDeliverySubscriber() {

View File

@ -0,0 +1,313 @@
package ca.uhn.fhir.jpa.subscription.websocket;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 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.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Map;
public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements ISubscriptionWebsocketHandler {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandler.class);
private static FhirContext ourCtx;
@Autowired
private SubscriptionWebsocketInterceptor mySubscriptionWebsocketInterceptor;
private IState myState = new InitialState();
@Override
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
super.afterConnectionClosed(theSession, theStatus);
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
}
@Override
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
super.afterConnectionEstablished(theSession);
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
}
protected void handleFailure(Exception theE) {
ourLog.error("Failure during communication", theE);
}
@Override
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
ourLog.info("Textmessage: " + theMessage.getPayload());
myState.handleTextMessage(theSession, theMessage);
}
@Override
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
super.handleTransportError(theSession, theException);
ourLog.error("Transport error", theException);
}
@PostConstruct
public synchronized void postConstruct() {
ourLog.info("Websocket connection has been created");
}
@PreDestroy
public synchronized void preDescroy() {
ourLog.info("Websocket connection is closing");
IState state = myState;
if (state != null) {
state.closing();
}
}
private interface IState {
void closing();
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
}
private class BoundStaticSubscipriptionState implements IState, MessageHandler {
private WebSocketSession mySession;
private CanonicalSubscription mySubscription;
public BoundStaticSubscipriptionState(WebSocketSession theSession, CanonicalSubscription theSubscription) {
mySession = theSession;
mySubscription = theSubscription;
mySubscriptionWebsocketInterceptor.getDeliveryChannel().subscribe(this);
}
@Override
public void closing() {
mySubscriptionWebsocketInterceptor.getDeliveryChannel().unsubscribe(this);
}
private void deliver() {
try {
String payload = "ping " + mySubscription.getIdElement().getIdPart();
ourLog.info("Sending WebSocket message: {}", payload);
mySession.sendMessage(new TextMessage(payload));
} catch (IOException e) {
handleFailure(e);
}
}
@Override
public void handleMessage(Message<?> theMessage) {
if (!(theMessage.getPayload() instanceof ResourceDeliveryMessage)) {
return;
}
try {
ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload();
if (mySubscription.equals(msg.getSubscription())) {
deliver();
}
} catch (Exception e) {
ourLog.error("Failure handling subscription payload", e);
throw new MessagingException(theMessage, "Failure handling subscription payload", e);
}
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
try {
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
} catch (IOException e) {
handleFailure(e);
}
}
}
private class InitialState implements IState {
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
IdType id = new IdType(theBindString);
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
return null;
}
if (id.hasResourceType() == false) {
id = id.withResourceType("Subscription");
}
try {
Map<String, CanonicalSubscription> idToSubscription = mySubscriptionWebsocketInterceptor.getIdToSubscription();
CanonicalSubscription subscription = idToSubscription.get(id.getIdPart());
myState = new BoundStaticSubscipriptionState(theSession, subscription);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}
return null;
}
return id;
}
@Override
public void closing() {
// nothing
}
@Override
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
String message = theMessage.getPayload();
if (message.startsWith("bind ")) {
String remaining = message.substring("bind ".length());
IIdType subscriptionId;
subscriptionId = bindSimple(theSession, remaining);
if (subscriptionId == null) {
return;
}
try {
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
} catch (IOException e) {
handleFailure(e);
}
}
}
}
}
// private IIdType bingSearch(WebSocketSession theSession, String theRemaining) {
// Subscription subscription = new Subscription();
// subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
// subscription.setStatus(SubscriptionStatus.ACTIVE);
// subscription.setCriteria(theRemaining);
//
// try {
// String params = theRemaining.substring(theRemaining.indexOf('?')+1);
// List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
// EncodingEnum encoding = EncodingEnum.JSON;
// for (NameValuePair nameValuePair : paramValues) {
// if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
// EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
// if (nextEncoding != null) {
// encoding = nextEncoding;
// }
// }
// }
//
// IIdType id = ourSubscriptionDao.create(subscription).getId();
//
// mySubscriptionPid = ourSubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
// mySubscriptionId = subscription.getIdElement();
// myState = new BoundDynamicSubscriptionState(theSession, encoding);
//
// return id;
// } catch (UnprocessableEntityException e) {
// ourLog.warn("Failed to bind subscription: " + e.getMessage());
// try {
// theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
// } catch (IOException e2) {
// handleFailure(e2);
// }
// } catch (Exception e) {
// handleFailure(e);
// try {
// theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
// } catch (IOException e2) {
// handleFailure(e2);
// }
// }
// return null;
// }
//private class BoundDynamicSubscriptionState implements SubscriptionWebsocketHandler.IState {
//
// private EncodingEnum myEncoding;
// private WebSocketSession mySession;
//
// public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
// mySession = theSession;
// myEncoding = theEncoding;
// }
//
// @Override
// public void closing() {
// ourLog.info("Deleting subscription {}", mySubscriptionId);
// try {
// ourSubscriptionDao.delete(mySubscriptionId, null);
// } catch (Exception e) {
// handleFailure(e);
// }
// }
//
// @Override
// public void deliver(List<IBaseResource> theResults) {
// try {
// for (IBaseResource nextResource : theResults) {
// ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
// String encoded = myEncoding.newParser(ourCtx).encodeResourceToString(nextResource);
// String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
// mySession.sendMessage(new TextMessage(payload));
// }
// } catch (IOException e) {
// handleFailure(e);
// }
// }
//
// @Override
// public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
// try {
// theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
// } catch (IOException e) {
// handleFailure(e);
// }
// }
//
//}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription;
package ca.uhn.fhir.jpa.subscription.websocket;
/*-
* #%L
@ -21,16 +21,13 @@ package ca.uhn.fhir.jpa.subscription;
*/
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
private SubscriptionDeliveringWebsocketSubscriber mySubscriptionDeliverySubscriber;
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@ -42,16 +39,25 @@ public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscript
private IResourceTableDao myResourceTableDao;
@Override
protected void registerDeliverySubscriber() {
if (mySubscriptionDeliverySubscriber == null) {
mySubscriptionDeliverySubscriber = new SubscriptionDeliveringWebsocketSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, mySubscriptionFlaggedResourceDataDao, mySubscriptionTableDao, myResourceTableDao);
}
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
public Subscription.SubscriptionChannelType getChannelType() {
return Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected void registerDeliverySubscriber() {
/*
* nothing, since individual websocket connections
* register themselves
*/
}
@Override
protected void unregisterDeliverySubscriber() {
getDeliveryChannel().unsubscribe(mySubscriptionDeliverySubscriber);
/*
* nothing, since individual websocket connections
* register themselves
*/
}
}

View File

@ -226,8 +226,6 @@ public abstract class BaseJpaTest {
public Void doInTransaction(TransactionStatus theStatus) {
entityManager.createQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SearchParam.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ForcedId.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();

View File

@ -1,13 +1,22 @@
package ca.uhn.fhir.jpa.dao.dstu2;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.io.InputStream;
import javax.persistence.EntityManager;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestDstu2Config;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
@ -24,26 +33,15 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestDstu2Config;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.model.dstu2.composite.*;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.TestUtil;
import javax.persistence.EntityManager;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
//@formatter:off
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class})
//@formatter:on
@ContextConfiguration(classes= {TestDstu2Config.class})
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Autowired
protected ApplicationContext myAppCtx;

View File

@ -1,484 +0,0 @@
package ca.uhn.fhir.jpa.dao.dstu2;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import javax.persistence.TypedQuery;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.valueset.*;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2SubscriptionTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@Before
public void beforeEnableSubscription() {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(60);
}
@Test
public void testSubscriptionGetsPurgedIfItIsNeverActive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Before
public void beforeDisableScheduling() {
myDaoConfig.setSchedulingDisabled(true);
}
@Test
public void testSubscriptionGetsPurgedIfItIsInactive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
mySubscriptionDao.getUndeliveredResourcesAndPurge(mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id));
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Test
public void testCreateSubscription() {
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
TypedQuery<SubscriptionTable> q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class);
q.setParameter("id", id.getIdPartAsLong());
final SubscriptionTable table = q.getSingleResult();
assertNotNull(table);
assertNotNull(table.getNextCheck());
assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue());
assertEquals(SubscriptionStatusEnum.REQUESTED.getCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValueAsEnum());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatusEnum.ACTIVE.getCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatusEnum.ACTIVE, mySubscriptionDao.read(id, mySrd).getStatusElement().getValueAsEnum());
mySubscriptionDao.delete(id, mySrd);
assertNull(myEntityManager.find(SubscriptionTable.class, table.getId()));
/*
* Re-create again
*/
subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setId(id);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatusEnum.REQUESTED.getCode(), myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus());
assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValueAsEnum());
}
@Test
public void testCreateSubscriptionInvalidCriteria() {
Subscription subs = new Subscription();
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("ObservationZZZZ?a=b");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated on this server"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
}
@Test
public void testDeleteSubscriptionWithFlaggedResources() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testDeleteSubscriptionWithFlaggedResources";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
IIdType subsId = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
Long subsPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(subsId);
assertNull(mySubscriptionTableDao.findOne(subsPid).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), equalTo(1L));
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
/*
* Delete the subscription
*/
mySubscriptionDao.delete(subsId, mySrd);
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), not(greaterThan(0L)));
/*
* Delete a second time just to make sure that works
*/
mySubscriptionDao.delete(subsId, mySrd);
/*
* Re-create the subscription
*/
subs.setId(subsId);
mySubscriptionDao.update(subs, mySrd).getId();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), (greaterThan(0L)));
/*
* Create another resource and make sure it gets flagged
*/
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
}
@Test
public void testSubscriptionResourcesAppear() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
Long subsId2 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
List<IBaseResource> results;
List<IIdType> resultIds;
assertEquals(4, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
Date lastClientPoll = mySubscriptionTableDao.findOne(subsId1).getLastClientPoll();
assertNotNull(lastClientPoll);
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
assertNotEquals(lastClientPoll, mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
/*
* Make sure that reindexing doesn't trigger
*/
mySystemDao.markAllResourcesForReindexing();
mySystemDao.performReindexingPass(100);
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
/*
* Update resources on disk
*/
IBundleProvider allObs = myObservationDao.search(new SearchParameterMap());
ourLog.info("Updating {} observations", allObs.size());
for (IBaseResource next : allObs.getResources(0, allObs.size())) {
ourLog.info("Updating observation");
Observation nextObs = (Observation) next;
nextObs.addPerformer().setDisplay("Some display");
myObservationDao.update(nextObs, mySrd);
}
assertEquals(6, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
}
@Test
public void testSubscriptionResourcesAppear2() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear2";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType oId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("pId: {} - oId: {}", pId, oId);
myObservationDao.update(myObservationDao.read(oId, mySrd), mySrd);
assertEquals(1, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("Between passes");
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
assertEquals(2, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
}
}

View File

@ -1,530 +0,0 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import javax.persistence.TypedQuery;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SubscriptionTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@Before
public void beforeEnableSubscription() {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(60);
}
@Test
public void testSubscriptionGetsPurgedIfItIsNeverActive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Before
public void beforeDisableScheduling() {
myDaoConfig.setSchedulingDisabled(true);
}
@Test
public void testSubscriptionGetsPurgedIfItIsInactive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
mySubscriptionDao.getUndeliveredResourcesAndPurge(mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id));
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Test
public void testCreateSubscription() {
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
TypedQuery<SubscriptionTable> q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class);
q.setParameter("id", id.getIdPartAsLong());
final SubscriptionTable table = q.getSingleResult();
assertNotNull(table);
assertNotNull(table.getNextCheck());
assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue());
assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
subs.setStatus(SubscriptionStatus.ACTIVE);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatus.ACTIVE.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatus.ACTIVE, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
mySubscriptionDao.delete(id, mySrd);
assertNull(myEntityManager.find(SubscriptionTable.class, table.getId()));
/*
* Re-create again
*/
subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setId(id);
subs.setStatus(SubscriptionStatus.REQUESTED);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus());
assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
}
@Test
public void testCreateSubscriptionInvalidCriteria() {
Subscription subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("ObservationZZZZ?a=b");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.channel.payload must be populated for rest-hook subscriptions"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("text/html");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Invalid value for Subscription.channel.payload: text/html"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("application/fhir+xml");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Rest-hook subscriptions must have Subscription.channel.endpoint defined"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("application/fhir+json");
subs.getChannel().setEndpoint("http://localhost:8080");
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
}
@Test
public void testDeleteSubscriptionWithFlaggedResources() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testDeleteSubscriptionWithFlaggedResources";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
IIdType subsId = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
Long subsPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(subsId);
assertNull(mySubscriptionTableDao.findOne(subsPid).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), equalTo(1L));
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
/*
* Delete the subscription
*/
mySubscriptionDao.delete(subsId, mySrd);
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), not(greaterThan(0L)));
/*
* Delete a second time just to make sure that works
*/
mySubscriptionDao.delete(subsId, mySrd);
/*
* Re-create the subscription
*/
subs.setId(subsId);
mySubscriptionDao.update(subs, mySrd).getId();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), (greaterThan(0L)));
/*
* Create another resource and make sure it gets flagged
*/
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
}
@Test
public void testSubscriptionResourcesAppear() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId2 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
List<IBaseResource> results;
List<IIdType> resultIds;
assertEquals(4, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
Date lastClientPoll = mySubscriptionTableDao.findOne(subsId1).getLastClientPoll();
assertNotNull(lastClientPoll);
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
assertNotEquals(lastClientPoll, mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
/*
* Make sure that reindexing doesn't trigger
*/
mySystemDao.markAllResourcesForReindexing();
mySystemDao.performReindexingPass(100);
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
/*
* Update resources on disk
*/
IBundleProvider allObs = myObservationDao.search(new SearchParameterMap());
ourLog.info("Updating {} observations", allObs.size());
for (IBaseResource next : allObs.getResources(0, allObs.size())) {
ourLog.info("Updating observation");
Observation nextObs = (Observation) next;
nextObs.addPerformer().setDisplay("Some display");
myObservationDao.update(nextObs, mySrd);
}
assertEquals(6, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
}
@Test
public void testSubscriptionResourcesAppear2() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear2";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType oId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("pId: {} - oId: {}", pId, oId);
myObservationDao.update(myObservationDao.read(oId, mySrd), mySrd);
assertEquals(1, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("Between passes");
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
assertEquals(2, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
}
}

View File

@ -1,530 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.List;
import javax.persistence.TypedQuery;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
public class FhirResourceDaoR4SubscriptionTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SubscriptionTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Autowired
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
@Autowired
private ISubscriptionTableDao mySubscriptionTableDao;
@Before
public void beforeEnableSubscription() {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(60);
}
@Test
public void testSubscriptionGetsPurgedIfItIsNeverActive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Before
public void beforeDisableScheduling() {
myDaoConfig.setSchedulingDisabled(true);
}
@Test
public void testSubscriptionGetsPurgedIfItIsInactive() throws Exception {
myDaoConfig.setSubscriptionPurgeInactiveAfterSeconds(1);
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.purgeInactiveSubscriptions();
mySubscriptionDao.read(id, mySrd);
mySubscriptionDao.getUndeliveredResourcesAndPurge(mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id));
Thread.sleep(1500);
myDaoConfig.setSchedulingDisabled(false);
mySubscriptionDao.purgeInactiveSubscriptions();
try {
mySubscriptionDao.read(id, mySrd);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Test
public void testCreateSubscription() {
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setStatus(SubscriptionStatus.REQUESTED);
IIdType id = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
TypedQuery<SubscriptionTable> q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class);
q.setParameter("id", id.getIdPartAsLong());
final SubscriptionTable table = q.getSingleResult();
assertNotNull(table);
assertNotNull(table.getNextCheck());
assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue());
assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
subs.setStatus(SubscriptionStatus.ACTIVE);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatus.ACTIVE.toCode(), myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatus.ACTIVE, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
mySubscriptionDao.delete(id, mySrd);
assertNull(myEntityManager.find(SubscriptionTable.class, table.getId()));
/*
* Re-create again
*/
subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setId(id);
subs.setStatus(SubscriptionStatus.REQUESTED);
mySubscriptionDao.update(subs, mySrd);
assertEquals(SubscriptionStatus.REQUESTED.toCode(), myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus());
assertEquals(SubscriptionStatus.REQUESTED, mySubscriptionDao.read(id, mySrd).getStatusElement().getValue());
}
@Test
public void testCreateSubscriptionInvalidCriteria() {
Subscription subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("ObservationZZZZ?a=b");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.channel.payload must be populated for rest-hook subscriptions"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("text/html");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Invalid value for Subscription.channel.payload: text/html"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("application/fhir+xml");
try {
mySubscriptionDao.create(subs, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Rest-hook subscriptions must have Subscription.channel.endpoint defined"));
}
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
subs = new Subscription();
subs.setStatus(SubscriptionStatus.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
subs.getChannel().setPayload("application/fhir+json");
subs.getChannel().setEndpoint("http://localhost:8080");
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
}
@Test
public void testDeleteSubscriptionWithFlaggedResources() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testDeleteSubscriptionWithFlaggedResources";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
IIdType subsId = mySubscriptionDao.create(subs, mySrd).getId().toUnqualifiedVersionless();
Long subsPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(subsId);
assertNull(mySubscriptionTableDao.findOne(subsPid).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), equalTo(1L));
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
/*
* Delete the subscription
*/
mySubscriptionDao.delete(subsId, mySrd);
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), not(greaterThan(0L)));
/*
* Delete a second time just to make sure that works
*/
mySubscriptionDao.delete(subsId, mySrd);
/*
* Re-create the subscription
*/
subs.setId(subsId);
mySubscriptionDao.update(subs, mySrd).getId();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), not(greaterThan(0L)));
assertThat(mySubscriptionTableDao.count(), (greaterThan(0L)));
/*
* Create another resource and make sure it gets flagged
*/
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertThat(mySubscriptionFlaggedResourceDataDao.count(), greaterThan(0L));
assertThat(mySubscriptionTableDao.count(), greaterThan(0L));
}
@Test
public void testSubscriptionResourcesAppear() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId2 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
List<IBaseResource> results;
List<IIdType> resultIds;
assertEquals(4, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
Date lastClientPoll = mySubscriptionTableDao.findOne(subsId1).getLastClientPoll();
assertNotNull(lastClientPoll);
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, contains(afterId1, afterId2));
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId1);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
assertNotEquals(lastClientPoll, mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
mySubscriptionDao.pollForNewUndeliveredResources();
results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subsId2);
resultIds = toUnqualifiedVersionlessIds(results);
assertThat(resultIds, empty());
/*
* Make sure that reindexing doesn't trigger
*/
mySystemDao.markAllResourcesForReindexing();
mySystemDao.performReindexingPass(100);
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
/*
* Update resources on disk
*/
IBundleProvider allObs = myObservationDao.search(new SearchParameterMap());
ourLog.info("Updating {} observations", allObs.size());
for (IBaseResource next : allObs.getResources(0, allObs.size())) {
ourLog.info("Updating observation");
Observation nextObs = (Observation) next;
nextObs.addPerformer().setDisplay("Some display");
myObservationDao.update(nextObs, mySrd);
}
assertEquals(6, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
}
@Test
public void testSubscriptionResourcesAppear2() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear2";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType oId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs, mySrd).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("pId: {} - oId: {}", pId, oId);
myObservationDao.update(myObservationDao.read(oId, mySrd), mySrd);
assertEquals(1, mySubscriptionDao.pollForNewUndeliveredResources());
ourLog.info("Between passes");
assertEquals(0, mySubscriptionDao.pollForNewUndeliveredResources());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
assertEquals(2, mySubscriptionDao.pollForNewUndeliveredResources());
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(4, mySubscriptionFlaggedResourceDataDao.count());
}
}

View File

@ -1,29 +1,9 @@
package ca.uhn.fhir.jpa.provider;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.*;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
@ -35,6 +15,26 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.TestUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
@ -45,7 +45,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
protected static Server ourServer;
protected static String ourServerBase;
protected static GenericWebApplicationContext ourWebApplicationContext;
protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static DatabaseBackedPagingProvider ourPagingProvider;
public BaseResourceProviderDstu2Test() {
@ -97,7 +97,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
ourWebApplicationContext.setParent(myAppCtx);
ourWebApplicationContext.refresh();
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(RestHookSubscriptionDstu2Interceptor.class);
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
@ -107,8 +107,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
subsServletHolder.setServlet(dispatcherServlet);
subsServletHolder.setInitParameter(
ContextLoader.CONFIG_LOCATION_PARAM,
WebsocketDstu2Config.class.getName() + "\n" +
WebsocketDstu2DispatcherConfig.class.getName());
WebsocketDispatcherConfig.class.getName());
proxyHandler.addServlet(subsServletHolder, "/*");

View File

@ -1,79 +1,32 @@
package ca.uhn.fhir.jpa.provider;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.*;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.valueset.*;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
public class BaseSocket {
protected String myError;
protected boolean myGotBound;
protected int myPingCount;
protected String mySubsId;
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionsDstu2Test.class);
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
client.stop();
/*
* When websocket closes, the subscription is automatically deleted. This
* can cause deadlocks if the test returns too quickly since it also
* tries to delete the subscription
*/
ca.uhn.fhir.model.dstu2.resource.Bundle found = null;
for (int i = 0; i < 10; i++) {
found = ourClient.search().forResource("Subscription").returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute();
if (found.getEntry().size() > 0) {
Thread.sleep(200);
} else {
break;
}
}
assertEquals(0, found.getEntry().size());
}
@Before
public void beforeEnableScheduling() {
myDaoConfig.setSchedulingDisabled(false);
}
@Override
public void beforeCreateInterceptor() {
super.beforeCreateInterceptor();
@ -83,46 +36,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
myDaoConfig.getInterceptors().add(interceptor);
}
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
/*
* In a separate thread, start a polling for new resources. Normally the scheduler would
* take care of this, but that can take longer which makes the unit tests run much slower
* so we simulate that part..
*/
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
ourLog.warn("Interrupted", e);
}
ourLog.info("About to poll in separate thread");
mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Done poll in separate thread");
}}.start();
ourLog.info("Entering loop");
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
ourLog.debug("Starting");
if (socket.myError != null) {
fail(socket.myError);
}
if (socket.myPingCount >= wantPingCount) {
ourLog.info("Breaking loop");
break;
}
ourLog.debug("Sleeping");
Thread.sleep(100);
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
assertNull(socket.myError, socket.myError);
assertEquals(wantPingCount, socket.myPingCount);
@Before
public void beforeEnableScheduling() {
myDaoConfig.setSchedulingDisabled(false);
}
@Test
public void testCreateInvalidNoStatus() {
Subscription subs = new Subscription();
@ -147,6 +71,75 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.update().resource(subs).execute();
}
@Test
public void testCreateInvalidWrongStatus() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.getChannel().setPayload("application/fhir+json");
subs.getChannel().setEndpoint("http://foo");
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
subs.setCriteria("Observation?identifier=123");
try {
ourClient.create().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
}
subs.setId("ABC");
try {
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
}
}
@Test
public void testCreateWithPopulatedButInvalidStatue() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?identifier=123");
subs.getStatusElement().setValue("aaaaa");
try {
ourClient.create().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
}
}
@Test
public void testUpdateFails() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
subs.setId(id);
try {
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage());
}
try {
subs.setStatus((SubscriptionStatusEnum) null);
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
}
subs.setStatus(SubscriptionStatusEnum.OFF);
}
@Test
public void testUpdateToInvalidStatus() {
Subscription subs = new Subscription();
@ -176,262 +169,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.update().resource(subs).execute();
}
@Test
public void testCreateWithPopulatedButInvalidStatue() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?identifier=123");
subs.getStatusElement().setValue("aaaaa");
try {
ourClient.create().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testCreateInvalidWrongStatus() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.getChannel().setPayload("application/fhir+json");
subs.getChannel().setEndpoint("http://foo");
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
subs.setCriteria("Observation?identifier=123");
try {
ourClient.create().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
}
public class BaseSocket {
protected String myError;
protected boolean myGotBound;
protected int myPingCount;
protected String mySubsId;
subs.setId("ABC");
try {
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
}
}
@Test
public void testSubscriptionDynamic() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionDynamicXml() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
ResourceMetadataKeyEnum.PROFILES.put(obs, Collections.singletonList(new IdDt("http://foo")));
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionSimple() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs = new Subscription();
ResourceMetadataKeyEnum.PROFILES.put(subs, Collections.singletonList(new IdDt("http://foo")));
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
Thread.sleep(100);
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
WebSocketClient client = new WebSocketClient();
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
ClientUpgradeRequest request = new ClientUpgradeRequest();
client.connect(socket, echoUri, request);
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 2);
} finally {
try {
client.stop();
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testUpdateFails() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
subs.setId(id);
try {
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage());
}
try {
subs.setStatus((SubscriptionStatusEnum) null);
ourClient.update().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
}
subs.setStatus(SubscriptionStatusEnum.OFF);
}
/**

View File

@ -1,34 +1,12 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.*;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.*;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -39,6 +17,32 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
@ -49,11 +53,11 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
protected static RestfulServer ourRestServer;
private static Server ourServer;
protected static String ourServerBase;
private static GenericWebApplicationContext ourWebApplicationContext;
protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
protected static DatabaseBackedPagingProvider ourPagingProvider;
protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
@ -119,8 +123,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
subsServletHolder.setServlet(dispatcherServlet);
subsServletHolder.setInitParameter(
ContextLoader.CONFIG_LOCATION_PARAM,
WebsocketDstu3Config.class.getName() + "\n" +
WebsocketDstu3DispatcherConfig.class.getName());
WebsocketDispatcherConfig.class.getName());
proxyHandler.addServlet(subsServletHolder, "/*");
// Register a CORS filter
@ -146,7 +149,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
mySearchEntityDao = wac.getBean(ISearchDao.class);
ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionDstu3Interceptor.class);
ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);

View File

@ -1,29 +1,27 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
@ -31,12 +29,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
private static final String WEBSOCKET_PATH = "/websocket/dstu3";
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Override
public void beforeCreateInterceptor() {
super.beforeCreateInterceptor();
@ -46,72 +38,17 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
myDaoConfig.getInterceptors().add(interceptor);
}
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Before
public void beforeEnableScheduling() {
myDaoConfig.setSchedulingDisabled(false);
}
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
/*
* In a separate thread, start a polling for new resources. Normally the scheduler would
* take care of this, but that can take longer which makes the unit tests run much slower
* so we simulate that part..
*/
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
ourLog.warn("Interrupted", e);
}
ourLog.info("About to poll in separate thread");
mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Done poll in separate thread");
}}.start();
ourLog.info("Entering loop");
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
ourLog.debug("Starting");
if (socket.myError != null) {
fail(socket.myError);
}
if (socket.myPingCount >= wantPingCount) {
ourLog.info("Breaking loop");
break;
}
ourLog.debug("Sleeping");
Thread.sleep(100);
}
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
assertNull(socket.myError, socket.myError);
assertEquals(wantPingCount, socket.myPingCount);
}
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
client.stop();
/*
* When websocket closes, the subscription is automatically deleted. This
* can cause deadlocks if the test returns too quickly since it also
* tries to delete the subscription
*/
Bundle found = null;
for (int i = 0; i < 10; i++) {
found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute();
if (found.getEntry().size() > 0) {
Thread.sleep(200);
} else {
break;
}
}
assertEquals(0, found.getEntry().size());
}
@Test
public void testCreateInvalidNoStatus() {
Subscription subs = new Subscription();
@ -162,196 +99,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
}
}
@Test
public void testSubscriptionDynamic() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionDynamicXml() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://foo");
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionSimple() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs = new Subscription();
subs.getMeta().addProfile("http://foo");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
Thread.sleep(100);
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
WebSocketClient client = new WebSocketClient();
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
ClientUpgradeRequest request = new ClientUpgradeRequest();
client.connect(socket, echoUri, request);
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 2);
} finally {
try {
client.stop();
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testUpdateFails() {

View File

@ -1,13 +1,12 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.config.r4.WebsocketR4Config;
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler;
@ -58,7 +57,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer;
private static GenericWebApplicationContext ourWebApplicationContext;
protected static GenericWebApplicationContext ourWebApplicationContext;
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
@ -122,8 +121,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
subsServletHolder.setServlet(dispatcherServlet);
subsServletHolder.setInitParameter(
ContextLoader.CONFIG_LOCATION_PARAM,
WebsocketR4Config.class.getName() + "\n" +
WebsocketR4DispatcherConfig.class.getName());
WebsocketDispatcherConfig.class.getName());
proxyHandler.addServlet(subsServletHolder, "/*");
// Register a CORS filter
@ -172,8 +170,8 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
/**
* This is lazy created so we only ask for it if its needed
*/
protected RestHookSubscriptionR4Interceptor getRestHookSubscriptionInterceptor() {
RestHookSubscriptionR4Interceptor retVal = ourWebApplicationContext.getBean(RestHookSubscriptionR4Interceptor.class);
protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() {
SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
ourRestHookSubscriptionInterceptorRequested = true;
return retVal;
}

View File

@ -1,29 +1,27 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
@ -31,12 +29,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
private static final String WEBSOCKET_PATH = "/websocket/r4";
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Override
public void beforeCreateInterceptor() {
super.beforeCreateInterceptor();
@ -46,72 +38,17 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
myDaoConfig.getInterceptors().add(interceptor);
}
@Before
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Before
public void beforeEnableScheduling() {
myDaoConfig.setSchedulingDisabled(false);
}
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
/*
* In a separate thread, start a polling for new resources. Normally the scheduler would
* take care of this, but that can take longer which makes the unit tests run much slower
* so we simulate that part..
*/
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
ourLog.warn("Interrupted", e);
}
ourLog.info("About to poll in separate thread");
mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Done poll in separate thread");
}}.start();
ourLog.info("Entering loop");
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
ourLog.debug("Starting");
if (socket.myError != null) {
fail(socket.myError);
}
if (socket.myPingCount >= wantPingCount) {
ourLog.info("Breaking loop");
break;
}
ourLog.debug("Sleeping");
Thread.sleep(100);
}
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
assertNull(socket.myError, socket.myError);
assertEquals(wantPingCount, socket.myPingCount);
}
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
client.stop();
/*
* When websocket closes, the subscription is automatically deleted. This
* can cause deadlocks if the test returns too quickly since it also
* tries to delete the subscription
*/
Bundle found = null;
for (int i = 0; i < 10; i++) {
found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute();
if (found.getEntry().size() > 0) {
Thread.sleep(200);
} else {
break;
}
}
assertEquals(0, found.getEntry().size());
}
@Test
public void testCreateInvalidNoStatus() {
Subscription subs = new Subscription();
@ -162,195 +99,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
}
}
@Test
public void testSubscriptionDynamic() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionDynamicXml() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionDynamic";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
WebSocketClient client = new WebSocketClient();
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
client.connect(socket, echoUri, new ClientUpgradeRequest());
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://foo");
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
sleepUntilPingCount(socket, 3);
obs = (Observation) socket.myReceived.get(0);
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = (Observation) socket.myReceived.get(1);
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 4);
obs = (Observation) socket.myReceived.get(2);
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
} finally {
try {
stopClientAndWaitForStopped(client);
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test
public void testSubscriptionSimple() throws Exception {
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear";
Patient p = new Patient();
p.addName().setFamily(methodName);
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
Subscription subs = new Subscription();
subs.getMeta().addProfile("http://foo");
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatus.ACTIVE);
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
Thread.sleep(100);
Observation obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Thread.sleep(100);
WebSocketClient client = new WebSocketClient();
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
try {
client.start();
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
ClientUpgradeRequest request = new ClientUpgradeRequest();
client.connect(socket, echoUri, request);
ourLog.info("Connecting to : {}", echoUri);
sleepUntilPingCount(socket, 1);
obs = new Observation();
obs.getSubject().setReferenceElement(pId);
obs.setStatus(ObservationStatus.FINAL);
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
sleepUntilPingCount(socket, 2);
} finally {
try {
client.stop();
} catch (Exception e) {
ourLog.error("Failure", e);
fail(e.getMessage());
}
}
}
@Test

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
@ -32,8 +31,7 @@ import org.junit.*;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
/**
* Test the rest-hook subscriptions

View File

@ -13,6 +13,7 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
@ -25,23 +26,10 @@ import ca.uhn.fhir.model.dstu2.valueset.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
* subscription
* <p>
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
* a test that returns the xml of the observation
* <p>
* To execute the following test, execute it the following way:
* 0. execute 'clean' test
* 1. Execute the 'createSubscription' test
* 2. Update the subscription id in the 'attachWebSocket' test
* 3. Execute the 'attachWebSocket' test
* 4. Execute the 'sendObservation' test
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu2Test.class);
// This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore
public class WebsocketWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu2Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -129,13 +117,8 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -158,12 +141,7 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
waitForSize(2, mySocketImplementation.getMessages());
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}

View File

@ -20,26 +20,11 @@ import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
* subscription
* <p>
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
* a test that returns the xml of the observation
* <p>
* To execute the following test, execute it the following way:
* 0. execute 'clean' test
* 1. Execute the 'createPatient' test
* 2. Update the patient id static variable
* 3. Execute the 'createSubscription' test
* 4. Update the subscription id static variable
* 5. Execute the 'attachWebSocket' test
* 6. Execute the 'sendObservation' test
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
// This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore
public class WebsocketWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu3Test.class);
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu3Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -127,13 +112,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -155,14 +135,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
observation.setId(observationId);
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}
}

View File

@ -8,6 +8,7 @@ import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
@ -40,8 +41,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
* 4. Execute the 'sendObservation' test
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2Test.class);
public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu2Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -53,6 +54,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
super.after();
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.unregisterInterceptor(interceptor);
}
@Before
@ -62,6 +66,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0L);
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.registerInterceptor(interceptor);
/*
* Create patient
*/
@ -95,7 +102,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
@ -129,13 +136,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -158,13 +160,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}
}

View File

@ -8,6 +8,7 @@ import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
@ -37,9 +38,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
* 6. Execute the 'sendObservation' test
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test {
public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class);
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu3Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -51,12 +52,19 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
super.after();
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.unregisterInterceptor(interceptor);
}
@Before
public void before() throws Exception {
super.before();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.registerInterceptor(interceptor);
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0L);
@ -93,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3");
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
@ -127,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -156,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}
}

View File

@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.subscription.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
@ -16,7 +16,6 @@ import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
@ -35,9 +34,9 @@ import static org.junit.Assert.*;
* 6. Execute the 'sendObservation' test
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProviderR4Test {
public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithEventDefinitionR4Test.class);
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookWithEventDefinitionR4Test.class);
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<String> ourContentTypes = new ArrayList<>();
private static List<String> ourHeaders = new ArrayList<>();
@ -80,6 +79,7 @@ public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProvi
}
@Test
@Ignore
public void testSubscriptionAddedTrigger() {
/*
* Create patient

View File

@ -21,26 +21,12 @@ import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
* subscription
* <p>
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdR4Test for
* a test that returns the xml of the observation
* <p>
* To execute the following test, execute it the following way:
* 0. execute 'clean' test
* 1. Execute the 'createPatient' test
* 2. Update the patient id static variable
* 3. Execute the 'createSubscription' test
* 4. Update the subscription id static variable
* 5. Execute the 'attachWebSocket' test
* 6. Execute the 'sendObservation' test
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaR4Test.class);
// This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore
public class WebsocketWithCriteriaR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaR4Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -130,13 +116,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -159,13 +140,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}
}

View File

@ -8,6 +8,7 @@ import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
@ -38,9 +39,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
* 6. Execute the 'sendObservation' test
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
*/
public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProviderR4Test {
public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdR4Test.class);
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdR4Test.class);
private String myPatientId;
private String mySubscriptionId;
@ -52,6 +53,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
super.after();
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.unregisterInterceptor(interceptor);
}
@Before
@ -61,6 +65,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0L);
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
ourRestServer.registerInterceptor(interceptor);
/*
* Create patient
*/
@ -94,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4");
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
@ -128,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(1, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
}
@ -157,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
ourLog.info("Observation id generated by server is: " + observationId);
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
ourLog.info("Polling showed {}", changes);
assertEquals(0, changes);
Thread.sleep(2000);
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
waitForSize(2, mySocketImplementation.getMessages());
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
}
}

View File

@ -1,35 +1,42 @@
package ca.uhn.fhirtest;
import java.util.*;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.*;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhirtest.config.*;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class TestRestfulServer extends RestfulServer {
@ -80,7 +87,7 @@ public class TestRestfulServer extends RestfulServer {
myAppCtx.register(TdlDstu2Config.class);
baseUrlProperty = FHIR_BASEURL_TDL2;
} else {
myAppCtx.register(TestDstu2Config.class, WebsocketDstu2DispatcherConfig.class);
myAppCtx.register(TestDstu2Config.class, WebsocketDispatcherConfig.class);
baseUrlProperty = FHIR_BASEURL_DSTU2;
}
myAppCtx.refresh();
@ -103,7 +110,7 @@ public class TestRestfulServer extends RestfulServer {
myAppCtx.register(TdlDstu3Config.class);
baseUrlProperty = FHIR_BASEURL_TDL3;
} else {
myAppCtx.register(TestDstu3Config.class, WebsocketDstu3DispatcherConfig.class);
myAppCtx.register(TestDstu3Config.class, WebsocketDispatcherConfig.class);
baseUrlProperty = FHIR_BASEURL_DSTU3;
}
myAppCtx.refresh();
@ -122,7 +129,7 @@ public class TestRestfulServer extends RestfulServer {
myAppCtx = new AnnotationConfigWebApplicationContext();
myAppCtx.setServletConfig(getServletConfig());
myAppCtx.setParent(parentAppCtx);
myAppCtx.register(TestR4Config.class, WebsocketR4DispatcherConfig.class);
myAppCtx.register(TestR4Config.class, WebsocketDispatcherConfig.class);
baseUrlProperty = FHIR_BASEURL_R4;
myAppCtx.refresh();
setFhirContext(FhirContext.forR4());

View File

@ -0,0 +1,118 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.*;
public class JsonParserR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(JsonParserR4Test.class);
private static FhirContext ourCtx = FhirContext.forR4();
@Test
public void testExcludeNothing() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
// excludes.add("*.id");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, containsString("BUNDLEID"));
assertThat(encoded, containsString("http://FOO"));
assertThat(encoded, containsString("PATIENTID"));
assertThat(encoded, containsString("http://BAR"));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testExcludeStarDotStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("*.id");
excludes.add("*.meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, not(containsString("PATIENTID")));
assertThat(encoded, not(containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertNotEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testExcludeRootStuff() {
IParser parser = ourCtx.newJsonParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("id");
excludes.add("meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, (containsString("PATIENTID")));
assertThat(encoded, (containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
private Bundle createBundleWithPatient() {
Bundle b = new Bundle();
b.setId("BUNDLEID");
b.getMeta().addProfile("http://FOO");
Patient p = new Patient();
p.setId("PATIENTID");
p.getMeta().addProfile("http://BAR");
p.addName().addGiven("GIVEN");
b.addEntry().setResource(p);
return b;
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,118 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.*;
public class XmlParserR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(XmlParserR4Test.class);
private static FhirContext ourCtx = FhirContext.forR4();
@Test
public void testExcludeNothing() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
// excludes.add("*.id");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, containsString("BUNDLEID"));
assertThat(encoded, containsString("http://FOO"));
assertThat(encoded, containsString("PATIENTID"));
assertThat(encoded, containsString("http://BAR"));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testExcludeStarDotStuff() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("*.id");
excludes.add("*.meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, not(containsString("PATIENTID")));
assertThat(encoded, not(containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertNotEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
@Test
public void testExcludeRootStuff() {
IParser parser = ourCtx.newXmlParser().setPrettyPrint(true);
Set<String> excludes = new HashSet<>();
excludes.add("id");
excludes.add("meta");
parser.setDontEncodeElements(excludes);
Bundle b = createBundleWithPatient();
String encoded = parser.encodeResourceToString(b);
ourLog.info(encoded);
assertThat(encoded, not(containsString("BUNDLEID")));
assertThat(encoded, not(containsString("http://FOO")));
assertThat(encoded, (containsString("PATIENTID")));
assertThat(encoded, (containsString("http://BAR")));
assertThat(encoded, containsString("GIVEN"));
b = parser.parseResource(Bundle.class, encoded);
assertNotEquals("BUNDLEID", b.getIdElement().getIdPart());
assertEquals("Patient/PATIENTID", ((Patient) b.getEntry().get(0).getResource()).getId());
assertEquals("GIVEN", ((Patient) b.getEntry().get(0).getResource()).getNameFirstRep().getGivenAsSingleString());
}
private Bundle createBundleWithPatient() {
Bundle b = new Bundle();
b.setId("BUNDLEID");
b.getMeta().addProfile("http://FOO");
Patient p = new Patient();
p.setId("PATIENTID");
p.getMeta().addProfile("http://BAR");
p.addName().addGiven("GIVEN");
b.addEntry().setResource(p);
return b;
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -370,6 +370,11 @@
resource was deleted, it was not possible to create a new resource
with the same URI as the previous one
</action>
<action type="fix">
When uploading a Bundle resource to the server (as a collection or
document, not as a transaction) the ID was incorrectly stripped from
resources being saved within the Bundle. This has been corrected.
</action>
</release>
<release version="2.5" date="2017-06-08">
<action type="fix">