Skip to content

Commit c712416

Browse files
artembilangaryrussell
authored andcommitted
GH-2748: Add bean definition info into exceptions (#2986)
* GH-2748: Add bean definition info into exceptions Fixes #2748 In many cases Spring Integration stack traces doesn't contain any relations to end-user code. Just because a target project code mostly contains only a configuration for out-of-the-box components without any custom code. When exception is thrown from such an out-of-the-box component, it is hard from the stack trace to determine a configuration source for those components. * Add a logic into the `IntegrationObjectSupport` to obtain a its own `BeanDefinition` from the `BeanFactory` to include a `resource` and `source` (if any) into the `toString()` representation, as well as add a new `getBeanDescription()` to get such an info at runtime * The `toString()` is simply used by `this` reference in the message for `MessagingException` thrown from the `IntegrationObjectSupport` implementations * Modify an exception message for the `MessageTransformingHandler` and `MessageFilter` to make it based on `this`. The `AbstractMessageHandler` already includes `this` into its exception message * Modify a `AbstractConsumerEndpointParser` and `AbstractAmqpInboundAdapterParser` (as a sample) to include a `resource` and `source` into a `MessageHandler` `BeanDefinition`. * Include an `IntegrationFlow` `BeanDefinition` `resource` (`@Configuration` class) and its bean method as a `source` into all child beans declared during flow parsing in the `IntegrationFlowBeanPostProcessor` * Add `IntegrationFlowRegistrationBuilder.setSource()` for manually registered flows: there is no configuration parsing phase to extract such an info from `BeanFactory` * Propagate that `source` into all the child beans provided by the `IntegrationFlow` * Modify a `LambdaMessageProcessor` exception message to include a method info in case of `InvocationTargetException` * Do not cast explicitly for `ConfigurableListableBeanFactory` in the `IntegrationObjectSupport` to avoid tests modifications for mocking directly into `ConfigurableListableBeanFactory`. Use `instanceof` instead in the `getBeanDescription()` * * Fix Checkstyle issues * * Fix `IntegrationGraphServer` and `IntegrationMBeanExporter` to rely on the `NamedComponent` for channel names instead of always call `toString()` which is now much more than just a bean name * Don't describe a `componentName` if it is the same as a `beanName` * Check for parent `BeanDefinition` in the `IntegrationFlowBeanPostProcessor` before calling its meta-info * Fix tests according new `IntegrationObjectSupport.toString()` behavior
1 parent ebb22c2 commit c712416

File tree

25 files changed

+319
-191
lines changed

25 files changed

+319
-191
lines changed

spring-integration-amqp/src/main/java/org/springframework/integration/amqp/config/AbstractAmqpInboundAdapterParser.java

+11-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
2626
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
2727
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2829
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2930
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
3031
import org.springframework.beans.factory.xml.ParserContext;
@@ -39,6 +40,7 @@
3940
* @author Mark Fisher
4041
* @author Oleg Zhurakousky
4142
* @author Gary Russell
43+
* @author Artem Bilan
4244
*
4345
* @since 2.1
4446
*/
@@ -103,7 +105,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
103105
builder.addConstructorArgReference(listenerContainerRef);
104106
}
105107
else {
106-
BeanDefinition listenerContainerBeanDef = this.buildListenerContainer(element, parserContext);
108+
BeanDefinition listenerContainerBeanDef = buildListenerContainer(element, parserContext);
107109
builder.addConstructorArgValue(listenerContainerBeanDef);
108110
}
109111
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
@@ -117,11 +119,12 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
117119
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");
118120
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
119121
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase");
120-
this.configureChannels(element, parserContext, builder);
122+
configureChannels(element, parserContext, builder);
123+
AbstractBeanDefinition adapterBeanDefinition = builder.getRawBeanDefinition();
124+
adapterBeanDefinition.setResource(parserContext.getReaderContext().getResource());
125+
adapterBeanDefinition.setSource(IntegrationNamespaceUtils.createElementDescription(element));
121126
}
122127

