BusJacksonAutoConfiguration.java
/*
* Copyright 2012-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.bus.jackson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.exc.InvalidTypeIdException;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.dataformat.cbor.CBORFactory;
import tools.jackson.dataformat.cbor.CBORMapper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.bus.BusAutoConfiguration;
import org.springframework.cloud.bus.ConditionalOnBusEnabled;
import org.springframework.cloud.bus.endpoint.RefreshBusEndpoint;
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
import org.springframework.cloud.bus.event.UnknownRemoteApplicationEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Donovan Muller
* @author Stefan Pfeiffer
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBusEnabled
@ConditionalOnClass({ RefreshBusEndpoint.class, ObjectMapper.class })
@AutoConfigureBefore(value = BusAutoConfiguration.class,
name = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")
public class BusJacksonAutoConfiguration {
// needed in the case where @RemoteApplicationEventScan is not used
// otherwise RemoteApplicationEventRegistrar will register the bean
@Bean
@ConditionalOnMissingBean(name = "busJsonConverter")
public AbstractMessageConverter busJsonConverter(@Autowired(required = false) JsonMapper objectMapper) {
return new BusJacksonMessageConverter(objectMapper);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CBORFactory.class)
protected static class CborConfiguration {
@Bean
public AbstractMessageConverter busCborConverter(CBORMapper objectMapper) {
return new BusJacksonMessageConverter(new MimeType("application", "cbor"), objectMapper);
}
}
}
class BusJacksonMessageConverter extends AbstractMessageConverter implements InitializingBean {
private static final Log log = LogFactory.getLog(BusJacksonMessageConverter.class);
private static final String DEFAULT_PACKAGE = ClassUtils.getPackageName(RemoteApplicationEvent.class);
private final MapperBuilder mapperBuilder;
private final boolean mapperCreated;
private String[] packagesToScan = new String[] { DEFAULT_PACKAGE };
private BusJacksonMessageConverter() {
this(null);
}
@Autowired(required = false)
BusJacksonMessageConverter(@Nullable ObjectMapper objectMapper) {
this(MimeTypeUtils.APPLICATION_JSON, objectMapper);
}
@Autowired(required = false)
BusJacksonMessageConverter(MimeType mimeType, @Nullable ObjectMapper objectMapper) {
super(mimeType);
if (objectMapper != null) {
this.mapperBuilder = objectMapper.rebuild();
this.mapperCreated = false;
}
else {
this.mapperBuilder = JsonMapper.builder();
this.mapperCreated = true;
}
}
/* for testing */ boolean isMapperCreated() {
return this.mapperCreated;
}
/* for testing */ MapperBuilder getMapper() {
return this.mapperBuilder;
}
public void setPackagesToScan(String[] packagesToScan) {
List<String> packages = new ArrayList<>(Arrays.asList(packagesToScan));
if (!packages.contains(DEFAULT_PACKAGE)) {
packages.add(DEFAULT_PACKAGE);
}
this.packagesToScan = packages.toArray(new String[0]);
}
private Class<?>[] findSubTypes() {
List<Class<?>> types = new ArrayList<>();
if (this.packagesToScan != null) {
for (String pkg : this.packagesToScan) {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false);
provider.addIncludeFilter(new AssignableTypeFilter(RemoteApplicationEvent.class));
Set<BeanDefinition> components = provider.findCandidateComponents(pkg);
for (BeanDefinition component : components) {
try {
types.add(Class.forName(component.getBeanClassName()));
}
catch (ClassNotFoundException e) {
throw new IllegalStateException("Failed to scan classpath for remote event classes", e);
}
}
}
}
if (log.isDebugEnabled()) {
log.debug("Found sub types: " + types);
}
return types.toArray(new Class<?>[0]);
}
@Override
protected boolean supports(Class<?> aClass) {
// This converter applies only to RemoteApplicationEvent and subclasses
return RemoteApplicationEvent.class.isAssignableFrom(aClass);
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
ObjectMapper mapper = this.mapperBuilder.build();
Object result = null;
try {
Object payload = message.getPayload();
if (payload instanceof byte[]) {
try {
result = mapper.readValue((byte[]) payload, targetClass);
}
catch (InvalidTypeIdException e) {
return new UnknownRemoteApplicationEvent(new Object(), e.getTypeId(), (byte[]) payload);
}
}
else if (payload instanceof String) {
try {
result = mapper.readValue((String) payload, targetClass);
}
catch (InvalidTypeIdException e) {
return new UnknownRemoteApplicationEvent(new Object(), e.getTypeId(),
((String) payload).getBytes());
}
// workaround for
// https://github.com/spring-cloud/spring-cloud-stream/issues/1564
}
else if (payload instanceof RemoteApplicationEvent) {
return payload;
}
}
catch (Exception e) {
this.logger.error(e.getMessage(), e);
return null;
}
return result;
}
@Override
public void afterPropertiesSet() {
this.mapperBuilder.subtypeResolver().registerSubtypes(findSubTypes());
}
}