Refactor websockets to use queuing mechanism
This commit is contained in:
parent
97b44965ce
commit
e94d639d29
|
@ -19,16 +19,6 @@ package ca.uhn.fhir.parser;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #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.*;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
|
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.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
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 {
|
public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param theParserErrorHandler
|
* @param theParserErrorHandler
|
||||||
*/
|
*/
|
||||||
public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
|
public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
|
||||||
|
@ -71,7 +71,7 @@ public abstract class BaseParser implements IParser {
|
||||||
myErrorHandler = theParserErrorHandler;
|
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());
|
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
|
||||||
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
|
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
|
||||||
|
@ -87,8 +87,7 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/ {
|
||||||
{
|
|
||||||
myChildrenIter = children.iterator();
|
myChildrenIter = children.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +104,7 @@ public abstract class BaseParser implements IParser {
|
||||||
return false;
|
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
|
* 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();
|
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 void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
|
||||||
|
|
||||||
protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
|
protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
|
||||||
|
@ -338,7 +303,7 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
|
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
|
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
|
||||||
}
|
}
|
||||||
|
|
||||||
doEncodeResourceToWriter(theResource, theWriter);
|
doEncodeResourceToWriter(theResource, theWriter);
|
||||||
|
@ -424,6 +389,11 @@ public abstract class BaseParser implements IParser {
|
||||||
return myContainedResources;
|
return myContainedResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
|
||||||
|
return myDontStripVersionsFromReferencesAtPaths;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link #setEncodeElements(Set)}
|
* See {@link #setEncodeElements(Set)}
|
||||||
*/
|
*/
|
||||||
|
@ -432,6 +402,21 @@ public abstract class BaseParser implements IParser {
|
||||||
return myEncodeElements;
|
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)}
|
* See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
|
||||||
*/
|
*/
|
||||||
|
@ -440,15 +425,38 @@ public abstract class BaseParser implements IParser {
|
||||||
return myEncodeElementsAppliesToResourceTypes;
|
return myEncodeElementsAppliesToResourceTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
|
||||||
|
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
|
||||||
|
myEncodeElementsAppliesToResourceTypes = null;
|
||||||
|
} else {
|
||||||
|
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIdType getEncodeForceResourceId() {
|
public IIdType getEncodeForceResourceId() {
|
||||||
return myEncodeForceResourceId;
|
return myEncodeForceResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
|
||||||
|
myEncodeForceResourceId = theEncodeForceResourceId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
protected IParserErrorHandler getErrorHandler() {
|
protected IParserErrorHandler getErrorHandler() {
|
||||||
return myErrorHandler;
|
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) {
|
protected TagList getMetaTagsForEncoding(IResource theIResource) {
|
||||||
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
|
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
|
||||||
if (shouldAddSubsettedTag()) {
|
if (shouldAddSubsettedTag()) {
|
||||||
|
@ -459,24 +467,44 @@ public abstract class BaseParser implements IParser {
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
|
||||||
|
return myOverrideResourceIdWithBundleEntryFullUrl;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Class<? extends IBaseResource>> getPreferTypes() {
|
public List<Class<? extends IBaseResource>> getPreferTypes() {
|
||||||
return myPreferTypes;
|
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")
|
@SuppressWarnings("deprecation")
|
||||||
protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
|
protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
|
||||||
switch (myContext.getAddProfileTagWhenEncoding()) {
|
switch (myContext.getAddProfileTagWhenEncoding()) {
|
||||||
case NEVER:
|
case NEVER:
|
||||||
return theProfiles;
|
|
||||||
case ONLY_FOR_CUSTOM:
|
|
||||||
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
|
|
||||||
if (resDef.isStandardType()) {
|
|
||||||
return theProfiles;
|
return theProfiles;
|
||||||
}
|
case ONLY_FOR_CUSTOM:
|
||||||
break;
|
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
|
||||||
case ALWAYS:
|
if (resDef.isStandardType()) {
|
||||||
break;
|
return theProfiles;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ALWAYS:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
|
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
|
||||||
|
@ -503,10 +531,19 @@ public abstract class BaseParser implements IParser {
|
||||||
return theProfiles;
|
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
|
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
|
||||||
* values.
|
* values.
|
||||||
*
|
*
|
||||||
* @deprecated Use {@link #isSuppressNarratives()}
|
* @deprecated Use {@link #isSuppressNarratives()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -516,7 +553,7 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
|
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
|
||||||
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
|
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
|
||||||
&& theIncludedResource == false;
|
&& theIncludedResource == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -524,14 +561,38 @@ public abstract class BaseParser implements IParser {
|
||||||
return myOmitResourceId;
|
return myOmitResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
|
||||||
public Boolean getStripVersionsFromReferences() {
|
Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
|
||||||
return myStripVersionsFromReferences;
|
if (overrideResourceIdWithBundleEntryFullUrl != null) {
|
||||||
|
return overrideResourceIdWithBundleEntryFullUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
|
||||||
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
|
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
|
||||||
return myOverrideResourceIdWithBundleEntryFullUrl;
|
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
|
@Override
|
||||||
|
@ -542,7 +603,7 @@ public abstract class BaseParser implements IParser {
|
||||||
/**
|
/**
|
||||||
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
|
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
|
||||||
* values.
|
* values.
|
||||||
*
|
*
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public boolean isSuppressNarratives() {
|
public boolean isSuppressNarratives() {
|
||||||
|
@ -623,9 +684,8 @@ public abstract class BaseParser implements IParser {
|
||||||
return parseResource(null, theMessageString);
|
return parseResource(null, theMessageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
|
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
|
||||||
CompositeChildElement theCompositeChildElement) {
|
CompositeChildElement theCompositeChildElement) {
|
||||||
if (myContext.getVersion().getVersion().isRi()) {
|
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
|
@Override
|
||||||
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
|
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
|
||||||
if (thePaths == null) {
|
if (thePaths == null) {
|
||||||
|
@ -827,8 +811,34 @@ public abstract class BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
|
public IParser setOmitResourceId(boolean theOmitResourceId) {
|
||||||
return myDontStripVersionsFromReferencesAtPaths;
|
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
|
@Override
|
||||||
|
@ -847,7 +857,7 @@ public abstract class BaseParser implements IParser {
|
||||||
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
|
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean shouldEncodeResourceId(IBaseResource theResource) {
|
protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) {
|
||||||
boolean retVal = true;
|
boolean retVal = true;
|
||||||
if (isOmitResourceId()) {
|
if (isOmitResourceId()) {
|
||||||
retVal = false;
|
retVal = false;
|
||||||
|
@ -858,24 +868,14 @@ public abstract class BaseParser implements IParser {
|
||||||
retVal = false;
|
retVal = false;
|
||||||
} else if (myDontEncodeElements.contains("*.id")) {
|
} else if (myDontEncodeElements.contains("*.id")) {
|
||||||
retVal = false;
|
retVal = false;
|
||||||
|
} else if (theSubResource == false && myDontEncodeElements.contains("id")) {
|
||||||
|
retVal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retVal;
|
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
|
* Used for DSTU2 only
|
||||||
*/
|
*/
|
||||||
|
@ -965,11 +965,13 @@ public abstract class BaseParser implements IParser {
|
||||||
private final BaseRuntimeChildDefinition myDef;
|
private final BaseRuntimeChildDefinition myDef;
|
||||||
private final CompositeChildElement myParent;
|
private final CompositeChildElement myParent;
|
||||||
private final RuntimeResourceDefinition myResDef;
|
private final RuntimeResourceDefinition myResDef;
|
||||||
|
private final boolean mySubResource;
|
||||||
|
|
||||||
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) {
|
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) {
|
||||||
myDef = theDef;
|
myDef = theDef;
|
||||||
myParent = theParent;
|
myParent = theParent;
|
||||||
myResDef = null;
|
myResDef = null;
|
||||||
|
mySubResource = theSubResource;
|
||||||
|
|
||||||
if (ourLog.isTraceEnabled()) {
|
if (ourLog.isTraceEnabled()) {
|
||||||
if (theParent != null) {
|
if (theParent != null) {
|
||||||
|
@ -984,12 +986,11 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean anyPathMatches(Set<String> thePaths) {
|
public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) {
|
||||||
StringBuilder b = new StringBuilder();
|
myResDef = theResDef;
|
||||||
addParent(this, b);
|
myDef = null;
|
||||||
|
myParent = null;
|
||||||
String path = b.toString();
|
mySubResource = theSubResource;
|
||||||
return thePaths.contains(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
|
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
|
||||||
|
@ -1012,10 +1013,12 @@ public abstract class BaseParser implements IParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompositeChildElement(RuntimeResourceDefinition theResDef) {
|
public boolean anyPathMatches(Set<String> thePaths) {
|
||||||
myResDef = theResDef;
|
StringBuilder b = new StringBuilder();
|
||||||
myDef = null;
|
addParent(this, b);
|
||||||
myParent = null;
|
|
||||||
|
String path = b.toString();
|
||||||
|
return thePaths.contains(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder buildPath() {
|
private StringBuilder buildPath() {
|
||||||
|
@ -1079,7 +1082,17 @@ public abstract class BaseParser implements IParser {
|
||||||
|
|
||||||
thePathBuilder.append('.');
|
thePathBuilder.append('.');
|
||||||
thePathBuilder.append(myDef.getElementName());
|
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;
|
return myResDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSubResource() {
|
||||||
|
return mySubResource;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean shouldBeEncoded() {
|
public boolean shouldBeEncoded() {
|
||||||
boolean retVal = true;
|
boolean retVal = true;
|
||||||
if (myEncodeElements != null) {
|
if (myEncodeElements != null) {
|
||||||
|
|
|
@ -191,7 +191,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
|
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 {
|
boolean theForceEmpty) throws IOException {
|
||||||
|
|
||||||
switch (theChildDef.getChildType()) {
|
switch (theChildDef.getChildType()) {
|
||||||
|
@ -264,7 +264,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
} else {
|
} else {
|
||||||
theEventWriter.beginObject();
|
theEventWriter.beginObject();
|
||||||
}
|
}
|
||||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem);
|
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
|
||||||
theEventWriter.endObject();
|
theEventWriter.endObject();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
|
|
||||||
for (IBaseResource next : containedResources) {
|
for (IBaseResource next : containedResources) {
|
||||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
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();
|
theEventWriter.endArray();
|
||||||
|
@ -320,7 +320,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
|
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);
|
String elementId = getCompositeElementId(theElement);
|
||||||
|
@ -330,7 +330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean haveWrittenExtensions = false;
|
boolean haveWrittenExtensions = false;
|
||||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
|
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
|
||||||
|
|
||||||
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
||||||
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
||||||
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
} else if (nextChild instanceof RuntimeChildContainedResources) {
|
} else if (nextChild instanceof RuntimeChildContainedResources) {
|
||||||
String childName = nextChild.getValidChildNames().iterator().next();
|
String childName = nextChild.getValidChildNames().iterator().next();
|
||||||
BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,15 +451,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
|
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
|
||||||
beginArray(theEventWriter, childName);
|
beginArray(theEventWriter, childName);
|
||||||
inArray = true;
|
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) {
|
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
|
||||||
// suppress narratives from contained resources
|
// suppress narratives from contained resources
|
||||||
} else {
|
} else {
|
||||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false);
|
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource,nextChildElem, false);
|
||||||
}
|
}
|
||||||
currentChildName = childName;
|
currentChildName = childName;
|
||||||
} else {
|
} else {
|
||||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force);
|
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource,theSubResource, nextChildElem, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
valueIdx++;
|
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 {
|
CompositeChildElement theParent) throws IOException, DataFormatException {
|
||||||
|
|
||||||
writeCommentsPreAndPost(theNextValue, theEventWriter);
|
writeCommentsPreAndPost(theNextValue, theEventWriter);
|
||||||
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent);
|
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -587,18 +587,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!theContainedResource) {
|
if (!theContainedResource) {
|
||||||
if (super.shouldEncodeResourceId(theResource) == false) {
|
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
|
||||||
resourceId = null;
|
resourceId = null;
|
||||||
} else if (!theSubResource && getEncodeForceResourceId() != null) {
|
} else if (!theSubResource && getEncodeForceResourceId() != null) {
|
||||||
resourceId = getEncodeForceResourceId();
|
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,
|
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) {
|
if (!theContainedResource) {
|
||||||
super.containResourcesForEncoding(theResource);
|
super.containResourcesForEncoding(theResource);
|
||||||
}
|
}
|
||||||
|
@ -670,7 +670,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
beginArray(theEventWriter, "security");
|
beginArray(theEventWriter, "security");
|
||||||
for (BaseCodingDt securityLabel : securityLabels) {
|
for (BaseCodingDt securityLabel : securityLabels) {
|
||||||
theEventWriter.beginObject();
|
theEventWriter.beginObject();
|
||||||
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null);
|
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
|
||||||
theEventWriter.endObject();
|
theEventWriter.endObject();
|
||||||
}
|
}
|
||||||
theEventWriter.endArray();
|
theEventWriter.endArray();
|
||||||
|
@ -706,7 +706,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
write(theEventWriter, "content", contentAsBase64);
|
write(theEventWriter, "content", contentAsBase64);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
|
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||||
}
|
}
|
||||||
|
|
||||||
theEventWriter.endObject();
|
theEventWriter.endObject();
|
||||||
|
@ -1366,7 +1366,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
|
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
|
||||||
} else {
|
} else {
|
||||||
String childName = myDef.getChildNameByDatatype(myValue.getClass());
|
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);
|
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1421,7 +1421,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
||||||
if (childDef == null) {
|
if (childDef == null) {
|
||||||
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
|
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);
|
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -214,7 +214,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
|
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 (theElement == null || theElement.isEmpty()) {
|
||||||
if (isChildContained(childDef, theIncludedResource)) {
|
if (isChildContained(childDef, theIncludedResource)) {
|
||||||
// We still want to go in..
|
// We still want to go in..
|
||||||
|
@ -234,7 +234,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
if (StringUtils.isNotBlank(encodedValue)) {
|
if (StringUtils.isNotBlank(encodedValue)) {
|
||||||
theEventWriter.writeAttribute("value", encodedValue);
|
theEventWriter.writeAttribute("value", encodedValue);
|
||||||
}
|
}
|
||||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
|
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -251,7 +251,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
theEventWriter.writeAttribute("value", value);
|
theEventWriter.writeAttribute("value", value);
|
||||||
}
|
}
|
||||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
|
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -266,7 +266,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
if (isNotBlank(theExtensionUrl)) {
|
if (isNotBlank(theExtensionUrl)) {
|
||||||
theEventWriter.writeAttribute("url", theExtensionUrl);
|
theEventWriter.writeAttribute("url", theExtensionUrl);
|
||||||
}
|
}
|
||||||
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent);
|
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -280,7 +280,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
for (IBaseResource next : getContainedResources().getContainedResources()) {
|
for (IBaseResource next : getContainedResources().getContainedResources()) {
|
||||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||||
theEventWriter.writeStartElement("contained");
|
theEventWriter.writeStartElement("contained");
|
||||||
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
|
encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue()));
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
}
|
}
|
||||||
break;
|
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 {
|
throws XMLStreamException, DataFormatException {
|
||||||
|
|
||||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
|
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
|
||||||
|
|
||||||
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
||||||
|
|
||||||
|
@ -352,13 +352,13 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
||||||
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
||||||
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextChild instanceof RuntimeChildContainedResources) {
|
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 {
|
} else {
|
||||||
|
|
||||||
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
|
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
|
||||||
|
@ -382,7 +382,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
|
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
|
||||||
|
|
||||||
if (extensionUrl != null && childName.equals("extension") == false) {
|
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) {
|
} else if (nextChild instanceof RuntimeChildExtension) {
|
||||||
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
|
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
|
||||||
if ((extension.getValue() == null || extension.getValue().isEmpty())) {
|
if ((extension.getValue() == null || extension.getValue().isEmpty())) {
|
||||||
|
@ -390,18 +390,18 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
continue;
|
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) {
|
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
|
||||||
// suppress narratives from contained resources
|
// suppress narratives from contained resources
|
||||||
} else {
|
} 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 {
|
throws XMLStreamException {
|
||||||
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
|
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
|
||||||
if (extDef.isModifier()) {
|
if (extDef.isModifier()) {
|
||||||
|
@ -416,23 +416,23 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
theEventWriter.writeAttribute("url", extensionUrl);
|
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();
|
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) {
|
if (theElement instanceof ISupportsUndeclaredExtensions) {
|
||||||
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
|
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
|
||||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource);
|
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource);
|
||||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource);
|
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource);
|
||||||
}
|
}
|
||||||
if (theElement instanceof IBaseHasExtensions) {
|
if (theElement instanceof IBaseHasExtensions) {
|
||||||
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
|
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
|
||||||
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource);
|
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource);
|
||||||
}
|
}
|
||||||
if (theElement instanceof IBaseHasModifierExtensions) {
|
if (theElement instanceof IBaseHasModifierExtensions) {
|
||||||
IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
|
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 (!theIncludedResource) {
|
||||||
if (super.shouldEncodeResourceId(theResource) == false) {
|
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
|
||||||
resourceId = null;
|
resourceId = null;
|
||||||
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
|
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
|
||||||
resourceId = getEncodeForceResourceId();
|
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) {
|
if (!theContainedResource) {
|
||||||
super.containResourcesForEncoding(theResource);
|
super.containResourcesForEncoding(theResource);
|
||||||
}
|
}
|
||||||
|
@ -476,12 +476,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
writeCommentsPre(theEventWriter, theResourceId);
|
writeCommentsPre(theEventWriter, theResourceId);
|
||||||
theEventWriter.writeStartElement("id");
|
theEventWriter.writeStartElement("id");
|
||||||
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
||||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
|
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
writeCommentsPost(theEventWriter, theResourceId);
|
writeCommentsPost(theEventWriter, theResourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
|
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -494,7 +494,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
writeCommentsPost(theEventWriter, theResourceId);*/
|
writeCommentsPost(theEventWriter, theResourceId);*/
|
||||||
theEventWriter.writeStartElement("id");
|
theEventWriter.writeStartElement("id");
|
||||||
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
||||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
|
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
writeCommentsPost(theEventWriter, theResourceId);
|
writeCommentsPost(theEventWriter, theResourceId);
|
||||||
}
|
}
|
||||||
|
@ -525,7 +525,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
}
|
}
|
||||||
for (BaseCodingDt securityLabel : securityLabels) {
|
for (BaseCodingDt securityLabel : securityLabels) {
|
||||||
theEventWriter.writeStartElement("security");
|
theEventWriter.writeStartElement("security");
|
||||||
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null);
|
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
}
|
}
|
||||||
if (tags != null) {
|
if (tags != null) {
|
||||||
|
@ -548,7 +548,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
||||||
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
|
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
|
||||||
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
|
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
|
||||||
} else {
|
} 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();
|
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 {
|
throws XMLStreamException, DataFormatException {
|
||||||
for (IBaseExtension<?, ?> next : theExtensions) {
|
for (IBaseExtension<?, ?> next : theExtensions) {
|
||||||
if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
|
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());
|
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
|
// child extensions
|
||||||
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource);
|
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource);
|
||||||
|
|
||||||
theEventWriter.writeEndElement();
|
theEventWriter.writeEndElement();
|
||||||
|
|
||||||
|
|
|
@ -20,20 +20,22 @@ package ca.uhn.fhir.jpa.config;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
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.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
import org.springframework.beans.factory.annotation.Autowire;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
|
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
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.concurrent.ScheduledExecutorFactoryBean;
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.search.*;
|
import javax.annotation.Resource;
|
||||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
|
||||||
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
||||||
public class BaseConfig implements SchedulingConfigurer {
|
public class BaseConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ApplicationContext myAppCtx;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected Environment myEnv;
|
protected Environment myEnv;
|
||||||
|
@Resource
|
||||||
|
private ApplicationContext myAppCtx;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) {
|
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) {
|
||||||
|
@ -67,14 +66,19 @@ public class BaseConfig implements SchedulingConfigurer {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IGraphQLStorageServices jpaStorageServices() {
|
||||||
|
return new JpaStorageServices();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean()
|
@Bean()
|
||||||
public ScheduledExecutorFactoryBean scheduledExecutorService() {
|
public ScheduledExecutorFactoryBean scheduledExecutorService() {
|
||||||
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
|
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
|
||||||
b.setPoolSize(5);
|
b.setPoolSize(5);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(autowire=Autowire.BY_TYPE)
|
@Bean(autowire = Autowire.BY_TYPE)
|
||||||
public ISearchCoordinatorSvc searchCoordinatorSvc() {
|
public ISearchCoordinatorSvc searchCoordinatorSvc() {
|
||||||
return new SearchCoordinatorSvcImpl();
|
return new SearchCoordinatorSvcImpl();
|
||||||
}
|
}
|
||||||
|
@ -84,11 +88,32 @@ public class BaseConfig implements SchedulingConfigurer {
|
||||||
return new SearchParamPresenceSvcImpl();
|
return new SearchParamPresenceSvcImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(autowire=Autowire.BY_TYPE)
|
@Bean(autowire = Autowire.BY_TYPE)
|
||||||
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
|
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
|
||||||
return new StaleSearchDeletingSvcImpl();
|
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
|
@Bean
|
||||||
public TaskScheduler taskScheduler() {
|
public TaskScheduler taskScheduler() {
|
||||||
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
||||||
|
@ -99,15 +124,6 @@ public class BaseConfig implements SchedulingConfigurer {
|
||||||
// retVal.setPoolSize(5);
|
// retVal.setPoolSize(5);
|
||||||
// return retVal;
|
// 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
|
* This lets the "@Value" fields reference properties from the properties file
|
||||||
|
@ -117,9 +133,5 @@ public class BaseConfig implements SchedulingConfigurer {
|
||||||
return new PropertySourcesPlaceholderConfigurer();
|
return new PropertySourcesPlaceholderConfigurer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public IGraphQLStorageServices jpaStorageServices() {
|
|
||||||
return new JpaStorageServices();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
package ca.uhn.fhir.jpa.config;
|
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.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||||
import org.springframework.beans.factory.annotation.Autowire;
|
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;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -15,9 +26,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -26,14 +37,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
* #L%
|
* #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
|
@Configuration
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
public class BaseDstu2Config extends BaseConfig {
|
public class BaseDstu2Config extends BaseConfig {
|
||||||
|
@ -65,12 +68,6 @@ public class BaseDstu2Config extends BaseConfig {
|
||||||
return ourFhirContextDstu2Hl7Org;
|
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")
|
@Bean(name = "myInstanceValidatorDstu2")
|
||||||
@Lazy
|
@Lazy
|
||||||
public IValidatorModule instanceValidatorDstu2() {
|
public IValidatorModule instanceValidatorDstu2() {
|
||||||
|
@ -80,6 +77,12 @@ public class BaseDstu2Config extends BaseConfig {
|
||||||
return retVal;
|
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)
|
@Bean(autowire = Autowire.BY_TYPE)
|
||||||
public IFulltextSearchSvc searchDao() {
|
public IFulltextSearchSvc searchDao() {
|
||||||
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
|
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
|
||||||
|
@ -115,10 +118,4 @@ public class BaseDstu2Config extends BaseConfig {
|
||||||
return new HapiTerminologySvcDstu2();
|
return new HapiTerminologySvcDstu2();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() {
|
|
||||||
return new RestHookSubscriptionDstu2Interceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config;
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -20,38 +20,32 @@ package ca.uhn.fhir.jpa.config;
|
||||||
* #L%
|
* #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.beans.factory.annotation.Autowire;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||||
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSocket()
|
@EnableWebSocket()
|
||||||
public class WebsocketDstu2Config implements WebSocketConfigurer {
|
@Controller
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketDstu2Config.class);
|
public class WebsocketDispatcherConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
|
||||||
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2").setAllowedOrigins("*");
|
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket").setAllowedOrigins("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(autowire = Autowire.BY_TYPE)
|
@Bean(autowire = Autowire.BY_TYPE)
|
||||||
public WebSocketHandler subscriptionWebSocketHandler() {
|
public WebSocketHandler subscriptionWebSocketHandler() {
|
||||||
return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class);
|
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandler.class);
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public TaskScheduler websocketTaskScheduler() {
|
|
||||||
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
|
|
||||||
retVal.setPoolSize(5);
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,36 +1,5 @@
|
||||||
package ca.uhn.fhir.jpa.config.dstu3;
|
package ca.uhn.fhir.jpa.config.dstu3;
|
||||||
|
|
||||||
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
|
|
||||||
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
|
|
||||||
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* #%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.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.FhirContext;
|
||||||
import ca.uhn.fhir.context.ParserOptions;
|
import ca.uhn.fhir.context.ParserOptions;
|
||||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||||
|
@ -40,7 +9,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
|
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
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.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
||||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||||
|
@ -48,6 +16,35 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
|
||||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
|
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
|
||||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||||
import ca.uhn.fhir.validation.IValidatorModule;
|
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
|
||||||
|
* 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%
|
||||||
|
*/
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
|
@ -133,10 +130,4 @@ public class BaseDstu3Config extends BaseConfig {
|
||||||
return new JpaValidationSupportChainDstu3();
|
return new JpaValidationSupportChainDstu3();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() {
|
|
||||||
return new RestHookSubscriptionDstu3Interceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,9 +1,31 @@
|
||||||
package ca.uhn.fhir.jpa.config.r4;
|
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.ctx.IValidationSupport;
|
||||||
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
|
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
|
||||||
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
|
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
|
||||||
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
|
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
|
* #%L
|
||||||
|
@ -14,9 +36,9 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -25,23 +47,6 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||||
* #L%
|
* #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
|
@Configuration
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
public class BaseR4Config extends BaseConfig {
|
public class BaseR4Config extends BaseConfig {
|
||||||
|
@ -131,11 +136,5 @@ public class BaseR4Config extends BaseConfig {
|
||||||
public IValidationSupport validationSupportChainR4() {
|
public IValidationSupport validationSupportChainR4() {
|
||||||
return new JpaValidationSupportChainR4();
|
return new JpaValidationSupportChainR4();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Lazy
|
|
||||||
public RestHookSubscriptionR4Interceptor restHookSubscriptionR4Interceptor() {
|
|
||||||
return new RestHookSubscriptionR4Interceptor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -123,8 +123,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
|
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
|
||||||
|
|
||||||
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
|
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
|
||||||
excludeElementsInEncoded.add("*.id");
|
excludeElementsInEncoded.add("id");
|
||||||
excludeElementsInEncoded.add("*.meta");
|
excludeElementsInEncoded.add("meta");
|
||||||
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,49 +20,27 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||||
import java.util.*;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||||
import javax.persistence.Query;
|
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||||
|
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
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 ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import java.util.Date;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
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;
|
|
||||||
|
|
||||||
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
|
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||||
|
|
||||||
|
@ -73,9 +51,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||||
subscriptionEntity.setCreated(new Date());
|
subscriptionEntity.setCreated(new Date());
|
||||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValue());
|
|
||||||
myEntityManager.persist(subscriptionEntity);
|
myEntityManager.persist(subscriptionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,145 +64,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||||
return table.getId();
|
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
|
@Override
|
||||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||||
|
@ -236,69 +72,14 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||||
createSubscriptionTable(theEntity, theSubscription);
|
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
|
@Override
|
||||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||||
|
|
||||||
Subscription resource = (Subscription) theResource;
|
|
||||||
Long resourceId = theEntity.getId();
|
|
||||||
if (theDeletedTimestampOrNull != null) {
|
if (theDeletedTimestampOrNull != null) {
|
||||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
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
|
* #%L
|
||||||
|
@ -11,9 +12,9 @@ import java.util.List;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -22,20 +23,8 @@ import java.util.List;
|
||||||
* #L%
|
* #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> {
|
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
|
||||||
|
|
||||||
int pollForNewUndeliveredResources();
|
|
||||||
|
|
||||||
List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid);
|
|
||||||
|
|
||||||
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId);
|
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId);
|
||||||
|
|
||||||
void purgeInactiveSubscriptions();
|
|
||||||
|
|
||||||
void pollForNewUndeliveredResourcesScheduler();
|
|
||||||
|
|
||||||
int pollForNewUndeliveredResources(String resourceType);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
|
@ -20,32 +20,20 @@ package ca.uhn.fhir.jpa.dao.data;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.Collection;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
import java.util.Date;
|
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Modifying;
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
|
||||||
|
|
||||||
public interface ISubscriptionTableDao extends JpaRepository<SubscriptionTable, Long> {
|
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")
|
@Query("SELECT t FROM SubscriptionTable t WHERE t.myResId = :pid")
|
||||||
SubscriptionTable findOneByResourcePid(@Param("pid") Long theId);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
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.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
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.dao.data.ISubscriptionTableDao;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
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.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.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
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 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;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.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.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
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);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu3.class);
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||||
|
|
||||||
|
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||||
subscriptionEntity.setCreated(new Date());
|
subscriptionEntity.setCreated(new Date());
|
||||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
|
|
||||||
myEntityManager.persist(subscriptionEntity);
|
myEntityManager.persist(subscriptionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,147 +68,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||||
return table.getId();
|
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
|
@Override
|
||||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||||
|
@ -252,69 +78,15 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||||
createSubscriptionTable(theEntity, theSubscription);
|
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
|
@Override
|
||||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||||
|
|
||||||
Subscription resource = (Subscription) theResource;
|
|
||||||
Long resourceId = theEntity.getId();
|
|
||||||
if (theDeletedTimestampOrNull != null) {
|
if (theDeletedTimestampOrNull != null) {
|
||||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.r4;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
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.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
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.dao.data.ISubscriptionTableDao;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
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.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.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
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 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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.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.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
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);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionR4.class);
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||||
|
|
||||||
|
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
||||||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||||
subscriptionEntity.setCreated(new Date());
|
subscriptionEntity.setCreated(new Date());
|
||||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
|
||||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
|
|
||||||
myEntityManager.persist(subscriptionEntity);
|
myEntityManager.persist(subscriptionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,146 +68,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
||||||
return table.getId();
|
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
|
@Override
|
||||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||||
|
@ -251,70 +76,19 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
||||||
createSubscriptionTable(theEntity, theSubscription);
|
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
|
@Override
|
||||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||||
|
|
||||||
Subscription resource = (Subscription) theResource;
|
|
||||||
Long resourceId = theEntity.getId();
|
|
||||||
if (theDeletedTimestampOrNull != null) {
|
if (theDeletedTimestampOrNull != null) {
|
||||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
||||||
if (subscriptionId != null) {
|
if (subscriptionId != null) {
|
||||||
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
|
mySubscriptionTableDao.deleteAllForSubscription(retVal);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -20,140 +20,56 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.Collection;
|
import javax.persistence.*;
|
||||||
import java.util.Date;
|
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
|
@Entity
|
||||||
@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= {
|
@Table(name = "HFJ_SUBSCRIPTION_STATS", uniqueConstraints = {
|
||||||
@UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }),
|
@UniqueConstraint(name = "IDX_SUBSC_RESID", columnNames = {"RES_ID"}),
|
||||||
@UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" })
|
|
||||||
})
|
})
|
||||||
@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 {
|
public class SubscriptionTable {
|
||||||
|
|
||||||
@Column(name = "CHECK_INTERVAL", nullable = false)
|
|
||||||
private long myCheckInterval;
|
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "CREATED_TIME", nullable = false, insertable = true, updatable = false)
|
@Column(name = "CREATED_TIME", nullable = false, insertable = true, updatable = false)
|
||||||
private Date myCreated;
|
private Date myCreated;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "mySubscription")
|
|
||||||
private Collection<SubscriptionFlaggedResource> myFlaggedResources;
|
|
||||||
|
|
||||||
@Id
|
@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")
|
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID")
|
||||||
@Column(name = "PID", insertable = false, updatable = false)
|
@Column(name = "PID", insertable = false, updatable = false)
|
||||||
private Long myId;
|
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)
|
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||||
private Long myResId;
|
private Long myResId;
|
||||||
|
|
||||||
@Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20)
|
|
||||||
private String myStatus;
|
|
||||||
|
|
||||||
@OneToOne()
|
@OneToOne()
|
||||||
@JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID",
|
@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;
|
private ResourceTable mySubscriptionResource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public SubscriptionTable(){
|
public SubscriptionTable() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getCheckInterval() {
|
|
||||||
return myCheckInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getCreated() {
|
public Date getCreated() {
|
||||||
return myCreated;
|
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) {
|
public void setCreated(Date theCreated) {
|
||||||
myCreated = theCreated;
|
myCreated = theCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastClientPoll(Date theLastClientPoll) {
|
public Long getId() {
|
||||||
myLastClientPoll = theLastClientPoll;
|
return myId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMostRecentMatch(Date theMostRecentMatch) {
|
public ResourceTable getSubscriptionResource() {
|
||||||
myMostRecentMatch = theMostRecentMatch;
|
return mySubscriptionResource;
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextCheck(Date theNextCheck) {
|
|
||||||
myNextCheck = theNextCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String theStatus) {
|
|
||||||
myStatus = theStatus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubscriptionResource(ResourceTable theSubscriptionResource) {
|
public void setSubscriptionResource(ResourceTable theSubscriptionResource) {
|
||||||
|
|
|
@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
* #L%
|
* #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.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
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.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
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 ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Subscription;
|
import org.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.MessageHandler;
|
||||||
import org.springframework.messaging.SubscribableChannel;
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||||
import org.springframework.messaging.support.GenericMessage;
|
import org.springframework.messaging.support.GenericMessage;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
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.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
|
@ -70,6 +77,14 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
private ThreadPoolExecutor myDeliveryExecutor;
|
private ThreadPoolExecutor myDeliveryExecutor;
|
||||||
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
||||||
private LinkedBlockingQueue<Runnable> myDeliveryExecutorQueue;
|
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
|
* Constructor
|
||||||
|
@ -79,7 +94,83 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
setExecutorThreadCount(5);
|
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();
|
public abstract Subscription.SubscriptionChannelType getChannelType();
|
||||||
|
|
||||||
|
@ -116,7 +207,9 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
myProcessingChannel = theProcessingChannel;
|
myProcessingChannel = theProcessingChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IFhirResourceDao<?> getSubscriptionDao();
|
protected IFhirResourceDao<?> getSubscriptionDao() {
|
||||||
|
return mySubscriptionDao;
|
||||||
|
}
|
||||||
|
|
||||||
public List<CanonicalSubscription> getSubscriptions() {
|
public List<CanonicalSubscription> getSubscriptions() {
|
||||||
return new ArrayList<>(myIdToSubscription.values());
|
return new ArrayList<>(myIdToSubscription.values());
|
||||||
|
@ -243,13 +336,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
initSubscriptions();
|
initSubscriptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void registerSubscriptionCheckingSubscriber() {
|
|
||||||
if (mySubscriptionCheckingSubscriber == null) {
|
|
||||||
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
|
|
||||||
}
|
|
||||||
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void preDestroy() {
|
public void preDestroy() {
|
||||||
|
@ -268,6 +354,13 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
myIdToSubscription.put(theId.getIdPart(), canonicalize(theSubscription));
|
myIdToSubscription.put(theId.getIdPart(), canonicalize(theSubscription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void registerSubscriptionCheckingSubscriber() {
|
||||||
|
if (mySubscriptionCheckingSubscriber == null) {
|
||||||
|
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
|
||||||
|
}
|
||||||
|
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||||
|
@ -294,6 +387,20 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
submitResourceModified(msg);
|
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) {
|
protected void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||||
final GenericMessage<ResourceModifiedMessage> message = new GenericMessage<>(theMsg);
|
final GenericMessage<ResourceModifiedMessage> message = new GenericMessage<>(theMsg);
|
||||||
mySubscriptionActivatingSubscriber.handleMessage(message);
|
mySubscriptionActivatingSubscriber.handleMessage(message);
|
||||||
|
@ -308,4 +415,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
|
|
||||||
myIdToSubscription.remove(theId.getIdPart());
|
myIdToSubscription.remove(theId.getIdPart());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,14 +29,14 @@ import org.springframework.messaging.MessageHandler;
|
||||||
|
|
||||||
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
||||||
|
|
||||||
private final IFhirResourceDao mySubscriptionDao;
|
private final IFhirResourceDao<?> mySubscriptionDao;
|
||||||
private final Subscription.SubscriptionChannelType myChannelType;
|
private final Subscription.SubscriptionChannelType myChannelType;
|
||||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public BaseSubscriptionSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
public BaseSubscriptionSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||||
mySubscriptionDao = theSubscriptionDao;
|
mySubscriptionDao = theSubscriptionDao;
|
||||||
myChannelType = theChannelType;
|
myChannelType = theChannelType;
|
||||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
||||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
* #L%
|
* #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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
@ -54,6 +56,19 @@ public class CanonicalSubscription implements Serializable {
|
||||||
myTrigger = theTrigger;
|
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() {
|
public IBaseResource getBackingSubscription() {
|
||||||
return myBackingSubscription;
|
return myBackingSubscription;
|
||||||
}
|
}
|
||||||
|
@ -90,10 +105,12 @@ public class CanonicalSubscription implements Serializable {
|
||||||
return myHeaders;
|
return myHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeaders(String theHeaders) {
|
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
||||||
myHeaders = new ArrayList<>();
|
myHeaders = new ArrayList<>();
|
||||||
if (isNotBlank(theHeaders)) {
|
for (IPrimitiveType<String> next : theHeader) {
|
||||||
myHeaders.add(theHeaders);
|
if (isNotBlank(next.getValueAsString())) {
|
||||||
|
myHeaders.add(next.getValueAsString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,12 +146,17 @@ public class CanonicalSubscription implements Serializable {
|
||||||
return myTrigger;
|
return myTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return new HashCodeBuilder(17, 37)
|
||||||
|
.append(getIdElement().getIdPart())
|
||||||
|
.toHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeaders(String theHeaders) {
|
||||||
myHeaders = new ArrayList<>();
|
myHeaders = new ArrayList<>();
|
||||||
for (IPrimitiveType<String> next : theHeader) {
|
if (isNotBlank(theHeaders)) {
|
||||||
if (isNotBlank(next.getValueAsString())) {
|
myHeaders.add(theHeaders);
|
||||||
myHeaders.add(next.getValueAsString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -21,6 +21,10 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
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.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
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.hl7.fhir.r4.model.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -38,7 +41,6 @@ import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -20,7 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||||
|
|
||||||
|
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
||||||
private SubscriptionDeliveringRestHookSubscriber mySubscriptionDeliverySubscriber;
|
private SubscriptionDeliveringRestHookSubscriber mySubscriptionDeliverySubscriber;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,6 +33,10 @@ public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscripti
|
||||||
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
|
||||||
|
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void unregisterDeliverySubscriber() {
|
protected void unregisterDeliverySubscriber() {
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
|
@ -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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.subscription;
|
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%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.IResourceTableDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
||||||
private SubscriptionDeliveringWebsocketSubscriber mySubscriptionDeliverySubscriber;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||||
|
@ -42,16 +39,25 @@ public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscript
|
||||||
private IResourceTableDao myResourceTableDao;
|
private IResourceTableDao myResourceTableDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void registerDeliverySubscriber() {
|
public Subscription.SubscriptionChannelType getChannelType() {
|
||||||
if (mySubscriptionDeliverySubscriber == null) {
|
return Subscription.SubscriptionChannelType.WEBSOCKET;
|
||||||
mySubscriptionDeliverySubscriber = new SubscriptionDeliveringWebsocketSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, mySubscriptionFlaggedResourceDataDao, mySubscriptionTableDao, myResourceTableDao);
|
|
||||||
}
|
|
||||||
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void registerDeliverySubscriber() {
|
||||||
|
/*
|
||||||
|
* nothing, since individual websocket connections
|
||||||
|
* register themselves
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void unregisterDeliverySubscriber() {
|
protected void unregisterDeliverySubscriber() {
|
||||||
getDeliveryChannel().unsubscribe(mySubscriptionDeliverySubscriber);
|
|
||||||
|
/*
|
||||||
|
* nothing, since individual websocket connections
|
||||||
|
* register themselves
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -226,8 +226,6 @@ public abstract class BaseJpaTest {
|
||||||
public Void doInTransaction(TransactionStatus theStatus) {
|
public Void doInTransaction(TransactionStatus theStatus) {
|
||||||
entityManager.createQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d").executeUpdate();
|
entityManager.createQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d").executeUpdate();
|
||||||
entityManager.createQuery("DELETE from " + SearchParam.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 " + ForcedId.class.getSimpleName() + " d").executeUpdate();
|
||||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
|
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
|
||||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();
|
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
package ca.uhn.fhir.jpa.dao.dstu2;
|
package ca.uhn.fhir.jpa.dao.dstu2;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import static org.mockito.Mockito.mock;
|
import ca.uhn.fhir.jpa.config.TestDstu2Config;
|
||||||
|
import ca.uhn.fhir.jpa.dao.*;
|
||||||
import java.io.IOException;
|
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||||
import java.io.InputStream;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||||
import javax.persistence.EntityManager;
|
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.apache.commons.io.IOUtils;
|
||||||
import org.hibernate.search.jpa.FullTextEntityManager;
|
import org.hibernate.search.jpa.FullTextEntityManager;
|
||||||
import org.hibernate.search.jpa.Search;
|
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.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import javax.persistence.EntityManager;
|
||||||
import ca.uhn.fhir.jpa.config.TestDstu2Config;
|
import java.io.IOException;
|
||||||
import ca.uhn.fhir.jpa.dao.*;
|
import java.io.InputStream;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import static org.junit.Assert.*;
|
||||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
import static org.mockito.Mockito.mock;
|
||||||
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;
|
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class})
|
@ContextConfiguration(classes= {TestDstu2Config.class})
|
||||||
//@formatter:on
|
|
||||||
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ApplicationContext myAppCtx;
|
protected ApplicationContext myAppCtx;
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +1,9 @@
|
||||||
package ca.uhn.fhir.jpa.provider;
|
package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||||
|
|
||||||
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.dao.dstu2.BaseJpaDstu2Test;
|
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.search.DatabaseBackedPagingProvider;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.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.client.interceptor.LoggingInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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 {
|
public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
||||||
protected static Server ourServer;
|
protected static Server ourServer;
|
||||||
protected static String ourServerBase;
|
protected static String ourServerBase;
|
||||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||||
protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor;
|
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||||
|
|
||||||
public BaseResourceProviderDstu2Test() {
|
public BaseResourceProviderDstu2Test() {
|
||||||
|
@ -97,7 +97,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
||||||
ourWebApplicationContext.setParent(myAppCtx);
|
ourWebApplicationContext.setParent(myAppCtx);
|
||||||
ourWebApplicationContext.refresh();
|
ourWebApplicationContext.refresh();
|
||||||
|
|
||||||
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(RestHookSubscriptionDstu2Interceptor.class);
|
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||||
|
|
||||||
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
|
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
|
||||||
|
|
||||||
|
@ -107,8 +107,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
||||||
subsServletHolder.setServlet(dispatcherServlet);
|
subsServletHolder.setServlet(dispatcherServlet);
|
||||||
subsServletHolder.setInitParameter(
|
subsServletHolder.setInitParameter(
|
||||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||||
WebsocketDstu2Config.class.getName() + "\n" +
|
WebsocketDispatcherConfig.class.getName());
|
||||||
WebsocketDstu2DispatcherConfig.class.getName());
|
|
||||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,79 +1,32 @@
|
||||||
package ca.uhn.fhir.jpa.provider;
|
package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||||
import static org.junit.Assert.*;
|
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||||
|
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||||
import java.net.URI;
|
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||||
import java.util.*;
|
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.Session;
|
||||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
import java.util.ArrayList;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import java.util.List;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.*;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import static org.junit.Assert.*;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
|
||||||
|
|
||||||
public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
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);
|
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
|
@Override
|
||||||
public void beforeCreateInterceptor() {
|
public void beforeCreateInterceptor() {
|
||||||
super.beforeCreateInterceptor();
|
super.beforeCreateInterceptor();
|
||||||
|
@ -83,46 +36,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
myDaoConfig.getInterceptors().add(interceptor);
|
myDaoConfig.getInterceptors().add(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
|
@Before
|
||||||
|
public void beforeDisableResultReuse() {
|
||||||
/*
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeEnableScheduling() {
|
||||||
|
myDaoConfig.setSchedulingDisabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateInvalidNoStatus() {
|
public void testCreateInvalidNoStatus() {
|
||||||
Subscription subs = new Subscription();
|
Subscription subs = new Subscription();
|
||||||
|
@ -147,6 +71,75 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
ourClient.update().resource(subs).execute();
|
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
|
@Test
|
||||||
public void testUpdateToInvalidStatus() {
|
public void testUpdateToInvalidStatus() {
|
||||||
Subscription subs = new Subscription();
|
Subscription subs = new Subscription();
|
||||||
|
@ -176,262 +169,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
ourClient.update().resource(subs).execute();
|
ourClient.update().resource(subs).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@AfterClass
|
||||||
public void testCreateWithPopulatedButInvalidStatue() {
|
public static void afterClassClearContext() {
|
||||||
Subscription subs = new Subscription();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
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 class BaseSocket {
|
||||||
public void testCreateInvalidWrongStatus() {
|
protected String myError;
|
||||||
Subscription subs = new Subscription();
|
protected boolean myGotBound;
|
||||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
protected int myPingCount;
|
||||||
subs.getChannel().setPayload("application/fhir+json");
|
protected String mySubsId;
|
||||||
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 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,34 +1,12 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||||
|
|
||||||
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.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
|
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
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.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
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.jpa.validation.JpaValidationSupportChainDstu3;
|
||||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
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.rest.server.interceptor.CorsInterceptor;
|
||||||
import ca.uhn.fhir.util.PortUtil;
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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 {
|
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
|
|
||||||
|
@ -49,11 +53,11 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
protected static RestfulServer ourRestServer;
|
protected static RestfulServer ourRestServer;
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
protected static String ourServerBase;
|
protected static String ourServerBase;
|
||||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||||
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
|
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
|
||||||
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
|
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
|
||||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||||
protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor;
|
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||||
protected static ISearchDao mySearchEntityDao;
|
protected static ISearchDao mySearchEntityDao;
|
||||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
|
|
||||||
|
@ -119,8 +123,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
subsServletHolder.setServlet(dispatcherServlet);
|
subsServletHolder.setServlet(dispatcherServlet);
|
||||||
subsServletHolder.setInitParameter(
|
subsServletHolder.setInitParameter(
|
||||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||||
WebsocketDstu3Config.class.getName() + "\n" +
|
WebsocketDispatcherConfig.class.getName());
|
||||||
WebsocketDstu3DispatcherConfig.class.getName());
|
|
||||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||||
|
|
||||||
// Register a CORS filter
|
// Register a CORS filter
|
||||||
|
@ -146,7 +149,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||||
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
|
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
|
||||||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
||||||
ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionDstu3Interceptor.class);
|
ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
|
||||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
|
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
|
||||||
|
|
||||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
|
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
package ca.uhn.fhir.jpa.provider.dstu3;
|
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||||
import static org.junit.Assert.*;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import java.net.URI;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
import org.hl7.fhir.dstu3.model.*;
|
import org.hl7.fhir.dstu3.model.Subscription;
|
||||||
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
|
|
||||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.*;
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
import java.util.ArrayList;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import java.util.List;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
|
|
||||||
|
@ -31,12 +29,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
|
|
||||||
private static final String WEBSOCKET_PATH = "/websocket/dstu3";
|
private static final String WEBSOCKET_PATH = "/websocket/dstu3";
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeDisableResultReuse() {
|
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeCreateInterceptor() {
|
public void beforeCreateInterceptor() {
|
||||||
super.beforeCreateInterceptor();
|
super.beforeCreateInterceptor();
|
||||||
|
@ -46,72 +38,17 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
myDaoConfig.getInterceptors().add(interceptor);
|
myDaoConfig.getInterceptors().add(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeDisableResultReuse() {
|
||||||
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeEnableScheduling() {
|
public void beforeEnableScheduling() {
|
||||||
myDaoConfig.setSchedulingDisabled(false);
|
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
|
@Test
|
||||||
public void testCreateInvalidNoStatus() {
|
public void testCreateInvalidNoStatus() {
|
||||||
Subscription subs = new Subscription();
|
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
|
@Test
|
||||||
public void testUpdateFails() {
|
public void testUpdateFails() {
|
||||||
|
@ -384,7 +131,7 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
subs.setStatus(SubscriptionStatus.OFF);
|
subs.setStatus(SubscriptionStatus.OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateToInvalidStatus() {
|
public void testUpdateToInvalidStatus() {
|
||||||
Subscription subs = new Subscription();
|
Subscription subs = new Subscription();
|
||||||
|
@ -415,7 +162,7 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
subs.setStatus(SubscriptionStatus.OFF);
|
subs.setStatus(SubscriptionStatus.OFF);
|
||||||
ourClient.update().resource(subs).execute();
|
ourClient.update().resource(subs).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4Config;
|
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
|
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
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.jpa.validation.JpaValidationSupportChainR4;
|
||||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
|
@ -58,7 +57,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
||||||
protected static ISearchDao mySearchEntityDao;
|
protected static ISearchDao mySearchEntityDao;
|
||||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||||
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
|
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
|
||||||
private Object ourGraphQLProvider;
|
private Object ourGraphQLProvider;
|
||||||
private boolean ourRestHookSubscriptionInterceptorRequested;
|
private boolean ourRestHookSubscriptionInterceptorRequested;
|
||||||
|
@ -122,8 +121,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
||||||
subsServletHolder.setServlet(dispatcherServlet);
|
subsServletHolder.setServlet(dispatcherServlet);
|
||||||
subsServletHolder.setInitParameter(
|
subsServletHolder.setInitParameter(
|
||||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||||
WebsocketR4Config.class.getName() + "\n" +
|
WebsocketDispatcherConfig.class.getName());
|
||||||
WebsocketR4DispatcherConfig.class.getName());
|
|
||||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||||
|
|
||||||
// Register a CORS filter
|
// 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
|
* This is lazy created so we only ask for it if its needed
|
||||||
*/
|
*/
|
||||||
protected RestHookSubscriptionR4Interceptor getRestHookSubscriptionInterceptor() {
|
protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() {
|
||||||
RestHookSubscriptionR4Interceptor retVal = ourWebApplicationContext.getBean(RestHookSubscriptionR4Interceptor.class);
|
SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||||
ourRestHookSubscriptionInterceptorRequested = true;
|
ourRestHookSubscriptionInterceptorRequested = true;
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.jpa.util.SubscriptionsRequireManualActivationInterceptorR4;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import org.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 {
|
public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
|
@ -31,12 +29,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
private static final String WEBSOCKET_PATH = "/websocket/r4";
|
private static final String WEBSOCKET_PATH = "/websocket/r4";
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeDisableResultReuse() {
|
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeCreateInterceptor() {
|
public void beforeCreateInterceptor() {
|
||||||
super.beforeCreateInterceptor();
|
super.beforeCreateInterceptor();
|
||||||
|
@ -46,72 +38,17 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||||
myDaoConfig.getInterceptors().add(interceptor);
|
myDaoConfig.getInterceptors().add(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeDisableResultReuse() {
|
||||||
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeEnableScheduling() {
|
public void beforeEnableScheduling() {
|
||||||
myDaoConfig.setSchedulingDisabled(false);
|
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
|
@Test
|
||||||
public void testCreateInvalidNoStatus() {
|
public void testCreateInvalidNoStatus() {
|
||||||
Subscription subs = new Subscription();
|
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
|
@Test
|
||||||
|
@ -384,7 +132,7 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||||
subs.setStatus(SubscriptionStatus.OFF);
|
subs.setStatus(SubscriptionStatus.OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateToInvalidStatus() {
|
public void testUpdateToInvalidStatus() {
|
||||||
Subscription subs = new Subscription();
|
Subscription subs = new Subscription();
|
||||||
|
@ -415,7 +163,7 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||||
subs.setStatus(SubscriptionStatus.OFF);
|
subs.setStatus(SubscriptionStatus.OFF);
|
||||||
ourClient.update().resource(subs).execute();
|
ourClient.update().resource(subs).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.subscription;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
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.CodeableConceptDt;
|
||||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||||
|
@ -32,8 +31,7 @@ import org.junit.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the rest-hook subscriptions
|
* Test the rest-hook subscriptions
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
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.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
|
||||||
/**
|
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
@Ignore
|
||||||
* subscription
|
public class WebsocketWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
* <p>
|
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu2Test.class);
|
||||||
* 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);
|
|
||||||
|
|
||||||
private String myPatientId;
|
private String myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -128,14 +116,9 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
|
||||||
observation.setId(observationId);
|
observation.setId(observationId);
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + observationId);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,13 +140,8 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
|
||||||
observation.setId(observationId);
|
observation.setId(observationId);
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + 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);
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
|
|
||||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
|
@ -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.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
|
||||||
/**
|
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
@Ignore
|
||||||
* subscription
|
public class WebsocketWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
* <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 {
|
|
||||||
|
|
||||||
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 myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -127,13 +112,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + observationId);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,14 +135,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
|
||||||
observation.setId(observationId);
|
observation.setId(observationId);
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + 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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
@ -40,8 +41,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
* 4. Execute the 'sendObservation' test
|
* 4. Execute the 'sendObservation' test
|
||||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||||
*/
|
*/
|
||||||
public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
|
public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
|
||||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2Test.class);
|
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu2Test.class);
|
||||||
|
|
||||||
private String myPatientId;
|
private String myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -53,15 +54,21 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
||||||
super.after();
|
super.after();
|
||||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.unregisterInterceptor(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
|
|
||||||
myDaoConfig.setSubscriptionEnabled(true);
|
myDaoConfig.setSubscriptionEnabled(true);
|
||||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.registerInterceptor(interceptor);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create patient
|
* Create patient
|
||||||
*/
|
*/
|
||||||
|
@ -95,7 +102,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
||||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||||
|
|
||||||
myWebSocketClient.start();
|
myWebSocketClient.start();
|
||||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
ourLog.info("Connecting to : {}", echoUri);
|
ourLog.info("Connecting to : {}", echoUri);
|
||||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||||
|
@ -128,14 +135,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
||||||
observation.setId(observationId);
|
observation.setId(observationId);
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + observationId);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
@ -37,9 +38,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
* 6. Execute the 'sendObservation' test
|
* 6. Execute the 'sendObservation' test
|
||||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
* 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 myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -51,12 +52,19 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
||||||
super.after();
|
super.after();
|
||||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.unregisterInterceptor(interceptor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.registerInterceptor(interceptor);
|
||||||
|
|
||||||
myDaoConfig.setSubscriptionEnabled(true);
|
myDaoConfig.setSubscriptionEnabled(true);
|
||||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||||
|
|
||||||
|
@ -93,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
||||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||||
|
|
||||||
myWebSocketClient.start();
|
myWebSocketClient.start();
|
||||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3");
|
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
ourLog.info("Connecting to : {}", echoUri);
|
ourLog.info("Connecting to : {}", echoUri);
|
||||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.subscription.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
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
|
* 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
|
* 6. Execute the 'sendObservation' test
|
||||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
* 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<Observation> ourUpdatedObservations = Lists.newArrayList();
|
||||||
private static List<String> ourContentTypes = new ArrayList<>();
|
private static List<String> ourContentTypes = new ArrayList<>();
|
||||||
private static List<String> ourHeaders = new ArrayList<>();
|
private static List<String> ourHeaders = new ArrayList<>();
|
||||||
|
@ -80,6 +79,7 @@ public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testSubscriptionAddedTrigger() {
|
public void testSubscriptionAddedTrigger() {
|
||||||
/*
|
/*
|
||||||
* Create patient
|
* Create patient
|
|
@ -21,26 +21,12 @@ import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
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 myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -130,13 +116,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te
|
||||||
|
|
||||||
ourLog.info("Observation id generated by server is: " + observationId);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
@ -38,9 +39,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
* 6. Execute the 'sendObservation' test
|
* 6. Execute the 'sendObservation' test
|
||||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
* 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 myPatientId;
|
||||||
private String mySubscriptionId;
|
private String mySubscriptionId;
|
||||||
|
@ -52,6 +53,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
||||||
super.after();
|
super.after();
|
||||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.unregisterInterceptor(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -60,7 +64,10 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
||||||
|
|
||||||
myDaoConfig.setSubscriptionEnabled(true);
|
myDaoConfig.setSubscriptionEnabled(true);
|
||||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||||
|
|
||||||
|
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||||
|
ourRestServer.registerInterceptor(interceptor);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create patient
|
* Create patient
|
||||||
*/
|
*/
|
||||||
|
@ -94,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
||||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||||
|
|
||||||
myWebSocketClient.start();
|
myWebSocketClient.start();
|
||||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4");
|
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
ourLog.info("Connecting to : {}", echoUri);
|
ourLog.info("Connecting to : {}", echoUri);
|
||||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
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);
|
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());
|
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||||
|
waitForSize(2, mySocketImplementation.getMessages());
|
||||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,35 +1,42 @@
|
||||||
package ca.uhn.fhirtest;
|
package ca.uhn.fhirtest;
|
||||||
|
|
||||||
import java.util.*;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||||
import javax.servlet.ServletContext;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import javax.servlet.ServletException;
|
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.JpaConformanceProviderR4;
|
||||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.context.ContextLoaderListener;
|
import org.springframework.web.context.ContextLoaderListener;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import javax.servlet.ServletContext;
|
||||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
|
import javax.servlet.ServletException;
|
||||||
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
|
import java.util.ArrayList;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import java.util.Arrays;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
import java.util.Collection;
|
||||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
import java.util.List;
|
||||||
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.*;
|
|
||||||
|
|
||||||
public class TestRestfulServer extends RestfulServer {
|
public class TestRestfulServer extends RestfulServer {
|
||||||
|
|
||||||
|
@ -80,7 +87,7 @@ public class TestRestfulServer extends RestfulServer {
|
||||||
myAppCtx.register(TdlDstu2Config.class);
|
myAppCtx.register(TdlDstu2Config.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_TDL2;
|
baseUrlProperty = FHIR_BASEURL_TDL2;
|
||||||
} else {
|
} else {
|
||||||
myAppCtx.register(TestDstu2Config.class, WebsocketDstu2DispatcherConfig.class);
|
myAppCtx.register(TestDstu2Config.class, WebsocketDispatcherConfig.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_DSTU2;
|
baseUrlProperty = FHIR_BASEURL_DSTU2;
|
||||||
}
|
}
|
||||||
myAppCtx.refresh();
|
myAppCtx.refresh();
|
||||||
|
@ -103,7 +110,7 @@ public class TestRestfulServer extends RestfulServer {
|
||||||
myAppCtx.register(TdlDstu3Config.class);
|
myAppCtx.register(TdlDstu3Config.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_TDL3;
|
baseUrlProperty = FHIR_BASEURL_TDL3;
|
||||||
} else {
|
} else {
|
||||||
myAppCtx.register(TestDstu3Config.class, WebsocketDstu3DispatcherConfig.class);
|
myAppCtx.register(TestDstu3Config.class, WebsocketDispatcherConfig.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_DSTU3;
|
baseUrlProperty = FHIR_BASEURL_DSTU3;
|
||||||
}
|
}
|
||||||
myAppCtx.refresh();
|
myAppCtx.refresh();
|
||||||
|
@ -122,7 +129,7 @@ public class TestRestfulServer extends RestfulServer {
|
||||||
myAppCtx = new AnnotationConfigWebApplicationContext();
|
myAppCtx = new AnnotationConfigWebApplicationContext();
|
||||||
myAppCtx.setServletConfig(getServletConfig());
|
myAppCtx.setServletConfig(getServletConfig());
|
||||||
myAppCtx.setParent(parentAppCtx);
|
myAppCtx.setParent(parentAppCtx);
|
||||||
myAppCtx.register(TestR4Config.class, WebsocketR4DispatcherConfig.class);
|
myAppCtx.register(TestR4Config.class, WebsocketDispatcherConfig.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_R4;
|
baseUrlProperty = FHIR_BASEURL_R4;
|
||||||
myAppCtx.refresh();
|
myAppCtx.refresh();
|
||||||
setFhirContext(FhirContext.forR4());
|
setFhirContext(FhirContext.forR4());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -370,6 +370,11 @@
|
||||||
resource was deleted, it was not possible to create a new resource
|
resource was deleted, it was not possible to create a new resource
|
||||||
with the same URI as the previous one
|
with the same URI as the previous one
|
||||||
</action>
|
</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>
|
||||||
<release version="2.5" date="2017-06-08">
|
<release version="2.5" date="2017-06-08">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
Loading…
Reference in New Issue