RSocket XML Config support

This commit is contained in:
Artem Bilan
2019-05-16 13:51:24 -04:00
committed by Gary Russell
parent 87169bc77a
commit bc9aa83aea
9 changed files with 537 additions and 12 deletions

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2019 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.integration.rsocket.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.config.xml.AbstractInboundGatewayParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.rsocket.inbound.RSocketInboundGateway;
/**
* Parser for the <inbound-gateway/> element of the 'rsocket' namespace.
*
* @author Mark Fisher
* @author Gary Russell
*/
public class RSocketInboundGatewayParser extends AbstractInboundGatewayParser {
@Override
protected Class<?> getBeanClass(Element element) {
return RSocketInboundGateway.class;
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return !attributeName.equals("path")
&& !attributeName.equals("rsocket-strategies")
&& !attributeName.equals("rsocket-connector")
&& !attributeName.equals("request-element-type")
&& super.isEligibleAttribute(attributeName);
}
@Override
protected void doPostProcess(BeanDefinitionBuilder builder, Element element) {
builder.addConstructorArgValue(element.getAttribute("path"));
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-element-type",
"requestElementClass");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "rsocket-strategies",
"rSocketStrategies");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "rsocket-connector",
"RSocketConnector");
}
}

View File

@@ -28,7 +28,8 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
public class RSocketNamespaceHandler extends AbstractIntegrationNamespaceHandler {
public void init() {
registerBeanDefinitionParser("inbound-gateway", new RSocketInboundGatewayParser());
registerBeanDefinitionParser("outbound-gateway", new RSocketOutboundGatewayParser());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2019 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.integration.rsocket.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Conventions;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway;
/**
* Parser for the 'outbound-gateway' element of the rsocket namespace.
*
* @author Artem Bilan
*
* @since 5.2
*/
public class RSocketOutboundGatewayParser extends AbstractConsumerEndpointParser {
@Override
protected String getInputChannelAttributeName() {
return "request-channel";
}
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RSocketOutboundGateway.class);
BeanDefinition routeExpression =
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("route", "route-expression",
parserContext, element, true);
builder.addConstructorArgValue(routeExpression);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "client-rsocket-connector",
"clientRSocketConnector");
populateValueOrExpressionIfAny(builder, element, parserContext, "command");
populateValueOrExpressionIfAny(builder, element, parserContext, "publisher-element-type");
populateValueOrExpressionIfAny(builder, element, parserContext, "expected-response-type");
return builder;
}
private static void populateValueOrExpressionIfAny(BeanDefinitionBuilder builder, Element element,
ParserContext parserContext, String valueAttributeName) {
String expressionAttributeName = valueAttributeName + "-expression";
BeanDefinition expression =
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(valueAttributeName,
expressionAttributeName, parserContext, element, false);
if (expression != null) {
builder.addPropertyValue(Conventions.attributeNameToPropertyName(expressionAttributeName), expression);
}
}
}

View File

