diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 6b550b68143..6e5b8290027 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -19,16 +19,6 @@ package ca.uhn.fhir.parser; * limitations under the License. * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.*; -import java.lang.reflect.Modifier; -import java.util.*; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; @@ -37,6 +27,16 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.UrlUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.*; + +import java.io.*; +import java.lang.reflect.Modifier; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseParser implements IParser { @@ -63,7 +63,7 @@ public abstract class BaseParser implements IParser { /** * Constructor - * + * * @param theParserErrorHandler */ public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { @@ -71,7 +71,7 @@ public abstract class BaseParser implements IParser { myErrorHandler = theParserErrorHandler; } - protected Iterable compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent) { + protected Iterable compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final boolean theSubResource, final CompositeChildElement theParent) { BaseRuntimeElementCompositeDefinition elementDef = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(theCompositeElement.getClass()); final List children = elementDef.getChildrenAndExtension(); @@ -87,8 +87,7 @@ public abstract class BaseParser implements IParser { /** * Constructor - */ - { + */ { myChildrenIter = children.iterator(); } @@ -105,7 +104,7 @@ public abstract class BaseParser implements IParser { return false; } - myNext = new CompositeChildElement(theParent, myChildrenIter.next()); + myNext = new CompositeChildElement(theParent, myChildrenIter.next(), theSubResource); /* * There are lots of reasons we might skip encoding a particular child @@ -282,40 +281,6 @@ public abstract class BaseParser implements IParser { return ref.getValue(); } - private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) { - Boolean stripVersionsFromReferences = myStripVersionsFromReferences; - if (stripVersionsFromReferences != null) { - return stripVersionsFromReferences; - } - - if (myContext.getParserOptions().isStripVersionsFromReferences() == false) { - return false; - } - - Set dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths; - if (dontStripVersionsFromReferencesAtPaths != null) { - if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) { - return false; - } - } - - dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths(); - if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) { - return false; - } - - return true; - } - - private boolean isOverrideResourceIdWithBundleEntryFullUrl() { - Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl; - if (overrideResourceIdWithBundleEntryFullUrl != null) { - return overrideResourceIdWithBundleEntryFullUrl; - } - - return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl(); - } - protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; protected abstract T doParseResource(Class theResourceType, Reader theReader) throws DataFormatException; @@ -338,7 +303,7 @@ public abstract class BaseParser implements IParser { if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { throw new IllegalArgumentException( - "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); + "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); } doEncodeResourceToWriter(theResource, theWriter); @@ -424,6 +389,11 @@ public abstract class BaseParser implements IParser { return myContainedResources; } + @Override + public Set getDontStripVersionsFromReferencesAtPaths() { + return myDontStripVersionsFromReferencesAtPaths; + } + /** * See {@link #setEncodeElements(Set)} */ @@ -432,6 +402,21 @@ public abstract class BaseParser implements IParser { return myEncodeElements; } + @Override + public void setEncodeElements(Set theEncodeElements) { + myEncodeElementsIncludesStars = false; + if (theEncodeElements == null || theEncodeElements.isEmpty()) { + myEncodeElements = null; + } else { + myEncodeElements = theEncodeElements; + for (String next : theEncodeElements) { + if (next.startsWith("*.")) { + myEncodeElementsIncludesStars = true; + } + } + } + } + /** * See {@link #setEncodeElementsAppliesToResourceTypes(Set)} */ @@ -440,15 +425,38 @@ public abstract class BaseParser implements IParser { return myEncodeElementsAppliesToResourceTypes; } + @Override + public void setEncodeElementsAppliesToResourceTypes(Set theEncodeElementsAppliesToResourceTypes) { + if (theEncodeElementsAppliesToResourceTypes == null || theEncodeElementsAppliesToResourceTypes.isEmpty()) { + myEncodeElementsAppliesToResourceTypes = null; + } else { + myEncodeElementsAppliesToResourceTypes = theEncodeElementsAppliesToResourceTypes; + } + } + @Override public IIdType getEncodeForceResourceId() { return myEncodeForceResourceId; } + @Override + public BaseParser setEncodeForceResourceId(IIdType theEncodeForceResourceId) { + myEncodeForceResourceId = theEncodeForceResourceId; + return this; + } + protected IParserErrorHandler getErrorHandler() { return myErrorHandler; } + protected String getExtensionUrl(final String extensionUrl) { + String url = extensionUrl; + if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) { + url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl; + } + return url; + } + protected TagList getMetaTagsForEncoding(IResource theIResource) { TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(theIResource); if (shouldAddSubsettedTag()) { @@ -459,24 +467,44 @@ public abstract class BaseParser implements IParser { return tags; } + @Override + public Boolean getOverrideResourceIdWithBundleEntryFullUrl() { + return myOverrideResourceIdWithBundleEntryFullUrl; + } + @Override public List> getPreferTypes() { return myPreferTypes; } + @Override + public void setPreferTypes(List> thePreferTypes) { + if (thePreferTypes != null) { + ArrayList> types = new ArrayList>(); + for (Class next : thePreferTypes) { + if (Modifier.isAbstract(next.getModifiers()) == false) { + types.add(next); + } + } + myPreferTypes = Collections.unmodifiableList(types); + } else { + myPreferTypes = thePreferTypes; + } + } + @SuppressWarnings("deprecation") protected > List getProfileTagsForEncoding(IBaseResource theResource, List theProfiles) { switch (myContext.getAddProfileTagWhenEncoding()) { - case NEVER: - return theProfiles; - case ONLY_FOR_CUSTOM: - RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); - if (resDef.isStandardType()) { + case NEVER: return theProfiles; - } - break; - case ALWAYS: - break; + case ONLY_FOR_CUSTOM: + RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); + if (resDef.isStandardType()) { + return theProfiles; + } + break; + case ALWAYS: + break; } RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(theResource); @@ -503,10 +531,19 @@ public abstract class BaseParser implements IParser { return theProfiles; } + protected String getServerBaseUrl() { + return myServerBaseUrl; + } + + @Override + public Boolean getStripVersionsFromReferences() { + return myStripVersionsFromReferences; + } + /** * If set to true (default is false), narratives will not be included in the encoded * values. - * + * * @deprecated Use {@link #isSuppressNarratives()} */ @Deprecated @@ -516,7 +553,7 @@ public abstract class BaseParser implements IParser { protected boolean isChildContained(BaseRuntimeElementDefinition childDef, boolean theIncludedResource) { return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false - && theIncludedResource == false; + && theIncludedResource == false; } @Override @@ -524,14 +561,38 @@ public abstract class BaseParser implements IParser { return myOmitResourceId; } - @Override - public Boolean getStripVersionsFromReferences() { - return myStripVersionsFromReferences; + private boolean isOverrideResourceIdWithBundleEntryFullUrl() { + Boolean overrideResourceIdWithBundleEntryFullUrl = myOverrideResourceIdWithBundleEntryFullUrl; + if (overrideResourceIdWithBundleEntryFullUrl != null) { + return overrideResourceIdWithBundleEntryFullUrl; + } + + return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl(); } - @Override - public Boolean getOverrideResourceIdWithBundleEntryFullUrl() { - return myOverrideResourceIdWithBundleEntryFullUrl; + private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) { + Boolean stripVersionsFromReferences = myStripVersionsFromReferences; + if (stripVersionsFromReferences != null) { + return stripVersionsFromReferences; + } + + if (myContext.getParserOptions().isStripVersionsFromReferences() == false) { + return false; + } + + Set 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 @@ -542,7 +603,7 @@ public abstract class BaseParser implements IParser { /** * If set to true (default is false), narratives will not be included in the encoded * values. - * + * * @since 1.2 */ public boolean isSuppressNarratives() { @@ -623,9 +684,8 @@ public abstract class BaseParser implements IParser { return parseResource(null, theMessageString); } - protected List preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List theValues, - CompositeChildElement theCompositeChildElement) { + CompositeChildElement theCompositeChildElement) { if (myContext.getVersion().getVersion().isRi()) { /* @@ -727,82 +787,6 @@ public abstract class BaseParser implements IParser { } } - @Override - public void setEncodeElements(Set 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 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> thePreferTypes) { - if (thePreferTypes != null) { - ArrayList> types = new ArrayList>(); - for (Class next : thePreferTypes) { - if (Modifier.isAbstract(next.getModifiers()) == false) { - types.add(next); - } - } - myPreferTypes = Collections.unmodifiableList(types); - } else { - myPreferTypes = thePreferTypes; - } - } - - @Override - public IParser setServerBaseUrl(String theUrl) { - myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; - return this; - } - - @Override - public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) { - myStripVersionsFromReferences = theStripVersionsFromReferences; - return this; - } - - @Override - public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) { - myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl; - return this; - } - @Override public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) { if (thePaths == null) { @@ -827,8 +811,34 @@ public abstract class BaseParser implements IParser { } @Override - public Set getDontStripVersionsFromReferencesAtPaths() { - return myDontStripVersionsFromReferencesAtPaths; + public IParser setOmitResourceId(boolean theOmitResourceId) { + myOmitResourceId = theOmitResourceId; + return this; + } + + @Override + public IParser setOverrideResourceIdWithBundleEntryFullUrl(Boolean theOverrideResourceIdWithBundleEntryFullUrl) { + myOverrideResourceIdWithBundleEntryFullUrl = theOverrideResourceIdWithBundleEntryFullUrl; + return this; + } + + @Override + public IParser setParserErrorHandler(IParserErrorHandler theErrorHandler) { + Validate.notNull(theErrorHandler, "theErrorHandler must not be null"); + myErrorHandler = theErrorHandler; + return this; + } + + @Override + public IParser setServerBaseUrl(String theUrl) { + myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; + return this; + } + + @Override + public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) { + myStripVersionsFromReferences = theStripVersionsFromReferences; + return this; } @Override @@ -847,7 +857,7 @@ public abstract class BaseParser implements IParser { return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null; } - protected boolean shouldEncodeResourceId(IBaseResource theResource) { + protected boolean shouldEncodeResourceId(IBaseResource theResource, boolean theSubResource) { boolean retVal = true; if (isOmitResourceId()) { retVal = false; @@ -858,24 +868,14 @@ public abstract class BaseParser implements IParser { retVal = false; } else if (myDontEncodeElements.contains("*.id")) { retVal = false; + } else if (theSubResource == false && myDontEncodeElements.contains("id")) { + retVal = false; } } } return retVal; } - protected String getExtensionUrl(final String extensionUrl) { - String url = extensionUrl; - if (StringUtils.isNotBlank(extensionUrl) && StringUtils.isNotBlank(myServerBaseUrl)) { - url = !UrlUtil.isValid(extensionUrl) && extensionUrl.startsWith("/") ? myServerBaseUrl + extensionUrl : extensionUrl; - } - return url; - } - - protected String getServerBaseUrl() { - return myServerBaseUrl; - } - /** * Used for DSTU2 only */ @@ -965,11 +965,13 @@ public abstract class BaseParser implements IParser { private final BaseRuntimeChildDefinition myDef; private final CompositeChildElement myParent; private final RuntimeResourceDefinition myResDef; + private final boolean mySubResource; - public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef) { + public CompositeChildElement(CompositeChildElement theParent, BaseRuntimeChildDefinition theDef, boolean theSubResource) { myDef = theDef; myParent = theParent; myResDef = null; + mySubResource = theSubResource; if (ourLog.isTraceEnabled()) { if (theParent != null) { @@ -984,12 +986,11 @@ public abstract class BaseParser implements IParser { } - public boolean anyPathMatches(Set thePaths) { - StringBuilder b = new StringBuilder(); - addParent(this, b); - - String path = b.toString(); - return thePaths.contains(path); + public CompositeChildElement(RuntimeResourceDefinition theResDef, boolean theSubResource) { + myResDef = theResDef; + myDef = null; + myParent = null; + mySubResource = theSubResource; } private void addParent(CompositeChildElement theParent, StringBuilder theB) { @@ -1012,10 +1013,12 @@ public abstract class BaseParser implements IParser { } } - public CompositeChildElement(RuntimeResourceDefinition theResDef) { - myResDef = theResDef; - myDef = null; - myParent = null; + public boolean anyPathMatches(Set thePaths) { + StringBuilder b = new StringBuilder(); + addParent(this, b); + + String path = b.toString(); + return thePaths.contains(path); } private StringBuilder buildPath() { @@ -1079,7 +1082,17 @@ public abstract class BaseParser implements IParser { thePathBuilder.append('.'); thePathBuilder.append(myDef.getElementName()); - return theElements.contains(thePathBuilder.toString()); + String currentPath = thePathBuilder.toString(); + boolean retVal = theElements.contains(currentPath); + int dotIdx = currentPath.indexOf('.'); + if (!retVal) { + if (dotIdx != -1 && theElements.contains(currentPath.substring(dotIdx + 1))) { + if (!myParent.isSubResource()) { + return true; + } + } + } + return retVal; } } @@ -1098,6 +1111,10 @@ public abstract class BaseParser implements IParser { return myResDef; } + private boolean isSubResource() { + return mySubResource; + } + public boolean shouldBeEncoded() { boolean retVal = true; if (myEncodeElements != null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index c28e948c026..55aa0bc7a7a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -191,7 +191,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue, - BaseRuntimeElementDefinition theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem, + BaseRuntimeElementDefinition theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem, boolean theForceEmpty) throws IOException { switch (theChildDef.getChildType()) { @@ -264,7 +264,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } else { theEventWriter.beginObject(); } - encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem); + encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem); theEventWriter.endObject(); break; } @@ -282,7 +282,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { for (IBaseResource next : containedResources) { IIdType resourceId = getContainedResources().getResourceId(next); - encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue())); + encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue())); } theEventWriter.endArray(); @@ -320,7 +320,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter, - boolean theContainedResource, CompositeChildElement theParent) throws IOException { + boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException { { String elementId = getCompositeElementId(theElement); @@ -330,7 +330,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } boolean haveWrittenExtensions = false; - for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) { + for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) { BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); @@ -360,7 +360,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; String childName = nextChild.getChildNameByDatatype(child.getDatatype()); BaseRuntimeElementDefinition type = child.getChildByName(childName); - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, theSubResource, nextChildElem, false); continue; } } @@ -368,7 +368,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } else if (nextChild instanceof RuntimeChildContainedResources) { String childName = nextChild.getValidChildNames().iterator().next(); BaseRuntimeElementDefinition child = nextChild.getChildByName(childName); - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, theSubResource, nextChildElem, false); continue; } @@ -451,15 +451,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) { beginArray(theEventWriter, childName); inArray = true; - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource,nextChildElem, force); } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) { // suppress narratives from contained resources } else { - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource,nextChildElem, false); } currentChildName = childName; } else { - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource,theSubResource, nextChildElem, force); } valueIdx++; @@ -540,11 +540,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } } - private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, + private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException, DataFormatException { writeCommentsPreAndPost(theNextValue, theEventWriter); - encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent); + encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent); } @Override @@ -587,18 +587,18 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } if (!theContainedResource) { - if (super.shouldEncodeResourceId(theResource) == false) { + if (super.shouldEncodeResourceId(theResource, theSubResource) == false) { resourceId = null; } else if (!theSubResource && getEncodeForceResourceId() != null) { resourceId = getEncodeForceResourceId(); } } - encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId); + encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, theSubResource, resourceId); } private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull, - boolean theContainedResource, IIdType theResourceId) throws IOException { + boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException { if (!theContainedResource) { super.containResourcesForEncoding(theResource); } @@ -670,7 +670,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { beginArray(theEventWriter, "security"); for (BaseCodingDt securityLabel : securityLabels) { theEventWriter.beginObject(); - encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null); + encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null); theEventWriter.endObject(); } theEventWriter.endArray(); @@ -706,7 +706,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { write(theEventWriter, "content", contentAsBase64); } } else { - encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef)); + encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); } theEventWriter.endObject(); @@ -1366,7 +1366,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null); } else { String childName = myDef.getChildNameByDatatype(myValue.getClass()); - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, myParent, false); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false); managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName); } @@ -1421,7 +1421,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (childDef == null) { throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName()); } - encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, myParent, false); + encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false); managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 63f67be73ad..1a577c5d567 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -214,7 +214,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { } private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition childDef, - String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + String theExtensionUrl, boolean theIncludedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { if (theElement == null || theElement.isEmpty()) { if (isChildContained(childDef, theIncludedResource)) { // We still want to go in.. @@ -234,7 +234,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { if (StringUtils.isNotBlank(encodedValue)) { theEventWriter.writeAttribute("value", encodedValue); } - encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); + encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource); theEventWriter.writeEndElement(); } break; @@ -251,7 +251,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { if (value != null) { theEventWriter.writeAttribute("value", value); } - encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); + encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource); theEventWriter.writeEndElement(); } break; @@ -266,7 +266,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { if (isNotBlank(theExtensionUrl)) { theEventWriter.writeAttribute("url", theExtensionUrl); } - encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent); + encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent); theEventWriter.writeEndElement(); break; } @@ -280,7 +280,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { for (IBaseResource next : getContainedResources().getContainedResources()) { IIdType resourceId = getContainedResources().getResourceId(next); theEventWriter.writeStartElement("contained"); - encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue())); + encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue())); theEventWriter.writeEndElement(); } break; @@ -319,10 +319,10 @@ public class XmlParser extends BaseParser /* implements IParser */ { } - private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent) + private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { - for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) { + for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) { BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); @@ -352,13 +352,13 @@ public class XmlParser extends BaseParser /* implements IParser */ { RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; String childName = nextChild.getChildNameByDatatype(child.getDatatype()); BaseRuntimeElementDefinition type = child.getChildByName(childName); - encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, theSubResource, nextChildElem); continue; } } if (nextChild instanceof RuntimeChildContainedResources) { - encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, theSubResource, nextChildElem); } else { List values = nextChild.getAccessor().getValues(theElement); @@ -382,7 +382,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); if (extensionUrl != null && childName.equals("extension") == false) { - encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef); + encodeExtension(theResource, theEventWriter, theContainedResource, theSubResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef); } else if (nextChild instanceof RuntimeChildExtension) { IBaseExtension extension = (IBaseExtension) nextValue; if ((extension.getValue() == null || extension.getValue().isEmpty())) { @@ -390,18 +390,18 @@ public class XmlParser extends BaseParser /* implements IParser */ { continue; } } - encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, theSubResource, nextChildElem); } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) { // suppress narratives from contained resources } else { - encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, theSubResource, nextChildElem); } } } } } - private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition childDef) + private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition childDef) throws XMLStreamException { BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; if (extDef.isModifier()) { @@ -416,23 +416,23 @@ public class XmlParser extends BaseParser /* implements IParser */ { } theEventWriter.writeAttribute("url", extensionUrl); - encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, theSubResource, nextChildElem); theEventWriter.writeEndElement(); } - private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException { if (theElement instanceof ISupportsUndeclaredExtensions) { ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; - encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource); - encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource); + encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource); + encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource); } if (theElement instanceof IBaseHasExtensions) { IBaseHasExtensions res = (IBaseHasExtensions) theElement; - encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource); + encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource); } if (theElement instanceof IBaseHasModifierExtensions) { IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; - encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource); + encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource,theSubResource); } } @@ -447,17 +447,17 @@ public class XmlParser extends BaseParser /* implements IParser */ { } if (!theIncludedResource) { - if (super.shouldEncodeResourceId(theResource) == false) { + if (super.shouldEncodeResourceId(theResource, theSubResource) == false) { resourceId = null; } else if (theSubResource == false && getEncodeForceResourceId() != null) { resourceId = getEncodeForceResourceId(); } } - encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId); + encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, theSubResource, resourceId); } - private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId) throws XMLStreamException { + private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws XMLStreamException { if (!theContainedResource) { super.containResourcesForEncoding(theResource); } @@ -476,12 +476,12 @@ public class XmlParser extends BaseParser /* implements IParser */ { writeCommentsPre(theEventWriter, theResourceId); theEventWriter.writeStartElement("id"); theEventWriter.writeAttribute("value", theResourceId.getIdPart()); - encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false); + encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false); theEventWriter.writeEndElement(); writeCommentsPost(theEventWriter, theResourceId); } - encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef)); + encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); } else { @@ -494,7 +494,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { writeCommentsPost(theEventWriter, theResourceId);*/ theEventWriter.writeStartElement("id"); theEventWriter.writeAttribute("value", theResourceId.getIdPart()); - encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false); + encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false); theEventWriter.writeEndElement(); writeCommentsPost(theEventWriter, theResourceId); } @@ -525,7 +525,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { } for (BaseCodingDt securityLabel : securityLabels) { theEventWriter.writeStartElement("security"); - encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null); + encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null); theEventWriter.writeEndElement(); } if (tags != null) { @@ -548,7 +548,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); } else { - encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef)); + encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); } } @@ -556,7 +556,7 @@ public class XmlParser extends BaseParser /* implements IParser */ { theEventWriter.writeEndElement(); } - private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List> theExtensions, String tagName, boolean theIncludedResource) + private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List> theExtensions, String tagName, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException { for (IBaseExtension next : theExtensions) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { @@ -592,11 +592,11 @@ public class XmlParser extends BaseParser /* implements IParser */ { throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); } } - encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null); + encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null); } // child extensions - encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource); + encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource); theEventWriter.writeEndElement(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index d95e7c77a92..03a99a2bb18 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -20,20 +20,22 @@ package ca.uhn.fhir.jpa.config; * #L% */ -import javax.annotation.Resource; - import ca.uhn.fhir.jpa.graphql.JpaStorageServices; -import org.hl7.fhir.r4.utils.GraphQLEngine; +import ca.uhn.fhir.jpa.search.*; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; +import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.hibernate5.HibernateExceptionTranslator; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; @@ -41,20 +43,17 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean; import org.springframework.scheduling.config.ScheduledTaskRegistrar; -import ca.uhn.fhir.jpa.search.*; -import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; +import javax.annotation.Resource; @Configuration @EnableScheduling @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") public class BaseConfig implements SchedulingConfigurer { - @Resource - private ApplicationContext myAppCtx; - @Autowired protected Environment myEnv; + @Resource + private ApplicationContext myAppCtx; @Override public void configureTasks(ScheduledTaskRegistrar theTaskRegistrar) { @@ -67,14 +66,19 @@ public class BaseConfig implements SchedulingConfigurer { return retVal; } + @Bean + public IGraphQLStorageServices jpaStorageServices() { + return new JpaStorageServices(); + } + @Bean() public ScheduledExecutorFactoryBean scheduledExecutorService() { ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); b.setPoolSize(5); return b; } - - @Bean(autowire=Autowire.BY_TYPE) + + @Bean(autowire = Autowire.BY_TYPE) public ISearchCoordinatorSvc searchCoordinatorSvc() { return new SearchCoordinatorSvcImpl(); } @@ -84,11 +88,32 @@ public class BaseConfig implements SchedulingConfigurer { return new SearchParamPresenceSvcImpl(); } - @Bean(autowire=Autowire.BY_TYPE) + @Bean(autowire = Autowire.BY_TYPE) public IStaleSearchDeletingSvc staleSearchDeletingSvc() { return new StaleSearchDeletingSvcImpl(); } - + + // @PostConstruct + // public void wireResourceDaos() { + // Map daoBeans = myAppCtx.getBeansOfType(IDao.class); + // List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class); + // for (IDao next : daoBeans.values()) { + // next.setResourceDaos(bean); + // } + // } + + @Bean + @Lazy + public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() { + return new SubscriptionRestHookInterceptor(); + } + + @Bean + @Lazy + public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() { + return new SubscriptionWebsocketInterceptor(); + } + @Bean public TaskScheduler taskScheduler() { ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler(); @@ -99,15 +124,6 @@ public class BaseConfig implements SchedulingConfigurer { // retVal.setPoolSize(5); // return retVal; } - - // @PostConstruct - // public void wireResourceDaos() { - // Map daoBeans = myAppCtx.getBeansOfType(IDao.class); - // List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class); - // for (IDao next : daoBeans.values()) { - // next.setResourceDaos(bean); - // } - // } /** * This lets the "@Value" fields reference properties from the properties file @@ -117,9 +133,5 @@ public class BaseConfig implements SchedulingConfigurer { return new PropertySourcesPlaceholderConfigurer(); } - @Bean - public IGraphQLStorageServices jpaStorageServices() { - return new JpaStorageServices(); - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index 3a70c9eabe1..a1c3f4ebf50 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -1,9 +1,20 @@ package ca.uhn.fhir.jpa.config; -import org.hl7.fhir.instance.hapi.validation.*; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.model.dstu2.composite.MetaDt; +import ca.uhn.fhir.validation.IValidatorModule; +import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; +import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain; import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel; import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; import org.springframework.transaction.annotation.EnableTransactionManagement; /* @@ -15,9 +26,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * 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. @@ -26,14 +37,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * #L% */ -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; -import ca.uhn.fhir.model.dstu2.composite.MetaDt; -import ca.uhn.fhir.validation.IValidatorModule; - @Configuration @EnableTransactionManagement public class BaseDstu2Config extends BaseConfig { @@ -65,12 +68,6 @@ public class BaseDstu2Config extends BaseConfig { return ourFhirContextDstu2Hl7Org; } - @Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME) - public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() { - ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2(); - return retVal; - } - @Bean(name = "myInstanceValidatorDstu2") @Lazy public IValidatorModule instanceValidatorDstu2() { @@ -80,6 +77,12 @@ public class BaseDstu2Config extends BaseConfig { return retVal; } + @Bean(name = "myJpaValidationSupportDstu2", autowire = Autowire.BY_NAME) + public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu2 jpaValidationSupportDstu2() { + ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2 retVal = new ca.uhn.fhir.jpa.dao.JpaValidationSupportDstu2(); + return retVal; + } + @Bean(autowire = Autowire.BY_TYPE) public IFulltextSearchSvc searchDao() { FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl(); @@ -115,10 +118,4 @@ public class BaseDstu2Config extends BaseConfig { return new HapiTerminologySvcDstu2(); } - @Bean - @Lazy - public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() { - return new RestHookSubscriptionDstu2Interceptor(); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java similarity index 68% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java index d57864e9fb9..e4dde80dd78 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,38 +20,32 @@ package ca.uhn.fhir.jpa.config; * #L% */ +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler; +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.stereotype.Controller; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; -import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2; - @Configuration @EnableWebSocket() -public class WebsocketDstu2Config implements WebSocketConfigurer { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketDstu2Config.class); +@Controller +public class WebsocketDispatcherConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) { - theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2").setAllowedOrigins("*"); + theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket").setAllowedOrigins("*"); } @Bean(autowire = Autowire.BY_TYPE) public WebSocketHandler subscriptionWebSocketHandler() { - return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class); - } - - @Bean - public TaskScheduler websocketTaskScheduler() { - ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler(); - retVal.setPoolSize(5); + PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketHandler.class); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java deleted file mode 100644 index d20b3d7e63b..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java +++ /dev/null @@ -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 mySubscriptionDao; - - @PostConstruct - public void postConstruct() { - SubscriptionWebsocketHandlerDstu2.setCtx(myCtx); - SubscriptionWebsocketHandlerDstu2.setSubscriptionDao((IFhirResourceDaoSubscription) mySubscriptionDao); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 444d5402f83..fd3d8b98935 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -1,36 +1,5 @@ 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.ParserOptions; 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.dstu3.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; -import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; @@ -48,6 +16,35 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.validation.IValidatorModule; +import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.dstu3.utils.IResourceValidator.BestPracticeWarningLevel; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/* + * #%L + * 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 @EnableTransactionManagement @@ -133,10 +130,4 @@ public class BaseDstu3Config extends BaseConfig { return new JpaValidationSupportChainDstu3(); } - @Bean - @Lazy - public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() { - return new RestHookSubscriptionDstu3Interceptor(); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java deleted file mode 100644 index e34817e7cfb..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java +++ /dev/null @@ -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(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java deleted file mode 100644 index c92ecfc6fc8..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java +++ /dev/null @@ -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 mySubscriptionDao; - - @PostConstruct - public void postConstruct() { - SubscriptionWebsocketHandlerDstu3.setCtx(myCtx); - SubscriptionWebsocketHandlerDstu3.setSubscriptionDao((IFhirResourceDaoSubscription) mySubscriptionDao); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 60df39dab23..e3e52479590 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -1,9 +1,31 @@ package ca.uhn.fhir.jpa.config.r4; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; +import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4; +import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; +import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; +import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; +import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; +import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; +import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; +import ca.uhn.fhir.validation.IValidatorModule; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider; import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.transaction.annotation.EnableTransactionManagement; /* * #%L @@ -14,9 +36,9 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel; * 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. @@ -25,23 +47,6 @@ import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel; * #L% */ -import org.springframework.beans.factory.annotation.Autowire; -import org.springframework.context.annotation.*; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.config.BaseConfig; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4; -import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; -import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor; -import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; -import ca.uhn.fhir.jpa.term.*; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; -import ca.uhn.fhir.validation.IValidatorModule; - @Configuration @EnableTransactionManagement public class BaseR4Config extends BaseConfig { @@ -131,11 +136,5 @@ public class BaseR4Config extends BaseConfig { public IValidationSupport validationSupportChainR4() { return new JpaValidationSupportChainR4(); } - - @Bean - @Lazy - public RestHookSubscriptionR4Interceptor restHookSubscriptionR4Interceptor() { - return new RestHookSubscriptionR4Interceptor(); - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java deleted file mode 100644 index 34446ed084f..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java +++ /dev/null @@ -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(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java deleted file mode 100644 index 0913f90b2da..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4DispatcherConfig.java +++ /dev/null @@ -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 mySubscriptionDao; - - @PostConstruct - public void postConstruct() { - SubscriptionWebsocketHandlerR4.setCtx(myCtx); - SubscriptionWebsocketHandlerR4.setSubscriptionDao((IFhirResourceDaoSubscription) mySubscriptionDao); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index e7b22e678e7..8eea659ea99 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -123,8 +123,8 @@ public abstract class BaseHapiFhirDao implements IDao { RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams); HashSet excludeElementsInEncoded = new HashSet(); - excludeElementsInEncoded.add("*.id"); - excludeElementsInEncoded.add("*.meta"); + excludeElementsInEncoded.add("id"); + excludeElementsInEncoded.add("meta"); EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java index 700f7b5eee1..9b7bc0769d8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -20,49 +20,27 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.util.*; - -import javax.persistence.Query; - -import org.apache.commons.lang3.time.DateUtils; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; +import ca.uhn.fhir.model.dstu2.resource.Subscription; +import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.*; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.transaction.PlatformTransactionManager; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; -import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; -import ca.uhn.fhir.jpa.entity.*; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.dstu2.resource.Subscription; -import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum; -import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import java.util.Date; + +import static org.apache.commons.lang3.StringUtils.isBlank; public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 implements IFhirResourceDaoSubscription { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class); - @Autowired - private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao; - @Autowired private ISubscriptionTableDao mySubscriptionTableDao; @@ -73,9 +51,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 getUndeliveredResourcesAndPurge(Long theSubscriptionPid) { - List retVal = new ArrayList(); - Page 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 subscriptions = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date()); - } - }); - - int retVal = 0; - for (final Long nextSubscriptionTablePid : subscriptions) { - retVal += txTemplate.execute(new TransactionCallback() { - @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 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 flags = new ArrayList(); - Date mostRecentMatch = null; - for (IBaseResource next : results.getResources(0, results.size())) { - - Date updated = ResourceMetadataKeyEnum.UPDATED.get((IResource) next).getValue(); - if (mostRecentMatch == null) { - mostRecentMatch = updated; - } else { - long mostRecentMatchTime = mostRecentMatch.getTime(); - long updatedTime = updated.getTime(); - if (mostRecentMatchTime < updatedTime) { - mostRecentMatch = updated; - } - } - - SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource(); - Long pid = IDao.RESOURCE_PID.get((IResource) next); - - ourLog.info("New resource for subscription: {}", pid); - - nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid)); - nextFlag.setSubscription(theSubscriptionTable); - nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong()); - flags.add(nextFlag); - } - - mySubscriptionFlaggedResourceDataDao.save(flags); - - ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getId().getIdPart(), new InstantDt(mostRecentMatch)); - - theSubscriptionTable.setMostRecentMatch(mostRecentMatch); - mySubscriptionTableDao.save(theSubscriptionTable); - - return results.size(); - } - - @Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND) - @Transactional(propagation = Propagation.NOT_SUPPORTED) - @Override - public synchronized void pollForNewUndeliveredResourcesScheduler() { - if (getConfig().isSchedulingDisabled()) { - return; - } - pollForNewUndeliveredResources(); - } @Override protected void postPersist(ResourceTable theEntity, Subscription theSubscription) { @@ -236,69 +72,14 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 toPurge = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - Collection 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() { - @Override - public Void doInTransaction(TransactionStatus theStatus) { - delete(subscriptionId, null); - return null; - } - }); - } - } @Override protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, - Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { + Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - Subscription resource = (Subscription) theResource; - Long resourceId = theEntity.getId(); if (theDeletedTimestampOrNull != null) { - Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt()); - if (subscriptionId != null) { - mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId); - mySubscriptionTableDao.deleteAllForSubscription(subscriptionId); - } - } else { - Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS"); - q.setParameter("res_id", resourceId); - q.setParameter("status", resource.getStatusElement().getValue()); - if (q.executeUpdate() > 0) { - ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatusElement().getValueAsEnum()); - } else { - createSubscriptionTable(retVal, resource); - } + mySubscriptionTableDao.deleteAllForSubscription(theEntity); } return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java index c54e5a0c5e6..cac4dfb3ef7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao; -import java.util.List; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; /* * #%L @@ -11,9 +12,9 @@ import java.util.List; * 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. @@ -22,20 +23,8 @@ import java.util.List; * #L% */ -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; - public interface IFhirResourceDaoSubscription extends IFhirResourceDao { - int pollForNewUndeliveredResources(); - - List getUndeliveredResourcesAndPurge(Long theSubscriptionPid); - Long getSubscriptionTablePidForSubscriptionResource(IIdType theId); - void purgeInactiveSubscriptions(); - - void pollForNewUndeliveredResourcesScheduler(); - - int pollForNewUndeliveredResources(String resourceType); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java deleted file mode 100644 index 91136029539..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java +++ /dev/null @@ -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 { - - @Query("SELECT r FROM SubscriptionFlaggedResource r WHERE r.mySubscription.myId = :id ORDER BY r.myId ASC") - public Page 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); - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionTableDao.java index a93ae6e4c09..f9c6f6a8fd4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionTableDao.java @@ -20,32 +20,20 @@ package ca.uhn.fhir.jpa.dao.data; * #L% */ -import java.util.Collection; -import java.util.Date; - +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ca.uhn.fhir.jpa.entity.SubscriptionTable; - public interface ISubscriptionTableDao extends JpaRepository { + @Modifying + @Query("DELETE FROM SubscriptionTable t WHERE t.mySubscriptionResource = :subscription ") + void deleteAllForSubscription(@Param("subscription") ResourceTable theSubscription); + @Query("SELECT t FROM SubscriptionTable t WHERE t.myResId = :pid") SubscriptionTable findOneByResourcePid(@Param("pid") Long theId); - @Modifying - @Query("DELETE FROM SubscriptionTable t WHERE t.myId = :id ") - void deleteAllForSubscription(@Param("id") Long theSubscriptionId); - - @Modifying - @Query("UPDATE SubscriptionTable t SET t.myLastClientPoll = :last_client_poll") - int updateLastClientPoll(@Param("last_client_poll") Date theLastClientPoll); - - @Query("SELECT t FROM SubscriptionTable t WHERE t.myLastClientPoll < :cutoff OR (t.myLastClientPoll IS NULL AND t.myCreated < :cutoff)") - Collection findInactiveBeforeCutoff(@Param("cutoff") Date theCutoff); - - @Query("SELECT t.myId FROM SubscriptionTable t WHERE t.myStatus = :status AND t.myNextCheck <= :next_check") - Collection findSubscriptionsWhichNeedToBeChecked(@Param("status") String theStatus, @Param("next_check") Date theNextCheck); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java index 8c4b25061fb..1cd47153f03 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java @@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.dstu3; */ import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; -import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource; import ca.uhn.fhir.jpa.entity.SubscriptionTable; -import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.api.SortOrderEnum; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; -import javax.persistence.Query; -import java.util.ArrayList; -import java.util.Collection; import java.util.Date; -import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3 getUndeliveredResourcesAndPurge(Long theSubscriptionPid) { - List retVal = new ArrayList(); - Page 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 subscriptions = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date()); - } - }); - - int retVal = 0; - for (final Long nextSubscriptionTablePid : subscriptions) { - retVal += txTemplate.execute(new TransactionCallback() { - @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 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 flags = new ArrayList(); - Date mostRecentMatch = null; - for (IBaseResource nextBase : results.getResources(0, results.size())) { - IAnyResource next = (IAnyResource) nextBase; - - Date updated = next.getMeta().getLastUpdated(); - if (mostRecentMatch == null) { - mostRecentMatch = updated; - } else { - long mostRecentMatchTime = mostRecentMatch.getTime(); - long updatedTime = updated.getTime(); - if (mostRecentMatchTime < updatedTime) { - mostRecentMatch = updated; - } - } - - SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource(); - Long pid = IDao.RESOURCE_PID.get(next); - - ourLog.info("New resource for subscription: {}", pid); - - nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid)); - nextFlag.setSubscription(theSubscriptionTable); - nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong()); - flags.add(nextFlag); - } - - mySubscriptionFlaggedResourceDataDao.save(flags); - - ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch)); - - theSubscriptionTable.setMostRecentMatch(mostRecentMatch); - mySubscriptionTableDao.save(theSubscriptionTable); - - return results.size(); - } - - @Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND) - @Transactional(propagation = Propagation.NOT_SUPPORTED) - @Override - public synchronized void pollForNewUndeliveredResourcesScheduler() { - if (getConfig().isSchedulingDisabled()) { - return; - } - pollForNewUndeliveredResources(); - } @Override protected void postPersist(ResourceTable theEntity, Subscription theSubscription) { @@ -252,69 +78,15 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3 toPurge = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - Collection 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() { - @Override - public Void doInTransaction(TransactionStatus theStatus) { - delete(subscriptionId, null); - return null; - } - }); - } - } @Override protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - Subscription resource = (Subscription) theResource; - Long resourceId = theEntity.getId(); if (theDeletedTimestampOrNull != null) { - Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt()); - if (subscriptionId != null) { - mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId); - mySubscriptionTableDao.deleteAllForSubscription(subscriptionId); - } - } else { - Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS"); - q.setParameter("res_id", resourceId); - q.setParameter("status", resource.getStatusElement().getValueAsString()); - if (q.executeUpdate() > 0) { - ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus()); - } else { - createSubscriptionTable(retVal, resource); - } + mySubscriptionTableDao.deleteAllForSubscription(theEntity); } return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java index 2678572bc2c..5a9ba46c05a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSubscriptionR4.java @@ -21,52 +21,23 @@ package ca.uhn.fhir.jpa.dao.r4; */ import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription; -import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource; import ca.uhn.fhir.jpa.entity.SubscriptionTable; -import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.api.SortOrderEnum; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; -import javax.persistence.Query; -import java.util.ArrayList; -import java.util.Collection; import java.util.Date; -import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -74,9 +45,6 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4 getUndeliveredResourcesAndPurge(Long theSubscriptionPid) { - List retVal = new ArrayList(); - Page 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 subscriptions = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - return mySubscriptionTableDao.findSubscriptionsWhichNeedToBeChecked(SubscriptionStatusEnum.ACTIVE.getCode(), new Date()); - } - }); - - int retVal = 0; - for (final Long nextSubscriptionTablePid : subscriptions) { - retVal += txTemplate.execute(new TransactionCallback() { - @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 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 flags = new ArrayList(); - Date mostRecentMatch = null; - for (IBaseResource nextBase : results.getResources(0, results.size())) { - IAnyResource next = (IAnyResource) nextBase; - - Date updated = next.getMeta().getLastUpdated(); - if (mostRecentMatch == null) { - mostRecentMatch = updated; - } else { - long mostRecentMatchTime = mostRecentMatch.getTime(); - long updatedTime = updated.getTime(); - if (mostRecentMatchTime < updatedTime) { - mostRecentMatch = updated; - } - } - - SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource(); - Long pid = IDao.RESOURCE_PID.get(next); - - ourLog.info("New resource for subscription: {}", pid); - - nextFlag.setResource(myEntityManager.find(ResourceTable.class, pid)); - nextFlag.setSubscription(theSubscriptionTable); - nextFlag.setVersion(next.getIdElement().getVersionIdPartAsLong()); - flags.add(nextFlag); - } - - mySubscriptionFlaggedResourceDataDao.save(flags); - - ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getIdElement().getIdPart(), new InstantDt(mostRecentMatch)); - - theSubscriptionTable.setMostRecentMatch(mostRecentMatch); - mySubscriptionTableDao.save(theSubscriptionTable); - - return results.size(); - } - - @Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND) - @Transactional(propagation = Propagation.NOT_SUPPORTED) - @Override - public synchronized void pollForNewUndeliveredResourcesScheduler() { - if (getConfig().isSchedulingDisabled()) { - return; - } - pollForNewUndeliveredResources(); - } @Override protected void postPersist(ResourceTable theEntity, Subscription theSubscription) { @@ -251,70 +76,19 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4 toPurge = txTemplate.execute(new TransactionCallback>() { - @Override - public Collection doInTransaction(TransactionStatus theStatus) { - Collection 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() { - @Override - public Void doInTransaction(TransactionStatus theStatus) { - delete(subscriptionId, null); - return null; - } - }); - } - } @Override protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); - Subscription resource = (Subscription) theResource; - Long resourceId = theEntity.getId(); if (theDeletedTimestampOrNull != null) { Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt()); if (subscriptionId != null) { - mySubscriptionFlaggedResourceDataDao.deleteAllForSubscription(subscriptionId); - mySubscriptionTableDao.deleteAllForSubscription(subscriptionId); - } - } else { - Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS"); - q.setParameter("res_id", resourceId); - q.setParameter("status", resource.getStatusElement().getValueAsString()); - if (q.executeUpdate() > 0) { - ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatus()); - } else { - createSubscriptionTable(retVal, resource); + mySubscriptionTableDao.deleteAllForSubscription(retVal); } } + return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java deleted file mode 100644 index 2e4618f21ae..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java +++ /dev/null @@ -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; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java index 3bcb6f165fe..acae07b8938 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java @@ -20,140 +20,56 @@ package ca.uhn.fhir.jpa.entity; * #L% */ -import java.util.Collection; +import javax.persistence.*; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.UniqueConstraint; - -//@formatter:off @Entity -@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= { - @UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }), - @UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" }) +@Table(name = "HFJ_SUBSCRIPTION_STATS", uniqueConstraints = { + @UniqueConstraint(name = "IDX_SUBSC_RESID", columnNames = {"RES_ID"}), }) -@NamedQueries({ - @NamedQuery(name="Q_HFJ_SUBSCRIPTION_SET_STATUS", query="UPDATE SubscriptionTable t SET t.myStatus = :status WHERE t.myResId = :res_id"), - @NamedQuery(name="Q_HFJ_SUBSCRIPTION_GET_BY_RES", query="SELECT t FROM SubscriptionTable t WHERE t.myResId = :res_id") -}) -//@formatter:on public class SubscriptionTable { - @Column(name = "CHECK_INTERVAL", nullable = false) - private long myCheckInterval; - @Temporal(TemporalType.TIMESTAMP) @Column(name = "CREATED_TIME", nullable = false, insertable = true, updatable = false) private Date myCreated; - @OneToMany(mappedBy = "mySubscription") - private Collection myFlaggedResources; - @Id - @GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SUBSCRIPTION_ID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SUBSCRIPTION_ID") @SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID") @Column(name = "PID", insertable = false, updatable = false) private Long myId; - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "LAST_CLIENT_POLL", nullable = true) - private Date myLastClientPoll; - - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "MOST_RECENT_MATCH", nullable = false) - private Date myMostRecentMatch; - - @Temporal(TemporalType.TIMESTAMP) - @Column(name = "NEXT_CHECK", nullable = false) - private Date myNextCheck; - @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResId; - @Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20) - private String myStatus; - @OneToOne() - @JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID", - foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID") + @JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID", + foreignKey = @ForeignKey(name = "FK_SUBSC_RESOURCE_ID") ) private ResourceTable mySubscriptionResource; /** * Constructor */ - public SubscriptionTable(){ + public SubscriptionTable() { super(); } - public long getCheckInterval() { - return myCheckInterval; - } public Date getCreated() { return myCreated; } - public Long getId() { - return myId; - } - - public Date getLastClientPoll() { - return myLastClientPoll; - } - - public Date getMostRecentMatch() { - return myMostRecentMatch; - } - - public Date getNextCheck() { - return myNextCheck; - } - - public String getStatus() { - return myStatus; - } - - public ResourceTable getSubscriptionResource() { - return mySubscriptionResource; - } - - public void setCheckInterval(long theCheckInterval) { - myCheckInterval = theCheckInterval; - } - public void setCreated(Date theCreated) { myCreated = theCreated; } - public void setLastClientPoll(Date theLastClientPoll) { - myLastClientPoll = theLastClientPoll; + public Long getId() { + return myId; } - public void setMostRecentMatch(Date theMostRecentMatch) { - myMostRecentMatch = theMostRecentMatch; - } - - public void setNextCheck(Date theNextCheck) { - myNextCheck = theNextCheck; - } - - public void setStatus(String theStatus) { - myStatus = theStatus; + public ResourceTable getSubscriptionResource() { + return mySubscriptionResource; } public void setSubscriptionResource(ResourceTable theSubscriptionResource) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java index 9f16cdd1f10..ed15bd2f824 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; @@ -29,21 +32,25 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; -import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -70,6 +77,14 @@ public abstract class BaseSubscriptionInterceptor exten private ThreadPoolExecutor myDeliveryExecutor; private LinkedBlockingQueue myProcessingExecutorQueue; private LinkedBlockingQueue myDeliveryExecutorQueue; + private IFhirResourceDao mySubscriptionDao; + @Autowired + private List> myResourceDaos; + @Autowired + private FhirContext myCtx; + @Autowired(required = false) + @Qualifier("myEventDefinitionDaoR4") + private IFhirResourceDao myEventDefinitionDaoR4; /** * Constructor @@ -79,7 +94,83 @@ public abstract class BaseSubscriptionInterceptor exten setExecutorThreadCount(5); } - protected abstract CanonicalSubscription canonicalize(S theSubscription); + protected CanonicalSubscription canonicalize(S theSubscription) { + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + return canonicalizeDstu2(theSubscription); + case DSTU3: + return canonicalizeDstu3(theSubscription); + case R4: + return canonicalizeR4(theSubscription); + default: + throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion()); + } + } + + protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) { + ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + try { + retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus())); + retVal.setBackingSubscription(theSubscription); + retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType())); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + } catch (FHIRException theE) { + throw new InternalErrorException(theE); + } + return retVal; + } + + protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) { + org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + try { + retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode())); + retVal.setBackingSubscription(theSubscription); + retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode())); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + } catch (FHIRException theE) { + throw new InternalErrorException(theE); + } + return retVal; + } + + protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) { + org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + retVal.setStatus(subscription.getStatus()); + retVal.setBackingSubscription(theSubscription); + retVal.setChannelType(subscription.getChannel().getType()); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + + List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics"); + if (topicExts.size() > 0) { + IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive(); + if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) { + throw new PreconditionFailedException("Topic reference must be an EventDefinition"); + } + + org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement()); + retVal.addTrigger(def.getTrigger()); + } + + return retVal; + } public abstract Subscription.SubscriptionChannelType getChannelType(); @@ -116,7 +207,9 @@ public abstract class BaseSubscriptionInterceptor exten myProcessingChannel = theProcessingChannel; } - protected abstract IFhirResourceDao getSubscriptionDao(); + protected IFhirResourceDao getSubscriptionDao() { + return mySubscriptionDao; + } public List getSubscriptions() { return new ArrayList<>(myIdToSubscription.values()); @@ -243,13 +336,6 @@ public abstract class BaseSubscriptionInterceptor exten initSubscriptions(); } - protected void registerSubscriptionCheckingSubscriber() { - if (mySubscriptionCheckingSubscriber == null) { - mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this); - } - getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber); - } - @SuppressWarnings("unused") @PreDestroy public void preDestroy() { @@ -268,6 +354,13 @@ public abstract class BaseSubscriptionInterceptor exten myIdToSubscription.put(theId.getIdPart(), canonicalize(theSubscription)); } + protected void registerSubscriptionCheckingSubscriber() { + if (mySubscriptionCheckingSubscriber == null) { + mySubscriptionCheckingSubscriber = new SubscriptionCheckingSubscriber(getSubscriptionDao(), getChannelType(), this); + } + getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber); + } + @Override public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { ResourceModifiedMessage msg = new ResourceModifiedMessage(); @@ -294,6 +387,20 @@ public abstract class BaseSubscriptionInterceptor exten submitResourceModified(msg); } + @PostConstruct + public void start() { + for (IFhirResourceDao next : myResourceDaos) { + if (myCtx.getResourceDefinition(next.getResourceType()).getName().equals("Subscription")) { + mySubscriptionDao = next; + } + } + Validate.notNull(mySubscriptionDao); + + if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) { + Validate.notNull(myEventDefinitionDaoR4); + } + } + protected void submitResourceModified(final ResourceModifiedMessage theMsg) { final GenericMessage message = new GenericMessage<>(theMsg); mySubscriptionActivatingSubscriber.handleMessage(message); @@ -308,4 +415,6 @@ public abstract class BaseSubscriptionInterceptor exten myIdToSubscription.remove(theId.getIdPart()); } + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java index 16133e8a03f..f2883be6141 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java @@ -29,14 +29,14 @@ import org.springframework.messaging.MessageHandler; public abstract class BaseSubscriptionSubscriber implements MessageHandler { - private final IFhirResourceDao mySubscriptionDao; + private final IFhirResourceDao mySubscriptionDao; private final Subscription.SubscriptionChannelType myChannelType; private final BaseSubscriptionInterceptor mySubscriptionInterceptor; /** * Constructor */ - public BaseSubscriptionSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { + public BaseSubscriptionSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { mySubscriptionDao = theSubscriptionDao; myChannelType = theChannelType; mySubscriptionInterceptor = theSubscriptionInterceptor; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java index 9d2c237e957..179068c9738 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -54,6 +56,19 @@ public class CanonicalSubscription implements Serializable { myTrigger = theTrigger; } + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (theO == null || getClass() != theO.getClass()) return false; + + CanonicalSubscription that = (CanonicalSubscription) theO; + + return new EqualsBuilder() + .append(getIdElement().getIdPart(), that.getIdElement().getIdPart()) + .isEquals(); + } + public IBaseResource getBackingSubscription() { return myBackingSubscription; } @@ -90,10 +105,12 @@ public class CanonicalSubscription implements Serializable { return myHeaders; } - public void setHeaders(String theHeaders) { + public void setHeaders(List> theHeader) { myHeaders = new ArrayList<>(); - if (isNotBlank(theHeaders)) { - myHeaders.add(theHeaders); + for (IPrimitiveType next : theHeader) { + if (isNotBlank(next.getValueAsString())) { + myHeaders.add(next.getValueAsString()); + } } } @@ -129,12 +146,17 @@ public class CanonicalSubscription implements Serializable { return myTrigger; } - public void setHeaders(List> theHeader) { + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getIdElement().getIdPart()) + .toHashCode(); + } + + public void setHeaders(String theHeaders) { myHeaders = new ArrayList<>(); - for (IPrimitiveType next : theHeader) { - if (isNotBlank(next.getValueAsString())) { - myHeaders.add(next.getValueAsString()); - } + if (isNotBlank(theHeaders)) { + myHeaders.add(theHeaders); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringWebsocketSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringWebsocketSubscriber.java deleted file mode 100644 index 06a89afaaa1..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringWebsocketSubscriber.java +++ /dev/null @@ -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; - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/RestHookSubscriptionDstu2Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/RestHookSubscriptionDstu2Interceptor.java deleted file mode 100644 index 80ca6da8dbe..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/RestHookSubscriptionDstu2Interceptor.java +++ /dev/null @@ -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 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; - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketHandlerDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketHandlerDstu2.java deleted file mode 100644 index e20de474463..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketHandlerDstu2.java +++ /dev/null @@ -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 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 results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid); - if (results.isEmpty() == false) { - myState.deliver(results); - } - } - - public static void setCtx(FhirContext theCtx) { - ourCtx = theCtx; - } - - public static void setSubscriptionDao(IFhirResourceDaoSubscription 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketReturnResourceHandlerDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketReturnResourceHandlerDstu2.java deleted file mode 100644 index d684b34f70f..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/SubscriptionWebsocketReturnResourceHandlerDstu2.java +++ /dev/null @@ -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 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 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 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/WebSocketSubscriptionDstu2Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/WebSocketSubscriptionDstu2Interceptor.java deleted file mode 100644 index 703753c8c30..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu2/WebSocketSubscriptionDstu2Interceptor.java +++ /dev/null @@ -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 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; - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/RestHookSubscriptionDstu3Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/RestHookSubscriptionDstu3Interceptor.java deleted file mode 100644 index ff096e56197..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/RestHookSubscriptionDstu3Interceptor.java +++ /dev/null @@ -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 mySubscriptionDao; - - @Override - protected IFhirResourceDao getSubscriptionDao() { - return mySubscriptionDao; - } - - public void setSubscriptionDao(IFhirResourceDao 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; - } - - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketHandlerDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketHandlerDstu3.java deleted file mode 100644 index 7429429e911..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketHandlerDstu3.java +++ /dev/null @@ -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 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 results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid); - if (results.isEmpty() == false) { - myState.deliver(results); - } - } - - public static void setCtx(FhirContext theCtx) { - ourCtx = theCtx; - } - - public static void setSubscriptionDao(IFhirResourceDaoSubscription 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketReturnResourceHandlerDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketReturnResourceHandlerDstu3.java deleted file mode 100644 index 059a518ee15..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/SubscriptionWebsocketReturnResourceHandlerDstu3.java +++ /dev/null @@ -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 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 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 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/WebSocketSubscriptionDstu3Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/WebSocketSubscriptionDstu3Interceptor.java deleted file mode 100644 index 69db0086257..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dstu3/WebSocketSubscriptionDstu3Interceptor.java +++ /dev/null @@ -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 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; - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/RestHookSubscriptionR4Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/RestHookSubscriptionR4Interceptor.java deleted file mode 100644 index 904f2546a5f..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/RestHookSubscriptionR4Interceptor.java +++ /dev/null @@ -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 mySubscriptionDao; - - @Autowired - @Qualifier("myEventDefinitionDaoR4") - private IFhirResourceDao 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 theSubscriptionDao) { - mySubscriptionDao = theSubscriptionDao; - } - - @Override - protected CanonicalSubscription canonicalize(IBaseResource theSubscription) { - return doCanonicalize(theSubscription, myEventDefinitionDao); - } - - static CanonicalSubscription doCanonicalize(IBaseResource theSubscription, IFhirResourceDao 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 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; - } - - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java deleted file mode 100644 index 2f0287f25b2..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketHandlerR4.java +++ /dev/null @@ -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 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 results = ourSubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid); - if (results.isEmpty() == false) { - myState.deliver(results); - } - } - - public static void setCtx(FhirContext theCtx) { - ourCtx = theCtx; - } - - public static void setSubscriptionDao(IFhirResourceDaoSubscription 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java deleted file mode 100644 index d147d004fae..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/SubscriptionWebsocketReturnResourceHandlerR4.java +++ /dev/null @@ -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 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 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 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 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 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 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 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 theResults); - - void handleTextMessage(WebSocketSession theSession, TextMessage theMessage); - - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/WebSocketSubscriptionR4Interceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/WebSocketSubscriptionR4Interceptor.java deleted file mode 100644 index f21030fe39b..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/r4/WebSocketSubscriptionR4Interceptor.java +++ /dev/null @@ -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 mySubscriptionDao; - - @Autowired - @Qualifier("myEventDefinitionDaoR4") - private IFhirResourceDao 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; - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringRestHookSubscriber.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java index a4cd55a42fa..ad3ca469e13 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; /*- * #%L @@ -21,6 +21,10 @@ package ca.uhn.fhir.jpa.subscription; */ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; +import ca.uhn.fhir.jpa.subscription.BaseSubscriptionSubscriber; +import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; @@ -30,7 +34,6 @@ import ca.uhn.fhir.rest.gclient.IClientExecutable; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +41,6 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionRestHookInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java similarity index 76% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionRestHookInterceptor.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java index 7aafaa5fd38..af59c4dac90 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionRestHookInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; /*- * #%L @@ -20,7 +20,9 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ -public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor { +import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; + +public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor { private SubscriptionDeliveringRestHookSubscriber mySubscriptionDeliverySubscriber; @Override @@ -31,6 +33,10 @@ public abstract class BaseSubscriptionRestHookInterceptor extends BaseSubscripti getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber); } + @Override + public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() { + return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK; + } @Override protected void unregisterDeliverySubscriber() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionWebsocketHandler.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java index 5f7a411d37f..79a411a4c06 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ISubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; /* * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java new file mode 100644 index 00000000000..ad062504864 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java @@ -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 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 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 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); +// } +// } +// +//} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionWebsocketInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java similarity index 60% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionWebsocketInterceptor.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java index 760f5b8ba94..9655e448d40 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionWebsocketInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; /*- * #%L @@ -21,16 +21,13 @@ package ca.uhn.fhir.jpa.subscription; */ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; -import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; +import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; +import org.hl7.fhir.r4.model.Subscription; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; -public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor { - private SubscriptionDeliveringWebsocketSubscriber mySubscriptionDeliverySubscriber; - - @Autowired - private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao; +public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor { @Autowired private ISubscriptionTableDao mySubscriptionTableDao; @@ -42,16 +39,25 @@ public abstract class BaseSubscriptionWebsocketInterceptor extends BaseSubscript private IResourceTableDao myResourceTableDao; @Override - protected void registerDeliverySubscriber() { - if (mySubscriptionDeliverySubscriber == null) { - mySubscriptionDeliverySubscriber = new SubscriptionDeliveringWebsocketSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, mySubscriptionFlaggedResourceDataDao, mySubscriptionTableDao, myResourceTableDao); - } - getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber); + public Subscription.SubscriptionChannelType getChannelType() { + return Subscription.SubscriptionChannelType.WEBSOCKET; } + @Override + protected void registerDeliverySubscriber() { + /* + * nothing, since individual websocket connections + * register themselves + */ + } @Override protected void unregisterDeliverySubscriber() { - getDeliveryChannel().unsubscribe(mySubscriptionDeliverySubscriber); + + /* + * nothing, since individual websocket connections + * register themselves + */ + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 83062e061cc..4f43f846140 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -226,8 +226,6 @@ public abstract class BaseJpaTest { public Void doInTransaction(TransactionStatus theStatus) { entityManager.createQuery("DELETE from " + SearchParamPresent.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + SearchParam.class.getSimpleName() + " d").executeUpdate(); - entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate(); - entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + ForcedId.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate(); entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java index ec8a5b9f58e..f1155c89eb4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java @@ -1,13 +1,22 @@ package ca.uhn.fhir.jpa.dao.dstu2; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; - -import java.io.IOException; -import java.io.InputStream; - -import javax.persistence.EntityManager; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestDstu2Config; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; +import ca.uhn.fhir.model.dstu2.composite.CodingDt; +import ca.uhn.fhir.model.dstu2.composite.MetaDt; +import ca.uhn.fhir.model.dstu2.resource.*; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; @@ -24,26 +33,15 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.config.TestDstu2Config; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.model.dstu2.composite.*; -import ca.uhn.fhir.model.dstu2.resource.*; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.util.TestUtil; +import javax.persistence.EntityManager; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; -//@formatter:off @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes= {TestDstu2Config.class, ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig.class}) -//@formatter:on +@ContextConfiguration(classes= {TestDstu2Config.class}) public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Autowired protected ApplicationContext myAppCtx; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SubscriptionTest.java deleted file mode 100644 index ccfcf57f734..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SubscriptionTest.java +++ /dev/null @@ -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 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 results; - List 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()); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SubscriptionTest.java deleted file mode 100644 index d4a2e28ff74..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SubscriptionTest.java +++ /dev/null @@ -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 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 results; - List 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()); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java deleted file mode 100644 index 278144a1d50..00000000000 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SubscriptionTest.java +++ /dev/null @@ -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 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 results; - List 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()); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java index ed6e1cc1dd1..b1372bd99e4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -1,29 +1,9 @@ package ca.uhn.fhir.jpa.provider; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.*; -import org.springframework.web.context.ContextLoader; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; - -import ca.uhn.fhir.jpa.config.WebsocketDstu2Config; -import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig; +import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; -import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; @@ -35,6 +15,26 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.TestUtil; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { @@ -45,7 +45,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { protected static Server ourServer; protected static String ourServerBase; protected static GenericWebApplicationContext ourWebApplicationContext; - protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor; + protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; protected static DatabaseBackedPagingProvider ourPagingProvider; public BaseResourceProviderDstu2Test() { @@ -97,7 +97,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { ourWebApplicationContext.setParent(myAppCtx); ourWebApplicationContext.refresh(); - ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(RestHookSubscriptionDstu2Interceptor.class); + ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class); proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext); @@ -107,8 +107,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { subsServletHolder.setServlet(dispatcherServlet); subsServletHolder.setInitParameter( ContextLoader.CONFIG_LOCATION_PARAM, - WebsocketDstu2Config.class.getName() + "\n" + - WebsocketDstu2DispatcherConfig.class.getName()); + WebsocketDispatcherConfig.class.getName()); proxyHandler.addServlet(subsServletHolder, "/*"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java index 31171aa8dcb..e7020470a33 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java @@ -1,79 +1,32 @@ package ca.uhn.fhir.jpa.provider; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; - -import java.net.URI; -import java.util.*; - +import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; +import ca.uhn.fhir.model.dstu2.resource.Subscription; +import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum; +import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.*; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.dstu2.resource.*; -import ca.uhn.fhir.model.dstu2.valueset.*; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { - public class BaseSocket { - protected String myError; - protected boolean myGotBound; - protected int myPingCount; - protected String mySubsId; - - } - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionsDstu2Test.class); - @Before - public void beforeDisableResultReuse() { - myDaoConfig.setReuseCachedSearchResultsForMillis(null); - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - - private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception { - client.stop(); - - /* - * When websocket closes, the subscription is automatically deleted. This - * can cause deadlocks if the test returns too quickly since it also - * tries to delete the subscription - */ - ca.uhn.fhir.model.dstu2.resource.Bundle found = null; - for (int i = 0; i < 10; i++) { - found = ourClient.search().forResource("Subscription").returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute(); - if (found.getEntry().size() > 0) { - Thread.sleep(200); - } else { - break; - } - } - assertEquals(0, found.getEntry().size()); - - } - - @Before - public void beforeEnableScheduling() { - myDaoConfig.setSchedulingDisabled(false); - } - @Override public void beforeCreateInterceptor() { super.beforeCreateInterceptor(); @@ -83,46 +36,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { myDaoConfig.getInterceptors().add(interceptor); } - private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException { - - /* - * In a separate thread, start a polling for new resources. Normally the scheduler would - * take care of this, but that can take longer which makes the unit tests run much slower - * so we simulate that part.. - */ - new Thread() { - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - ourLog.warn("Interrupted", e); - } - ourLog.info("About to poll in separate thread"); - mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Done poll in separate thread"); - }}.start(); - - ourLog.info("Entering loop"); - for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) { - ourLog.debug("Starting"); - if (socket.myError != null) { - fail(socket.myError); - } - if (socket.myPingCount >= wantPingCount) { - ourLog.info("Breaking loop"); - break; - } - ourLog.debug("Sleeping"); - Thread.sleep(100); - } - - ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError); - - assertNull(socket.myError, socket.myError); - assertEquals(wantPingCount, socket.myPingCount); + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); } + @Before + public void beforeEnableScheduling() { + myDaoConfig.setSchedulingDisabled(false); + } + + @Test public void testCreateInvalidNoStatus() { Subscription subs = new Subscription(); @@ -147,6 +71,75 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { ourClient.update().resource(subs).execute(); } + @Test + public void testCreateInvalidWrongStatus() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); + subs.getChannel().setPayload("application/fhir+json"); + subs.getChannel().setEndpoint("http://foo"); + subs.setStatus(SubscriptionStatusEnum.ACTIVE); + subs.setCriteria("Observation?identifier=123"); + try { + ourClient.create().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); + } + + subs.setId("ABC"); + try { + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); + } + } + + @Test + public void testCreateWithPopulatedButInvalidStatue() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET); + subs.setCriteria("Observation?identifier=123"); + subs.getStatusElement().setValue("aaaaa"); + + try { + ourClient.create().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server")); + } + } + + + @Test + public void testUpdateFails() { + Subscription subs = new Subscription(); + subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); + subs.setStatus(SubscriptionStatusEnum.REQUESTED); + subs.setCriteria("Observation?identifier=123"); + IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless(); + + subs.setId(id); + + try { + subs.setStatus(SubscriptionStatusEnum.ACTIVE); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage()); + } + + try { + subs.setStatus((SubscriptionStatusEnum) null); + ourClient.update().resource(subs).execute(); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server")); + } + + subs.setStatus(SubscriptionStatusEnum.OFF); + } + @Test public void testUpdateToInvalidStatus() { Subscription subs = new Subscription(); @@ -176,262 +169,17 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { ourClient.update().resource(subs).execute(); } - @Test - public void testCreateWithPopulatedButInvalidStatue() { - Subscription subs = new Subscription(); - subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET); - subs.setCriteria("Observation?identifier=123"); - subs.getStatusElement().setValue("aaaaa"); - - try { - ourClient.create().resource(subs).execute(); - fail(); - } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server")); - } + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); } - @Test - public void testCreateInvalidWrongStatus() { - Subscription subs = new Subscription(); - subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); - subs.getChannel().setPayload("application/fhir+json"); - subs.getChannel().setEndpoint("http://foo"); - subs.setStatus(SubscriptionStatusEnum.ACTIVE); - subs.setCriteria("Observation?identifier=123"); - try { - ourClient.create().resource(subs).execute(); - fail(); - } catch (UnprocessableEntityException e) { - assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); - } + public class BaseSocket { + protected String myError; + protected boolean myGotBound; + protected int myPingCount; + protected String mySubsId; - subs.setId("ABC"); - try { - ourClient.update().resource(subs).execute(); - fail(); - } catch (UnprocessableEntityException e) { - assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'off' or 'requested' on a newly created subscription", e.getMessage()); - } - } - - @Test - public void testSubscriptionDynamic() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().addFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart(); - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2"); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - - @Test - public void testSubscriptionDynamicXml() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().addFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml"; - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2"); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - ResourceMetadataKeyEnum.PROFILES.put(obs, Collections.singletonList(new IdDt("http://foo"))); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - @Test - public void testSubscriptionSimple() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionResourcesAppear"; - Patient p = new Patient(); - p.addName().addFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - Subscription subs = new Subscription(); - ResourceMetadataKeyEnum.PROFILES.put(subs, Collections.singletonList(new IdDt("http://foo"))); - subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET); - subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); - subs.setStatus(SubscriptionStatusEnum.ACTIVE); - String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart(); - - Thread.sleep(100); - - Observation obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - WebSocketClient client = new WebSocketClient(); - SimpleEchoSocket socket = new SimpleEchoSocket(subsId); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2"); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - client.connect(socket, echoUri, request); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - obs = new Observation(); - obs.getSubject().setReference(pId); - obs.setStatus(ObservationStatusEnum.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 2); - - } finally { - try { - client.stop(); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - @Test - public void testUpdateFails() { - Subscription subs = new Subscription(); - subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); - subs.setStatus(SubscriptionStatusEnum.REQUESTED); - subs.setCriteria("Observation?identifier=123"); - IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless(); - - subs.setId(id); - - try { - subs.setStatus(SubscriptionStatusEnum.ACTIVE); - ourClient.update().resource(subs).execute(); - fail(); - } catch (UnprocessableEntityException e) { - assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage()); - } - - try { - subs.setStatus((SubscriptionStatusEnum) null); - ourClient.update().resource(subs).execute(); - fail(); - } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server")); - } - - subs.setStatus(SubscriptionStatusEnum.OFF); } /** diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java index 1301edb3930..969906c4136 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java @@ -1,34 +1,12 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.*; -import java.util.concurrent.TimeUnit; - -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.Patient; -import org.junit.*; -import org.springframework.web.context.ContextLoader; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.*; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.servlet.DispatcherServlet; - -import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config; -import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig; +import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; -import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -39,6 +17,32 @@ import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.TestUtil; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.DispatcherServlet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { @@ -49,11 +53,11 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { protected static RestfulServer ourRestServer; private static Server ourServer; protected static String ourServerBase; - private static GenericWebApplicationContext ourWebApplicationContext; + protected static GenericWebApplicationContext ourWebApplicationContext; private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider; protected static SearchParamRegistryDstu3 ourSearchParamRegistry; protected static DatabaseBackedPagingProvider ourPagingProvider; - protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor; + protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; protected static ISearchDao mySearchEntityDao; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; @@ -119,8 +123,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { subsServletHolder.setServlet(dispatcherServlet); subsServletHolder.setInitParameter( ContextLoader.CONFIG_LOCATION_PARAM, - WebsocketDstu3Config.class.getName() + "\n" + - WebsocketDstu3DispatcherConfig.class.getName()); + WebsocketDispatcherConfig.class.getName()); proxyHandler.addServlet(subsServletHolder, "/*"); // Register a CORS filter @@ -146,7 +149,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); mySearchEntityDao = wac.getBean(ISearchDao.class); - ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionDstu3Interceptor.class); + ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class); myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SubscriptionsDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SubscriptionsDstu3Test.java index 1c5761741ed..dcea27d1c25 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SubscriptionsDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SubscriptionsDstu3Test.java @@ -1,29 +1,27 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - +import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.TestUtil; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.*; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { @@ -31,12 +29,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { private static final String WEBSOCKET_PATH = "/websocket/dstu3"; - @Before - public void beforeDisableResultReuse() { - myDaoConfig.setReuseCachedSearchResultsForMillis(null); - } - - @Override public void beforeCreateInterceptor() { super.beforeCreateInterceptor(); @@ -46,72 +38,17 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { myDaoConfig.getInterceptors().add(interceptor); } + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + @Before public void beforeEnableScheduling() { myDaoConfig.setSchedulingDisabled(false); } - private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException { - /* - * In a separate thread, start a polling for new resources. Normally the scheduler would - * take care of this, but that can take longer which makes the unit tests run much slower - * so we simulate that part.. - */ - new Thread() { - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - ourLog.warn("Interrupted", e); - } - ourLog.info("About to poll in separate thread"); - mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Done poll in separate thread"); - }}.start(); - - ourLog.info("Entering loop"); - for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) { - ourLog.debug("Starting"); - if (socket.myError != null) { - fail(socket.myError); - } - if (socket.myPingCount >= wantPingCount) { - ourLog.info("Breaking loop"); - break; - } - ourLog.debug("Sleeping"); - Thread.sleep(100); - } - - ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError); - - assertNull(socket.myError, socket.myError); - assertEquals(wantPingCount, socket.myPingCount); - } - - private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception { - client.stop(); - - /* - * When websocket closes, the subscription is automatically deleted. This - * can cause deadlocks if the test returns too quickly since it also - * tries to delete the subscription - */ - Bundle found = null; - for (int i = 0; i < 10; i++) { - found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute(); - if (found.getEntry().size() > 0) { - Thread.sleep(200); - } else { - break; - } - } - assertEquals(0, found.getEntry().size()); - - } - @Test public void testCreateInvalidNoStatus() { Subscription subs = new Subscription(); @@ -162,196 +99,6 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { } } - @Test - public void testSubscriptionDynamic() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart(); - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - - @Test - public void testSubscriptionDynamicXml() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml"; - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - obs.getMeta().addProfile("http://foo"); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - @Test - public void testSubscriptionSimple() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionResourcesAppear"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - Subscription subs = new Subscription(); - subs.getMeta().addProfile("http://foo"); - subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); - subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); - subs.setStatus(SubscriptionStatus.ACTIVE); - String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart(); - - Thread.sleep(100); - - Observation obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - WebSocketClient client = new WebSocketClient(); - SimpleEchoSocket socket = new SimpleEchoSocket(subsId); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - client.connect(socket, echoUri, request); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 2); - - } finally { - try { - client.stop(); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - @Test public void testUpdateFails() { @@ -384,7 +131,7 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { subs.setStatus(SubscriptionStatus.OFF); } - + @Test public void testUpdateToInvalidStatus() { Subscription subs = new Subscription(); @@ -415,7 +162,7 @@ public class SubscriptionsDstu3Test extends BaseResourceProviderDstu3Test { subs.setStatus(SubscriptionStatus.OFF); ourClient.update().resource(subs).execute(); } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index 24241390292..b051b94e155 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -1,13 +1,12 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.config.r4.WebsocketR4Config; -import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig; +import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor; +import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -58,7 +57,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { protected static ISearchDao mySearchEntityDao; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; private static Server ourServer; - private static GenericWebApplicationContext ourWebApplicationContext; + protected static GenericWebApplicationContext ourWebApplicationContext; private TerminologyUploaderProviderR4 myTerminologyUploaderProvider; private Object ourGraphQLProvider; private boolean ourRestHookSubscriptionInterceptorRequested; @@ -122,8 +121,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { subsServletHolder.setServlet(dispatcherServlet); subsServletHolder.setInitParameter( ContextLoader.CONFIG_LOCATION_PARAM, - WebsocketR4Config.class.getName() + "\n" + - WebsocketR4DispatcherConfig.class.getName()); + WebsocketDispatcherConfig.class.getName()); proxyHandler.addServlet(subsServletHolder, "/*"); // Register a CORS filter @@ -172,8 +170,8 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { /** * This is lazy created so we only ask for it if its needed */ - protected RestHookSubscriptionR4Interceptor getRestHookSubscriptionInterceptor() { - RestHookSubscriptionR4Interceptor retVal = ourWebApplicationContext.getBean(RestHookSubscriptionR4Interceptor.class); + protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() { + SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class); ourRestHookSubscriptionInterceptorRequested = true; return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java index 5b15640708c..eac3edd6280 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SubscriptionsR4Test.java @@ -1,29 +1,27 @@ package ca.uhn.fhir.jpa.provider.r4; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.*; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Observation.ObservationStatus; -import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; -import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; - import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorR4; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Subscription; +import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; public class SubscriptionsR4Test extends BaseResourceProviderR4Test { @@ -31,12 +29,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test { private static final String WEBSOCKET_PATH = "/websocket/r4"; - @Before - public void beforeDisableResultReuse() { - myDaoConfig.setReuseCachedSearchResultsForMillis(null); - } - - @Override public void beforeCreateInterceptor() { super.beforeCreateInterceptor(); @@ -46,72 +38,17 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test { myDaoConfig.getInterceptors().add(interceptor); } + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + } + @Before public void beforeEnableScheduling() { myDaoConfig.setSchedulingDisabled(false); } - private void sleepUntilPingCount(BaseSocket socket, int wantPingCount) throws InterruptedException { - /* - * In a separate thread, start a polling for new resources. Normally the scheduler would - * take care of this, but that can take longer which makes the unit tests run much slower - * so we simulate that part.. - */ - new Thread() { - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - ourLog.warn("Interrupted", e); - } - ourLog.info("About to poll in separate thread"); - mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Done poll in separate thread"); - }}.start(); - - ourLog.info("Entering loop"); - for (long start = System.currentTimeMillis(), now = System.currentTimeMillis(); now - start <= 20000; now = System.currentTimeMillis()) { - ourLog.debug("Starting"); - if (socket.myError != null) { - fail(socket.myError); - } - if (socket.myPingCount >= wantPingCount) { - ourLog.info("Breaking loop"); - break; - } - ourLog.debug("Sleeping"); - Thread.sleep(100); - } - - ourLog.info("Out of loop, pingcount {} error {}", socket.myPingCount, socket.myError); - - assertNull(socket.myError, socket.myError); - assertEquals(wantPingCount, socket.myPingCount); - } - - private void stopClientAndWaitForStopped(WebSocketClient client) throws Exception { - client.stop(); - - /* - * When websocket closes, the subscription is automatically deleted. This - * can cause deadlocks if the test returns too quickly since it also - * tries to delete the subscription - */ - Bundle found = null; - for (int i = 0; i < 10; i++) { - found = ourClient.search().forResource("Subscription").returnBundle(Bundle.class).execute(); - if (found.getEntry().size() > 0) { - Thread.sleep(200); - } else { - break; - } - } - assertEquals(0, found.getEntry().size()); - - } - @Test public void testCreateInvalidNoStatus() { Subscription subs = new Subscription(); @@ -162,195 +99,6 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test { } } - @Test - public void testSubscriptionDynamic() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart(); - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.JSON); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - - @Test - public void testSubscriptionDynamicXml() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionDynamic"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - String criteria = "Observation?subject=Patient/" + pId.getIdPart() + "&_format=xml"; - DynamicEchoSocket socket = new DynamicEchoSocket(criteria, EncodingEnum.XML); - WebSocketClient client = new WebSocketClient(); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - client.connect(socket, echoUri, new ClientUpgradeRequest()); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - mySubscriptionDao.read(new IdDt("Subscription", socket.mySubsId), mySrd); - - Observation obs = new Observation(); - obs.getMeta().addProfile("http://foo"); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - sleepUntilPingCount(socket, 3); - - obs = (Observation) socket.myReceived.get(0); - assertEquals(afterId1.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = (Observation) socket.myReceived.get(1); - assertEquals(afterId2.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 4); - - obs = (Observation) socket.myReceived.get(2); - assertEquals(afterId3.getValue(), obs.getIdElement().toUnqualifiedVersionless().getValue()); - - } finally { - try { - stopClientAndWaitForStopped(client); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } - - @Test - public void testSubscriptionSimple() throws Exception { - myDaoConfig.setSubscriptionEnabled(true); - myDaoConfig.setSubscriptionPollDelay(0); - - String methodName = "testSubscriptionResourcesAppear"; - Patient p = new Patient(); - p.addName().setFamily(methodName); - IIdType pId = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless(); - - Subscription subs = new Subscription(); - subs.getMeta().addProfile("http://foo"); - subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET); - subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart()); - subs.setStatus(SubscriptionStatus.ACTIVE); - String subsId = mySubscriptionDao.create(subs, mySrd).getId().getIdPart(); - - Thread.sleep(100); - - Observation obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - Thread.sleep(100); - - WebSocketClient client = new WebSocketClient(); - SimpleEchoSocket socket = new SimpleEchoSocket(subsId); - try { - client.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + WEBSOCKET_PATH); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - client.connect(socket, echoUri, request); - ourLog.info("Connecting to : {}", echoUri); - - sleepUntilPingCount(socket, 1); - - obs = new Observation(); - obs.getSubject().setReferenceElement(pId); - obs.setStatus(ObservationStatus.FINAL); - IIdType afterId3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); - - sleepUntilPingCount(socket, 2); - - } finally { - try { - client.stop(); - } catch (Exception e) { - ourLog.error("Failure", e); - fail(e.getMessage()); - } - } - - } @Test @@ -384,7 +132,7 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test { subs.setStatus(SubscriptionStatus.OFF); } - + @Test public void testUpdateToInvalidStatus() { Subscription subs = new Subscription(); @@ -415,7 +163,7 @@ public class SubscriptionsR4Test extends BaseResourceProviderR4Test { subs.setStatus(SubscriptionStatus.OFF); ourClient.update().resource(subs).execute(); } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java index f4cf91d0867..4816740657d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java @@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; -import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Observation; @@ -32,8 +31,7 @@ import org.junit.*; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * Test the rest-hook subscriptions diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java similarity index 81% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java index 33ae989c7aa..9046fa8c368 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java @@ -13,6 +13,7 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; @@ -25,23 +26,10 @@ import ca.uhn.fhir.model.dstu2.valueset.*; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; -/** - * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the - * subscription - *

- * Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for - * a test that returns the xml of the observation - *

- * To execute the following test, execute it the following way: - * 0. execute 'clean' test - * 1. Execute the 'createSubscription' test - * 2. Update the subscription id in the 'attachWebSocket' test - * 3. Execute the 'attachWebSocket' test - * 4. Execute the 'sendObservation' test - * 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id - */ -public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu2Test.class); +// This is currently disabled as the criteria mechanism was a non-standard experiment +@Ignore +public class WebsocketWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test { + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu2Test.class); private String myPatientId; private String mySubscriptionId; @@ -128,14 +116,9 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD observation.setId(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()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -157,13 +140,8 @@ public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderD observation.setId(observationId); ourLog.info("Observation id generated by server is: " + observationId); - - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - Thread.sleep(2000); - + waitForSize(2, mySocketImplementation.getMessages()); ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java similarity index 79% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java index d861e6a683e..c3792a06523 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithCriteriaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java @@ -20,26 +20,11 @@ import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; -/** - * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the - * subscription - *

- * Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for - * a test that returns the xml of the observation - *

- * To execute the following test, execute it the following way: - * 0. execute 'clean' test - * 1. Execute the 'createPatient' test - * 2. Update the patient id static variable - * 3. Execute the 'createSubscription' test - * 4. Update the subscription id static variable - * 5. Execute the 'attachWebSocket' test - * 6. Execute the 'sendObservation' test - * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id - */ -public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test { +// This is currently disabled as the criteria mechanism was a non-standard experiment +@Ignore +public class WebsocketWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu3Test.class); + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaDstu3Test.class); private String myPatientId; private String mySubscriptionId; @@ -127,13 +112,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(1, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -155,14 +135,8 @@ public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderD observation.setId(observationId); ourLog.info("Observation id generated by server is: " + observationId); - - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java similarity index 89% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java index 517db9253db..0c8b8539546 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java @@ -8,6 +8,7 @@ import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -40,8 +41,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome; * 4. Execute the 'sendObservation' test * 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id */ -public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2Test.class); +public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test { + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu2Test.class); private String myPatientId; private String mySubscriptionId; @@ -53,15 +54,21 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro super.after(); myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled()); myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay()); + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.unregisterInterceptor(interceptor); } @Before public void before() throws Exception { super.before(); - + myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionPollDelay(0L); - + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.registerInterceptor(interceptor); + /* * Create patient */ @@ -95,7 +102,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON); myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2"); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket"); ClientUpgradeRequest request = new ClientUpgradeRequest(); ourLog.info("Connecting to : {}", echoUri); Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); @@ -128,14 +135,9 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro observation.setId(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()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -158,13 +160,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourcePro ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java similarity index 89% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java index e92bcc63397..007c45699fc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirSubscriptionWithSubscriptionIdDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java @@ -8,6 +8,7 @@ import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -37,9 +38,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome; * 6. Execute the 'sendObservation' test * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id */ -public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test { +public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class); + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdDstu3Test.class); private String myPatientId; private String mySubscriptionId; @@ -51,12 +52,19 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro super.after(); myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled()); myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay()); + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.unregisterInterceptor(interceptor); + } @Before public void before() throws Exception { super.before(); - + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.registerInterceptor(interceptor); + myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionPollDelay(0L); @@ -93,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON); myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3"); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket"); ClientUpgradeRequest request = new ClientUpgradeRequest(); ourLog.info("Connecting to : {}", echoUri); Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); @@ -127,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(1, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -156,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourcePro ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithEventDefinitionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java similarity index 94% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithEventDefinitionR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java index e44eafb782c..7d8235c1511 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithEventDefinitionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java @@ -2,13 +2,13 @@ package ca.uhn.fhir.jpa.subscription.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.rest.api.MethodOutcome; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.List; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; /** * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the @@ -35,9 +34,9 @@ import static org.junit.Assert.*; * 6. Execute the 'sendObservation' test * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id */ -public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProviderR4Test { +public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithEventDefinitionR4Test.class); + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookWithEventDefinitionR4Test.class); private static List ourUpdatedObservations = Lists.newArrayList(); private static List ourContentTypes = new ArrayList<>(); private static List ourHeaders = new ArrayList<>(); @@ -80,6 +79,7 @@ public class FhirSubscriptionWithEventDefinitionR4Test extends BaseResourceProvi } @Test + @Ignore public void testSubscriptionAddedTrigger() { /* * Create patient diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java index 6adbf4d71ee..d4ad2dd1f39 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithCriteriaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java @@ -21,26 +21,12 @@ import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; -/** - * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the - * subscription - *

- * Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdR4Test for - * a test that returns the xml of the observation - *

- * To execute the following test, execute it the following way: - * 0. execute 'clean' test - * 1. Execute the 'createPatient' test - * 2. Update the patient id static variable - * 3. Execute the 'createSubscription' test - * 4. Update the subscription id static variable - * 5. Execute the 'attachWebSocket' test - * 6. Execute the 'sendObservation' test - * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id - */ -public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaR4Test.class); +// This is currently disabled as the criteria mechanism was a non-standard experiment +@Ignore +public class WebsocketWithCriteriaR4Test extends BaseResourceProviderR4Test { + + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithCriteriaR4Test.class); private String myPatientId; private String mySubscriptionId; @@ -130,13 +116,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(1, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -159,13 +140,8 @@ public class FhirSubscriptionWithCriteriaR4Test extends BaseResourceProviderR4Te ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java similarity index 89% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java index 8c93d50ab6f..356a42a8d69 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirSubscriptionWithSubscriptionIdR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java @@ -8,6 +8,7 @@ import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -38,9 +39,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome; * 6. Execute the 'sendObservation' test * 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id */ -public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProviderR4Test { +public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Test { - private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdR4Test.class); + private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebsocketWithSubscriptionIdR4Test.class); private String myPatientId; private String mySubscriptionId; @@ -52,6 +53,9 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid super.after(); myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled()); myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay()); + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.unregisterInterceptor(interceptor); } @Before @@ -60,7 +64,10 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionPollDelay(0L); - + + SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); + ourRestServer.registerInterceptor(interceptor); + /* * Create patient */ @@ -94,7 +101,7 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON); myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/r4"); + URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket"); ClientUpgradeRequest request = new ClientUpgradeRequest(); ourLog.info("Connecting to : {}", echoUri); Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); @@ -128,13 +135,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(1, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId)); } @@ -157,13 +159,8 @@ public class FhirSubscriptionWithSubscriptionIdR4Test extends BaseResourceProvid ourLog.info("Observation id generated by server is: " + observationId); - int changes = mySubscriptionDao.pollForNewUndeliveredResources(); - ourLog.info("Polling showed {}", changes); - assertEquals(0, changes); - - Thread.sleep(2000); - ourLog.info("WS Messages: {}", mySocketImplementation.getMessages()); + waitForSize(2, mySocketImplementation.getMessages()); assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId)); } } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index e93221901f5..ab5189cdbe7 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -1,35 +1,42 @@ package ca.uhn.fhirtest; -import java.util.*; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; +import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; +import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; +import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; +import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhirtest.config.*; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.cors.CorsConfiguration; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig; -import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig; -import ca.uhn.fhir.jpa.config.r4.WebsocketR4DispatcherConfig; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; -import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; -import ca.uhn.fhir.jpa.provider.dstu3.*; -import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.interceptor.*; -import ca.uhn.fhirtest.config.*; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; public class TestRestfulServer extends RestfulServer { @@ -80,7 +87,7 @@ public class TestRestfulServer extends RestfulServer { myAppCtx.register(TdlDstu2Config.class); baseUrlProperty = FHIR_BASEURL_TDL2; } else { - myAppCtx.register(TestDstu2Config.class, WebsocketDstu2DispatcherConfig.class); + myAppCtx.register(TestDstu2Config.class, WebsocketDispatcherConfig.class); baseUrlProperty = FHIR_BASEURL_DSTU2; } myAppCtx.refresh(); @@ -103,7 +110,7 @@ public class TestRestfulServer extends RestfulServer { myAppCtx.register(TdlDstu3Config.class); baseUrlProperty = FHIR_BASEURL_TDL3; } else { - myAppCtx.register(TestDstu3Config.class, WebsocketDstu3DispatcherConfig.class); + myAppCtx.register(TestDstu3Config.class, WebsocketDispatcherConfig.class); baseUrlProperty = FHIR_BASEURL_DSTU3; } myAppCtx.refresh(); @@ -122,7 +129,7 @@ public class TestRestfulServer extends RestfulServer { myAppCtx = new AnnotationConfigWebApplicationContext(); myAppCtx.setServletConfig(getServletConfig()); myAppCtx.setParent(parentAppCtx); - myAppCtx.register(TestR4Config.class, WebsocketR4DispatcherConfig.class); + myAppCtx.register(TestR4Config.class, WebsocketDispatcherConfig.class); baseUrlProperty = FHIR_BASEURL_R4; myAppCtx.refresh(); setFhirContext(FhirContext.forR4()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java new file mode 100644 index 00000000000..93fe167ff77 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -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 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 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 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(); + } + +} diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java new file mode 100644 index 00000000000..2d16f0e9c56 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -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 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 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 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(); + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f503654c5c3..b2c2f37a071 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -370,6 +370,11 @@ resource was deleted, it was not possible to create a new resource with the same URI as the previous one + + 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. +