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.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
|
||||
|
@ -37,6 +27,16 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseParser implements IParser {
|
||||
|
||||
|
@ -71,7 +71,7 @@ public abstract class BaseParser implements IParser {
|
|||
myErrorHandler = theParserErrorHandler;
|
||||
}
|
||||
|
||||
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent) {
|
||||
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) {
|
||||
|
||||
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
|
||||
final List<BaseRuntimeChildDefinition> children = elementDef.getChildrenAndExtension();
|
||||
|
@ -87,8 +87,7 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
{
|
||||
*/ {
|
||||
myChildrenIter = children.iterator();
|
||||
}
|
||||
|
||||
|
@ -105,7 +104,7 @@ public abstract class BaseParser implements IParser {
|
|||
return false;
|
||||
}
|
||||
|
||||
myNext = new CompositeChildElement(theParent, myChildrenIter.next());
|
||||
myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource);
|
||||
|
||||
/*
|
||||
* There are lots of reasons we might skip encoding a particular child
|
||||
|
@ -282,40 +281,6 @@ public abstract class BaseParser implements IParser {
|
|||
return ref.getValue();
|
||||
}
|
||||
|
||||
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
|
||||
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
|
||||
if (stripVersionsFromReferences != null) {
|
||||
return stripVersionsFromReferences;
|
||||
}
|
||||
|
||||
if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
|
||||
if (dontStripVersionsFromReferencesAtPaths != null) {
|
||||
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
|
||||
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
|
||||
Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
|
||||
if (overrideResourceIdWithBundleEntryFullUrl != null) {
|
||||
return overrideResourceIdWithBundleEntryFullUrl;
|
||||
}
|
||||
|
||||
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
|
||||
}
|
||||
|
||||
protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException;
|
||||
|
||||
protected abstract <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException;
|
||||
|
@ -338,7 +303,7 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
|
||||
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);
|
||||
|
@ -424,6 +389,11 @@ public abstract class BaseParser implements IParser {
|
|||
return myContainedResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
|
||||
return myDontStripVersionsFromReferencesAtPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #setEncodeElements(Set)}
|
||||
*/
|
||||
|
@ -432,6 +402,21 @@ public abstract class BaseParser implements IParser {
|
|||
return myEncodeElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncodeElements(Set<String> theEncodeElements) {
|
||||
myEncodeElementsIncludesStars = false;
|
||||
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
|
||||
myEncodeElements = null;
|
||||
} else {
|
||||
myEncodeElements = theEncodeElements;
|
||||
for (String next : theEncodeElements) {
|
||||
if (next.startsWith("*.")) {
|
||||
myEncodeElementsIncludesStars = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #setEncodeElementsAppliesToResourceTypes(Set)}
|
||||
*/
|
||||
|
@ -440,15 +425,38 @@ public abstract class BaseParser implements IParser {
|
|||
return myEncodeElementsAppliesToResourceTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
|
||||
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
|
||||
myEncodeElementsAppliesToResourceTypes = null;
|
||||
} else {
|
||||
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIdType getEncodeForceResourceId() {
|
||||
return myEncodeForceResourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
|
||||
myEncodeForceResourceId = theEncodeForceResourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected IParserErrorHandler getErrorHandler() {
|
||||
return myErrorHandler;
|
||||
}
|
||||
|
||||
protected String getExtensionUrl(final String extensionUrl) {
|
||||
String url = extensionUrl;
|
||||
if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
|
||||
url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
protected TagList getMetaTagsForEncoding(IResource theIResource) {
|
||||
TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource);
|
||||
if (shouldAddSubsettedTag()) {
|
||||
|
@ -459,24 +467,44 @@ public abstract class BaseParser implements IParser {
|
|||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
|
||||
return myOverrideResourceIdWithBundleEntryFullUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends IBaseResource>> getPreferTypes() {
|
||||
return myPreferTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
|
||||
if (thePreferTypes != null) {
|
||||
ArrayList<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
|
||||
for (Class<? extends IBaseResource> next : thePreferTypes) {
|
||||
if (Modifier.isAbstract(next.getModifiers()) == false) {
|
||||
types.add(next);
|
||||
}
|
||||
}
|
||||
myPreferTypes = Collections.unmodifiableList(types);
|
||||
} else {
|
||||
myPreferTypes = thePreferTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
|
||||
switch (myContext.getAddProfileTagWhenEncoding()) {
|
||||
case NEVER:
|
||||
return theProfiles;
|
||||
case ONLY_FOR_CUSTOM:
|
||||
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
|
||||
if (resDef.isStandardType()) {
|
||||
case NEVER:
|
||||
return theProfiles;
|
||||
}
|
||||
break;
|
||||
case ALWAYS:
|
||||
break;
|
||||
case ONLY_FOR_CUSTOM:
|
||||
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
|
||||
if (resDef.isStandardType()) {
|
||||
return theProfiles;
|
||||
}
|
||||
break;
|
||||
case ALWAYS:
|
||||
break;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource);
|
||||
|
@ -503,6 +531,15 @@ public abstract class BaseParser implements IParser {
|
|||
return theProfiles;
|
||||
}
|
||||
|
||||
protected String getServerBaseUrl() {
|
||||
return myServerBaseUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getStripVersionsFromReferences() {
|
||||
return myStripVersionsFromReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>), narratives will not be included in the encoded
|
||||
* values.
|
||||
|
@ -516,7 +553,7 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
|
||||
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
|
||||
&& theIncludedResource == false;
|
||||
&& theIncludedResource == false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -524,14 +561,38 @@ public abstract class BaseParser implements IParser {
|
|||
return myOmitResourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getStripVersionsFromReferences() {
|
||||
return myStripVersionsFromReferences;
|
||||
private boolean isOverrideResourceIdWithBundleEntryFullUrl() {
|
||||
Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl;
|
||||
if (overrideResourceIdWithBundleEntryFullUrl != null) {
|
||||
return overrideResourceIdWithBundleEntryFullUrl;
|
||||
}
|
||||
|
||||
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getOverrideResourceIdWithBundleEntryFullUrl() {
|
||||
return myOverrideResourceIdWithBundleEntryFullUrl;
|
||||
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
|
||||
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
|
||||
if (stripVersionsFromReferences != null) {
|
||||
return stripVersionsFromReferences;
|
||||
}
|
||||
|
||||
if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
|
||||
if (dontStripVersionsFromReferencesAtPaths != null) {
|
||||
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
|
||||
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -623,9 +684,8 @@ public abstract class BaseParser implements IParser {
|
|||
return parseResource(null, theMessageString);
|
||||
}
|
||||
|
||||
|
||||
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
|
||||
CompositeChildElement theCompositeChildElement) {
|
||||
CompositeChildElement theCompositeChildElement) {
|
||||
if (myContext.getVersion().getVersion().isRi()) {
|
||||
|
||||
/*
|
||||
|
@ -727,82 +787,6 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncodeElements(Set<String> theEncodeElements) {
|
||||
myEncodeElementsIncludesStars = false;
|
||||
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
|
||||
myEncodeElements = null;
|
||||
} else {
|
||||
myEncodeElements = theEncodeElements;
|
||||
for (String next : theEncodeElements) {
|
||||
if (next.startsWith("*.")) {
|
||||
myEncodeElementsIncludesStars = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncodeElementsAppliesToResourceTypes(Set<String> theEncodeElementsAppliesToResourceTypes) {
|
||||
if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) {
|
||||
myEncodeElementsAppliesToResourceTypes = null;
|
||||
} else {
|
||||
myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) {
|
||||
myEncodeForceResourceId = theEncodeForceResourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setOmitResourceId(boolean theOmitResourceId) {
|
||||
myOmitResourceId = theOmitResourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
|
||||
Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
|
||||
myErrorHandler = theErrorHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
|
||||
if (thePreferTypes != null) {
|
||||
ArrayList<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
|
||||
for (Class<? extends IBaseResource> next : thePreferTypes) {
|
||||
if (Modifier.isAbstract(next.getModifiers()) == false) {
|
||||
types.add(next);
|
||||
}
|
||||
}
|
||||
myPreferTypes = Collections.unmodifiableList(types);
|
||||
} else {
|
||||
myPreferTypes = thePreferTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setServerBaseUrl(String theUrl) {
|
||||
myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
|
||||
myStripVersionsFromReferences = theStripVersionsFromReferences;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
|
||||
myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
|
||||
if (thePaths == null) {
|
||||
|
@ -827,8 +811,34 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
|
||||
return myDontStripVersionsFromReferencesAtPaths;
|
||||
public IParser setOmitResourceId(boolean theOmitResourceId) {
|
||||
myOmitResourceId = theOmitResourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) {
|
||||
myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
|
||||
Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
|
||||
myErrorHandler = theErrorHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setServerBaseUrl(String theUrl) {
|
||||
myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
|
||||
myStripVersionsFromReferences = theStripVersionsFromReferences;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -847,7 +857,7 @@ public abstract class BaseParser implements IParser {
|
|||
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
|
||||
}
|
||||
|
||||
protected boolean shouldEncodeResourceId(IBaseResource theResource) {
|
||||
protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) {
|
||||
boolean retVal = true;
|
||||
if (isOmitResourceId()) {
|
||||
retVal = false;
|
||||
|
@ -858,24 +868,14 @@ public abstract class BaseParser implements IParser {
|
|||
retVal = false;
|
||||
} else if (myDontEncodeElements.contains("*.id")) {
|
||||
retVal = false;
|
||||
} else if (theSubResource == false && myDontEncodeElements.contains("id")) {
|
||||
retVal = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected String getExtensionUrl(final String extensionUrl) {
|
||||
String url = extensionUrl;
|
||||
if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) {
|
||||
url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
protected String getServerBaseUrl() {
|
||||
return myServerBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for DSTU2 only
|
||||
*/
|
||||
|
@ -965,11 +965,13 @@ public abstract class BaseParser implements IParser {
|
|||
private final BaseRuntimeChildDefinition myDef;
|
||||
private final CompositeChildElement myParent;
|
||||
private final RuntimeResourceDefinition myResDef;
|
||||
private final boolean mySubResource;
|
||||
|
||||
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) {
|
||||
public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) {
|
||||
myDef = theDef;
|
||||
myParent = theParent;
|
||||
myResDef = null;
|
||||
mySubResource = theSubResource;
|
||||
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
if (theParent != null) {
|
||||
|
@ -984,12 +986,11 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
}
|
||||
|
||||
public boolean anyPathMatches(Set<String> thePaths) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
addParent(this, b);
|
||||
|
||||
String path = b.toString();
|
||||
return thePaths.contains(path);
|
||||
public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) {
|
||||
myResDef = theResDef;
|
||||
myDef = null;
|
||||
myParent = null;
|
||||
mySubResource = theSubResource;
|
||||
}
|
||||
|
||||
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
|
||||
|
@ -1012,10 +1013,12 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
}
|
||||
|
||||
public CompositeChildElement(RuntimeResourceDefinition theResDef) {
|
||||
myResDef = theResDef;
|
||||
myDef = null;
|
||||
myParent = null;
|
||||
public boolean anyPathMatches(Set<String> thePaths) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
addParent(this, b);
|
||||
|
||||
String path = b.toString();
|
||||
return thePaths.contains(path);
|
||||
}
|
||||
|
||||
private StringBuilder buildPath() {
|
||||
|
@ -1079,7 +1082,17 @@ public abstract class BaseParser implements IParser {
|
|||
|
||||
thePathBuilder.append('.');
|
||||
thePathBuilder.append(myDef.getElementName());
|
||||
return theElements.contains(thePathBuilder.toString());
|
||||
String currentPath = thePathBuilder.toString();
|
||||
boolean retVal = theElements.contains(currentPath);
|
||||
int dotIdx = currentPath.indexOf('.');
|
||||
if (!retVal) {
|
||||
if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) {
|
||||
if (!myParent.isSubResource()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1098,6 +1111,10 @@ public abstract class BaseParser implements IParser {
|
|||
return myResDef;
|
||||
}
|
||||
|
||||
private boolean isSubResource() {
|
||||
return mySubResource;
|
||||
}
|
||||
|
||||
public boolean shouldBeEncoded() {
|
||||
boolean retVal = true;
|
||||
if (myEncodeElements != null) {
|
||||
|
|
|
@ -191,7 +191,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
|
||||
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
|
||||
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem,
|
||||
boolean theForceEmpty) throws IOException {
|
||||
|
||||
switch (theChildDef.getChildType()) {
|
||||
|
@ -264,7 +264,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
} else {
|
||||
theEventWriter.beginObject();
|
||||
}
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem);
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
|
||||
theEventWriter.endObject();
|
||||
break;
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
|
||||
for (IBaseResource next : containedResources) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()));
|
||||
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue()));
|
||||
}
|
||||
|
||||
theEventWriter.endArray();
|
||||
|
@ -320,7 +320,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
|
||||
boolean theContainedResource, CompositeChildElement theParent) throws IOException {
|
||||
boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException {
|
||||
|
||||
{
|
||||
String elementId = getCompositeElementId(theElement);
|
||||
|
@ -330,7 +330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
boolean haveWrittenExtensions = false;
|
||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
|
||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
|
||||
|
||||
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
||||
|
||||
|
@ -360,7 +360,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
||||
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
||||
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, theSubResource, nextChildElem, false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
} else if (nextChild instanceof RuntimeChildContainedResources) {
|
||||
String childName = nextChild.getValidChildNames().iterator().next();
|
||||
BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, theSubResource, nextChildElem, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -451,15 +451,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
|
||||
beginArray(theEventWriter, childName);
|
||||
inArray = true;
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource,nextChildElem, force);
|
||||
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
|
||||
// suppress narratives from contained resources
|
||||
} else {
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource,nextChildElem, false);
|
||||
}
|
||||
currentChildName = childName;
|
||||
} else {
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource,theSubResource, nextChildElem, force);
|
||||
}
|
||||
|
||||
valueIdx++;
|
||||
|
@ -540,11 +540,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource,
|
||||
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource,
|
||||
CompositeChildElement theParent) throws IOException, DataFormatException {
|
||||
|
||||
writeCommentsPreAndPost(theNextValue, theEventWriter);
|
||||
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent);
|
||||
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -587,18 +587,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
if (!theContainedResource) {
|
||||
if (super.shouldEncodeResourceId(theResource) == false) {
|
||||
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
|
||||
resourceId = null;
|
||||
} else if (!theSubResource && getEncodeForceResourceId() != null) {
|
||||
resourceId = getEncodeForceResourceId();
|
||||
}
|
||||
}
|
||||
|
||||
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId);
|
||||
encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, theSubResource, resourceId);
|
||||
}
|
||||
|
||||
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
|
||||
boolean theContainedResource, IIdType theResourceId) throws IOException {
|
||||
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
|
||||
if (!theContainedResource) {
|
||||
super.containResourcesForEncoding(theResource);
|
||||
}
|
||||
|
@ -670,7 +670,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
beginArray(theEventWriter, "security");
|
||||
for (BaseCodingDt securityLabel : securityLabels) {
|
||||
theEventWriter.beginObject();
|
||||
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null);
|
||||
encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
theEventWriter.endArray();
|
||||
|
@ -706,7 +706,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
write(theEventWriter, "content", contentAsBase64);
|
||||
}
|
||||
} else {
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||
}
|
||||
|
||||
theEventWriter.endObject();
|
||||
|
@ -1366,7 +1366,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null);
|
||||
} else {
|
||||
String childName = myDef.getChildNameByDatatype(myValue.getClass());
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false);
|
||||
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
|
||||
}
|
||||
|
||||
|
@ -1421,7 +1421,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
if (childDef == null) {
|
||||
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
|
||||
}
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, myParent, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false);
|
||||
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
|
||||
}
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
}
|
||||
|
||||
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
|
||||
String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
|
||||
String theExtensionUrl, boolean theIncludedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
|
||||
if (theElement == null || theElement.isEmpty()) {
|
||||
if (isChildContained(childDef, theIncludedResource)) {
|
||||
// We still want to go in..
|
||||
|
@ -234,7 +234,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
if (StringUtils.isNotBlank(encodedValue)) {
|
||||
theEventWriter.writeAttribute("value", encodedValue);
|
||||
}
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource);
|
||||
theEventWriter.writeEndElement();
|
||||
}
|
||||
break;
|
||||
|
@ -251,7 +251,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
if (value != null) {
|
||||
theEventWriter.writeAttribute("value", value);
|
||||
}
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource);
|
||||
theEventWriter.writeEndElement();
|
||||
}
|
||||
break;
|
||||
|
@ -266,7 +266,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
if (isNotBlank(theExtensionUrl)) {
|
||||
theEventWriter.writeAttribute("url", theExtensionUrl);
|
||||
}
|
||||
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent);
|
||||
encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent);
|
||||
theEventWriter.writeEndElement();
|
||||
break;
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
for (IBaseResource next : getContainedResources().getContainedResources()) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
theEventWriter.writeStartElement("contained");
|
||||
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
|
||||
encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue()));
|
||||
theEventWriter.writeEndElement();
|
||||
}
|
||||
break;
|
||||
|
@ -319,10 +319,10 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
|
||||
}
|
||||
|
||||
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent)
|
||||
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent)
|
||||
throws XMLStreamException, DataFormatException {
|
||||
|
||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
|
||||
for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
|
||||
|
||||
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
||||
|
||||
|
@ -352,13 +352,13 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
|
||||
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
|
||||
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, theSubResource, nextChildElem);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextChild instanceof RuntimeChildContainedResources) {
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, theSubResource, nextChildElem);
|
||||
} else {
|
||||
|
||||
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
|
||||
|
@ -382,7 +382,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
|
||||
|
||||
if (extensionUrl != null && childName.equals("extension") == false) {
|
||||
encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
|
||||
encodeExtension(theResource, theEventWriter, theContainedResource, theSubResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
|
||||
} else if (nextChild instanceof RuntimeChildExtension) {
|
||||
IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
|
||||
if ((extension.getValue() == null || extension.getValue().isEmpty())) {
|
||||
|
@ -390,18 +390,18 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, theSubResource, nextChildElem);
|
||||
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
|
||||
// suppress narratives from contained resources
|
||||
} else {
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, theSubResource, nextChildElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
|
||||
private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
|
||||
throws XMLStreamException {
|
||||
BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
|
||||
if (extDef.isModifier()) {
|
||||
|
@ -416,23 +416,23 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
}
|
||||
|
||||
theEventWriter.writeAttribute("url", extensionUrl);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, theSubResource, nextChildElem);
|
||||
theEventWriter.writeEndElement();
|
||||
}
|
||||
|
||||
private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
|
||||
private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
|
||||
if (theElement instanceof ISupportsUndeclaredExtensions) {
|
||||
ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
|
||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource);
|
||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource);
|
||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource);
|
||||
encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource);
|
||||
}
|
||||
if (theElement instanceof IBaseHasExtensions) {
|
||||
IBaseHasExtensions res = (IBaseHasExtensions) theElement;
|
||||
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource);
|
||||
encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource);
|
||||
}
|
||||
if (theElement instanceof IBaseHasModifierExtensions) {
|
||||
IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
|
||||
encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource);
|
||||
encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource,theSubResource);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,17 +447,17 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
}
|
||||
|
||||
if (!theIncludedResource) {
|
||||
if (super.shouldEncodeResourceId(theResource) == false) {
|
||||
if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
|
||||
resourceId = null;
|
||||
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
|
||||
resourceId = getEncodeForceResourceId();
|
||||
}
|
||||
}
|
||||
|
||||
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
|
||||
encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, theSubResource, resourceId);
|
||||
}
|
||||
|
||||
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId) throws XMLStreamException {
|
||||
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws XMLStreamException {
|
||||
if (!theContainedResource) {
|
||||
super.containResourcesForEncoding(theResource);
|
||||
}
|
||||
|
@ -476,12 +476,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
writeCommentsPre(theEventWriter, theResourceId);
|
||||
theEventWriter.writeStartElement("id");
|
||||
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false);
|
||||
theEventWriter.writeEndElement();
|
||||
writeCommentsPost(theEventWriter, theResourceId);
|
||||
}
|
||||
|
||||
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
|
||||
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -494,7 +494,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
writeCommentsPost(theEventWriter, theResourceId);*/
|
||||
theEventWriter.writeStartElement("id");
|
||||
theEventWriter.writeAttribute("value", theResourceId.getIdPart());
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false);
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false);
|
||||
theEventWriter.writeEndElement();
|
||||
writeCommentsPost(theEventWriter, theResourceId);
|
||||
}
|
||||
|
@ -525,7 +525,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
}
|
||||
for (BaseCodingDt securityLabel : securityLabels) {
|
||||
theEventWriter.writeStartElement("security");
|
||||
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null);
|
||||
encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
|
||||
theEventWriter.writeEndElement();
|
||||
}
|
||||
if (tags != null) {
|
||||
|
@ -548,7 +548,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
|
||||
writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
|
||||
} else {
|
||||
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
|
||||
encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -556,7 +556,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
theEventWriter.writeEndElement();
|
||||
}
|
||||
|
||||
private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource)
|
||||
private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, boolean theSubResource)
|
||||
throws XMLStreamException, DataFormatException {
|
||||
for (IBaseExtension<?, ?> next : theExtensions) {
|
||||
if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
|
||||
|
@ -592,11 +592,11 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null);
|
||||
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null);
|
||||
}
|
||||
|
||||
// child extensions
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource);
|
||||
encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource);
|
||||
|
||||
theEventWriter.writeEndElement();
|
||||
|
||||
|
|
|
@ -20,20 +20,22 @@ package ca.uhn.fhir.jpa.config;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
||||
import org.hl7.fhir.r4.utils.GraphQLEngine;
|
||||
import ca.uhn.fhir.jpa.search.*;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
|
@ -41,20 +43,17 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
|
|||
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import ca.uhn.fhir.jpa.search.*;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
||||
public class BaseConfig implements SchedulingConfigurer {
|
||||
|
||||
@Resource
|
||||
private ApplicationContext myAppCtx;
|
||||
|
||||
@Autowired
|
||||
protected Environment myEnv;
|
||||
@Resource
|
||||
private ApplicationContext myAppCtx;
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) {
|
||||
|
@ -67,6 +66,11 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IGraphQLStorageServices jpaStorageServices() {
|
||||
return new JpaStorageServices();
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public ScheduledExecutorFactoryBean scheduledExecutorService() {
|
||||
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
|
||||
|
@ -74,7 +78,7 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return b;
|
||||
}
|
||||
|
||||
@Bean(autowire=Autowire.BY_TYPE)
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public ISearchCoordinatorSvc searchCoordinatorSvc() {
|
||||
return new SearchCoordinatorSvcImpl();
|
||||
}
|
||||
|
@ -84,11 +88,32 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return new SearchParamPresenceSvcImpl();
|
||||
}
|
||||
|
||||
@Bean(autowire=Autowire.BY_TYPE)
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IStaleSearchDeletingSvc staleSearchDeletingSvc() {
|
||||
return new StaleSearchDeletingSvcImpl();
|
||||
}
|
||||
|
||||
// @PostConstruct
|
||||
// public void wireResourceDaos() {
|
||||
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
|
||||
// List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class);
|
||||
// for (IDao next : daoBeans.values()) {
|
||||
// next.setResourceDaos(bean);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
|
||||
return new SubscriptionRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
|
||||
return new SubscriptionWebsocketInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
||||
|
@ -100,15 +125,6 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
// return retVal;
|
||||
}
|
||||
|
||||
// @PostConstruct
|
||||
// public void wireResourceDaos() {
|
||||
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
|
||||
// List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class);
|
||||
// for (IDao next : daoBeans.values()) {
|
||||
// next.setResourceDaos(bean);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* This lets the "@Value" fields reference properties from the properties file
|
||||
*/
|
||||
|
@ -117,9 +133,5 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IGraphQLStorageServices jpaStorageServices() {
|
||||
return new JpaStorageServices();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import org.hl7.fhir.instance.hapi.validation.*;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
|
||||
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
|
||||
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/*
|
||||
|
@ -26,14 +37,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseDstu2Config extends BaseConfig {
|
||||
|
@ -65,12 +68,6 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
return ourFhirContextDstu2Hl7Org;
|
||||
}
|
||||
|
||||
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
|
||||
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
|
||||
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(name = "myInstanceValidatorDstu2")
|
||||
@Lazy
|
||||
public IValidatorModule instanceValidatorDstu2() {
|
||||
|
@ -80,6 +77,12 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
|
||||
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
|
||||
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IFulltextSearchSvc searchDao() {
|
||||
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
|
||||
|
@ -115,10 +118,4 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
return new HapiTerminologySvcDstu2();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() {
|
||||
return new RestHookSubscriptionDstu2Interceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config;
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -20,38 +20,32 @@ package ca.uhn.fhir.jpa.config;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket()
|
||||
public class WebsocketDstu2Config implements WebSocketConfigurer {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketDstu2Config.class);
|
||||
@Controller
|
||||
public class WebsocketDispatcherConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
|
||||
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2").setAllowedOrigins("*");
|
||||
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket").setAllowedOrigins("*");
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public WebSocketHandler subscriptionWebSocketHandler() {
|
||||
return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler websocketTaskScheduler() {
|
||||
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
|
||||
retVal.setPoolSize(5);
|
||||
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandler.class);
|
||||
return retVal;
|
||||
}
|
||||
|
|
@ -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,8 +1,30 @@
|
|||
package ca.uhn.fhir.jpa.config.dstu3;
|
||||
|
||||
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
|
||||
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -24,31 +46,6 @@ import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseDstu3Config extends BaseConfig {
|
||||
|
@ -133,10 +130,4 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return new JpaValidationSupportChainDstu3();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() {
|
||||
return new RestHookSubscriptionDstu3Interceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
|
||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
|
||||
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
|
||||
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
|
||||
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -25,23 +47,6 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.ParserOptions;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
|
||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.jpa.term.*;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class BaseR4Config extends BaseConfig {
|
||||
|
@ -132,10 +137,4 @@ public class BaseR4Config extends BaseConfig {
|
|||
return new JpaValidationSupportChainR4();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionR4Interceptor restHookSubscriptionR4Interceptor() {
|
||||
return new RestHookSubscriptionR4Interceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
HashSet<String> excludeElementsInEncoded = new HashSet<String>();
|
||||
excludeElementsInEncoded.add("*.id");
|
||||
excludeElementsInEncoded.add("*.meta");
|
||||
excludeElementsInEncoded.add("id");
|
||||
excludeElementsInEncoded.add("meta");
|
||||
EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,49 +20,27 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.*;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||
|
||||
|
@ -73,9 +51,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||
subscriptionEntity.setCreated(new Date());
|
||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValue());
|
||||
myEntityManager.persist(subscriptionEntity);
|
||||
}
|
||||
|
||||
|
@ -89,145 +64,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||
return table.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
|
||||
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
|
||||
retVal.add(toResource(nextFlaggedResource.getResource(), false));
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
|
||||
mySubscriptionFlaggedResourceDataDao.flush();
|
||||
|
||||
mySubscriptionTableDao.updateLastClientPoll(new Date());
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public int pollForNewUndeliveredResources() {
|
||||
return pollForNewUndeliveredResources((String) null);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return 0;
|
||||
}
|
||||
ourLog.trace("Beginning pollForNewUndeliveredResources()");
|
||||
|
||||
// SubscriptionCandidateResource
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
||||
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
|
||||
@Override
|
||||
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
|
||||
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
|
||||
}
|
||||
});
|
||||
|
||||
int retVal = 0;
|
||||
for (final Long nextSubscriptionTablePid : subscriptions) {
|
||||
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
|
||||
if (!subscription.getChannel().getType().equals(SubscriptionChannelTypeEnum.WEBSOCKET.getCode())){
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
return 0;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
|
||||
long start = theSubscriptionTable.getMostRecentMatch().getTime();
|
||||
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
|
||||
if (end <= start) {
|
||||
ourLog.trace("Skipping search for subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.debug("Subscription {} search from {} to {}", new Object[] { subscription.getId().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end)) });
|
||||
|
||||
DateRangeParam range = new DateRangeParam();
|
||||
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
|
||||
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
|
||||
criteriaUrl.setLastUpdated(range);
|
||||
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
|
||||
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
|
||||
IBundleProvider results = dao.search(criteriaUrl);
|
||||
if (results.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getId().getIdPart());
|
||||
|
||||
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
|
||||
Date mostRecentMatch = null;
|
||||
for (IBaseResource next : results.getResources(0, results.size())) {
|
||||
|
||||
Date updated = ResourceMetadataKeyEnum.UPDATED.get((IResource) next).getValue();
|
||||
if (mostRecentMatch == null) {
|
||||
mostRecentMatch = updated;
|
||||
} else {
|
||||
long mostRecentMatchTime = mostRecentMatch.getTime();
|
||||
long updatedTime = updated.getTime();
|
||||
if (mostRecentMatchTime < updatedTime) {
|
||||
mostRecentMatch = updated;
|
||||
}
|
||||
}
|
||||
|
||||
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
|
||||
Long pid = IDao.RESOURCE_PID.get((IResource) next);
|
||||
|
||||
ourLog.info("New resource for subscription: {}", pid);
|
||||
|
||||
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
|
||||
nextFlag.setSubscription(theSubscriptionTable);
|
||||
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
|
||||
flags.add(nextFlag);
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.save(flags);
|
||||
|
||||
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getId().getIdPart(), new InstantDt(mostRecentMatch));
|
||||
|
||||
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
|
||||
mySubscriptionTableDao.save(theSubscriptionTable);
|
||||
|
||||
return results.size();
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public synchronized void pollForNewUndeliveredResourcesScheduler() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
pollForNewUndeliveredResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||
|
@ -236,69 +72,14 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||
createSubscriptionTable(theEntity, theSubscription);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public void purgeInactiveSubscriptions() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
|
||||
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
||||
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
|
||||
@Override
|
||||
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
|
||||
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
|
||||
toPurge.size();
|
||||
return toPurge;
|
||||
}
|
||||
});
|
||||
|
||||
for (SubscriptionTable subscriptionTable : toPurge) {
|
||||
|
||||
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
|
||||
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
|
||||
new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
|
||||
txTemplate.execute(new TransactionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
delete(subscriptionId, null);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||
|
||||
Subscription resource = (Subscription) theResource;
|
||||
Long resourceId = theEntity.getId();
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
||||
if (subscriptionId != null) {
|
||||
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
|
||||
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
|
||||
}
|
||||
} else {
|
||||
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
|
||||
q.setParameter("res_id", resourceId);
|
||||
q.setParameter("status", resource.getStatusElement().getValue());
|
||||
if (q.executeUpdate() > 0) {
|
||||
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatusElement().getValueAsEnum());
|
||||
} else {
|
||||
createSubscriptionTable(retVal, resource);
|
||||
}
|
||||
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import java.util.List;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -22,20 +23,8 @@ import java.util.List;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
|
||||
|
||||
int pollForNewUndeliveredResources();
|
||||
|
||||
List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid);
|
||||
|
||||
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId);
|
||||
|
||||
void purgeInactiveSubscriptions();
|
||||
|
||||
void pollForNewUndeliveredResourcesScheduler();
|
||||
|
||||
int pollForNewUndeliveredResources(String resourceType);
|
||||
}
|
||||
|
|
|
@ -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%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
|
||||
public interface ISubscriptionTableDao extends JpaRepository<SubscriptionTable, Long> {
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM SubscriptionTable t WHERE t.mySubscriptionResource = :subscription ")
|
||||
void deleteAllForSubscription(@Param("subscription") ResourceTable theSubscription);
|
||||
|
||||
@Query("SELECT t FROM SubscriptionTable t WHERE t.myResId = :pid")
|
||||
SubscriptionTable findOneByResourcePid(@Param("pid") Long theId);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM SubscriptionTable t WHERE t.myId = :id ")
|
||||
void deleteAllForSubscription(@Param("id") Long theSubscriptionId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE SubscriptionTable t SET t.myLastClientPoll = :last_client_poll")
|
||||
int updateLastClientPoll(@Param("last_client_poll") Date theLastClientPoll);
|
||||
|
||||
@Query("SELECT t FROM SubscriptionTable t WHERE t.myLastClientPoll < :cutoff OR (t.myLastClientPoll IS NULL AND t.myCreated < :cutoff)")
|
||||
Collection<SubscriptionTable> findInactiveBeforeCutoff(@Param("cutoff") Date theCutoff);
|
||||
|
||||
@Query("SELECT t.myId FROM SubscriptionTable t WHERE t.myStatus = :status AND t.myNextCheck <= :next_check")
|
||||
Collection<Long> findSubscriptionsWhichNeedToBeChecked(@Param("status") String theStatus, @Param("next_check") Date theNextCheck);
|
||||
}
|
||||
|
|
|
@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.Query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
|
@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu3.class);
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||
|
||||
|
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||
subscriptionEntity.setCreated(new Date());
|
||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
|
||||
myEntityManager.persist(subscriptionEntity);
|
||||
}
|
||||
|
||||
|
@ -103,147 +68,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
return table.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
|
||||
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
|
||||
retVal.add(toResource(nextFlaggedResource.getResource(), false));
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
|
||||
mySubscriptionFlaggedResourceDataDao.flush();
|
||||
|
||||
mySubscriptionTableDao.updateLastClientPoll(new Date());
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public int pollForNewUndeliveredResources() {
|
||||
return pollForNewUndeliveredResources((String) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return 0;
|
||||
}
|
||||
ourLog.trace("Beginning pollForNewUndeliveredResources()");
|
||||
|
||||
// SubscriptionCandidateResource
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
||||
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
|
||||
@Override
|
||||
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
|
||||
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
|
||||
}
|
||||
});
|
||||
|
||||
int retVal = 0;
|
||||
for (final Long nextSubscriptionTablePid : subscriptions) {
|
||||
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET) {
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
return 0;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
criteriaUrl.setLoadSynchronousUpTo(1000);
|
||||
|
||||
long start = theSubscriptionTable.getMostRecentMatch().getTime();
|
||||
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
|
||||
if (end <= start) {
|
||||
ourLog.trace("Skipping search for subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Subscription {} search from {} to {}", new Object[]{subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end))});
|
||||
|
||||
DateRangeParam range = new DateRangeParam();
|
||||
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
|
||||
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
|
||||
criteriaUrl.setLastUpdated(range);
|
||||
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
|
||||
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
|
||||
IBundleProvider results = dao.search(criteriaUrl);
|
||||
if (results.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getIdElement().getIdPart());
|
||||
|
||||
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
|
||||
Date mostRecentMatch = null;
|
||||
for (IBaseResource nextBase : results.getResources(0, results.size())) {
|
||||
IAnyResource next = (IAnyResource) nextBase;
|
||||
|
||||
Date updated = next.getMeta().getLastUpdated();
|
||||
if (mostRecentMatch == null) {
|
||||
mostRecentMatch = updated;
|
||||
} else {
|
||||
long mostRecentMatchTime = mostRecentMatch.getTime();
|
||||
long updatedTime = updated.getTime();
|
||||
if (mostRecentMatchTime < updatedTime) {
|
||||
mostRecentMatch = updated;
|
||||
}
|
||||
}
|
||||
|
||||
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
|
||||
Long pid = IDao.RESOURCE_PID.get(next);
|
||||
|
||||
ourLog.info("New resource for subscription: {}", pid);
|
||||
|
||||
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
|
||||
nextFlag.setSubscription(theSubscriptionTable);
|
||||
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
|
||||
flags.add(nextFlag);
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.save(flags);
|
||||
|
||||
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch));
|
||||
|
||||
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
|
||||
mySubscriptionTableDao.save(theSubscriptionTable);
|
||||
|
||||
return results.size();
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public synchronized void pollForNewUndeliveredResourcesScheduler() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
pollForNewUndeliveredResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||
|
@ -252,69 +78,15 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
createSubscriptionTable(theEntity, theSubscription);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public void purgeInactiveSubscriptions() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
|
||||
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
|
||||
|
||||
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
|
||||
@Override
|
||||
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
|
||||
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
|
||||
toPurge.size();
|
||||
return toPurge;
|
||||
}
|
||||
});
|
||||
|
||||
for (SubscriptionTable subscriptionTable : toPurge) {
|
||||
|
||||
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
|
||||
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
|
||||
new Object[]{subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll()});
|
||||
txTemplate.execute(new TransactionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
delete(subscriptionId, null);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||
|
||||
Subscription resource = (Subscription) theResource;
|
||||
Long resourceId = theEntity.getId();
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
||||
if (subscriptionId != null) {
|
||||
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
|
||||
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
|
||||
}
|
||||
} else {
|
||||
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
|
||||
q.setParameter("res_id", resourceId);
|
||||
q.setParameter("status", resource.getStatusElement().getValueAsString());
|
||||
if (q.executeUpdate() > 0) {
|
||||
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus());
|
||||
} else {
|
||||
createSubscriptionTable(retVal, resource);
|
||||
}
|
||||
mySubscriptionTableDao.deleteAllForSubscription(theEntity);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.Query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
|
@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
|||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionR4.class);
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||
|
||||
|
@ -87,9 +55,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
|||
SubscriptionTable subscriptionEntity = new SubscriptionTable();
|
||||
subscriptionEntity.setCreated(new Date());
|
||||
subscriptionEntity.setSubscriptionResource(theEntity);
|
||||
subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue());
|
||||
subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsString());
|
||||
myEntityManager.persist(subscriptionEntity);
|
||||
}
|
||||
|
||||
|
@ -103,146 +68,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
|||
return table.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid) {
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
Page<SubscriptionFlaggedResource> flaggedResources = mySubscriptionFlaggedResourceDataDao.findAllBySubscriptionId(theSubscriptionPid, new PageRequest(0, 100));
|
||||
for (SubscriptionFlaggedResource nextFlaggedResource : flaggedResources) {
|
||||
retVal.add(toResource(nextFlaggedResource.getResource(), false));
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.delete(flaggedResources);
|
||||
mySubscriptionFlaggedResourceDataDao.flush();
|
||||
|
||||
mySubscriptionTableDao.updateLastClientPoll(new Date());
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public int pollForNewUndeliveredResources() {
|
||||
return pollForNewUndeliveredResources(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return 0;
|
||||
}
|
||||
ourLog.trace("Beginning pollForNewUndeliveredResources()");
|
||||
|
||||
// SubscriptionCandidateResource
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
||||
Collection<Long> subscriptions = txTemplate.execute(new TransactionCallback<Collection<Long>>() {
|
||||
@Override
|
||||
public Collection<Long> doInTransaction(TransactionStatus theStatus) {
|
||||
return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date());
|
||||
}
|
||||
});
|
||||
|
||||
int retVal = 0;
|
||||
for (final Long nextSubscriptionTablePid : subscriptions) {
|
||||
retVal += txTemplate.execute(new TransactionCallback<Integer>() {
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET) {
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
return 0;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
criteriaUrl.setLoadSynchronousUpTo(1000);
|
||||
|
||||
long start = theSubscriptionTable.getMostRecentMatch().getTime();
|
||||
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
|
||||
if (end <= start) {
|
||||
ourLog.trace("Skipping search for subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Subscription {} search from {} to {}", new Object[]{subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end))});
|
||||
|
||||
DateRangeParam range = new DateRangeParam();
|
||||
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
|
||||
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
|
||||
criteriaUrl.setLastUpdated(range);
|
||||
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
|
||||
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
|
||||
IBundleProvider results = dao.search(criteriaUrl);
|
||||
if (results.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getIdElement().getIdPart());
|
||||
|
||||
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
|
||||
Date mostRecentMatch = null;
|
||||
for (IBaseResource nextBase : results.getResources(0, results.size())) {
|
||||
IAnyResource next = (IAnyResource) nextBase;
|
||||
|
||||
Date updated = next.getMeta().getLastUpdated();
|
||||
if (mostRecentMatch == null) {
|
||||
mostRecentMatch = updated;
|
||||
} else {
|
||||
long mostRecentMatchTime = mostRecentMatch.getTime();
|
||||
long updatedTime = updated.getTime();
|
||||
if (mostRecentMatchTime < updatedTime) {
|
||||
mostRecentMatch = updated;
|
||||
}
|
||||
}
|
||||
|
||||
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
|
||||
Long pid = IDao.RESOURCE_PID.get(next);
|
||||
|
||||
ourLog.info("New resource for subscription: {}", pid);
|
||||
|
||||
nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid));
|
||||
nextFlag.setSubscription(theSubscriptionTable);
|
||||
nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong());
|
||||
flags.add(nextFlag);
|
||||
}
|
||||
|
||||
mySubscriptionFlaggedResourceDataDao.save(flags);
|
||||
|
||||
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch));
|
||||
|
||||
theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
|
||||
mySubscriptionTableDao.save(theSubscriptionTable);
|
||||
|
||||
return results.size();
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public synchronized void pollForNewUndeliveredResourcesScheduler() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
pollForNewUndeliveredResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
|
||||
|
@ -251,70 +76,19 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
|
|||
createSubscriptionTable(theEntity, theSubscription);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
@Override
|
||||
public void purgeInactiveSubscriptions() {
|
||||
if (getConfig().isSchedulingDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
|
||||
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
final Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
|
||||
|
||||
Collection<SubscriptionTable> toPurge = txTemplate.execute(new TransactionCallback<Collection<SubscriptionTable>>() {
|
||||
@Override
|
||||
public Collection<SubscriptionTable> doInTransaction(TransactionStatus theStatus) {
|
||||
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
|
||||
toPurge.size();
|
||||
return toPurge;
|
||||
}
|
||||
});
|
||||
|
||||
for (SubscriptionTable subscriptionTable : toPurge) {
|
||||
|
||||
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
|
||||
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
|
||||
new Object[]{subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll()});
|
||||
txTemplate.execute(new TransactionCallback<Void>() {
|
||||
@Override
|
||||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
delete(subscriptionId, null);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||
Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
|
||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry);
|
||||
|
||||
Subscription resource = (Subscription) theResource;
|
||||
Long resourceId = theEntity.getId();
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt());
|
||||
if (subscriptionId != null) {
|
||||
mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId);
|
||||
mySubscriptionTableDao.deleteAllForSubscription(subscriptionId);
|
||||
}
|
||||
} else {
|
||||
Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
|
||||
q.setParameter("res_id", resourceId);
|
||||
q.setParameter("status", resource.getStatusElement().getValueAsString());
|
||||
if (q.executeUpdate() > 0) {
|
||||
ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus());
|
||||
} else {
|
||||
createSubscriptionTable(retVal, resource);
|
||||
mySubscriptionTableDao.deleteAllForSubscription(retVal);
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -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%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
//@formatter:off
|
||||
@Entity
|
||||
@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= {
|
||||
@UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }),
|
||||
@UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" })
|
||||
@Table(name = "HFJ_SUBSCRIPTION_STATS", uniqueConstraints = {
|
||||
@UniqueConstraint(name = "IDX_SUBSC_RESID", columnNames = {"RES_ID"}),
|
||||
})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_SET_STATUS", query="UPDATE SubscriptionTable t SET t.myStatus = :status WHERE t.myResId = :res_id"),
|
||||
@NamedQuery(name="Q_HFJ_SUBSCRIPTION_GET_BY_RES", query="SELECT t FROM SubscriptionTable t WHERE t.myResId = :res_id")
|
||||
})
|
||||
//@formatter:on
|
||||
public class SubscriptionTable {
|
||||
|
||||
@Column(name = "CHECK_INTERVAL", nullable = false)
|
||||
private long myCheckInterval;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "CREATED_TIME", nullable = false, insertable = true, updatable = false)
|
||||
private Date myCreated;
|
||||
|
||||
@OneToMany(mappedBy = "mySubscription")
|
||||
private Collection<SubscriptionFlaggedResource> myFlaggedResources;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SUBSCRIPTION_ID")
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SUBSCRIPTION_ID")
|
||||
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID")
|
||||
@Column(name = "PID", insertable = false, updatable = false)
|
||||
private Long myId;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "LAST_CLIENT_POLL", nullable = true)
|
||||
private Date myLastClientPoll;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "MOST_RECENT_MATCH", nullable = false)
|
||||
private Date myMostRecentMatch;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "NEXT_CHECK", nullable = false)
|
||||
private Date myNextCheck;
|
||||
|
||||
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||
private Long myResId;
|
||||
|
||||
@Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20)
|
||||
private String myStatus;
|
||||
|
||||
@OneToOne()
|
||||
@JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID",
|
||||
foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID")
|
||||
foreignKey = @ForeignKey(name = "FK_SUBSC_RESOURCE_ID")
|
||||
)
|
||||
private ResourceTable mySubscriptionResource;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SubscriptionTable(){
|
||||
public SubscriptionTable() {
|
||||
super();
|
||||
}
|
||||
|
||||
public long getCheckInterval() {
|
||||
return myCheckInterval;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return myCreated;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public Date getLastClientPoll() {
|
||||
return myLastClientPoll;
|
||||
}
|
||||
|
||||
public Date getMostRecentMatch() {
|
||||
return myMostRecentMatch;
|
||||
}
|
||||
|
||||
public Date getNextCheck() {
|
||||
return myNextCheck;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return myStatus;
|
||||
}
|
||||
|
||||
public ResourceTable getSubscriptionResource() {
|
||||
return mySubscriptionResource;
|
||||
}
|
||||
|
||||
public void setCheckInterval(long theCheckInterval) {
|
||||
myCheckInterval = theCheckInterval;
|
||||
}
|
||||
|
||||
public void setCreated(Date theCreated) {
|
||||
myCreated = theCreated;
|
||||
}
|
||||
|
||||
public void setLastClientPoll(Date theLastClientPoll) {
|
||||
myLastClientPoll = theLastClientPoll;
|
||||
public Long getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public void setMostRecentMatch(Date theMostRecentMatch) {
|
||||
myMostRecentMatch = theMostRecentMatch;
|
||||
}
|
||||
|
||||
public void setNextCheck(Date theNextCheck) {
|
||||
myNextCheck = theNextCheck;
|
||||
}
|
||||
|
||||
public void setStatus(String theStatus) {
|
||||
myStatus = theStatus;
|
||||
public ResourceTable getSubscriptionResource() {
|
||||
return mySubscriptionResource;
|
||||
}
|
||||
|
||||
public void setSubscriptionResource(ResourceTable theSubscriptionResource) {
|
||||
|
|
|
@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
|
@ -29,21 +32,25 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
@ -70,6 +77,14 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
private ThreadPoolExecutor myDeliveryExecutor;
|
||||
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
||||
private LinkedBlockingQueue<Runnable> myDeliveryExecutorQueue;
|
||||
private IFhirResourceDao<?> mySubscriptionDao;
|
||||
@Autowired
|
||||
private List<IFhirResourceDao<?>> myResourceDaos;
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
@Autowired(required = false)
|
||||
@Qualifier("myEventDefinitionDaoR4")
|
||||
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -79,7 +94,83 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
setExecutorThreadCount(5);
|
||||
}
|
||||
|
||||
protected abstract CanonicalSubscription canonicalize(S theSubscription);
|
||||
protected CanonicalSubscription canonicalize(S theSubscription) {
|
||||
switch (myCtx.getVersion().getVersion()) {
|
||||
case DSTU2:
|
||||
return canonicalizeDstu2(theSubscription);
|
||||
case DSTU3:
|
||||
return canonicalizeDstu3(theSubscription);
|
||||
case R4:
|
||||
return canonicalizeR4(theSubscription);
|
||||
default:
|
||||
throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
|
||||
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
|
||||
retVal.setBackingSubscription(theSubscription);
|
||||
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
|
||||
retVal.setBackingSubscription(theSubscription);
|
||||
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
retVal.setStatus(subscription.getStatus());
|
||||
retVal.setBackingSubscription(theSubscription);
|
||||
retVal.setChannelType(subscription.getChannel().getType());
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
|
||||
List<org.hl7.fhir.r4.model.Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
|
||||
if (topicExts.size() > 0) {
|
||||
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
|
||||
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
|
||||
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
|
||||
}
|
||||
|
||||
org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement());
|
||||
retVal.addTrigger(def.getTrigger());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public abstract Subscription.SubscriptionChannelType getChannelType();
|
||||
|
||||
|
@ -116,7 +207,9 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
myProcessingChannel = theProcessingChannel;
|
||||
}
|
||||
|
||||
protected abstract IFhirResourceDao<?> getSubscriptionDao();
|
||||
protected IFhirResourceDao<?> getSubscriptionDao() {
|
||||
return mySubscriptionDao;
|
||||
}
|
||||
|
||||
public List<CanonicalSubscription> getSubscriptions() {
|
||||
return new ArrayList<>(myIdToSubscription.values());
|
||||
|
@ -243,13 +336,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
initSubscriptions();
|
||||
}
|
||||
|
||||
protected void registerSubscriptionCheckingSubscriber() {
|
||||
if (mySubscriptionCheckingSubscriber == null) {
|
||||
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
|
||||
}
|
||||
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
|
@ -268,6 +354,13 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
myIdToSubscription.put(theId.getIdPart(), canonicalize(theSubscription));
|
||||
}
|
||||
|
||||
protected void registerSubscriptionCheckingSubscriber() {
|
||||
if (mySubscriptionCheckingSubscriber == null) {
|
||||
mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this);
|
||||
}
|
||||
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||
|
@ -294,6 +387,20 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
submitResourceModified(msg);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
for (IFhirResourceDao<?> next : myResourceDaos) {
|
||||
if (myCtx.getResourceDefinition(next.getResourceType()).getName().equals("Subscription")) {
|
||||
mySubscriptionDao = next;
|
||||
}
|
||||
}
|
||||
Validate.notNull(mySubscriptionDao);
|
||||
|
||||
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
|
||||
Validate.notNull(myEventDefinitionDaoR4);
|
||||
}
|
||||
}
|
||||
|
||||
protected void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||
final GenericMessage<ResourceModifiedMessage> message = new GenericMessage<>(theMsg);
|
||||
mySubscriptionActivatingSubscriber.handleMessage(message);
|
||||
|
@ -308,4 +415,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
|
||||
myIdToSubscription.remove(theId.getIdPart());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -29,14 +29,14 @@ import org.springframework.messaging.MessageHandler;
|
|||
|
||||
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
||||
|
||||
private final IFhirResourceDao mySubscriptionDao;
|
||||
private final IFhirResourceDao<?> mySubscriptionDao;
|
||||
private final Subscription.SubscriptionChannelType myChannelType;
|
||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BaseSubscriptionSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||
public BaseSubscriptionSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||
mySubscriptionDao = theSubscriptionDao;
|
||||
myChannelType = theChannelType;
|
||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -54,6 +56,19 @@ public class CanonicalSubscription implements Serializable {
|
|||
myTrigger = theTrigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theO) {
|
||||
if (this == theO) return true;
|
||||
|
||||
if (theO == null || getClass() != theO.getClass()) return false;
|
||||
|
||||
CanonicalSubscription that = (CanonicalSubscription) theO;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(getIdElement().getIdPart(), that.getIdElement().getIdPart())
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
public IBaseResource getBackingSubscription() {
|
||||
return myBackingSubscription;
|
||||
}
|
||||
|
@ -90,10 +105,12 @@ public class CanonicalSubscription implements Serializable {
|
|||
return myHeaders;
|
||||
}
|
||||
|
||||
public void setHeaders(String theHeaders) {
|
||||
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
||||
myHeaders = new ArrayList<>();
|
||||
if (isNotBlank(theHeaders)) {
|
||||
myHeaders.add(theHeaders);
|
||||
for (IPrimitiveType<String> next : theHeader) {
|
||||
if (isNotBlank(next.getValueAsString())) {
|
||||
myHeaders.add(next.getValueAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,12 +146,17 @@ public class CanonicalSubscription implements Serializable {
|
|||
return myTrigger;
|
||||
}
|
||||
|
||||
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(getIdElement().getIdPart())
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
public void setHeaders(String theHeaders) {
|
||||
myHeaders = new ArrayList<>();
|
||||
for (IPrimitiveType<String> next : theHeader) {
|
||||
if (isNotBlank(next.getValueAsString())) {
|
||||
myHeaders.add(next.getValueAsString());
|
||||
}
|
||||
if (isNotBlank(theHeaders)) {
|
||||
myHeaders.add(theHeaders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -21,6 +21,10 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionSubscriber;
|
||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
|
@ -30,7 +34,6 @@ import ca.uhn.fhir.rest.gclient.IClientExecutable;
|
|||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -38,7 +41,6 @@ import org.springframework.messaging.Message;
|
|||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
|
@ -20,7 +20,9 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
|
||||
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
||||
private SubscriptionDeliveringRestHookSubscriber mySubscriptionDeliverySubscriber;
|
||||
|
||||
@Override
|
||||
|
@ -31,6 +33,10 @@ public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscripti
|
|||
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
|
||||
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterDeliverySubscriber() {
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
/*
|
||||
* #%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
|
||||
|
@ -21,16 +21,13 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
||||
private SubscriptionDeliveringWebsocketSubscriber mySubscriptionDeliverySubscriber;
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao;
|
||||
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionTableDao mySubscriptionTableDao;
|
||||
|
@ -42,16 +39,25 @@ public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscript
|
|||
private IResourceTableDao myResourceTableDao;
|
||||
|
||||
@Override
|
||||
protected void registerDeliverySubscriber() {
|
||||
if (mySubscriptionDeliverySubscriber == null) {
|
||||
mySubscriptionDeliverySubscriber = new SubscriptionDeliveringWebsocketSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, mySubscriptionFlaggedResourceDataDao, mySubscriptionTableDao, myResourceTableDao);
|
||||
}
|
||||
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
||||
public Subscription.SubscriptionChannelType getChannelType() {
|
||||
return Subscription.SubscriptionChannelType.WEBSOCKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerDeliverySubscriber() {
|
||||
/*
|
||||
* nothing, since individual websocket connections
|
||||
* register themselves
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterDeliverySubscriber() {
|
||||
getDeliveryChannel().unsubscribe(mySubscriptionDeliverySubscriber);
|
||||
|
||||
/*
|
||||
* nothing, since individual websocket connections
|
||||
* register themselves
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
|
@ -226,8 +226,6 @@ public abstract class BaseJpaTest {
|
|||
public Void doInTransaction(TransactionStatus theStatus) {
|
||||
entityManager.createQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + SearchParam.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ForcedId.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
|
||||
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu2;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.TestDstu2Config;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hibernate.search.jpa.FullTextEntityManager;
|
||||
import org.hibernate.search.jpa.Search;
|
||||
|
@ -24,26 +33,15 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.TestDstu2Config;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.model.dstu2.composite.*;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import javax.persistence.EntityManager;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
//@formatter:off
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class})
|
||||
//@formatter:on
|
||||
@ContextConfiguration(classes= {TestDstu2Config.class})
|
||||
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
||||
@Autowired
|
||||
protected ApplicationContext myAppCtx;
|
||||
|
|
|
@ -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;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.*;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||
|
@ -35,6 +15,26 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
|||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
||||
|
||||
|
@ -45,7 +45,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
protected static Server ourServer;
|
||||
protected static String ourServerBase;
|
||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||
|
||||
public BaseResourceProviderDstu2Test() {
|
||||
|
@ -97,7 +97,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourWebApplicationContext.setParent(myAppCtx);
|
||||
ourWebApplicationContext.refresh();
|
||||
|
||||
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(RestHookSubscriptionDstu2Interceptor.class);
|
||||
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||
|
||||
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
|
||||
|
||||
|
@ -107,8 +107,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
subsServletHolder.setServlet(dispatcherServlet);
|
||||
subsServletHolder.setInitParameter(
|
||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||
WebsocketDstu2Config.class.getName() + "\n" +
|
||||
WebsocketDstu2DispatcherConfig.class.getName());
|
||||
WebsocketDispatcherConfig.class.getName());
|
||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||
|
||||
|
||||
|
|
|
@ -1,79 +1,32 @@
|
|||
package ca.uhn.fhir.jpa.provider;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.*;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
|
||||
public class BaseSocket {
|
||||
protected String myError;
|
||||
protected boolean myGotBound;
|
||||
protected int myPingCount;
|
||||
protected String mySubsId;
|
||||
|
||||
}
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionsDstu2Test.class);
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
|
||||
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
|
||||
client.stop();
|
||||
|
||||
/*
|
||||
* When websocket closes, the subscription is automatically deleted. This
|
||||
* can cause deadlocks if the test returns too quickly since it also
|
||||
* tries to delete the subscription
|
||||
*/
|
||||
ca.uhn.fhir.model.dstu2.resource.Bundle found = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
found = ourClient.search().forResource("Subscription").returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute();
|
||||
if (found.getEntry().size() > 0) {
|
||||
Thread.sleep(200);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertEquals(0, found.getEntry().size());
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEnableScheduling() {
|
||||
myDaoConfig.setSchedulingDisabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCreateInterceptor() {
|
||||
super.beforeCreateInterceptor();
|
||||
|
@ -83,46 +36,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
myDaoConfig.getInterceptors().add(interceptor);
|
||||
}
|
||||
|
||||
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
|
||||
|
||||
/*
|
||||
* In a separate thread, start a polling for new resources. Normally the scheduler would
|
||||
* take care of this, but that can take longer which makes the unit tests run much slower
|
||||
* so we simulate that part..
|
||||
*/
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
ourLog.warn("Interrupted", e);
|
||||
}
|
||||
ourLog.info("About to poll in separate thread");
|
||||
mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Done poll in separate thread");
|
||||
}}.start();
|
||||
|
||||
ourLog.info("Entering loop");
|
||||
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
|
||||
ourLog.debug("Starting");
|
||||
if (socket.myError != null) {
|
||||
fail(socket.myError);
|
||||
}
|
||||
if (socket.myPingCount >= wantPingCount) {
|
||||
ourLog.info("Breaking loop");
|
||||
break;
|
||||
}
|
||||
ourLog.debug("Sleeping");
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
|
||||
|
||||
assertNull(socket.myError, socket.myError);
|
||||
assertEquals(wantPingCount, socket.myPingCount);
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEnableScheduling() {
|
||||
myDaoConfig.setSchedulingDisabled(false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
|
@ -147,6 +71,75 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourClient.update().resource(subs).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidWrongStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
subs.getChannel().setPayload("application/fhir+json");
|
||||
subs.getChannel().setEndpoint("http://foo");
|
||||
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
try {
|
||||
ourClient.create().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
|
||||
}
|
||||
|
||||
subs.setId("ABC");
|
||||
try {
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithPopulatedButInvalidStatue() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getStatusElement().setValue("aaaaa");
|
||||
|
||||
try {
|
||||
ourClient.create().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateFails() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
subs.setId(id);
|
||||
|
||||
try {
|
||||
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
subs.setStatus((SubscriptionStatusEnum) null);
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
|
||||
}
|
||||
|
||||
subs.setStatus(SubscriptionStatusEnum.OFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateToInvalidStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
|
@ -176,262 +169,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourClient.update().resource(subs).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithPopulatedButInvalidStatue() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getStatusElement().setValue("aaaaa");
|
||||
|
||||
try {
|
||||
ourClient.create().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
|
||||
}
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidWrongStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
subs.getChannel().setPayload("application/fhir+json");
|
||||
subs.getChannel().setEndpoint("http://foo");
|
||||
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
try {
|
||||
ourClient.create().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
|
||||
}
|
||||
public class BaseSocket {
|
||||
protected String myError;
|
||||
protected boolean myGotBound;
|
||||
protected int myPingCount;
|
||||
protected String mySubsId;
|
||||
|
||||
subs.setId("ABC");
|
||||
try {
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamic() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamicXml() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
ResourceMetadataKeyEnum.PROFILES.put(obs, Collections.singletonList(new IdDt("http://foo")));
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionSimple() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionResourcesAppear";
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Subscription subs = new Subscription();
|
||||
ResourceMetadataKeyEnum.PROFILES.put(subs, Collections.singletonList(new IdDt("http://foo")));
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
|
||||
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
client.connect(socket, echoUri, request);
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReference(pId);
|
||||
obs.setStatus(ObservationStatusEnum.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 2);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateFails() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
subs.setId(id);
|
||||
|
||||
try {
|
||||
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
subs.setStatus((SubscriptionStatusEnum) null);
|
||||
ourClient.update().resource(subs).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
|
||||
}
|
||||
|
||||
subs.setStatus(SubscriptionStatusEnum.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,34 +1,12 @@
|
|||
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.*;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.*;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
|
||||
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
|
@ -39,6 +17,32 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
|||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||
|
||||
|
@ -49,11 +53,11 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
protected static RestfulServer ourRestServer;
|
||||
private static Server ourServer;
|
||||
protected static String ourServerBase;
|
||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
|
||||
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
|
||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||
protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static ISearchDao mySearchEntityDao;
|
||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
|
||||
|
@ -119,8 +123,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
subsServletHolder.setServlet(dispatcherServlet);
|
||||
subsServletHolder.setInitParameter(
|
||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||
WebsocketDstu3Config.class.getName() + "\n" +
|
||||
WebsocketDstu3DispatcherConfig.class.getName());
|
||||
WebsocketDispatcherConfig.class.getName());
|
||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||
|
||||
// Register a CORS filter
|
||||
|
@ -146,7 +149,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
|
||||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
||||
ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionDstu3Interceptor.class);
|
||||
ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
|
||||
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.*;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
|
@ -31,12 +29,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
|
||||
private static final String WEBSOCKET_PATH = "/websocket/dstu3";
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void beforeCreateInterceptor() {
|
||||
super.beforeCreateInterceptor();
|
||||
|
@ -46,72 +38,17 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
myDaoConfig.getInterceptors().add(interceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEnableScheduling() {
|
||||
myDaoConfig.setSchedulingDisabled(false);
|
||||
}
|
||||
|
||||
|
||||
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
|
||||
/*
|
||||
* In a separate thread, start a polling for new resources. Normally the scheduler would
|
||||
* take care of this, but that can take longer which makes the unit tests run much slower
|
||||
* so we simulate that part..
|
||||
*/
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
ourLog.warn("Interrupted", e);
|
||||
}
|
||||
ourLog.info("About to poll in separate thread");
|
||||
mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Done poll in separate thread");
|
||||
}}.start();
|
||||
|
||||
ourLog.info("Entering loop");
|
||||
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
|
||||
ourLog.debug("Starting");
|
||||
if (socket.myError != null) {
|
||||
fail(socket.myError);
|
||||
}
|
||||
if (socket.myPingCount >= wantPingCount) {
|
||||
ourLog.info("Breaking loop");
|
||||
break;
|
||||
}
|
||||
ourLog.debug("Sleeping");
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
|
||||
|
||||
assertNull(socket.myError, socket.myError);
|
||||
assertEquals(wantPingCount, socket.myPingCount);
|
||||
}
|
||||
|
||||
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
|
||||
client.stop();
|
||||
|
||||
/*
|
||||
* When websocket closes, the subscription is automatically deleted. This
|
||||
* can cause deadlocks if the test returns too quickly since it also
|
||||
* tries to delete the subscription
|
||||
*/
|
||||
Bundle found = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute();
|
||||
if (found.getEntry().size() > 0) {
|
||||
Thread.sleep(200);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertEquals(0, found.getEntry().size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
|
@ -162,196 +99,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamic() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamicXml() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getMeta().addProfile("http://foo");
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionSimple() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionResourcesAppear";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Subscription subs = new Subscription();
|
||||
subs.getMeta().addProfile("http://foo");
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
|
||||
subs.setStatus(SubscriptionStatus.ACTIVE);
|
||||
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
client.connect(socket, echoUri, request);
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 2);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateFails() {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4Config;
|
||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
|
@ -58,7 +57,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
protected static ISearchDao mySearchEntityDao;
|
||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
private static Server ourServer;
|
||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
|
||||
private Object ourGraphQLProvider;
|
||||
private boolean ourRestHookSubscriptionInterceptorRequested;
|
||||
|
@ -122,8 +121,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
subsServletHolder.setServlet(dispatcherServlet);
|
||||
subsServletHolder.setInitParameter(
|
||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||
WebsocketR4Config.class.getName() + "\n" +
|
||||
WebsocketR4DispatcherConfig.class.getName());
|
||||
WebsocketDispatcherConfig.class.getName());
|
||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||
|
||||
// Register a CORS filter
|
||||
|
@ -172,8 +170,8 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
/**
|
||||
* This is lazy created so we only ask for it if its needed
|
||||
*/
|
||||
protected RestHookSubscriptionR4Interceptor getRestHookSubscriptionInterceptor() {
|
||||
RestHookSubscriptionR4Interceptor retVal = ourWebApplicationContext.getBean(RestHookSubscriptionR4Interceptor.class);
|
||||
protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() {
|
||||
SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourRestHookSubscriptionInterceptorRequested = true;
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.*;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
|
@ -31,12 +29,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
|||
|
||||
private static final String WEBSOCKET_PATH = "/websocket/r4";
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void beforeCreateInterceptor() {
|
||||
super.beforeCreateInterceptor();
|
||||
|
@ -46,72 +38,17 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
|||
myDaoConfig.getInterceptors().add(interceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEnableScheduling() {
|
||||
myDaoConfig.setSchedulingDisabled(false);
|
||||
}
|
||||
|
||||
|
||||
private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException {
|
||||
/*
|
||||
* In a separate thread, start a polling for new resources. Normally the scheduler would
|
||||
* take care of this, but that can take longer which makes the unit tests run much slower
|
||||
* so we simulate that part..
|
||||
*/
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
ourLog.warn("Interrupted", e);
|
||||
}
|
||||
ourLog.info("About to poll in separate thread");
|
||||
mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Done poll in separate thread");
|
||||
}}.start();
|
||||
|
||||
ourLog.info("Entering loop");
|
||||
for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) {
|
||||
ourLog.debug("Starting");
|
||||
if (socket.myError != null) {
|
||||
fail(socket.myError);
|
||||
}
|
||||
if (socket.myPingCount >= wantPingCount) {
|
||||
ourLog.info("Breaking loop");
|
||||
break;
|
||||
}
|
||||
ourLog.debug("Sleeping");
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError);
|
||||
|
||||
assertNull(socket.myError, socket.myError);
|
||||
assertEquals(wantPingCount, socket.myPingCount);
|
||||
}
|
||||
|
||||
private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception {
|
||||
client.stop();
|
||||
|
||||
/*
|
||||
* When websocket closes, the subscription is automatically deleted. This
|
||||
* can cause deadlocks if the test returns too quickly since it also
|
||||
* tries to delete the subscription
|
||||
*/
|
||||
Bundle found = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute();
|
||||
if (found.getEntry().size() > 0) {
|
||||
Thread.sleep(200);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertEquals(0, found.getEntry().size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoStatus() {
|
||||
Subscription subs = new Subscription();
|
||||
|
@ -162,195 +99,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamic() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart();
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSubscriptionDynamicXml() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionDynamic";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml";
|
||||
DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
client.connect(socket, echoUri, new ClientUpgradeRequest());
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getMeta().addProfile("http://foo");
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
sleepUntilPingCount(socket, 3);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(0);
|
||||
assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = (Observation) socket.myReceived.get(1);
|
||||
assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 4);
|
||||
|
||||
obs = (Observation) socket.myReceived.get(2);
|
||||
assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
||||
} finally {
|
||||
try {
|
||||
stopClientAndWaitForStopped(client);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionSimple() throws Exception {
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0);
|
||||
|
||||
String methodName = "testSubscriptionResourcesAppear";
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Subscription subs = new Subscription();
|
||||
subs.getMeta().addProfile("http://foo");
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
|
||||
subs.setStatus(SubscriptionStatus.ACTIVE);
|
||||
String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
SimpleEchoSocket socket = new SimpleEchoSocket(subsId);
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
client.connect(socket, echoUri, request);
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
|
||||
sleepUntilPingCount(socket, 1);
|
||||
|
||||
obs = new Observation();
|
||||
obs.getSubject().setReferenceElement(pId);
|
||||
obs.setStatus(ObservationStatus.FINAL);
|
||||
IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
sleepUntilPingCount(socket, 2);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
|
@ -32,8 +31,7 @@ import org.junit.*;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
|||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
@ -25,23 +26,10 @@ import ca.uhn.fhir.model.dstu2.valueset.*;
|
|||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createSubscription' test
|
||||
* 2. Update the subscription id in the 'attachWebSocket' test
|
||||
* 3. Execute the 'attachWebSocket' test
|
||||
* 4. Execute the 'sendObservation' test
|
||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu2Test.class);
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||
@Ignore
|
||||
public class WebsocketWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu2Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -129,13 +117,8 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -158,12 +141,7 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
|
@ -20,26 +20,11 @@ import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
|||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createPatient' test
|
||||
* 2. Update the patient id static variable
|
||||
* 3. Execute the 'createSubscription' test
|
||||
* 4. Update the subscription id static variable
|
||||
* 5. Execute the 'attachWebSocket' test
|
||||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||
@Ignore
|
||||
public class WebsocketWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu3Test.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu3Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -127,13 +112,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -155,14 +135,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD
|
|||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
@ -40,8 +41,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
* 4. Execute the 'sendObservation' test
|
||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2Test.class);
|
||||
public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu2Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -53,6 +54,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
|||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -62,6 +66,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
|||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
@ -95,7 +102,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
|||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
|
@ -129,13 +136,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -158,13 +160,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
@ -37,9 +38,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu3Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -51,12 +52,19 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
|||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
|
@ -93,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
|||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3");
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
|
@ -127,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -156,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.subscription.r4;
|
|||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
@ -16,7 +16,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
|
@ -35,9 +34,9 @@ import static org.junit.Assert.*;
|
|||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProviderR4Test {
|
||||
public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithEventDefinitionR4Test.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookWithEventDefinitionR4Test.class);
|
||||
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
|
||||
private static List<String> ourContentTypes = new ArrayList<>();
|
||||
private static List<String> ourHeaders = new ArrayList<>();
|
||||
|
@ -80,6 +79,7 @@ public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProvi
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSubscriptionAddedTrigger() {
|
||||
/*
|
||||
* Create patient
|
|
@ -21,26 +21,12 @@ import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
|||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdR4Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createPatient' test
|
||||
* 2. Update the patient id static variable
|
||||
* 3. Execute the 'createSubscription' test
|
||||
* 4. Update the subscription id static variable
|
||||
* 5. Execute the 'attachWebSocket' test
|
||||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaR4Test.class);
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||
@Ignore
|
||||
public class WebsocketWithCriteriaR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaR4Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -130,13 +116,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -159,13 +140,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import java.net.URI;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
@ -38,9 +39,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProviderR4Test {
|
||||
public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdR4Test.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdR4Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
|
@ -52,6 +53,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
|||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -61,6 +65,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
|||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
@ -94,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
|||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4");
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
|
@ -128,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
|
@ -157,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid
|
|||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
waitForSize(2, mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -1,35 +1,42 @@
|
|||
package ca.uhn.fhirtest;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import ca.uhn.fhirtest.config.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.*;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.interceptor.*;
|
||||
import ca.uhn.fhirtest.config.*;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class TestRestfulServer extends RestfulServer {
|
||||
|
||||
|
@ -80,7 +87,7 @@ public class TestRestfulServer extends RestfulServer {
|
|||
myAppCtx.register(TdlDstu2Config.class);
|
||||
baseUrlProperty = FHIR_BASEURL_TDL2;
|
||||
} else {
|
||||
myAppCtx.register(TestDstu2Config.class, WebsocketDstu2DispatcherConfig.class);
|
||||
myAppCtx.register(TestDstu2Config.class, WebsocketDispatcherConfig.class);
|
||||
baseUrlProperty = FHIR_BASEURL_DSTU2;
|
||||
}
|
||||
myAppCtx.refresh();
|
||||
|
@ -103,7 +110,7 @@ public class TestRestfulServer extends RestfulServer {
|
|||
myAppCtx.register(TdlDstu3Config.class);
|
||||
baseUrlProperty = FHIR_BASEURL_TDL3;
|
||||
} else {
|
||||
myAppCtx.register(TestDstu3Config.class, WebsocketDstu3DispatcherConfig.class);
|
||||
myAppCtx.register(TestDstu3Config.class, WebsocketDispatcherConfig.class);
|
||||
baseUrlProperty = FHIR_BASEURL_DSTU3;
|
||||
}
|
||||
myAppCtx.refresh();
|
||||
|
@ -122,7 +129,7 @@ public class TestRestfulServer extends RestfulServer {
|
|||
myAppCtx = new AnnotationConfigWebApplicationContext();
|
||||
myAppCtx.setServletConfig(getServletConfig());
|
||||
myAppCtx.setParent(parentAppCtx);
|
||||
myAppCtx.register(TestR4Config.class, WebsocketR4DispatcherConfig.class);
|
||||
myAppCtx.register(TestR4Config.class, WebsocketDispatcherConfig.class);
|
||||
baseUrlProperty = FHIR_BASEURL_R4;
|
||||
myAppCtx.refresh();
|
||||
setFhirContext(FhirContext.forR4());
|
||||
|
|
|
@ -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
|
||||
with the same URI as the previous one
|
||||
</action>
|
||||
<action type="fix">
|
||||
When uploading a Bundle resource to the server (as a collection or
|
||||
document, not as a transaction) the ID was incorrectly stripped from
|
||||
resources being saved within the Bundle. This has been corrected.
|
||||
</action>
|
||||
</release>
|
||||
<release version="2.5" date="2017-06-08">
|
||||
<action type="fix">
|
||||
|
|
Loading…
Reference in New Issue