From 29c51684bbdf7c09948e45ae45c0220b5ea18f1d Mon Sep 17 00:00:00 2001 From: Daniel Frey Date: Mon, 7 Nov 2022 10:02:01 -0500 Subject: [PATCH] Xmpp supplier (#403) * WIP: initial xmpp supplier support * implement xmpp supplier and test * remove test logging config * update for consistency * add documentation references for xmpp consumer and supplier, fix formatting for functions where geode was commented out --- function-dependencies/pom.xml | 5 + supplier/pom.xml | 1 + supplier/xmpp-supplier/README.adoc | 30 ++++ supplier/xmpp-supplier/pom.xml | 33 +++++ .../xmpp/XmppSupplierConfiguration.java | 67 +++++++++ .../supplier/xmpp/XmppSupplierProperties.java | 54 ++++++++ .../xmpp/XmppSupplierConfigurationTests.java | 128 ++++++++++++++++++ 7 files changed, 318 insertions(+) create mode 100644 supplier/xmpp-supplier/README.adoc create mode 100644 supplier/xmpp-supplier/pom.xml create mode 100644 supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfiguration.java create mode 100644 supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierProperties.java create mode 100644 supplier/xmpp-supplier/src/test/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfigurationTests.java diff --git a/function-dependencies/pom.xml b/function-dependencies/pom.xml index 6e685ac0..878f4b81 100644 --- a/function-dependencies/pom.xml +++ b/function-dependencies/pom.xml @@ -102,6 +102,11 @@ websocket-supplier ${project.version} + + org.springframework.cloud.fn + xmpp-supplier + ${project.version} + org.springframework.cloud.fn zeromq-supplier diff --git a/supplier/pom.xml b/supplier/pom.xml index 910a315b..7b9f3d34 100644 --- a/supplier/pom.xml +++ b/supplier/pom.xml @@ -28,6 +28,7 @@ twitter-supplier cdc-debezium-supplier syslog-supplier + xmpp-supplier zeromq-supplier diff --git a/supplier/xmpp-supplier/README.adoc b/supplier/xmpp-supplier/README.adoc new file mode 100644 index 00000000..ba4bf9f2 --- /dev/null +++ b/supplier/xmpp-supplier/README.adoc @@ -0,0 +1,30 @@ +# XMPP Supplier + +A supplier that allows you to receive messages through a XMPP server. + +## Beans for injection + +You can import the `XmppSupplierConfiguration` in the application and then inject the following bean. + +`Supplier xmppSupplier` + +You need to inject this as `Supplier xmppSupplier`. + +You can use `xmppSupplier` as a qualifier when injecting. + +**NOTE:** This is a functional endpoint. One will need to subscribe to this endpoint in order to start accepting data +on it. + +## Configuration Options + +All configuration properties are prefixed with `xmpp.supplier`. + +For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/consumer/xmpp/XmppSupplierProperties.java[XmppSupplierProperties]. + +## Tests + +See this link:src/test/java/org/springframework/cloud/fn/consumer/xmpp/[test suite] for the various ways, this supplier is used. + +## Other usage + +See this https://github.com/spring-cloud/stream-applications/blob/master/applications/source/xmpp-source/README.adoc[README] where this supplier is used to create a Spring Cloud Stream application where it makes a XMPP Source. diff --git a/supplier/xmpp-supplier/pom.xml b/supplier/xmpp-supplier/pom.xml new file mode 100644 index 00000000..cce9a457 --- /dev/null +++ b/supplier/xmpp-supplier/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + + org.springframework.cloud.fn + spring-functions-parent + 4.0.0-SNAPSHOT + ../../spring-functions-parent/pom.xml + + + xmpp-supplier + xmpp-supplier + XMPP supplier + + + + org.springframework.cloud.fn + xmpp-common + ${project.version} + + + + + org.springframework.cloud.fn + function-test-support + ${project.version} + test + + + + diff --git a/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfiguration.java b/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfiguration.java new file mode 100644 index 00000000..2fa79356 --- /dev/null +++ b/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfiguration.java @@ -0,0 +1,67 @@ +/* + * 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.fn.supplier.xmpp; + +import java.util.function.Supplier; + +import org.jivesoftware.smack.XMPPConnection; +import reactor.core.publisher.Flux; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.fn.common.xmpp.XmppConnectionFactoryConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.integration.channel.FluxMessageChannel; +import org.springframework.integration.xmpp.inbound.ChatMessageListeningEndpoint; +import org.springframework.messaging.Message; + +/** + * A source module that receives data from ZeroMQ. + * + * @author Daniel Frey + * @since 4.0.0 + */ +@Configuration +@EnableConfigurationProperties(XmppSupplierProperties.class) +@Import(XmppConnectionFactoryConfiguration.class) +public class XmppSupplierConfiguration { + + private FluxMessageChannel output = new FluxMessageChannel(); + + @Bean + public ChatMessageListeningEndpoint chatMessageListeningEndpoint(XMPPConnection xmppConnection, XmppSupplierProperties properties) { + + var chatMessageListeningEndpoint = new ChatMessageListeningEndpoint(xmppConnection); + + if (properties.getPayloadExpression() != null) { + chatMessageListeningEndpoint.setPayloadExpression(properties.getPayloadExpression()); + } + + chatMessageListeningEndpoint.setStanzaFilter(properties.getStanzaFilter()); + chatMessageListeningEndpoint.setOutputChannel(output); + chatMessageListeningEndpoint.setAutoStartup(false); + + return chatMessageListeningEndpoint; + } + + @Bean + public Supplier>> xmppSupplier(ChatMessageListeningEndpoint chatMessageListeningEndpoint) { + return () -> Flux.from(output).doOnSubscribe(subscription -> chatMessageListeningEndpoint.start()); + } + +} diff --git a/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierProperties.java b/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierProperties.java new file mode 100644 index 00000000..7e8c4a26 --- /dev/null +++ b/supplier/xmpp-supplier/src/main/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierProperties.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2022 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.fn.supplier.xmpp; + +import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.filter.StanzaTypeFilter; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.expression.Expression; +import org.springframework.validation.annotation.Validated; + +/** + * + * @author Daniel Frey + * @since 4.0.0 + */ +@ConfigurationProperties("xmpp.supplier") +@Validated +public class XmppSupplierProperties { + + private StanzaFilter stanzaFilter = StanzaTypeFilter.MESSAGE; + + private Expression payloadExpression; + + public void setStanzaFilter(StanzaFilter stanzaFilter) { + this.stanzaFilter = stanzaFilter; + } + + public StanzaFilter getStanzaFilter() { + return stanzaFilter; + } + + public void setPayloadExpression(Expression payloadExpression) { + this.payloadExpression = payloadExpression; + } + public Expression getPayloadExpression() { + return payloadExpression; + } + +} diff --git a/supplier/xmpp-supplier/src/test/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfigurationTests.java b/supplier/xmpp-supplier/src/test/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfigurationTests.java new file mode 100644 index 00000000..ce34757f --- /dev/null +++ b/supplier/xmpp-supplier/src/test/java/org/springframework/cloud/fn/supplier/xmpp/XmppSupplierConfigurationTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014-2022 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.fn.supplier.xmpp; + +import java.io.IOException; +import java.time.Duration; +import java.util.function.Supplier; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +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 reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.fn.test.support.xmpp.XmppTestContainerSupport; +import org.springframework.context.annotation.Import; +import org.springframework.integration.xmpp.XmppHeaders; +import org.springframework.messaging.Message; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Daniel Frey + */ +@SpringBootTest +public class XmppSupplierConfigurationTests implements XmppTestContainerSupport { + + @DynamicPropertySource + static void registerConfigurationProperties(DynamicPropertyRegistry registry) { + registry.add("xmpp.factory.host", () -> XmppTestContainerSupport.getXmppHost()); + registry.add("xmpp.factory.port", () -> XmppTestContainerSupport.getXmppMappedPort()); + registry.add("xmpp.factory.user", () -> JANE_USER); // Connect as user intended to listen for messages on behalf of + registry.add("xmpp.factory.password", () -> USER_PW); + registry.add("xmpp.factory.service-name", () -> SERVICE_NAME); + registry.add("xmpp.factory.security-mode", () -> "disabled"); + } + + @Autowired + Supplier>> subject; + + // A client source connection is needed to send the message to the xmpp server + private XMPPTCPConnection sourceConnection; + + @BeforeEach + void setup() throws IOException, SmackException, XMPPException, InterruptedException { + + 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 + void testSubscriptionConfiguration() throws XmppStringprepException, SmackException.NotConnectedException, InterruptedException { + + var payload = "test"; + + var stepVerifier = + StepVerifier.create(subject.get()) + .assertNext((message) -> { + + assertThat(message.getPayload()) + .asInstanceOf(InstanceOfAssertFactories.type(String.class)) + .isEqualTo(payload); + + assertThat(message.getHeaders().containsKey(XmppHeaders.TO)).isTrue(); + assertThat(message.getHeaders().get(XmppHeaders.TO, String.class)).isEqualTo(JANE_USER + "@" + SERVICE_NAME); + + }) + .thenCancel() + .verifyLater(); + + var chatManager = ChatManager.getInstanceFor(this.sourceConnection); + var jid = JidCreate.entityBareFrom(JANE_USER + "@" + SERVICE_NAME); + var chat = chatManager.chatWith(jid); + chat.send(payload); + + stepVerifier.verify(Duration.ofSeconds(10)); + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @Import(XmppSupplierConfiguration.class) + static class XmppSupplierTestApplication { } + +}