@@ -94,7 +94,7 @@ public class RSocketInboundGateway extends MessagingGatewaySupport implements In
private ResolvableType requestElementType;
/**
* Instantiate based on the provided path patterns to map this endpoint for incoming RSocket requests.
* Instantiate based on the provided Ant-style path patterns to map this endpoint for incoming RSocket requests.
* @param path the mapping patterns to use.
*/
public RSocketInboundGateway(String... path) {

View File

@@ -1,21 +1,246 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/rsocket"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/rsocket"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/rsocket"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="https://www.springframework.org/schema/integration/spring-integration-5.2.xsd"/>
schemaLocation="https://www.springframework.org/schema/integration/spring-integration-5.2.xsd"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements for Spring Integration's RSocket adapters.
]]></xsd:documentation>
<xsd:documentation>
Defines the configuration elements for Spring Integration's RSocket channel adapters.
</xsd:documentation>
</xsd:annotation>
<xsd:element name="inbound-gateway">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Configures a Messaging Gateway Endpoint for the
'org.springframework.integration.rsocket.inbound.RSocketInboundGateway to receive RSocket
requests and produce RSocket responses.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="gatewayType">
<xsd:attribute name="path" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The comma separated Ant-style path patterns this endpoint is mapped onto.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.messaging.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
If a downstream exception is thrown and an error-channel is specified,
the MessagingException will be sent to this channel. Otherwise, any such exception
will be propagated to the calling system.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rsocket-strategies" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
An 'RSocketStrategies' bean reference for encoding/decoding requests/replies.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.messaging.rsocket.RSocketStrategies"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rsocket-connector" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
An optional 'AbstractRSocketConnector' bean reference for endpoint mapping registration.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.rsocket.AbstractRSocketConnector"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-element-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A 'Class' for a request message payload type (plain or `Publisher` element).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-gateway">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Configures a Consumer Endpoint for the
'org.springframework.integration.rsocket.outbound.RSocketOutboundGateway' to send requests
over RSocket connections.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="gatewayType">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0"/>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0"/>
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0"/>
</xsd:choice>
<xsd:attribute name="order">
<xsd:annotation>
<xsd:documentation>
Specifies the order for invocation when this endpoint is connected as a
subscriber to a SubscribableChannel.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="route" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A 'route' for the target RSocket endpoint.
Mutually exclusive with 'route-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="route-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to evaluate a 'route' for target RSocket endpoint at runtime
against request message.
Mutually exclusive with 'route'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="command" default="requestResponse">
<xsd:annotation>
<xsd:documentation>
A 'Command' for RSocket request type.
Mutually exclusive with 'command-expression'.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="commandType xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="command-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to evaluate a 'command' for RSocket request type at runtime
against request message.
Mutually exclusive with 'command'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="publisher-element-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A 'Class' for a request message payload 'Publisher' type.
Mutually exclusive with 'publisher-element-type-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="publisher-element-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to evaluate a 'Class' or 'ParameterizedTypeReference'
for a request message payload 'Publisher' type at runtime
against request message.
Mutually exclusive with 'publisher-element-type'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A 'Class' for an RSocket response.
Mutually exclusive with 'expected-response-type-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to evaluate a 'Class' or 'ParameterizedTypeReference'
for for an RSocket response at runtime
against request message.
Mutually exclusive with 'expected-response-type'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="client-rsocket-connector" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A 'ClientRSocketConnector' for client side requests.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.rsocket.ClientRSocketConnector"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="gatewayType">
<xsd:annotation>
<xsd:documentation>
Defines common configuration for gateway adapters.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="request-channel" type="xsd:string" use="required">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.messaging.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.messaging.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-timeout" type="xsd:string"/>
<xsd:attribute name="reply-timeout" type="xsd:string"/>
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
</xsd:complexType>
<xsd:simpleType name="commandType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="fireAndForget"/>
<xsd:enumeration value="requestResponse"/>
<xsd:enumeration value="requestStreamOrChannel"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-rsocket="http://www.springframework.org/schema/integration/rsocket"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/rsocket https://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
<bean id="clientRSocketConnector" class="org.springframework.integration.rsocket.ClientRSocketConnector">
<constructor-arg value="https://example.org"/>
</bean>
<bean id="rsocketStrategies" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.messaging.rsocket.RSocketStrategies"/>
</bean>
<int:channel id="requestChannel"/>
<int-rsocket:inbound-gateway id="inboundGateway"
path="testPath"
rsocket-connector="clientRSocketConnector"
auto-startup="false"
request-channel="requestChannel"
rsocket-strategies="rsocketStrategies"
request-element-type="byte[]"/>
</beans>

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019 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.integration.rsocket.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.rsocket.ClientRSocketConnector;
import org.springframework.integration.rsocket.inbound.RSocketInboundGateway;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Artem Bilan
*
* @since 5.2
*/
@SpringJUnitConfig
@DirtiesContext
public class RSocketInboundGatewayParserTests {
@Autowired
private ClientRSocketConnector clientRSocketConnector;
@Autowired
private RSocketInboundGateway inboundGateway;
@Test
void testOutboundGatewayParser() {
assertThat(TestUtils.getPropertyValue(this.inboundGateway, "rsocketConnector"))
.isSameAs(this.clientRSocketConnector);
assertThat(TestUtils.getPropertyValue(this.inboundGateway, "rsocketStrategies"))
.isSameAs(this.clientRSocketConnector.getRSocketStrategies());
assertThat(TestUtils.getPropertyValue(this.inboundGateway, "path"))
.isEqualTo(new String[] { "testPath" });
assertThat(TestUtils.getPropertyValue(this.inboundGateway, "requestElementType.resolved"))
.isEqualTo(byte[].class);
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-rsocket="http://www.springframework.org/schema/integration/rsocket"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/rsocket https://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
<bean id="clientRSocketConnector" class="org.springframework.integration.rsocket.ClientRSocketConnector">
<constructor-arg value="https://example.org"/>
</bean>
<int:channel id="requestChannel"/>
<int-rsocket:outbound-gateway id="outboundGateway"
client-rsocket-connector="clientRSocketConnector"
auto-startup="false"
command="fireAndForget"
route-expression="'testRoute'"
request-channel="requestChannel"
publisher-element-type="byte[]"
expected-response-type="java.util.Date"/>
</beans>

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2019 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.integration.rsocket.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.rsocket.ClientRSocketConnector;
import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Artem Bilan
*
* @since 5.2
*/
@SpringJUnitConfig
@DirtiesContext
public class RSocketOutboundGatewayParserTests {
@Autowired
private ClientRSocketConnector clientRSocketConnector;
@Autowired
private RSocketOutboundGateway outboundGateway;
@Test
void testOutboundGatewayParser() {
assertThat(TestUtils.getPropertyValue(this.outboundGateway, "clientRSocketConnector"))
.isSameAs(this.clientRSocketConnector);
assertThat(TestUtils.getPropertyValue(this.outboundGateway, "commandExpression.literalValue"))
.isEqualTo("fireAndForget");
assertThat(TestUtils.getPropertyValue(this.outboundGateway, "routeExpression.expression"))
.isEqualTo("'testRoute'");
assertThat(TestUtils.getPropertyValue(this.outboundGateway, "publisherElementTypeExpression.literalValue"))
.isEqualTo("byte[]");
assertThat(TestUtils.getPropertyValue(this.outboundGateway, "expectedResponseTypeExpression.literalValue"))
.isEqualTo("java.util.Date");
}
}