Refactor websockets to use queuing mechanism

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,20 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import org.hl7.fhir.instance.hapi.validation.*; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel; import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.*; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
/* /*
@ -15,9 +26,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -26,14 +37,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
@Configuration @Configuration
@EnableTransactionManagement @EnableTransactionManagement
public class BaseDstu2Config extends BaseConfig { public class BaseDstu2Config extends BaseConfig {
@ -65,12 +68,6 @@ public class BaseDstu2Config extends BaseConfig {
return ourFhirContextDstu2Hl7Org; return ourFhirContextDstu2Hl7Org;
} }
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(name = "myInstanceValidatorDstu2") @Bean(name = "myInstanceValidatorDstu2")
@Lazy @Lazy
public IValidatorModule instanceValidatorDstu2() { public IValidatorModule instanceValidatorDstu2() {
@ -80,6 +77,12 @@ public class BaseDstu2Config extends BaseConfig {
return retVal; return retVal;
} }
@Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() {
ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2();
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDao() { public IFulltextSearchSvc searchDao() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl(); FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();
@ -115,10 +118,4 @@ public class BaseDstu2Config extends BaseConfig {
return new HapiTerminologySvcDstu2(); return new HapiTerminologySvcDstu2();
} }
@Bean
@Lazy
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() {
return new RestHookSubscriptionDstu2Interceptor();
}
} }

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -20,38 +20,32 @@ package ca.uhn.fhir.jpa.config;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Controller;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
@Configuration @Configuration
@EnableWebSocket() @EnableWebSocket()
public class WebsocketDstu2Config implements WebSocketConfigurer { @Controller
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketDstu2Config.class); public class WebsocketDispatcherConfig implements WebSocketConfigurer {
@Override @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) { public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2").setAllowedOrigins("*"); theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket").setAllowedOrigins("*");
} }
@Bean(autowire = Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public WebSocketHandler subscriptionWebSocketHandler() { public WebSocketHandler subscriptionWebSocketHandler() {
return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class); PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandler.class);
}
@Bean
public TaskScheduler websocketTaskScheduler() {
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
retVal.setPoolSize(5);
return retVal; return retVal;
} }

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
@Configuration
public class WebsocketDstu2DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerDstu2.setCtx(myCtx);
SubscriptionWebsocketHandlerDstu2.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

@ -1,36 +1,5 @@
package ca.uhn.fhir.jpa.config.dstu3; package ca.uhn.fhir.jpa.config.dstu3;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.BaseConfig;
@ -40,7 +9,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
@ -48,6 +16,35 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
@Configuration @Configuration
@EnableTransactionManagement @EnableTransactionManagement
@ -133,10 +130,4 @@ public class BaseDstu3Config extends BaseConfig {
return new JpaValidationSupportChainDstu3(); return new JpaValidationSupportChainDstu3();
} }
@Bean
@Lazy
public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() {
return new RestHookSubscriptionDstu3Interceptor();
}
} }

View File

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

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.annotation.PostConstruct;
import org.hl7.fhir.dstu3.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.dstu3.SubscriptionWebsocketHandlerDstu3;
@Configuration
public class WebsocketDstu3DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<org.hl7.fhir.dstu3.model.Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerDstu3.setCtx(myCtx);
SubscriptionWebsocketHandlerDstu3.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

@ -1,9 +1,31 @@
package ca.uhn.fhir.jpa.config.r4; package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider; import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel; import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/* /*
* #%L * #%L
@ -14,9 +36,9 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -25,23 +47,6 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
* #L% * #L%
*/ */
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
@Configuration @Configuration
@EnableTransactionManagement @EnableTransactionManagement
public class BaseR4Config extends BaseConfig { public class BaseR4Config extends BaseConfig {
@ -131,11 +136,5 @@ public class BaseR4Config extends BaseConfig {
public IValidationSupport validationSupportChainR4() { public IValidationSupport validationSupportChainR4() {
return new JpaValidationSupportChainR4(); return new JpaValidationSupportChainR4();
} }
@Bean
@Lazy
public RestHookSubscriptionR4Interceptor restHookSubscriptionR4Interceptor() {
return new RestHookSubscriptionR4Interceptor();
}
} }

View File

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

View File

@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.config.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.annotation.PostConstruct;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4;
@Configuration
public class WebsocketR4DispatcherConfig {
@Autowired
private FhirContext myCtx;
@Autowired
private IFhirResourceDao<Subscription> mySubscriptionDao;
@PostConstruct
public void postConstruct() {
SubscriptionWebsocketHandlerR4.setCtx(myCtx);
SubscriptionWebsocketHandlerR4.setSubscriptionDao((IFhirResourceDaoSubscription<Subscription>) mySubscriptionDao);
}
}

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import java.util.List; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
/* /*
* #%L * #%L
@ -11,9 +12,9 @@ import java.util.List;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,20 +23,8 @@ import java.util.List;
* #L% * #L%
*/ */
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> { public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
int pollForNewUndeliveredResources();
List<IBaseResource> getUndeliveredResourcesAndPurge(Long theSubscriptionPid);
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId); Long getSubscriptionTablePidForSubscriptionResource(IIdType theId);
void purgeInactiveSubscriptions();
void pollForNewUndeliveredResourcesScheduler();
int pollForNewUndeliveredResources(String resourceType);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,72 +0,0 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.persistence.*;
@Entity
@Table(name = "HFJ_SUBSCRIPTION_FLAG_RES")
public class SubscriptionFlaggedResource {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SUBSCRIPTION_FLAG_ID")
@SequenceGenerator(name = "SEQ_SUBSCRIPTION_FLAG_ID", sequenceName = "SEQ_SUBSCRIPTION_FLAG_ID")
@Column(name = "PID", insertable = false, updatable = false)
private Long myId;
@ManyToOne()
@JoinColumn(name = "RES_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_SUBSFLAGRES_RES"))
private ResourceTable myResource;
@ManyToOne()
@JoinColumn(name = "SUBSCRIPTION_ID",
foreignKey = @ForeignKey(name = "FK_SUBSFLAG_SUBS")
)
private SubscriptionTable mySubscription;
@Column(name = "RES_VERSION", nullable = false)
private Long myVersion;
public ResourceTable getResource() {
return myResource;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
public SubscriptionTable getSubscription() {
return mySubscription;
}
public void setSubscription(SubscriptionTable theSubscription) {
mySubscription = theSubscription;
}
public Long getVersion() {
return myVersion;
}
public void setVersion(Long theVersion) {
myVersion = theVersion;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,157 +0,0 @@
package ca.uhn.fhir.jpa.subscription;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.gclient.IClientExecutable;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
public class SubscriptionDeliveringWebsocketSubscriber extends BaseSubscriptionSubscriber {
private final PlatformTransactionManager myTxManager;
private final ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDao;
private final ISubscriptionTableDao mySubscriptionTableDao;
private final IResourceTableDao myResourceTableDao;
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringWebsocketSubscriber.class);
public SubscriptionDeliveringWebsocketSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTxManager, ISubscriptionFlaggedResourceDataDao theSubscriptionFlaggedResourceDataDao, ISubscriptionTableDao theSubscriptionTableDao, IResourceTableDao theResourceTableDao) {
super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
myTxManager = theTxManager;
mySubscriptionFlaggedResourceDao = theSubscriptionFlaggedResourceDataDao;
mySubscriptionTableDao = theSubscriptionTableDao;
myResourceTableDao = theResourceTableDao;
}
@Override
public void handleMessage(final Message<?> theMessage) throws MessagingException {
if (!(theMessage.getPayload() instanceof ResourceDeliveryMessage)) {
return;
}
final ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload();
if (!subscriptionTypeApplies(getContext(), msg.getSubscription().getBackingSubscription())) {
return;
}
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
IBaseResource payload = msg.getPayload();
Long payloadPid = extractResourcePid(payload);
ResourceTable payloadTable = myResourceTableDao.findOne(payloadPid);
IBaseResource subscription = msg.getSubscription().getBackingSubscription();
Long subscriptionPid = extractResourcePid(subscription);
SubscriptionTable subscriptionTable = mySubscriptionTableDao.findOneByResourcePid(subscriptionPid);
ourLog.info("Adding new resource {} for subscription: {}", payload.getIdElement().toUnqualified().getValue(), subscription.getIdElement().toUnqualifiedVersionless().getValue());
SubscriptionFlaggedResource candidate = new SubscriptionFlaggedResource();
candidate.setResource(payloadTable);
candidate.setSubscription(subscriptionTable);
candidate.setVersion(payload.getIdElement().getVersionIdPartAsLong());
mySubscriptionFlaggedResourceDao.save(candidate);
}
});
RestOperationTypeEnum operationType = msg.getOperationType();
CanonicalSubscription subscription = msg.getSubscription();
// Grab the endpoint from the subscription
String endpointUrl = subscription.getEndpointUrl();
// Grab the payload type (encoding mimetype ) from the subscription
String payloadString = subscription.getPayloadString();
if (payloadString.contains(";")) {
payloadString = payloadString.substring(0, payloadString.indexOf(';'));
}
payloadString = payloadString.trim();
EncodingEnum payloadType = EncodingEnum.forContentType(payloadString);
payloadType = ObjectUtils.defaultIfNull(payloadType, EncodingEnum.XML);
getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
IGenericClient client = getContext().newRestfulGenericClient(endpointUrl);
IBaseResource payloadResource = msg.getPayload();
IClientExecutable<?, ?> operation;
switch (operationType) {
case CREATE:
operation = client.create().resource(payloadResource);
break;
case UPDATE:
operation = client.update().resource(payloadResource);
break;
case DELETE:
operation = client.delete().resourceById(msg.getPayloadId());
break;
default:
ourLog.warn("Ignoring delivery message of type: {}", msg.getOperationType());
return;
}
operation.encoded(payloadType);
ourLog.info("Delivering {} rest-hook payload {} for {}", operationType, payloadResource.getIdElement().toUnqualified().getValue(), subscription.getIdElement().toUnqualifiedVersionless().getValue());
operation.execute();
}
private Long extractResourcePid(IBaseResource thePayoad) {
Long pid;
if (thePayoad instanceof IResource) {
pid = IDao.RESOURCE_PID.get((IResource) thePayoad);
} else {
pid = IDao.RESOURCE_PID.get((IAnyResource) thePayoad);
}
return pid;
}
}

