CompositeMessageConverterFactory.java
/*
* Copyright 2015-present 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.converter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import tools.jackson.databind.ObjectMapper;
import org.springframework.cloud.function.context.config.JsonMessageConverter;
import org.springframework.cloud.function.json.JacksonMapper;
import org.springframework.cloud.function.json.JsonMapper;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.DefaultContentTypeResolver;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
/**
* A factory for creating an instance of {@link CompositeMessageConverter} for a given
* target MIME type.
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
* @author Vinicius Carvalho
* @author Oleg Zhurakousky
*/
public class CompositeMessageConverterFactory {
private final Log log = LogFactory.getLog(CompositeMessageConverterFactory.class);
//private final ObjectMapper objectMapper;
private final List<MessageConverter> converters;
private final JsonMapper jsonMapper;
public CompositeMessageConverterFactory() {
this(Collections.<MessageConverter>emptyList(), null);
}
/**
* @param customConverters a list of {@link AbstractMessageConverter}
*/
public CompositeMessageConverterFactory(
List<? extends MessageConverter> customConverters, JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper == null ? new JacksonMapper(new ObjectMapper()) : jsonMapper;
if (!CollectionUtils.isEmpty(customConverters)) {
this.converters = new ArrayList<>(customConverters);
}
else {
this.converters = new ArrayList<>();
}
initDefaultConverters();
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver() {
@Override
public MimeType resolve(@Nullable MessageHeaders headers) {
Map<String, Object> messageHeaders = new HashMap<>(headers);
Object contentType = headers.get(MessageHeaders.CONTENT_TYPE);
if (contentType instanceof byte[]) {
contentType = new String((byte[]) contentType, StandardCharsets.UTF_8);
contentType = ((String) contentType).replace("\"", "");
messageHeaders.put(MessageHeaders.CONTENT_TYPE, contentType);
}
return super.resolve(new MessageHeaders(messageHeaders));
}
};
resolver.setDefaultMimeType(BindingProperties.DEFAULT_CONTENT_TYPE);
this.converters.stream().filter(mc -> mc instanceof AbstractMessageConverter)
.forEach(mc -> ((AbstractMessageConverter) mc)
.setContentTypeResolver(resolver));
}
private void initDefaultConverters() {
this.converters.add(new JsonMessageConverter(this.jsonMapper) {
@Override
protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers,
@Nullable Object conversionHint) {
/*
* We must revisit this. This is a copy from ApplicationMarshallingMessageConverter which derived from an older class etc. . .
* This attempts to use JSON conversion to convert something that is not json in the first place.
* For example Integer payload with application/json CT should actually fail since Integer is not a JSON.
* This is !!!!wrong!!!!! and ONLY remains here for backward compatibility.
*/
if (payload instanceof String) {
return ((String) payload).getBytes(StandardCharsets.UTF_8);
}
return super.convertToInternal(payload, headers, conversionHint);
}
});
this.converters.add(new ByteArrayMessageConverter() {
@Override
protected boolean supports(Class<?> clazz) {
if (!super.supports(clazz)) {
return (Object.class == clazz);
}
return true;
}
});
this.converters.add(new ObjectStringMessageConverter());
}
/**
* Creation method.
* @param mimeType the target MIME type
* @return a converter for the target MIME type
*/
public MessageConverter getMessageConverterForType(MimeType mimeType) {
List<MessageConverter> converters = new ArrayList<>();
for (MessageConverter converter : this.converters) {
if (converter instanceof AbstractMessageConverter) {
for (MimeType type : ((AbstractMessageConverter) converter)
.getSupportedMimeTypes()) {
if (type.includes(mimeType)) {
converters.add(converter);
}
}
}
else {
if (this.log.isDebugEnabled()) {
this.log.debug("Ommitted " + converter + " of type "
+ converter.getClass().toString() + " for '"
+ mimeType.toString()
+ "' as it is not an AbstractMessageConverter");
}
}
}
if (CollectionUtils.isEmpty(converters)) {
throw new ConversionException(
"No message converter is registered for " + mimeType.toString());
}
if (converters.size() > 1) {
return new CompositeMessageConverter(converters);
}
else {
return converters.get(0);
}
}
public CompositeMessageConverter getMessageConverterForAllRegistered() {
return new CompositeMessageConverter(new ArrayList<>(this.converters));
}
}