From 72aef6b10281de05b9c7df99c53f57d0787d61dc Mon Sep 17 00:00:00 2001 From: Daniel Frey Date: Thu, 10 Nov 2022 23:45:29 -0500 Subject: [PATCH] Add Xmpp source app (#413) --- README.adoc | 2 +- applications/source/pom.xml | 19 +-- applications/source/xmpp-source/README.adoc | 62 +++++++++ applications/source/xmpp-source/pom.xml | 85 ++++++++++++ ...onfiguration-metadata-whitelist.properties | 1 + ...dataflow-configuration-metadata.properties | 1 + .../xmpp/XmppSourceInvalidConfigTests.java | 61 +++++++++ .../app/source/xmpp/XmppSourceTests.java | 122 ++++++++++++++++++ 8 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 applications/source/xmpp-source/README.adoc create mode 100644 applications/source/xmpp-source/pom.xml create mode 100644 applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties create mode 100644 applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata.properties create mode 100644 applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceInvalidConfigTests.java create mode 100644 applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceTests.java diff --git a/README.adoc b/README.adoc index 77bec007..6ef2677a 100644 --- a/README.adoc +++ b/README.adoc @@ -158,7 +158,7 @@ The following are the various components of this repository. |link:applications/source/websocket-source/README.adoc[Websocket] | |link:applications/sink/twitter-update-sink/README.adoc[Twitter Update] -| +|link:applications/source/xmpp-source/README.adoc[XMPP] | |link:applications/sink/wavefront-sink/README.adoc[Wavefront] | diff --git a/applications/source/pom.xml b/applications/source/pom.xml index 2753466e..4d319a5e 100644 --- a/applications/source/pom.xml +++ b/applications/source/pom.xml @@ -10,26 +10,27 @@ pom + cdc-debezium-source file-source ftp-source jdbc-source jms-source http-source - mqtt-source - time-source load-generator-source + mail-source mongodb-source - tcp-source + mqtt-source rabbit-source s3-source sftp-source - twitter-stream-source - twitter-search-source - twitter-message-source - websocket-source - cdc-debezium-source syslog-source - mail-source + tcp-source + time-source + twitter-message-source + twitter-search-source + twitter-stream-source + websocket-source + xmpp-source zeromq-source diff --git a/applications/source/xmpp-source/README.adoc b/applications/source/xmpp-source/README.adoc new file mode 100644 index 00000000..6048f9bd --- /dev/null +++ b/applications/source/xmpp-source/README.adoc @@ -0,0 +1,62 @@ +//tag::ref-doc[] += XMPP Source + +The "xmpp" source enables receiving messages from an XMPP Server. + +== Input + +N/A + +== Output + +=== Payload + +* `byte[]` + +== Options + +The **$$xmpp$$** $$source$$ has the following options: + +//tag::configuration-properties[] +Properties grouped by prefix: + + +=== xmpp.factory + +$$host$$:: $$XMPP Host server to connect to.$$ *($$String$$, default: `$$$$`)* +$$password$$:: $$The Password for the connected user.$$ *($$String$$, default: `$$$$`)* +$$port$$:: $$Port for connecting to the host. - Default Client Port: 5222$$ *($$Integer$$, default: `$$5222$$`)* +$$resource$$:: $$The Resource to bind to on the XMPP Host. - Can be empty, server will generate one if not set$$ *($$String$$, default: `$$$$`)* +$$security-mode$$:: $$$$ *($$SecurityMode$$, default: `$$$$`, possible values: `required`,`ifpossible`,`disabled`)* +$$service-name$$:: $$The Service Name to set for the XMPP Domain.$$ *($$String$$, default: `$$$$`)* +$$subscription-mode$$:: $$$$ *($$SubscriptionMode$$, default: `$$$$`, possible values: `accept_all`,`reject_all`,`manual`)* +$$user$$:: $$The User the connection should connect as.$$ *($$String$$, default: `$$$$`)* + +=== xmpp.supplier + +$$payload-expression$$:: $$$$ *($$Expression$$, default: `$$$$`)* +$$stanza-filter$$:: $$$$ *($$StanzaFilter$$, default: `$$$$`)* +//end::configuration-properties[] + +Also see the https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html[Spring Boot Documentation] +for addition properties for the broker connections and listener properties. + +== Build + +``` +$ ./mvnw clean install -PgenerateApps +$ cd apps +``` +You can find the corresponding binder based projects here. +You can then cd into one of the folders and build it: +``` +$ ./mvnw clean package +``` + +== Examples + +``` +java -jar xmpp-source.jar --xmpp.factory.host=localhost --xmpp.factory.port=5222 --xmpp.factory.user=jane --xmpp.factory.password=secret --xmpp.factory.service-name=localhost +``` + +//end::ref-doc[] diff --git a/applications/source/xmpp-source/pom.xml b/applications/source/xmpp-source/pom.xml new file mode 100644 index 00000000..9f873231 --- /dev/null +++ b/applications/source/xmpp-source/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + xmpp-source + xmpp-source + XMPP source app + jar + + + org.springframework.cloud.stream.app + stream-applications-core + 4.0.0-SNAPSHOT + ../../stream-applications-core/pom.xml + + + + + + + org.springframework.cloud.fn + xmpp-supplier + + + + + io.projectreactor + reactor-test + test + + + org.awaitility + awaitility + test + + + + org.springframework.cloud.fn + function-test-support + ${project.version} + test + + + + + + + + org.springframework.cloud + spring-cloud-dataflow-apps-docs-plugin + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + + + xmpp + source + ${project.version} + org.springframework.cloud.fn.supplier.xmpp.XmppSupplierConfiguration.class + + + + + + org.springframework.cloud.fn + xmpp-supplier + + + org.springframework.cloud.stream.app + stream-applications-composite-function-support + ${stream-apps-core.version} + + + + + + + + + + + + diff --git a/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties b/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties new file mode 100644 index 00000000..7fe4f978 --- /dev/null +++ b/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties @@ -0,0 +1 @@ +configuration-properties.classes=org.springframework.cloud.fn.supplier.xmpp.XmppSupplierProperties,org.springframework.cloud.fn.common.xmpp.XmppConnectionFactoryProperties diff --git a/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata.properties b/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata.properties new file mode 100644 index 00000000..7fe4f978 --- /dev/null +++ b/applications/source/xmpp-source/src/main/resources/META-INF/dataflow-configuration-metadata.properties @@ -0,0 +1 @@ +configuration-properties.classes=org.springframework.cloud.fn.supplier.xmpp.XmppSupplierProperties,org.springframework.cloud.fn.common.xmpp.XmppConnectionFactoryProperties diff --git a/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceInvalidConfigTests.java b/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceInvalidConfigTests.java new file mode 100644 index 00000000..4dd3ced2 --- /dev/null +++ b/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceInvalidConfigTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package org.springframework.cloud.stream.app.source.xmpp; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.cloud.fn.common.xmpp.XmppConnectionFactoryProperties; +import org.springframework.cloud.fn.supplier.xmpp.XmppSupplierProperties; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for XmppSource with invalid config. + * + * @author Daniel Frey + * @since 4.0.0 + */ +public class XmppSourceInvalidConfigTests { + + @ParameterizedTest + @ValueSource(strings = { "host", "user", "password" }) + void requiredXmppConnectionFactoryPropertyIsSetEmpty(String propertyName) { + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + TestPropertyValues.of(String.format("xmpp.factory.%s: ", propertyName)).applyTo(context); + context.register(Config.class); + context.refresh(); + }) + .withMessageContaining("Error creating bean with name 'xmpp.factory-org.springframework.cloud.fn.common.xmpp.XmppConnectionFactoryProperties': Could not bind properties to 'XmppConnectionFactoryProperties'") + .havingRootCause() + .withMessageContaining("Binding validation errors on xmpp.factory") + .withMessageContaining("Field error in object 'xmpp.factory' on field '%s': rejected value []", propertyName); + } + + @Configuration + @EnableConfigurationProperties({XmppConnectionFactoryProperties.class, XmppSupplierProperties.class}) + static class Config { + } + +} diff --git a/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceTests.java b/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceTests.java new file mode 100644 index 00000000..f9ebf3a5 --- /dev/null +++ b/applications/source/xmpp-source/src/test/java/org/springframework/cloud/stream/app/source/xmpp/XmppSourceTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020-2020 the original author or authors. + * + * 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 + * + * https://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. + */ + +package org.springframework.cloud.stream.app.source.xmpp; + +import java.nio.charset.StandardCharsets; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.chat2.ChatManager; +import org.jivesoftware.smack.tcp.XMPPTCPConnection; +import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.fn.supplier.xmpp.XmppSupplierConfiguration; +import org.springframework.cloud.fn.test.support.xmpp.XmppTestContainerSupport; +import org.springframework.cloud.stream.binder.test.OutputDestination; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.cloud.stream.config.BindingServiceConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.integration.xmpp.XmppHeaders; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for XmppSource. + * + * @author Daniel Frey + * @since 4.0.0 + */ +public class XmppSourceTests implements XmppTestContainerSupport { + + private XMPPTCPConnection sourceConnection; + + @BeforeEach + void prepareForTest() throws Exception { + + var builder = XMPPTCPConnectionConfiguration.builder(); + builder.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); + builder.setHost(XmppTestContainerSupport.getXmppHost()); + builder.setPort(XmppTestContainerSupport.getXmppMappedPort()); + builder.setResource(SERVICE_NAME); + builder.setUsernameAndPassword(JOHN_USER, USER_PW) // Connect as user intended to send messages from + .setXmppDomain(SERVICE_NAME); + this.sourceConnection = new XMPPTCPConnection(builder.build()); + this.sourceConnection.connect(); + this.sourceConnection.login(); + + } + + @AfterEach + void teardown() { + this.sourceConnection.instantShutdown(); + } + + @Test + public void sourceFromSupplier() throws InterruptedException { + + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(XmppSourceTestApplication.class)).run( + "--spring.cloud.function.definition=xmppSupplier", + "--xmpp.factory.host=" + XmppTestContainerSupport.getXmppHost(), + "--xmpp.factory.port=" + XmppTestContainerSupport.getXmppMappedPort(), + "--xmpp.factory.user=" + JANE_USER, + "--xmpp.factory.password=" + USER_PW, + "--xmpp.factory.service-name=" + SERVICE_NAME, + "--xmpp.factory.security-mode=disabled" + )) { + + var outputDestination = context.getBean(OutputDestination.class); + + var payload = "test"; + + var chatManager = ChatManager.getInstanceFor(this.sourceConnection); + var jid = JidCreate.entityBareFrom(JANE_USER + "@" + SERVICE_NAME); + var chat = chatManager.chatWith(jid); + chat.send(payload); + + var message = outputDestination.receive(10000, "xmppSupplier-out-0"); + + assertThat(message.getPayload()) + .asInstanceOf(InstanceOfAssertFactories.type(byte[].class)) + .isEqualTo(payload.getBytes(StandardCharsets.UTF_8)); + + assertThat(message.getHeaders().containsKey(XmppHeaders.TO)).isTrue(); + assertThat(message.getHeaders().get(XmppHeaders.TO, String.class)).isEqualTo(JANE_USER + "@" + SERVICE_NAME); + + } + catch (SmackException.NotConnectedException | XmppStringprepException e) { + throw new RuntimeException(e); + } + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @Import({XmppSupplierConfiguration.class, TestChannelBinderConfiguration.class, BindingServiceConfiguration.class}) + public static class XmppSourceTestApplication { } + +}