INT-3045: Add in & out ZeroMq channel adapters (#3388)

* INT-3045: Add in & out ZeroMq channel adapters

JIRA: https://jira.spring.io/browse/INT-3045

* Add `ZeroMqMessageHandler` to produce messages into one-way ZeroMq sockets
* Add `ZeroMqMessageProducer` to consumer messages from one-way ZeroMq sockets
* Add `ConvertingBytesMessageMapper` impl for the `BytesMessageMapper` to
delegate an actual conversion into the provided `MessageConverter`
* Add `ZeroMqHeaders` for message headers constants representing ZeroMq message attributes
* Fix `ZeroMqChannel` for the proper deferred `zeroMqProxy` evaluation
* Add more JavaDocs
* Fix `ZeroMqChannelTests.testPubSubBind()` to be sure that really all the
subscribed channels get the same message from the `PUB` socket

* * Fix typo in the `ConvertingBytesMessageMapper`
* Add `this` for `doOnError()` in the `ZeroMqChannel` & `ZeroMqMessageProducer`
* Change the bind logic in the `ZeroMqMessageProducer` to `port` and let it to
bind to random port.
The actual port is available later via `getBoundPort()`
* Introduce a `ZeroMqMessageProducer.receiveRaw()` to let received `ZMsg` to
be produce as a `payload`
* Add a logic into `ZeroMqMessageHandler` to treat `ZMsg` in the payload of
request message as is without any conversion
* Fix race condition in the `ZeroMqMessageProducer` to destroy `consumerScheduler`
when the main `Flux` is complete

* * Add Java DSL for ZeroMq components
* Extract `ReactiveMessageHandlerSpec` for `ReactiveMessageHandler` impls
* Add debug message into `EmbeddedJsonHeadersMessageMapper` when cannot `decodeNativeFormat()`
* Make `ReactiveMongoDbMessageHandlerSpec` extending `ReactiveMessageHandlerSpec`
* Make `ZeroMqProxy` `autoStartup` by default
* Add `ZeroMqDslTests` to cover all the Java DSL for ZeroMq
* Introduce a `MimeTypeSerializer`  to serialize a `MimeType` into JSON as a plain string;
use it as extra serializer in the `JacksonJsonUtils.messagingAwareMapper()`
* Fix typo for the `AllowListTypeResolverBuilder` inner class

* * Add some docs
* Fix Checkstyle violations

* * More docs

* Fix language in Docs

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2020-09-25 13:46:01 -04:00
committed by GitHub
parent 9d34cfd4dd
commit a62a7d1ddd
24 changed files with 1864 additions and 53 deletions

View File

@@ -0,0 +1,48 @@
/*
* Copyright 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.integration.dsl;
import java.util.Collections;
import java.util.Map;
import org.springframework.integration.handler.ReactiveMessageHandlerAdapter;
import org.springframework.messaging.ReactiveMessageHandler;
/**
* The {@link MessageHandlerSpec} extension for {@link ReactiveMessageHandler}.
*
* @author Artem Bilan
*
* @since 5.4
*/
public abstract class ReactiveMessageHandlerSpec<S extends ReactiveMessageHandlerSpec<S, H>, H extends ReactiveMessageHandler>
extends MessageHandlerSpec<S, ReactiveMessageHandlerAdapter>
implements ComponentsRegistration {
protected final H reactiveMessageHandler; // NOSONAR - final
protected ReactiveMessageHandlerSpec(H reactiveMessageHandler) {
this.reactiveMessageHandler = reactiveMessageHandler;
this.target = new ReactiveMessageHandlerAdapter(this.reactiveMessageHandler);
}
@Override
public Map<Object, String> getComponentsToRegister() {
return Collections.singletonMap(this.reactiveMessageHandler, null);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 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.integration.mapping;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.Assert;
/**
* The {@link BytesMessageMapper} implementation to delegate to/from {@link Message}
* conversion into the provided {@link MessageConverter}.
* <p>
* The {@link MessageConverter} must not return {@code null} from its
* {@link MessageConverter#fromMessage(Message, Class)} and {@link MessageConverter#toMessage(Object, MessageHeaders)}
* methods.
* <p>
* If {@link MessageConverter#fromMessage(Message, Class)} returns {@link String}, it is converted to {@link byte[]}
* using a {@link StandardCharsets#UTF_8} encoding.
*
* @author Artem Bilan
*
* @since 5.4
*/
public class ConvertingBytesMessageMapper implements BytesMessageMapper {
private final MessageConverter messageConverter;
public ConvertingBytesMessageMapper(MessageConverter messageConverter) {
Assert.notNull(messageConverter, "'messageConverter' must not be null");
this.messageConverter = messageConverter;
}
@Override
@NonNull
public Message<?> toMessage(byte[] bytes, @Nullable Map<String, Object> headers) {
MessageHeaders messageHeaders = null;
if (headers != null) {
messageHeaders = new MessageHeaders(headers);
}
Message<?> message = this.messageConverter.toMessage(bytes, messageHeaders);
Assert.state(message != null, () ->
"the '" + this.messageConverter + "' produced null for bytes:" + Arrays.toString(bytes));
return message;
}
@Override
@NonNull
public byte[] fromMessage(Message<?> message) {
Object result = this.messageConverter.fromMessage(message, byte[].class);
Assert.state(result != null, () -> "the '" + this.messageConverter + "' produced null for message: " + message);
return result instanceof String
? ((String) result).getBytes(StandardCharsets.UTF_8)
: (byte[]) result;
}
}

View File

@@ -224,16 +224,14 @@ public class EmbeddedJsonHeadersMessageMapper implements BytesMessageMapper {
message = decodeNativeFormat(bytes, headers);
}
catch (@SuppressWarnings("unused") Exception e) {
// empty
this.logger.debug("Failed to decode native format", e);
}
if (message == null) {
try {
message = (Message<?>) this.objectMapper.readValue(bytes, Object.class);
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to decode JSON", e);
}
this.logger.debug("Failed to decode JSON", e);
}
}
if (message != null) {

View File

@@ -67,7 +67,7 @@ public final class JacksonJsonUtils {
if (JacksonPresent.isJackson2Present()) {
ObjectMapper mapper = new Jackson2JsonObjectMapper().getObjectMapper();
mapper.setDefaultTyping(new AllowlistTypeResolverBuilder(trustedPackages));
mapper.setDefaultTyping(new AllowListTypeResolverBuilder(trustedPackages));
GenericMessageJacksonDeserializer genericMessageDeserializer = new GenericMessageJacksonDeserializer();
genericMessageDeserializer.setMapper(mapper);
@@ -83,6 +83,7 @@ public final class JacksonJsonUtils {
SimpleModule simpleModule = new SimpleModule()
.addSerializer(new MessageHeadersJacksonSerializer())
.addSerializer(new MimeTypeSerializer())
.addDeserializer(GenericMessage.class, genericMessageDeserializer)
.addDeserializer(ErrorMessage.class, errorMessageDeserializer)
.addDeserializer(AdviceMessage.class, adviceMessageDeserializer)
@@ -107,13 +108,13 @@ public final class JacksonJsonUtils {
*
* @since 4.3.11
*/
private static final class AllowlistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
private static final class AllowListTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
private static final long serialVersionUID = 1L;
private final String[] trustedPackages;
AllowlistTypeResolverBuilder(String... trustedPackages) {
AllowListTypeResolverBuilder(String... trustedPackages) {
super(ObjectMapper.DefaultTyping.NON_FINAL,
//we do explicit validation in the TypeIdResolver
BasicPolymorphicTypeValidator.builder()

View File

@@ -0,0 +1,57 @@
/*
* Copyright 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.integration.support.json;
import java.io.IOException;
import org.springframework.util.MimeType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/**
* Simple {@link JsonSerializer} extension to represent a {@link MimeType} object in the
* target JSON as a plain string.
*
* @author Artem Bilan
*
* @since 5.4
*/
public class MimeTypeSerializer extends StdSerializer<MimeType> {
private static final long serialVersionUID = 1L;
public MimeTypeSerializer() {
super(MimeType.class);
}
@Override
public void serializeWithType(MimeType value, JsonGenerator generator, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
serialize(value, generator, serializers);
}
@Override
public void serialize(MimeType value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.toString());
}
}