Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ InitializingBean supplierInitializer(FunctionCatalog functionCatalog, StreamFunc

return () -> {
for (BindableFunctionProxyFactory proxyFactory : proxyFactories) {
// see https://github.com/spring-cloud/spring-cloud-stream/issues/3203
// A standalone output-/input-binding has no backing function, yet its
// function definition is now the binding name (since gh-3166 stopped
// null-ing it out in getFunctionDefinition()). Skip such bindings here so
// that a plain output channel is never resolved and treated as a Supplier.
if (!proxyFactory.isFunctionExist()) {
continue;
}
FunctionInvocationWrapper functionWrapper = functionCatalog.lookup(proxyFactory.getFunctionDefinition());
if (functionWrapper != null && functionWrapper.isSupplier()) {
// gather output content types
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2024-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.function;

import java.util.Map;

import org.junit.jupiter.api.Test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Regression test for gh-3203: an output-only binding that has no backing
* {@code Supplier}/{@code Function} bean must not be treated as a {@code Supplier}.
* Prior to the fix the standalone output binding (whose function definition is the
* binding name) reached {@code supplierInitializer}, which built a
* {@link SourcePollingChannelAdapter} on top of the plain output channel and threw a
* {@code ClassCastException} (the channel cannot be cast to {@code Supplier}) on the
* first poll.
*
* @author Spring Cloud Stream contributors
*/
class OutputOnlyBindingSupplierTests {

@Test
void outputOnlyBindingIsNotTreatedAsSupplier() {
try (ConfigurableApplicationContext context = new SpringApplication(EmptyConfiguration.class).run(
"--spring.cloud.stream.output-bindings=testOutput",
"--spring.jmx.enabled=false",
"--spring.main.web-application-type=none",
"--spring.cloud.stream.default-binder=mock")) {

Map<String, SourcePollingChannelAdapter> pollingAdapters =
context.getBeansOfType(SourcePollingChannelAdapter.class);

assertThat(pollingAdapters)
.as("No SourcePollingChannelAdapter must be created for an output-only "
+ "binding without a backing Supplier")
.isEmpty();
}
}

@EnableAutoConfiguration
@Configuration
public static class EmptyConfiguration {

}

}
Loading