GH-8757: Add nullabilty to .support.converter

Fixes https://github.com/spring-projects/spring-integration/issues/8757

* Add ctor `ObjectStringMessageConverter(Charset)`
* create tests for `ObjectStringMessageConverter`
* fix codestyle issues
* revise JavaDoc
* `@NonNullApi` for `org.springframework.integration.support.converter`
* revise `SimpleMessageConverter`
* `inboundMessageMapper` and `outboundMessageMapper` are initialized to their default implementations
Bboth are set in non-default ctors with final setters
* remove `@Nullable` from `private BeanFactory beanFactory`
* Handle empty `String... headerNames`
* Some code clean up
This commit is contained in:
Falk Hanisch
2023-10-10 03:50:08 -04:00
committed by Artem Bilan
parent 6909fbe7ad
commit 29186e2b46
8 changed files with 160 additions and 58 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -30,6 +30,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.support.SerializationFailedException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
@@ -55,6 +56,7 @@ public class AllowListDeserializingConverter implements Converter<byte[], Object
private final Deserializer<Object> deserializer;
@Nullable
private final ClassLoader defaultDeserializerClassLoader;
private final boolean usingDefaultDeserializer;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2023 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.
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
@@ -33,22 +34,21 @@ import org.springframework.util.Assert;
* if present.
*
* @author Gary Russell
*
* @since 4.0
*
*/
public class DefaultDatatypeChannelMessageConverter implements MessageConverter,
BeanFactoryAware {
public class DefaultDatatypeChannelMessageConverter implements MessageConverter, BeanFactoryAware {
private volatile ConversionService conversionService = DefaultConversionService.getSharedInstance();
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
private volatile boolean conversionServiceSet;
private boolean conversionServiceSet;
/**
* Specify the {@link ConversionService} to use when trying to convert to
* requested type. If this property is not set explicitly but
* the converter is managed within a context, it will attempt to locate a
* bean named "integrationConversionService" defined within that context.
*
* @param conversionService The conversion service.
*/
public void setConversionService(ConversionService conversionService) {
@@ -59,7 +59,7 @@ public class DefaultDatatypeChannelMessageConverter implements MessageConverter,
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!this.conversionServiceSet && beanFactory != null) {
if (!this.conversionServiceSet) {
ConversionService integrationConversionService = IntegrationUtils.getConversionService(beanFactory);
if (integrationConversionService != null) {
this.conversionService = integrationConversionService;
@@ -81,7 +81,7 @@ public class DefaultDatatypeChannelMessageConverter implements MessageConverter,
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders header) {
public Message<?> toMessage(Object payload, @Nullable MessageHeaders header) {
throw new UnsupportedOperationException("This converter does not support this method");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 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.
@@ -31,6 +31,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Converts to/from a Map with 2 keys ('headers' and 'payload').
@@ -43,15 +44,15 @@ import org.springframework.util.Assert;
*/
public class MapMessageConverter implements MessageConverter, BeanFactoryAware {
private volatile String[] headerNames;
private String[] headerNames = {};
private volatile boolean filterHeadersInToMessage;
private boolean filterHeadersInToMessage;
private volatile BeanFactory beanFactory;
private BeanFactory beanFactory;
private volatile MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
private volatile boolean messageBuilderFactorySet;
private boolean messageBuilderFactorySet;
@Override
@@ -77,16 +78,19 @@ public class MapMessageConverter implements MessageConverter, BeanFactoryAware {
* @param headerNames The header names.
*/
public void setHeaderNames(String... headerNames) {
Assert.notEmpty(headerNames, "at least one header name is required");
this.headerNames = Arrays.copyOf(headerNames, headerNames.length);
if (ObjectUtils.isEmpty(headerNames)) {
this.headerNames = new String[] {};
}
else {
this.headerNames = Arrays.copyOf(headerNames, headerNames.length);
}
}
/**
* By default all headers on Map passed to {@link #toMessage(Object, MessageHeaders)}
* By default, all headers on Map passed to {@link #toMessage(Object, MessageHeaders)}
* will be mapped. Set this property
* to 'true' if you wish to limit the inbound headers to those in
* the #headerNames.
*
* to 'true' if you wish to limit the inbound headers to those
* in the {@link #headerNames}.
* @param filterHeadersInToMessage true if the headers should be filtered.
*/
public void setFilterHeadersInToMessage(boolean filterHeadersInToMessage) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2023 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.
@@ -16,6 +16,9 @@
package org.springframework.integration.support.converter;
import java.nio.charset.Charset;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.StringMessageConverter;
@@ -24,16 +27,31 @@ import org.springframework.messaging.converter.StringMessageConverter;
* <p>
* Delegates to super when payload is {@code byte[]} or {@code String}.
* Performs {@link Object#toString()} in other cases.
* <p>
* This class is intended to serve as a fallback converter for internal message deserialization purposes. Therefore, it
* is recommended to exclusively use the
* {@link org.springframework.messaging.converter.AbstractMessageConverter#fromMessage(Message, Class) fromMessage}
* method with {@code String.class} as the {@code targetClass}.
*
* @author Marius Bogoevici
* @author Artem Bilan
* @author Falk Hanisch
*
* @since 5.0
*/
public class ObjectStringMessageConverter extends StringMessageConverter {
public ObjectStringMessageConverter(Charset defaultCharset) {
super(defaultCharset);
}
public ObjectStringMessageConverter() {
super();
}
@Override
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
Object payload = message.getPayload();
if (payload instanceof String || payload instanceof byte[]) {
return super.convertFromInternal(message, targetClass, conversionHint);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2023 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.
@@ -16,11 +16,11 @@
package org.springframework.integration.support.converter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
/**
* The simple {@link MessageConverter} implementation which contract is to return
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
* operates only with {@link Message}s, e.g. Spring Integration Adapters.
*
* @author Artem Bilan
*
* @since 4.2
*/
public class PassThruMessageConverter implements MessageConverter {
@@ -41,9 +42,13 @@ public class PassThruMessageConverter implements MessageConverter {
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers) {
Assert.isInstanceOf(byte[].class, payload, "'payload' must be of 'byte[]' type.");
return MessageBuilder.createMessage(payload, headers);
public Message<?> toMessage(Object payload, @Nullable MessageHeaders headers) {
if (payload instanceof byte[]) {
return MessageBuilder.withPayload(payload).copyHeaders(headers).build();
}
else {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -42,51 +42,50 @@ import org.springframework.messaging.converter.MessageConverter;
@SuppressWarnings({"unchecked", "rawtypes"})
public class SimpleMessageConverter implements MessageConverter, BeanFactoryAware {
private volatile InboundMessageMapper inboundMessageMapper;
private InboundMessageMapper inboundMessageMapper = new DefaultInboundMessageMapper();
private volatile OutboundMessageMapper outboundMessageMapper;
private OutboundMessageMapper outboundMessageMapper = new DefaultOutboundMessageMapper();
private volatile MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
private volatile boolean messageBuilderFactorySet;
private boolean messageBuilderFactorySet;
private BeanFactory beanFactory;
public SimpleMessageConverter() {
this(null, null);
}
public SimpleMessageConverter() { }
public SimpleMessageConverter(InboundMessageMapper<?> inboundMessageMapper) {
this(inboundMessageMapper,
(inboundMessageMapper instanceof OutboundMessageMapper
? (OutboundMessageMapper<?>) inboundMessageMapper
: null));
setInboundMessageMapper(inboundMessageMapper);
if (inboundMessageMapper instanceof OutboundMessageMapper<?> messageMapper) {
setOutboundMessageMapper(messageMapper);
}
}
public SimpleMessageConverter(OutboundMessageMapper<?> outboundMessageMapper) {
this(outboundMessageMapper instanceof InboundMessageMapper
? (InboundMessageMapper<?>) outboundMessageMapper
: null,
outboundMessageMapper);
if (outboundMessageMapper instanceof InboundMessageMapper<?> messageMapper) {
setInboundMessageMapper(messageMapper);
}
setOutboundMessageMapper(outboundMessageMapper);
}
public SimpleMessageConverter(InboundMessageMapper<?> inboundMessageMapper,
OutboundMessageMapper<?> outboundMessageMapper) {
this.setInboundMessageMapper(inboundMessageMapper);
this.setOutboundMessageMapper(outboundMessageMapper);
setInboundMessageMapper(inboundMessageMapper);
setOutboundMessageMapper(outboundMessageMapper);
}
public final void setInboundMessageMapper(InboundMessageMapper<?> inboundMessageMapper) {
this.inboundMessageMapper = (inboundMessageMapper != null)
? inboundMessageMapper
: new DefaultInboundMessageMapper();
public final void setInboundMessageMapper(@Nullable InboundMessageMapper<?> inboundMessageMapper) {
if (inboundMessageMapper != null) {
this.inboundMessageMapper = inboundMessageMapper;
}
}
public final void setOutboundMessageMapper(OutboundMessageMapper<?> outboundMessageMapper) {
this.outboundMessageMapper = (outboundMessageMapper != null
? outboundMessageMapper
: new DefaultOutboundMessageMapper());
public final void setOutboundMessageMapper(@Nullable OutboundMessageMapper<?> outboundMessageMapper) {
if (outboundMessageMapper != null) {
this.outboundMessageMapper = outboundMessageMapper;
}
}
@Override
@@ -110,8 +109,8 @@ public class SimpleMessageConverter implements MessageConverter, BeanFactoryAwar
try {
return this.inboundMessageMapper.toMessage(object, headers);
}
catch (Exception e) {
throw new MessageConversionException("failed to convert object to Message", e);
catch (Exception ex) {
throw new MessageConversionException("failed to convert object to Message", ex);
}
}
@@ -121,8 +120,8 @@ public class SimpleMessageConverter implements MessageConverter, BeanFactoryAwar
try {
return this.outboundMessageMapper.fromMessage(message);
}
catch (Exception e) {
throw new MessageConversionException(message, "failed to convert Message to object", e);
catch (Exception ex) {
throw new MessageConversionException(message, "failed to convert Message to object", ex);
}
}

View File

@@ -1,4 +1,6 @@
/**
* Provides classes supporting message conversion.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.integration.support.converter;

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2023 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.converter;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Falk Hanisch
*
* @since 6.2
*/
class ObjectStringMessageConverterTests {
private static final ArbitraryPayload PAYLOAD = new ArbitraryPayload(1234, "ÄÖÜ");
@Test
void testFromMessage() {
Message<ArbitraryPayload> message = MessageBuilder.withPayload(PAYLOAD).build();
ObjectStringMessageConverter converter = new ObjectStringMessageConverter();
Object result = converter.fromMessage(message, String.class);
assertThat(message.getPayload()).isEqualTo(PAYLOAD);
assertThat(result).isEqualTo(PAYLOAD.toString());
result = converter.fromMessage(message, Object.class);
assertThat(result).isNull();
}
@Test
void testToMessage() {
String text = PAYLOAD.text();
ObjectStringMessageConverter converter = new ObjectStringMessageConverter(StandardCharsets.ISO_8859_1);
Message<?> converted = converter.toMessage(text, null);
assertThat(converted).isNotNull();
assertThat(converted.getPayload()).isEqualTo(text.getBytes(StandardCharsets.ISO_8859_1));
converter.setSerializedPayloadClass(String.class);
converted = converter.toMessage(text, null);
assertThat(converted).isNotNull();
assertThat(converted.getPayload()).isEqualTo(text);
converted = converter.toMessage(PAYLOAD, null);
assertThat(converted).isNull();
}
private record ArbitraryPayload(Integer id, String text) {
}
}