Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INT-3297: Add @MessagingGateway support #1055

Closed
wants to merge 4 commits into from
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 Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -44,6 +44,7 @@
* *
* @author Mark Fisher * @author Mark Fisher
* @author Gary Russell * @author Gary Russell
* @author Artem Bilan
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
Expand All @@ -59,4 +60,8 @@


long replyTimeout() default Long.MIN_VALUE; long replyTimeout() default Long.MIN_VALUE;


String payloadExpression() default "";

GatewayHeader[] headers() default {};

} }
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2014 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
*
* http://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.annotation;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Provides the message header {@code value} or {@code expression}.
*
* @author Artem Bilan
* @since 4.0
*/
@Target({ })
@Retention(RUNTIME)
public @interface GatewayHeader {

/**
* @return The name of the header.
*/
String name();

/**
* @return The value for the header.
*/
String value() default "";

/**
* @return The {@code Expression} to be evaluated to produce a value for the header.
*/
String expression() default "";

}
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2014 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
*
* http://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.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;
import org.springframework.integration.config.IntegrationComponentScanRegistrar;

/**
* Configures component scanning directives for use with @{@link org.springframework.context.annotation.Configuration} classes.
* Scan Spring Integration specific components.
*
* @author Artem Bilan
* @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(IntegrationComponentScanRegistrar.class)
public @interface IntegrationComponentScan {

/**
* Alias for the {@link #basePackages()} attribute.
* Allows for more concise annotation declarations e.g.:
* {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
*/
String[] value() default {};

/**
* Base packages to scan for annotated components.
* <p>{@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};

/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};

}
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2014 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
*
* http://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.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* The stereotype annotation to provide the Integration Messaging Gateway Proxy ({@code <gateway/>}).
*
* @author Artem Bilan
* @since 4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MessagingGateway {

/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
*
* @return the suggested component name, if any
*/
String name() default "";

/**
* Identifies default channel the messages will be sent to upon invocation of methods of the gateway proxy.
*
* @return the suggested channel name, if any
*/
String defaultRequestChannel() default "";

/**
* Identifies default channel the gateway proxy will subscribe to to receive reply {@code Message}s, which will then be
* converted to the return type of the method signature.
*
* @return the suggested channel name, if any
*/
String defaultReplyChannel() default "";

/**
* Identifies a channel that error messages will be sent to if a failure occurs in the
* gateway's proxy invocation. If no {@code errorChannel} reference is provided, the gateway will
* propagate {@code Exception}s to the caller. To completely suppress {@code Exception}s, provide a
* reference to the {@code nullChannel} here.
*
* @return the suggested channel name, if any
*/
String errorChannel() default "";

/**
* Provides the amount of time dispatcher would wait to send a {@code Message}.
* This timeout would only apply if there is a potential to block in the send call.
* For example if this gateway is hooked up to a {@code QueueChannel}. 
*
* @return the suggested timeout in milliseconds, if any
*/
long defaultRequestTimeout() default Long.MIN_VALUE;

/**
* Allows to specify how long this gateway will wait for the reply {@code Message}
* before returning. By default it will wait indefinitely. {@code null} is returned
if the gateway times out.
*
* @return the suggested timeout in milliseconds, if any
*/
long defaultReplyTimeout() default Long.MIN_VALUE;

/**
* Provide a reference to an implementation of {@link java.util.concurrent.Executor}
* to use for any of the interface methods that have a {@link java.util.concurrent.Future} return type.
* This {@code Executor} will only be used for those async methods; the sync methods
* will be invoked in the caller's thread.
*
* @return the suggested executor bean name, if any
*/
String asyncExecutor() default "";

/**
* An expression that will be used to generate the {@code payload} for all methods in the service interface
* unless explicitly overridden by a method declaration. Variables include {@code #args}, {@code #methodName},
* {@code #methodString} and {@code #methodObject};
* a bean resolver is also available, enabling expressions like {@code @someBean(#args)}.
*
* @return the suggested payload expression, if any
*/
String defaultPayloadExpression() default "";

/**
* Provides custom message headers. These default headers are created for
* all methods on the service-interface (unless overridden by a specific method).
*
* @return the suggested payload expression, if any
*/
GatewayHeader[] defaultHeaders() default {};

/**
* An {@link org.springframework.integration.gateway.MethodArgsMessageMapper}
* to map the method arguments to a {@link org.springframework.messaging.Message}. When this
* is provided, no {@code payload-expression}s or {@code header}s are allowed; the custom mapper is
* responsible for creating the message.
*
* @return the suggested mapper bean name, if any
*/
String mapper() default "";

}
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2014 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
*
* http://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.config;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
* {@link ImportBeanDefinitionRegistrar} implementation to scan and register Integration specific components.
*
* @author Artem Bilan
* @since 4.0
*/
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars = new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();

private ResourceLoader resourceLoader;

public IntegrationComponentScanRegistrar() {
this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class), new MessagingGatewayRegistrar());
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> componentScan = importingClassMetadata.getAnnotationAttributes("org.springframework.integration.annotation.IntegrationComponentScan");

Set<String> basePackages = new HashSet<String>();
for (String pkg : (String[]) componentScan.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) componentScan.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) componentScan.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}

if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}

ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {

@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isIndependent();
}
};

for (TypeFilter typeFilter : componentRegistrars.keySet()) {
scanner.addIncludeFilter(typeFilter);
}

scanner.setResourceLoader(resourceLoader);

for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; except I wonder if we should add this...

Assert.isTrue(((AnnotatedBeanDefinition) candidateComponent).getMetadata().isInterface(),
                        "@MessagingGateway can only be specified on an interface");

...here. Without it, we get...

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableIntegrationTests$TestGateway2': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.integration.gateway.GatewayProxyFactoryBean]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException: 'serviceInterface' must be an interface
...
Caused by: java.lang.IllegalArgumentException: 'serviceInterface' must be an interface

... which I suppose is fairly obvious, to those who understand what's happening, but perhaps the Assert would be more helpful to newbies?

WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Thanks. Wouldn't you mind to do it on merge ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However it should be in concrete visitor - MessagingGatewayRegistrar

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - ok - will do during merge.

for (ImportBeanDefinitionRegistrar importBeanDefinitionRegistrar : componentRegistrars.values()) {
importBeanDefinitionRegistrar.registerBeanDefinitions(((AnnotatedBeanDefinition) candidateComponent).getMetadata(), registry);
}
}
}
}
}

}
Loading