123-
protected abstract void configureChannels(Element element, ParserContext parserContext, BeanDefinitionBuilder builder);
124-
125128
private BeanDefinition buildListenerContainer(Element element, ParserContext parserContext) {
126129
if (!element.hasAttribute("queue-names")) {
127130
parserContext.getReaderContext().error("If no 'listener-container' reference is provided, " +
@@ -163,7 +166,7 @@ private BeanDefinition buildListenerContainer(Element element, ParserContext par
163166

164167
private void assertNoContainerAttributes(Element element, ParserContext parserContext) {
165168
Object source = parserContext.extractSource(element);
166-
List<String> allContainerAttributes = new ArrayList<String>(Arrays.asList(CONTAINER_VALUE_ATTRIBUTES));
169+
List<String> allContainerAttributes = new ArrayList<>(Arrays.asList(CONTAINER_VALUE_ATTRIBUTES));
167170
allContainerAttributes.addAll(Arrays.asList(CONTAINER_REFERENCE_ATTRIBUTES));
168171
for (String attributeName : allContainerAttributes) {
169172
if (StringUtils.hasText(element.getAttribute(attributeName))) {
@@ -173,4 +176,7 @@ private void assertNoContainerAttributes(Element element, ParserContext parserCo
173176
}
174177
}
175178

179+
protected abstract void configureChannels(Element element, ParserContext parserContext,
180+
BeanDefinitionBuilder builder);
181+
176182
}

spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java

+13-8
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit
5858
@Override
5959
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
6060
throws BeanDefinitionStoreException {
61+
6162
String id = element.getAttribute(ID_ATTRIBUTE);
6263
if (!StringUtils.hasText(id)) {
6364
id = element.getAttribute("name");
@@ -103,10 +104,12 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont
103104
}
104105

105106
AbstractBeanDefinition handlerBeanDefinition = handlerBuilder.getBeanDefinition();
106-
String inputChannelAttributeName = this.getInputChannelAttributeName();
107+
handlerBeanDefinition.setResource(parserContext.getReaderContext().getResource());
108+
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
109+
handlerBeanDefinition.setSource(elementDescription);
110+
String inputChannelAttributeName = getInputChannelAttributeName();
107111
boolean hasInputChannelAttribute = element.hasAttribute(inputChannelAttributeName);
108112
if (parserContext.isNested()) {
109-
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
110113
if (hasInputChannelAttribute) {
111114
parserContext.getReaderContext().error("The '" + inputChannelAttributeName
112115
+ "' attribute isn't allowed for a nested (e.g. inside a <chain/>) endpoint element: "
@@ -122,7 +125,6 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont
122125
}
123126
else {
124127
if (!hasInputChannelAttribute) {
125-
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
126128
parserContext.getReaderContext().error("The '" + inputChannelAttributeName
127129
+ "' attribute is required for the top-level endpoint element: "
128130
+ elementDescription + ".", element);
@@ -135,9 +137,11 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont
135137
builder.addPropertyValue("adviceChain", adviceChain);
136138
}
137139

138-
String handlerBeanName = BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry());
140+
String handlerBeanName =
141+
BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry());
139142
String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element);
140-
parserContext.registerBeanComponent(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias));
143+
parserContext.registerBeanComponent(
144+
new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias));
141145

142146
builder.addPropertyReference("handler", handlerBeanName);
143147

@@ -157,7 +161,7 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont
157161
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.ROLE);
158162

159163
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
160-
String beanName = this.resolveId(element, beanDefinition, parserContext);
164+
String beanName = resolveId(element, beanDefinition, parserContext);
161165
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName));
162166
return null;
163167
}
@@ -187,12 +191,13 @@ private void registerChannelForCreation(ParserContext parserContext, String inpu
187191

188192
@SuppressWarnings("unchecked")
189193
Collection<String> channelCandidateNames =
190-
(Collection<String>) caValues.getArgumentValue(0, Collection.class).getValue(); // NOSONAR see comment above
194+
(Collection<String>) caValues.getArgumentValue(0, Collection.class)
195+
.getValue(); // NOSONAR see comment above
191196
channelCandidateNames.add(inputChannelName); // NOSONAR
192197
}
193198
else {
194199
parserContext.getReaderContext().error("Failed to locate '" +
195-
IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'",
200+
IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'",
196201
parserContext.getRegistry());
197202
}
198203
}

spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.beans.factory.BeanFactoryAware;
2929
import org.springframework.beans.factory.BeanNameAware;
3030
import org.springframework.beans.factory.InitializingBean;
31+
import org.springframework.beans.factory.config.BeanDefinition;
32+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3133
import org.springframework.context.ApplicationContext;
3234
import org.springframework.context.ApplicationContextAware;
3335
import org.springframework.core.convert.ConversionService;
@@ -136,6 +138,32 @@ public String getComponentType() {
136138
return null;
137139
}
138140