View File

@ -1,75 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays;
public class RestHookSubscriptionDstu2Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

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

View File

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

View File

@ -1,51 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu2;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class WebSocketSubscriptionDstu2Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionDstu2Interceptor.doCanonicalize(theSubscription);
}
@Override
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,79 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Arrays;
public class RestHookSubscriptionDstu3Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<Subscription> mySubscriptionDao;
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
try {
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
throw new InternalErrorException(theE);
}
return retVal;
}
}

View File

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

View File

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

View File

@ -1,64 +0,0 @@
package ca.uhn.fhir.jpa.subscription.dstu3;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
public class WebSocketSubscriptionDstu3Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<org.hl7.fhir.dstu3.model.Subscription> mySubscriptionDao;
@Override
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionDstu3Interceptor.doCanonicalize(theSubscription);
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

@ -1,97 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.EventDefinition;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.TriggerDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.List;
public class RestHookSubscriptionR4Interceptor extends BaseSubscriptionRestHookInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> mySubscriptionDao;
@Autowired
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDao;
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
public void setSubscriptionDao(IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> theSubscriptionDao) {
mySubscriptionDao = theSubscriptionDao;
}
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return doCanonicalize(theSubscription, myEventDefinitionDao);
}
static CanonicalSubscription doCanonicalize(IBaseResource theSubscription, IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> theEventDefinitionDao) {
Subscription subscription = (Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
retVal.setStatus(subscription.getStatus());
retVal.setBackingSubscription(theSubscription);
retVal.setChannelType(subscription.getChannel().getType());
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
List<Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
if (topicExts.size() > 0) {
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
}
EventDefinition def = theEventDefinitionDao.read(ref.getReferenceElement());
retVal.addTrigger(def.getTrigger());
}
return retVal;
}
}

View File

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

View File

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

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.jpa.subscription.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionWebsocketInterceptor;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class WebSocketSubscriptionR4Interceptor extends BaseSubscriptionWebsocketInterceptor {
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.Subscription> mySubscriptionDao;
@Autowired
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDao;
@Override
protected CanonicalSubscription canonicalize(IBaseResource theSubscription) {
return RestHookSubscriptionR4Interceptor.doCanonicalize(theSubscription, myEventDefinitionDao);
}
@Override
public Subscription.SubscriptionChannelType getChannelType() {
return Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Override
protected IFhirResourceDao<?> getSubscriptionDao() {
return mySubscriptionDao;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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