141+
public String getBeanDescription() {
142+
String description = null;
143+
Object source = null;
144+
145+
if (this.beanFactory instanceof ConfigurableListableBeanFactory &&
146+
((ConfigurableListableBeanFactory) this.beanFactory).containsBeanDefinition(this.beanName)) {
147+
BeanDefinition beanDefinition =
148+
((ConfigurableListableBeanFactory) this.beanFactory).getBeanDefinition(this.beanName);
149+
description = beanDefinition.getResourceDescription();
150+
source = beanDefinition.getSource();
151+
}
152+
153+
StringBuilder sb = new StringBuilder("bean '")
154+
.append(this.beanName).append("'");
155+
if (!this.beanName.equals(getComponentName())) {
156+
sb.append("for component '").append(getComponentName()).append("'");
157+
}
158+
if (description != null) {
159+
sb.append("; defined in: '").append(description).append("'");
160+
}
161+
if (source != null) {
162+
sb.append("; from source: '").append(source).append("'");
163+
}
164+
return sb.toString();
165+
}
166+
139167
@Override
140168
public void setBeanFactory(BeanFactory beanFactory) {
141169
Assert.notNull(beanFactory, "'beanFactory' must not be null");
@@ -297,7 +325,7 @@ protected <T> T getIntegrationProperty(String key, Class<T> tClass) {
297325

298326
@Override
299327
public String toString() {
300-
return (this.beanName != null) ? this.beanName : super.toString();
328+
return (this.beanName != null) ? getBeanDescription() : super.toString();
301329
}
302330

303331
@SuppressWarnings("unchecked")

spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowBeanPostProcessor.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.springframework.context.ResourceLoaderAware;
5050
import org.springframework.context.SmartLifecycle;
5151
import org.springframework.core.io.DescriptiveResource;
52+
import org.springframework.core.type.MethodMetadata;
5253
import org.springframework.integration.channel.AbstractMessageChannel;
5354
import org.springframework.integration.channel.DirectChannel;
5455
import org.springframework.integration.channel.FixedSubscriberChannel;
@@ -426,16 +427,25 @@ private void registerComponent(Object component, String beanName) {
426427
private void registerComponent(Object component, String beanName, String parentName,
427428
BeanDefinitionCustomizer... customizers) {
428429

429-
BeanDefinition beanDefinition =
430+
AbstractBeanDefinition beanDefinition =
430431
BeanDefinitionBuilder.genericBeanDefinition((Class<Object>) component.getClass(), () -> component)
431-
.applyCustomizers(customizers)
432-
.getRawBeanDefinition();
432+
.applyCustomizers(customizers)
433+
.getRawBeanDefinition();
434+
435+
if (parentName != null && this.beanFactory.containsBeanDefinition(parentName)) {
436+
AbstractBeanDefinition parentBeanDefinition =
437+
(AbstractBeanDefinition) this.beanFactory.getBeanDefinition(parentName);
438+
beanDefinition.setResource(parentBeanDefinition.getResource());
439+
Object source = parentBeanDefinition.getSource();
440+
if (source instanceof MethodMetadata) {
441+
source = "bean method " + ((MethodMetadata) source).getMethodName();
442+
}
443+
beanDefinition.setSource(source);
444+
this.beanFactory.registerDependentBean(parentName, beanName);
445+
}
433446

434447
((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(beanName, beanDefinition);
435448

436-
if (parentName != null) {
437-
this.beanFactory.registerDependentBean(parentName, beanName);
438-
}
439449

440450
this.beanFactory.getBean(beanName);
441451
}

spring-integration-core/src/main/java/org/springframework/integration/dsl/context/IntegrationFlowContext.java

+10
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ interface IntegrationFlowRegistrationBuilder {
219219
*/
220220
IntegrationFlowRegistrationBuilder addBean(String name, Object bean);
221221

222+
/**
223+
* Set the configuration source {@code Object} for this manual Integration flow definition.
224+
* Can be any arbitrary object which could easily lead to a source code for the flow when
225+
* a messaging exception happens at runtime.
226+
* @param source the configuration source representation.
227+
* @return the current builder instance
228+
* @since 5.2
229+
*/
230+
IntegrationFlowRegistrationBuilder setSource(Object source);
231+
222232
/**
223233
* Invoke this method to prefix bean names in the flow with the (required) flow id
224234
* and a period. This is useful if you wish to register the same flow multiple times

spring-integration-core/src/main/java/org/springframework/integration/dsl/context/StandardIntegrationFlowContext.java

+32-8
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
import org.springframework.beans.factory.BeanFactory;
2828
import org.springframework.beans.factory.BeanFactoryAware;
2929
import org.springframework.beans.factory.BeanFactoryUtils;
30-
import org.springframework.beans.factory.config.BeanDefinition;
3130
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
31+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3232
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3333
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
34+
import org.springframework.core.type.MethodMetadata;
3435
import org.springframework.integration.core.MessagingTemplate;
3536
import org.springframework.integration.dsl.IntegrationFlow;
3637
import org.springframework.integration.support.context.NamedComponent;
@@ -105,7 +106,7 @@ else if (this.registry.containsKey(flowId)) {
105106
"An existing IntegrationFlowRegistration must be destroyed before overriding.");
106107
}
107108

108-
integrationFlow = (IntegrationFlow) registerBean(integrationFlow, flowId, null);
109+
integrationFlow = registerFlowBean(integrationFlow, flowId, builder.source);
109110
}
110111
finally {
111112
if (registerBeanLock != null) {
@@ -124,24 +125,38 @@ else if (this.registry.containsKey(flowId)) {
124125
this.registry.put(flowId, builder.integrationFlowRegistration);
125126
}
126127

128+
private IntegrationFlow registerFlowBean(IntegrationFlow flow, String flowId, @Nullable Object source) {
129+
AbstractBeanDefinition beanDefinition =
130+
BeanDefinitionBuilder.genericBeanDefinition(IntegrationFlow.class, () -> flow)
131+
.getRawBeanDefinition();
132+
beanDefinition.setSource(source);
133+
this.beanDefinitionRegistry.registerBeanDefinition(flowId, beanDefinition);
134+
return this.beanFactory.getBean(flowId, IntegrationFlow.class);
135+
}
136+
127137
@SuppressWarnings("unchecked")
128-
private Object registerBean(Object bean, @Nullable String beanNameArg, String parentName) {
138+
private void registerBean(Object bean, @Nullable String beanNameArg, String parentName) {
129139
String beanName = beanNameArg;
130140
if (beanName == null) {
131141
beanName = generateBeanName(bean, parentName);
132142
}
133143

134-
BeanDefinition beanDefinition =
144+
AbstractBeanDefinition beanDefinition =
135145
BeanDefinitionBuilder.genericBeanDefinition((Class<Object>) bean.getClass(), () -> bean)
136146
.getRawBeanDefinition();
137147

138-
this.beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
139-
140148
if (parentName != null) {
149+
AbstractBeanDefinition parentBeanDefinition =
150+
(AbstractBeanDefinition) this.beanFactory.getBeanDefinition(parentName);
151+
Object source = parentBeanDefinition.getSource();
152+
if (source instanceof MethodMetadata) {
153+
source = "bean method " + ((MethodMetadata) source).getMethodName();
154+
}
155+
beanDefinition.setSource(source);
141156
this.beanFactory.registerDependentBean(parentName, beanName);
142157
}
143-
144-
return this.beanFactory.getBean(beanName);
158+
this.beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
159+
this.beanFactory.getBean(beanName);
145160
}
146161

147162
/**
@@ -247,6 +262,9 @@ public final class StandardIntegrationFlowRegistrationBuilder implements Integra
247262

248263
private boolean idAsPrefix;
249264

265+
@Nullable
266+
private Object source;
267+
250268
StandardIntegrationFlowRegistrationBuilder(IntegrationFlow integrationFlow) {
251269
this.integrationFlowRegistration = new StandardIntegrationFlowRegistration(integrationFlow);
252270
this.integrationFlowRegistration.setBeanFactory(StandardIntegrationFlowContext.this.beanFactory);
@@ -306,6 +324,12 @@ public StandardIntegrationFlowRegistrationBuilder addBean(String name, Object be
306324
return this;
307325
}
308326

327+
@Override
328+
public IntegrationFlowRegistrationBuilder setSource(Object source) {
329+
this.source = source;
330+
return this;
331+
}
332+
309333
@Override
310334
public IntegrationFlowRegistrationBuilder useFlowIdAsPrefix() {
311335
this.idAsPrefix = true;

spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public Object postProcess(Message<?> message, Object result) {
177177
this.messagingTemplate.send(channel, message);
178178
}
179179
if (this.throwExceptionOnRejection) {
180-
throw new MessageRejectedException(message, "MessageFilter '" + getBeanName() + "' rejected Message");
180+
throw new MessageRejectedException(message, "message has been rejected in filter: " + this);
181181
}
182182
}
183183
return result;

0 commit comments

Comments
 (0)