├── gradle.properties ├── reactor-spring-context └── src │ ├── main │ └── java │ │ └── reactor │ │ └── spring │ │ ├── package-info.java │ │ └── context │ │ ├── annotation │ │ ├── package-info.java │ │ ├── SelectorType.java │ │ ├── Consumer.java │ │ ├── ReplyTo.java │ │ └── Selector.java │ │ ├── config │ │ ├── package-info.java │ │ ├── EnableReactor.java │ │ ├── ReactorBeanDefinitionRegistrar.java │ │ └── ConsumerBeanAutoConfiguration.java │ │ └── RingBufferApplicationEventPublisher.java │ └── test │ ├── groovy │ └── reactor │ │ └── spring │ │ ├── package-info.java │ │ └── context │ │ └── config │ │ ├── CustomRuntimeException.groovy │ │ ├── ReactorBeanDefinitionRegistrarSpec.groovy │ │ └── ConsumerBeanAutoConfigurationSpec.groovy │ └── resources │ └── logback.xml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── setup.gradle ├── reactor-spring-core └── src │ ├── main │ └── java │ │ └── reactor │ │ └── spring │ │ ├── package-info.java │ │ ├── core │ │ ├── package-info.java │ │ └── task │ │ │ ├── package-info.java │ │ │ ├── AsyncTaskExceptionEvent.java │ │ │ ├── WorkQueueAsyncTaskExecutor.java │ │ │ ├── RingBufferAsyncTaskExecutor.java │ │ │ └── AbstractAsyncTaskExecutor.java │ │ ├── selector │ │ ├── package-info.java │ │ └── ExpressionSelector.java │ │ └── factory │ │ ├── package-info.java │ │ └── CreateOrReuseFactoryBean.java │ └── test │ └── groovy │ └── reactor │ └── spring │ ├── selector │ └── ExpressionSelectorSpec.groovy │ └── task │ ├── TaskExecutorsSpec.groovy │ └── TaskExecutorTests.java ├── settings.gradle ├── .gitignore ├── README.md ├── gradlew.bat ├── reactor-spring-messaging └── src │ ├── main │ └── java │ │ └── reactor │ │ └── spring │ │ └── messaging │ │ └── ReactorSubscribableChannel.java │ └── test │ └── java │ └── reactor │ └── spring │ └── messaging │ └── ReactorSubscribableChannelThroughputTests.java ├── gradlew ├── LICENSE └── src └── api └── stylesheet.css /gradle.properties: -------------------------------------------------------------------------------- 1 | version=3.0.2.BUILD-SNAPSHOT -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/package-info.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /reactor-spring-context/src/test/groovy/reactor/spring/package-info.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/reactor-spring/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Reactor integration with the Spring Framework. 3 | */ 4 | package reactor.spring; -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reactor-spring' 2 | 3 | include 'reactor-spring-core', 4 | 'reactor-spring-context', 5 | 'reactor-spring-messaging' 6 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Components to integrate with Spring Framework Core. 3 | */ 4 | package reactor.spring.core; -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/task/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Spring {@link org.springframework.core.task.TaskExecutor} integration. 3 | */ 4 | package reactor.spring.core.task; -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/selector/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Assign {@link reactor.bus.selector.Selector Selectors} based on SpEL expressions. 3 | */ 4 | package reactor.spring.selector; -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Annotation classes to support wiring event handlers via component scanning. 3 | */ 4 | package reactor.spring.context.annotation; -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/factory/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link org.springframework.beans.factory.FactoryBean} implementations to provide various components. 3 | */ 4 | package reactor.spring.factory; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | build 4 | *.i* 5 | *.class 6 | atlassian-ide-plugin.xml 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # ECLIPSE/STS # 14 | .settings/ 15 | .gradle/ 16 | .project 17 | .classpath 18 | .gradle 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 23 12:07:39 BST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactor-spring is no longer actively maintained by VMware, Inc. 2 | 3 | reactor-spring 4 | ============== 5 | 6 | Reactor Spring Components 7 | 8 | -- Warning this repository is deprecated due to full reactive support planned by Spring 5 - see https://spring.io/blog/2016/02/09/reactive-spring 9 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Automatic configuration of {@link org.springframework.context.ApplicationContext ApplicationContexts} via a 3 | * Reactor-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}. 4 | */ 5 | package reactor.spring.context.config; -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/annotation/SelectorType.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.annotation; 2 | 3 | /** 4 | * Enum for indicating the type of {@link reactor.bus.selector.Selector} that should be created. 5 | * 6 | * @author Jon Brisbin 7 | */ 8 | public enum SelectorType { 9 | OBJECT, REGEX, URI, TYPE, JSON_PATH 10 | } 11 | -------------------------------------------------------------------------------- /reactor-spring-context/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/task/AsyncTaskExceptionEvent.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.core.task; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | /** 6 | * {@link org.springframework.context.ApplicationEvent} for publishing uncaught errors from asynchronous task execution. 7 | * 8 | * @author Jon Brisbin 9 | * @since 1.1 10 | */ 11 | public class AsyncTaskExceptionEvent extends ApplicationEvent { 12 | 13 | private static final long serialVersionUID = 5172014386416785095L; 14 | 15 | public AsyncTaskExceptionEvent(Object source) { 16 | super(source); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/annotation/Consumer.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.annotation; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Denotes a class as being eligible to scan for event handling methods. Composite annotation that includes the {@link 12 | * org.springframework.stereotype.Component} annotation. 13 | * 14 | * @author Jon Brisbin 15 | */ 16 | @Target(ElementType.TYPE) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Component 19 | public @interface Consumer { 20 | } 21 | -------------------------------------------------------------------------------- /reactor-spring-context/src/test/groovy/reactor/spring/context/config/CustomRuntimeException.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reactor.spring.context.config 17 | 18 | import groovy.transform.CompileStatic 19 | 20 | /** 21 | * @author Stephane Maldini 22 | */ 23 | @CompileStatic 24 | class CustomRuntimeException extends RuntimeException { 25 | CustomRuntimeException(String var1) { 26 | super(var1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /reactor-spring-core/src/test/groovy/reactor/spring/selector/ExpressionSelectorSpec.groovy: -------------------------------------------------------------------------------- 1 | package reactor.spring.selector 2 | 3 | import reactor.bus.Event 4 | import reactor.bus.EventBus 5 | import spock.lang.Specification 6 | 7 | import java.util.function.Consumer 8 | 9 | import static ExpressionSelector.E 10 | 11 | /** 12 | * @author Jon Brisbin 13 | */ 14 | class ExpressionSelectorSpec extends Specification { 15 | 16 | 17 | def "SpEL Expressions can be used as Selectors"() { 18 | 19 | given: 20 | "a plain Reactor" 21 | def r = EventBus.config().get() 22 | def names = [] 23 | 24 | when: 25 | "a SpEL expression is used as a Selector and the Reactor is notified" 26 | r.on(E("name == 'John Doe'"), { ev -> names << ev.key.name } as Consumer>) 27 | r.notify(new TestBean(name: "Jane Doe")) 28 | r.notify(new TestBean(name: "Jim Doe")) 29 | r.notify(new TestBean(name: "John Doe")) 30 | 31 | then: 32 | "only one should have matched" 33 | names == ["John Doe"] 34 | 35 | } 36 | 37 | } 38 | 39 | class TestBean { 40 | String name 41 | } 42 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/annotation/ReplyTo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc., Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reactor.spring.context.annotation; 17 | 18 | import java.lang.annotation.*; 19 | 20 | /** 21 | * Indicate a method return is to be sent to the key referenced by the given expression. 22 | * 23 | * @author Jon Brisbin 24 | * @author Stephane Maldini 25 | */ 26 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Inherited 29 | public @interface ReplyTo { 30 | 31 | /** 32 | * An expression which evaluates to a key to which is sent the method return value. 33 | * If empty, consumer will try to use {@link reactor.bus.Event#getReplyTo()} header. 34 | * 35 | * @return The expression. 36 | */ 37 | String value() default ""; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/config/EnableReactor.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.context.annotation.Import; 9 | 10 | /** 11 | * Helper annotation to be placed on {@link org.springframework.context.annotation.Configuration} classes to ensure 12 | * a {@link reactor.core.scheduler.TimedScheduler} and 13 | * {@link org.reactivestreams.Processor} are 14 | * created in application context as well as create the necessary beans for 15 | * automatic wiring of annotated beans. 16 | * 17 | * @author Jon Brisbin 18 | * @author Stephane Maldini 19 | */ 20 | @Target(ElementType.TYPE) 21 | @Retention(RetentionPolicy.RUNTIME) 22 | @Import(ReactorBeanDefinitionRegistrar.class) 23 | public @interface EnableReactor { 24 | 25 | /** 26 | * The bean name of {@link java.util.function.Supplier} that can provide an instance (or instances) of 27 | * {@link reactor.core.scheduler.TimedScheduler} to be registered in the {@link org.springframework.context.ApplicationContext}. 28 | * 29 | * @return bean name of {@link reactor.core.scheduler.TimedScheduler} {@link java.util.function.Supplier} 30 | */ 31 | String timerSupplier() default ""; 32 | 33 | /** 34 | * The bean name of {@link java.util.function.Supplier} that can provide an instance (or instances) of 35 | * {@link org.reactivestreams.Processor} to be registered in the {@link org.springframework.context.ApplicationContext}. 36 | * 37 | * @return bean name of {@link org.reactivestreams.Processor} {@link java.util.function.Supplier} 38 | */ 39 | String processorSupplier() default ""; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/annotation/Selector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc., Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reactor.spring.context.annotation; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Inherited; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * Placed on a method to denote that it is an event handler. 26 | * 27 | * @author Jon Brisbin 28 | * @author Stephane Maldini 29 | */ 30 | @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | @Inherited 33 | public @interface Selector { 34 | 35 | /** 36 | * An expression that evaluates to a {@link reactor.bus.selector.Selector} to register 37 | * this handler with the {@link 38 | * reactor.bus.EventBus}. 39 | * If empty, consumer will be subscribed on the global reactor selector 40 | * {@link reactor.bus.EventBus#on(reactor.bus.selector.Selector selector, java.util.function.Consumer)} 41 | * 42 | * @return An expression to be evaluated. 43 | */ 44 | String value() default ""; 45 | 46 | /** 47 | * An expression that evaluates to the {@link reactor.bus.EventBus} on which to place this handler. 48 | * 49 | * @return An expression to be evaluated. 50 | */ 51 | String eventBus() default "eventBus"; 52 | 53 | /** 54 | * The type of {@link reactor.bus.selector.Selector} to register. 55 | * 56 | * @return The type of the {@link reactor.bus.selector.Selector}. 57 | */ 58 | SelectorType type() default SelectorType.OBJECT; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/task/WorkQueueAsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.core.task; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import reactor.core.publisher.WorkQueueProcessor; 6 | import reactor.core.scheduler.Schedulers; 7 | import reactor.core.scheduler.TimedScheduler; 8 | import reactor.util.concurrent.WaitStrategy; 9 | 10 | import org.springframework.context.ApplicationEventPublisherAware; 11 | 12 | /** 13 | * Implementation of an {@link org.springframework.core.task.AsyncTaskExecutor} that is backed by a Reactor {@link 14 | * WorkQueueProcessor}. 15 | * 16 | * @author Jon Brisbin 17 | * @author Stephane Maldini 18 | * @since 1.1, 2.5 19 | */ 20 | public class WorkQueueAsyncTaskExecutor extends AbstractAsyncTaskExecutor implements ApplicationEventPublisherAware { 21 | 22 | private final Logger log = LoggerFactory.getLogger(WorkQueueAsyncTaskExecutor.class); 23 | 24 | private WaitStrategy waitStrategy; 25 | private WorkQueueProcessor workQueue; 26 | 27 | public WorkQueueAsyncTaskExecutor() { 28 | this(Schedulers.timer()); 29 | } 30 | 31 | public WorkQueueAsyncTaskExecutor(TimedScheduler timer) { 32 | super(timer); 33 | } 34 | 35 | @Override 36 | public void afterPropertiesSet() throws Exception { 37 | if (!isShared()) { 38 | this.workQueue = WorkQueueProcessor.create( 39 | getName(), 40 | getBacklog(), 41 | (null != waitStrategy ? waitStrategy : WaitStrategy.blocking()) 42 | ); 43 | } else { 44 | this.workQueue = WorkQueueProcessor.share( 45 | getName(), 46 | getBacklog(), 47 | (null != waitStrategy ? waitStrategy : WaitStrategy.blocking()) 48 | ); 49 | } 50 | if (isAutoStartup()) { 51 | start(); 52 | } 53 | } 54 | 55 | /** 56 | * Get the {@link reactor.util.concurrent.WaitStrategy} this {@link reactor.util.concurrent.RingBuffer} is using. 57 | * 58 | * @return the {@link reactor.util.concurrent.WaitStrategy} 59 | */ 60 | public WaitStrategy getWaitStrategy() { 61 | return waitStrategy; 62 | } 63 | 64 | /** 65 | * Set the {@link reactor.util.concurrent.WaitStrategy} to use when creating the internal {@link 66 | * reactor.util.concurrent.RingBuffer}. 67 | * 68 | * @param waitStrategy 69 | * the {@link reactor.util.concurrent.WaitStrategy} 70 | */ 71 | public void setWaitStrategy(WaitStrategy waitStrategy) { 72 | this.waitStrategy = waitStrategy; 73 | } 74 | 75 | @Override 76 | protected WorkQueueProcessor getProcessor() { 77 | return workQueue; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/factory/CreateOrReuseFactoryBean.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.factory; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.BeanFactory; 7 | import org.springframework.beans.factory.BeanFactoryAware; 8 | import org.springframework.beans.factory.BeanFactoryUtils; 9 | import org.springframework.beans.factory.FactoryBean; 10 | import org.springframework.beans.factory.InitializingBean; 11 | import org.springframework.beans.factory.ListableBeanFactory; 12 | import org.springframework.util.Assert; 13 | 14 | /** 15 | * Spring {@link org.springframework.beans.factory.FactoryBean} implementation to provide either a new bean, created on 16 | * the first injection, or the previously-created bean thereafter. 17 | *

This is slightly different than letting the Spring container handle this behaviour as the instance will come from 18 | * the given {@link java.util.function.Supplier} the first time around.

19 | * 20 | * @author Jon Brisbin 21 | */ 22 | public class CreateOrReuseFactoryBean implements FactoryBean, 23 | BeanFactoryAware, 24 | InitializingBean { 25 | 26 | private final Object monitor = new Object() {}; 27 | private final String self; 28 | private final Class type; 29 | private final Supplier supplier; 30 | private ListableBeanFactory beanFactory; 31 | private T instance; 32 | 33 | public CreateOrReuseFactoryBean(String self, Class type, Supplier supplier) { 34 | Assert.notNull(self, "'self' Bean name cannot be null."); 35 | Assert.notNull(type, "Bean type cannot be null."); 36 | Assert.notNull(supplier, "Supplier cannot be null."); 37 | this.self = self; 38 | this.type = type; 39 | this.supplier = supplier; 40 | } 41 | 42 | @Override 43 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 44 | if(beanFactory instanceof ListableBeanFactory) { 45 | this.beanFactory = (ListableBeanFactory)beanFactory; 46 | } 47 | } 48 | 49 | @Override 50 | public void afterPropertiesSet() throws Exception { 51 | Assert.notNull(beanFactory, "ListableBeanFactory cannot be null."); 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | @Override 56 | public T getObject() throws Exception { 57 | synchronized(monitor) { 58 | if(null == instance) { 59 | String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type); 60 | if(names.length == 0 || names[0].equals(self)) { 61 | instance = supplier.get(); 62 | } else { 63 | instance = (T)beanFactory.getBean(names[0]); 64 | } 65 | } 66 | return instance; 67 | } 68 | } 69 | 70 | @Override 71 | public Class getObjectType() { 72 | return type; 73 | } 74 | 75 | @Override 76 | public boolean isSingleton() { 77 | return true; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /reactor-spring-core/src/test/groovy/reactor/spring/task/TaskExecutorsSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc., Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package reactor.spring.task 18 | 19 | import org.springframework.beans.factory.annotation.Autowired 20 | import org.springframework.context.annotation.Bean 21 | import org.springframework.context.annotation.Configuration 22 | import org.springframework.test.context.ContextConfiguration 23 | import reactor.util.concurrent.WaitStrategy 24 | import reactor.spring.core.task.WorkQueueAsyncTaskExecutor 25 | import spock.lang.Ignore 26 | import spock.lang.Specification 27 | 28 | import java.util.concurrent.Callable 29 | import java.util.concurrent.CountDownLatch 30 | import java.util.concurrent.TimeUnit 31 | 32 | /** 33 | * @author Jon Brisbin 34 | * @author Stephane Maldini 35 | */ 36 | @ContextConfiguration 37 | class TaskExecutorsSpec extends Specification { 38 | 39 | 40 | @Autowired 41 | WorkQueueAsyncTaskExecutor workQueue 42 | 43 | def "Work queue executor executes tasks"() { 44 | 45 | when: "a task is submitted" 46 | def latch = new CountDownLatch(1) 47 | workQueue.execute({ 48 | latch.countDown() 49 | }) 50 | 51 | then: "latch was counted down" 52 | latch.await(1, TimeUnit.SECONDS) 53 | 54 | when: "a value-returning task is submitted" 55 | def f = workQueue.submit({ 56 | return "Hello World!" 57 | } as Callable) 58 | 59 | then: "the Future blocks until completion and the value is returned" 60 | f.get(1, TimeUnit.SECONDS) == "Hello World!" 61 | 62 | } 63 | 64 | @Ignore 65 | def "Work queue executor is performant"() { 66 | 67 | when: "a Closure is submitted" 68 | def count = 0 69 | def start = System.currentTimeMillis() 70 | def counter = { count++ } 71 | (1..1000).each { 72 | workQueue.execute counter 73 | } 74 | workQueue.stop() 75 | def end = System.currentTimeMillis() 76 | def elapsed = end - start 77 | int throughput = count / (elapsed / 1000) 78 | println "throughput: $throughput" 79 | 80 | then: 81 | // really small just to make sure it passes on CI servers 82 | throughput > 1000 83 | 84 | } 85 | 86 | @Configuration 87 | static class TestConfig { 88 | 89 | @Bean 90 | WorkQueueAsyncTaskExecutor workQueueAsyncTaskExecutor() { 91 | def ex = new WorkQueueAsyncTaskExecutor() 92 | ex.shared = false 93 | ex.waitStrategy = WaitStrategy.yielding() 94 | return ex 95 | } 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/task/RingBufferAsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.core.task; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import reactor.core.publisher.TopicProcessor; 6 | import reactor.core.scheduler.Schedulers; 7 | import reactor.core.scheduler.TimedScheduler; 8 | import reactor.util.concurrent.WaitStrategy; 9 | 10 | import org.springframework.beans.factory.BeanNameAware; 11 | import org.springframework.util.Assert; 12 | 13 | /** 14 | * Implementation of {@link org.springframework.core.task.AsyncTaskExecutor} that uses a {@link TopicProcessor} 15 | * to 16 | * execute tasks. 17 | * 18 | * @author Jon Brisbin 19 | * @author Stephane Maldini 20 | * @since 1.1, 2.5 21 | */ 22 | public class RingBufferAsyncTaskExecutor extends AbstractAsyncTaskExecutor implements BeanNameAware { 23 | 24 | private final Logger log = LoggerFactory.getLogger(RingBufferAsyncTaskExecutor.class); 25 | 26 | private WaitStrategy waitStrategy; 27 | private TopicProcessor dispatcher; 28 | 29 | public RingBufferAsyncTaskExecutor() { 30 | this(Schedulers.timer()); 31 | } 32 | 33 | public RingBufferAsyncTaskExecutor(TimedScheduler timer) { 34 | super(timer); 35 | } 36 | 37 | @Override 38 | public void afterPropertiesSet() throws Exception { 39 | if (!isShared()) { 40 | this.dispatcher = TopicProcessor.create( 41 | getName(), 42 | getBacklog(), 43 | (null != waitStrategy ? waitStrategy : WaitStrategy.blocking()) 44 | ); 45 | } else { 46 | this.dispatcher = TopicProcessor.share( 47 | getName(), 48 | getBacklog(), 49 | (null != waitStrategy ? waitStrategy : WaitStrategy.blocking()) 50 | ); 51 | } 52 | if (isAutoStartup()) { 53 | start(); 54 | } 55 | } 56 | 57 | @Override 58 | public void setBeanName(String name) { 59 | setName(name); 60 | } 61 | 62 | @Override 63 | public int getThreads() { 64 | // RingBufferDispatchers are always single-threaded 65 | return 1; 66 | } 67 | 68 | @Override 69 | public void setThreads(int threads) { 70 | Assert.isTrue(threads == 1, "A RingBufferAsyncTaskExecutor is always single-threaded"); 71 | log.warn("RingBufferAsyncTaskExecutors are always single-threaded. Ignoring request to use " + 72 | threads + 73 | " threads."); 74 | } 75 | 76 | /** 77 | * Get the {@link reactor.util.concurrent.WaitStrategy} this {@link reactor.core.queue 78 | * .RingBuffer} is using. 79 | * 80 | * @return the {@link reactor.util.concurrent.WaitStrategy} 81 | */ 82 | public WaitStrategy getWaitStrategy() { 83 | return waitStrategy; 84 | } 85 | 86 | /** 87 | * Set the {@link reactor.util.concurrent.WaitStrategy} to use when creating the internal {@link 88 | * reactor.util.concurrent.RingBuffer}. 89 | * 90 | * @param waitStrategy the {@link reactor.util.concurrent.WaitStrategy} 91 | */ 92 | public void setWaitStrategy(WaitStrategy waitStrategy) { 93 | this.waitStrategy = waitStrategy; 94 | } 95 | 96 | @Override 97 | protected TopicProcessor getProcessor() { 98 | return dispatcher; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /reactor-spring-messaging/src/main/java/reactor/spring/messaging/ReactorSubscribableChannel.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.messaging; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.function.Consumer; 6 | 7 | import org.reactivestreams.Processor; 8 | import reactor.core.Cancellation; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.TopicProcessor; 11 | 12 | import org.springframework.beans.factory.BeanNameAware; 13 | import org.springframework.messaging.Message; 14 | import org.springframework.messaging.MessageChannel; 15 | import org.springframework.messaging.MessageHandler; 16 | import org.springframework.messaging.SubscribableChannel; 17 | import org.springframework.util.ObjectUtils; 18 | 19 | /** 20 | * Subscribable {@link org.springframework.messaging.MessageChannel} implementation that uses the RinBuffer-based 21 | * Reactor {@link reactor.core.publisher.TopicProcessor} to publish messages for efficiency at high volumes. 22 | * 23 | * @author Jon Brisbin 24 | * @author Stephane Maldini 25 | */ 26 | public class ReactorSubscribableChannel implements BeanNameAware, MessageChannel, SubscribableChannel { 27 | 28 | private final Map 29 | messageHandlerConsumers = 30 | new ConcurrentHashMap<>(); 31 | 32 | private final Processor, Message> processor; 33 | 34 | private String beanName; 35 | 36 | /** 37 | * Create a default multi-threaded producer channel. 38 | */ 39 | public ReactorSubscribableChannel() { 40 | this(false); 41 | } 42 | 43 | /** 44 | * Create a {@literal ReactorSubscribableChannel} with a {@code ProducerType.SINGLE} if {@code 45 | * singleThreadedProducer} is {@code true}, otherwise use {@code ProducerType.MULTI}. 46 | * 47 | * @param singleThreadedProducer whether to create a single-threaded producer or not 48 | */ 49 | public ReactorSubscribableChannel(boolean singleThreadedProducer) { 50 | this.beanName = String.format("%s@%s", getClass().getSimpleName(), ObjectUtils.getIdentityHexString(this)); 51 | if (singleThreadedProducer) { 52 | this.processor = TopicProcessor.create(); 53 | } else { 54 | this.processor = TopicProcessor.share(); 55 | } 56 | } 57 | 58 | @Override 59 | public void setBeanName(String beanName) { 60 | this.beanName = beanName; 61 | } 62 | 63 | public String getBeanName() { 64 | return beanName; 65 | } 66 | 67 | @Override 68 | public boolean subscribe(final MessageHandler handler) { 69 | Consumer> consumer = handler::handleMessage; 70 | Cancellation c = Flux.from(processor).subscribe(consumer); 71 | messageHandlerConsumers.put(handler, c); 72 | 73 | return true; 74 | } 75 | 76 | @SuppressWarnings("unchecked") 77 | @Override 78 | public boolean unsubscribe(MessageHandler handler) { 79 | Cancellation control = messageHandlerConsumers.remove(handler); 80 | if (null == control) { 81 | return false; 82 | } 83 | control.dispose(); 84 | return true; 85 | } 86 | 87 | @Override 88 | public boolean send(Message message) { 89 | return send(message, 0); 90 | } 91 | 92 | @Override 93 | public boolean send(Message message, long timeout) { 94 | processor.onNext(message); 95 | return true; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/RingBufferApplicationEventPublisher.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context; 2 | 3 | import org.reactivestreams.Subscriber; 4 | import org.reactivestreams.Subscription; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import reactor.core.publisher.TopicProcessor; 8 | 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | import org.springframework.context.ApplicationEvent; 13 | import org.springframework.context.ApplicationEventPublisher; 14 | import org.springframework.context.SmartLifecycle; 15 | 16 | /** 17 | * Implementation of {@link org.springframework.context.ApplicationEventPublisher} that uses a {@link 18 | * reactor.util.concurrent.RingBuffer} to dispatch events. 19 | * 20 | * @author Jon Brisbin 21 | */ 22 | public class RingBufferApplicationEventPublisher implements ApplicationEventPublisher, 23 | ApplicationContextAware, 24 | SmartLifecycle { 25 | 26 | private final Logger log = LoggerFactory.getLogger(getClass()); 27 | 28 | private final boolean autoStartup; 29 | private final TopicProcessor processor; 30 | 31 | private volatile boolean running = false; 32 | 33 | private ApplicationContext appCtx; 34 | 35 | public RingBufferApplicationEventPublisher(int backlog, boolean autoStartup) { 36 | this.autoStartup = autoStartup; 37 | 38 | this.processor = TopicProcessor.share("ringBufferAppEventPublisher", backlog); 39 | 40 | if(autoStartup) { 41 | start(); 42 | } 43 | } 44 | 45 | @Override 46 | public void setApplicationContext(ApplicationContext appCtx) throws BeansException { 47 | this.appCtx = appCtx; 48 | } 49 | 50 | @Override 51 | public boolean isAutoStartup() { 52 | return autoStartup; 53 | } 54 | 55 | @Override 56 | public void stop(Runnable callback) { 57 | processor.onComplete(); 58 | if(null != callback) { 59 | callback.run(); 60 | } 61 | synchronized(this) { 62 | running = false; 63 | } 64 | } 65 | 66 | @Override 67 | public void start() { 68 | synchronized(this) { 69 | processor.subscribe(new Subscriber() { 70 | @Override 71 | public void onSubscribe(Subscription s) { 72 | s.request(Long.MAX_VALUE); 73 | } 74 | 75 | @Override 76 | public void onNext(ApplicationEvent applicationEvent) { 77 | appCtx.publishEvent(applicationEvent); 78 | } 79 | 80 | @Override 81 | public void onError(Throwable t) { 82 | log.error("", t); 83 | } 84 | 85 | @Override 86 | public void onComplete() { 87 | log.trace("AppEvent Publisher has shutdown"); 88 | } 89 | }); 90 | running = true; 91 | } 92 | } 93 | 94 | @Override 95 | public void stop() { 96 | stop(null); 97 | } 98 | 99 | @Override 100 | public boolean isRunning() { 101 | synchronized(this) { 102 | return running; 103 | } 104 | } 105 | 106 | @Override 107 | public int getPhase() { 108 | return 0; 109 | } 110 | 111 | @Override 112 | public void publishEvent(ApplicationEvent event) { 113 | processor.onNext(event); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /gradle/setup.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | apply plugin: "sonar-runner" 17 | 18 | sonarRunner { 19 | sonarProperties { 20 | property "sonar.host.url", "$sonarHostUrl" 21 | property "sonar.jdbc.url", "$sonarJdbcUrl" 22 | property "sonar.jdbc.driverClassName", "$sonarJdbcDriver" 23 | property "sonar.jdbc.username", "$sonarJdbcUsername" 24 | property "sonar.jdbc.password", "$sonarJdbcPassword" 25 | property "sonar.core.codeCoveragePlugin", "jacoco" 26 | property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec" 27 | property "sonar.links.ci", "https://build.spring.io/browse/REACTOR-SPRING" 28 | property "sonar.links.issue", "https://github.com/reactor/reactor-spring/issues" 29 | property "sonar.links.scm", "https://github.com/reactor/reactor-spring.git" 30 | } 31 | } 32 | 33 | configure(subprojects) { subproject -> 34 | apply plugin: 'propdeps-maven' 35 | apply plugin: 'maven' 36 | 37 | install { 38 | repositories.mavenInstaller { 39 | customizePom(pom, subproject) 40 | } 41 | } 42 | } 43 | 44 | 45 | def customizePom(def pom, def gradleProject) { 46 | pom.whenConfigured { generatedPom -> 47 | // eliminate test-scoped dependencies (no need in maven central poms) 48 | generatedPom.dependencies.removeAll { dep -> 49 | dep.scope == "test" 50 | } 51 | 52 | // sort to make pom dependencies order consistent to ease comparison of older poms 53 | generatedPom.dependencies = generatedPom.dependencies.sort { dep -> 54 | "$dep.scope:$dep.groupId:$dep.artifactId" 55 | } 56 | 57 | // add all items necessary for maven central publication 58 | generatedPom.project { 59 | name = gradleProject.description 60 | description = gradleProject.description 61 | url = 'https://github.com/reactor/reactor-spring' 62 | organization { 63 | name = 'reactor' 64 | url = 'http://github.com/reactor' 65 | } 66 | licenses { 67 | license { 68 | name 'The Apache Software License, Version 2.0' 69 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 70 | distribution 'repo' 71 | } 72 | } 73 | scm { 74 | url = 'https://github.com/reactor/reactor-spring.git' 75 | connection = 'scm:git:git://github.com/reactor/reactor-spring.git' 76 | developerConnection = 'scm:git:git://github.com/reactor/reactor-spring.git' 77 | } 78 | developers { 79 | developer { 80 | id = 'smaldini' 81 | name = 'Stephane Maldini' 82 | email = 'smaldini@pivotal.io' 83 | } 84 | developer { 85 | id = 'jbrisbin' 86 | name = 'Jon Brisbin' 87 | email = 'jbrisbin@pivotal.io' 88 | } 89 | developer { 90 | id = 'awilkinson' 91 | name = 'Andy Wilkinson' 92 | email = 'awilkinson@pivotal.io' 93 | } 94 | } 95 | issueManagement { 96 | system = "GitHub Issues" 97 | url = "https://github.com/reactor/reactor-spring/issues" 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /reactor-spring-messaging/src/test/java/reactor/spring/messaging/ReactorSubscribableChannelThroughputTests.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.messaging; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.reactivestreams.Processor; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.messaging.Message; 14 | import org.springframework.messaging.MessageHandler; 15 | import org.springframework.messaging.MessagingException; 16 | import org.springframework.messaging.support.MessageBuilder; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 19 | import reactor.bus.Event; 20 | import reactor.bus.EventBus; 21 | import reactor.spring.context.config.EnableReactor; 22 | 23 | import java.util.concurrent.CountDownLatch; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import static org.junit.Assert.assertTrue; 27 | 28 | /** 29 | * Comparing a Reactor {@code Processor}-backed Channel to a plain {@code Reactor}-backed Channel for comparing the net 30 | * effect of dynamic event routing on total throughput. It's pretty clear here that a simple Channel doesn't benefit 31 | * from the Reactor dynamic event dispatching and is better-suited to the single-task {@code Processor}. 32 | * 33 | * @author Jon Brisbin 34 | */ 35 | @RunWith(SpringJUnit4ClassRunner.class) 36 | @ContextConfiguration 37 | public class ReactorSubscribableChannelThroughputTests { 38 | 39 | static final Logger LOG = LoggerFactory.getLogger(ReactorSubscribableChannelThroughputTests.class); 40 | static final int MSGS = 5000; 41 | 42 | @Autowired 43 | EventBus eventBus; 44 | 45 | CountDownLatch latch; 46 | long start; 47 | long end; 48 | double elapsed; 49 | int throughput; 50 | 51 | @Before 52 | public void init() { 53 | latch = new CountDownLatch(MSGS); 54 | } 55 | 56 | @After 57 | public void cleanup() { 58 | end = System.currentTimeMillis(); 59 | elapsed = end - start; 60 | throughput = (int)(MSGS / (elapsed / 1000)); 61 | LOG.info("Processed {} msgs in {}ms for throughput of {}/sec", MSGS, (long)elapsed, throughput); 62 | } 63 | 64 | @Test 65 | public void reactorSubscribableChannelMultiProducerThroughput() throws InterruptedException { 66 | doTest(new ReactorSubscribableChannel()); 67 | } 68 | 69 | @Test 70 | public void reactorSubscribableChannelSingleProducerThroughput() throws InterruptedException { 71 | doTest(new ReactorSubscribableChannel(true)); 72 | } 73 | 74 | private void doTest(ReactorSubscribableChannel channel) throws InterruptedException { 75 | channel.subscribe(new MessageHandler() { 76 | @Override 77 | public void handleMessage(Message message) throws MessagingException { 78 | latch.countDown(); 79 | } 80 | }); 81 | 82 | Message msg = MessageBuilder.withPayload("Hello World!").build(); 83 | start = System.currentTimeMillis(); 84 | for(int i = 0; i < MSGS; i++) { 85 | channel.send(msg); 86 | } 87 | assertTrue("latch did not time out", latch.await(5, TimeUnit.SECONDS)); 88 | } 89 | 90 | @Configuration 91 | @EnableReactor 92 | static class ReactorConfig { 93 | 94 | @Bean 95 | public EventBus eventBus(Processor, Event> env) { 96 | return EventBus.create(env); 97 | } 98 | 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /reactor-spring-context/src/test/groovy/reactor/spring/context/config/ReactorBeanDefinitionRegistrarSpec.groovy: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.config 2 | 3 | import org.reactivestreams.Processor 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import org.springframework.context.annotation.AnnotationConfigApplicationContext 8 | import org.springframework.context.annotation.Bean 9 | import org.springframework.context.annotation.Configuration 10 | import reactor.bus.Event 11 | import reactor.bus.EventBus 12 | import reactor.spring.context.annotation.Consumer 13 | import reactor.spring.context.annotation.ReplyTo 14 | import reactor.spring.context.annotation.Selector 15 | import spock.lang.Specification 16 | 17 | import java.util.concurrent.CountDownLatch 18 | import java.util.concurrent.TimeUnit 19 | 20 | /** 21 | * @author Jon Brisbin 22 | * @author Stephane Maldini 23 | */ 24 | class ReactorBeanDefinitionRegistrarSpec extends Specification { 25 | 26 | def "EnableReactor annotation causes default components to be created"() { 27 | 28 | given: 29 | "an annotated configuration" 30 | def appCtx = new AnnotationConfigApplicationContext(ReactorConfig) 31 | def consumer = appCtx.getBean(LoggingConsumer) 32 | def reactor = appCtx.getBean(EventBus) 33 | 34 | when: 35 | "notifying the injected Reactor" 36 | def latch = consumer.reset(2) 37 | reactor.notify("test", Event.wrap("World")) 38 | 39 | then: 40 | "the method will have been invoked" 41 | latch.await(3, TimeUnit.SECONDS) 42 | 43 | when: 44 | "notifying the injected Reactor with replyTo header" 45 | latch = consumer.reset(2) 46 | reactor.notify("test2", Event.wrap("World").setReplyTo("reply2")) 47 | 48 | then: 49 | "the method will have been invoked" 50 | latch.await(3, TimeUnit.SECONDS) 51 | 52 | } 53 | 54 | def "ReplyTo annotation causes replies to be handled"() { 55 | 56 | given: 57 | 'an annotated configuration' 58 | def appCtx = new AnnotationConfigApplicationContext(ReactorConfig) 59 | def consumer = appCtx.getBean(LoggingConsumer) 60 | def reactor = appCtx.getBean(EventBus) 61 | 62 | when: 63 | "notifying the injected Reactor" 64 | def latch = consumer.reset(2) 65 | reactor.notify("test", Event.wrap("World")) 66 | 67 | then: 68 | "the method will have been invoked" 69 | latch.await(3, TimeUnit.SECONDS) 70 | 71 | } 72 | 73 | } 74 | 75 | @Consumer 76 | class LoggingConsumer { 77 | @Autowired 78 | EventBus eventBus 79 | Logger log = LoggerFactory.getLogger(LoggingConsumer) 80 | 81 | CountDownLatch latch 82 | 83 | CountDownLatch reset(int count){ 84 | latch = new CountDownLatch(count) 85 | } 86 | 87 | @Selector("test2") 88 | @ReplyTo 89 | String onTest2(String s) { 90 | log.info("Hello again ${s}!") 91 | latch?.countDown() 92 | "Goodbye again ${s}!" 93 | } 94 | 95 | @Selector("reply2") 96 | void onReply2(String s) { 97 | log.info("Got reply: ${s}") 98 | latch?.countDown() 99 | } 100 | 101 | @Selector("test") 102 | @ReplyTo("reply") 103 | String onTest(String s) { 104 | log.info("Hello ${s}!") 105 | latch?.countDown() 106 | "Goodbye ${s}!" 107 | } 108 | 109 | @Selector("reply") 110 | void onReply(String s) { 111 | log.info("Got reply: ${s}") 112 | latch?.countDown() 113 | } 114 | } 115 | 116 | @Configuration 117 | @EnableReactor 118 | public class ReactorConfig { 119 | 120 | @Bean 121 | EventBus eventBus(Processor, Event> processor) { 122 | EventBus.create(processor) 123 | } 124 | 125 | @Bean 126 | LoggingConsumer loggingConsumer() { 127 | new LoggingConsumer() 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /reactor-spring-core/src/test/groovy/reactor/spring/task/TaskExecutorTests.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.task; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import reactor.util.concurrent.WaitStrategy; 10 | import reactor.spring.core.task.RingBufferAsyncTaskExecutor; 11 | import reactor.spring.core.task.WorkQueueAsyncTaskExecutor; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.core.task.AsyncTaskExecutor; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 19 | 20 | /** 21 | * @author Jon Brisbin 22 | */ 23 | @RunWith(SpringJUnit4ClassRunner.class) 24 | @ContextConfiguration(classes = {TaskExecutorTests.TestConfig.class}) 25 | public class TaskExecutorTests { 26 | 27 | static int runs = 1000; 28 | 29 | @Autowired 30 | WorkQueueAsyncTaskExecutor workQueue; 31 | @Autowired 32 | RingBufferAsyncTaskExecutor ringBuffer; 33 | AtomicLong counter; 34 | long start; 35 | long end; 36 | double elapsed; 37 | int throughput; 38 | 39 | @Before 40 | public void setup() { 41 | counter = new AtomicLong(0); 42 | } 43 | 44 | @Test 45 | public void testWorkQueueRunnableThroughput() { 46 | doAsyncRunnableTest("work queue runnable", workQueue); 47 | } 48 | 49 | @Test 50 | public void testWorkQueueCallableThroughput() { 51 | doAsyncCallableTest("work queue callable", workQueue); 52 | } 53 | 54 | @Test 55 | public void testRingBufferRunnableThroughput() { 56 | doAsyncRunnableTest("ring buffer runnable", ringBuffer); 57 | } 58 | 59 | @Test 60 | public void testRingBufferCallableThroughput() { 61 | doAsyncCallableTest("ring buffer callable", ringBuffer); 62 | } 63 | 64 | private void doStart() { 65 | start = System.currentTimeMillis(); 66 | } 67 | 68 | private void doStop(String test) { 69 | end = System.currentTimeMillis(); 70 | elapsed = end - start; 71 | throughput = (int) (counter.get() / (elapsed / 1000)); 72 | 73 | System.out.println(test + " throughput: " + throughput + "/sec"); 74 | } 75 | 76 | private void doAsyncRunnableTest(String test, AsyncTaskExecutor executor) { 77 | Runnable r = new Runnable() { 78 | @Override 79 | public void run() { 80 | counter.incrementAndGet(); 81 | } 82 | }; 83 | 84 | doStart(); 85 | for (int i = 0; i < runs; i++) { 86 | executor.execute(r); 87 | } 88 | doStop(test); 89 | } 90 | 91 | @SuppressWarnings("unchecked") 92 | private void doAsyncCallableTest(String test, AsyncTaskExecutor executor) { 93 | Callable c = new Callable() { 94 | @Override 95 | public Object call() throws Exception { 96 | return counter.incrementAndGet(); 97 | } 98 | }; 99 | 100 | doStart(); 101 | for (int i = 0; i < runs; i++) { 102 | executor.submit(c); 103 | } 104 | doStop(test); 105 | } 106 | 107 | @Configuration 108 | static class TestConfig { 109 | 110 | @Bean 111 | public WorkQueueAsyncTaskExecutor workQueue() { 112 | WorkQueueAsyncTaskExecutor ex = new WorkQueueAsyncTaskExecutor(); 113 | ex.setBacklog(4096); 114 | ex.setShared(false); 115 | ex.setWaitStrategy(WaitStrategy.yielding()); 116 | return ex; 117 | } 118 | 119 | @Bean 120 | public RingBufferAsyncTaskExecutor ringBuffer() { 121 | RingBufferAsyncTaskExecutor ex = new RingBufferAsyncTaskExecutor(); 122 | ex.setShared(false); 123 | ex.setWaitStrategy(WaitStrategy.yielding()); 124 | return ex; 125 | } 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/config/ReactorBeanDefinitionRegistrar.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.config; 2 | 3 | import java.util.Map; 4 | import java.util.function.Supplier; 5 | 6 | import org.reactivestreams.Processor; 7 | import reactor.core.publisher.EmitterProcessor; 8 | import reactor.core.publisher.FluxProcessor; 9 | import reactor.core.scheduler.Scheduler; 10 | import reactor.core.scheduler.Schedulers; 11 | import reactor.core.scheduler.TimedScheduler; 12 | import reactor.util.Loggers; 13 | import reactor.spring.factory.CreateOrReuseFactoryBean; 14 | 15 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 16 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 17 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 18 | import org.springframework.core.type.AnnotationMetadata; 19 | import org.springframework.util.StringUtils; 20 | 21 | /** 22 | * {@link ImportBeanDefinitionRegistrar} implementation that configures necessary Reactor components. 23 | * 24 | * @author Jon Brisbin 25 | * @author Stephane Maldini 26 | */ 27 | public class ReactorBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 28 | 29 | private static final String DEFAULT_TIMER_SUPPLIER_NAME = "reactorTimer"; 30 | private static final String DEFAULT_SCHEDULER_GROUP_NAME = "reactorGroupedProcessors"; 31 | 32 | private static final Supplier> DEFAULT_SCHEDULER_GROUP = () -> { 33 | final Scheduler group = 34 | Schedulers.newParallel(DEFAULT_SCHEDULER_GROUP_NAME + "-spring"); 35 | 36 | 37 | 38 | 39 | return (Supplier) () -> { 40 | FluxProcessor emitter = EmitterProcessor.create(); 41 | return FluxProcessor.wrap(emitter, emitter.publishOn(group)); 42 | }; 43 | 44 | }; 45 | 46 | private static final Supplier> DEFAULT_TIMER_SUPPLIER = () -> { 47 | final TimedScheduler timer = Schedulers.newTimer("spring-timer"); 48 | return () -> timer; 49 | }; 50 | 51 | protected void registerReactorBean(BeanDefinitionRegistry registry, 52 | String attrValue, 53 | String name, Class tClass, Supplier> supplier) { 54 | 55 | // Create a root Enivronment 56 | if (!registry.containsBeanDefinition(name)) { 57 | BeanDefinitionBuilder envBeanDef = BeanDefinitionBuilder.rootBeanDefinition(CreateOrReuseFactoryBean 58 | .class); 59 | envBeanDef.addConstructorArgValue(name); 60 | envBeanDef.addConstructorArgValue(tClass); 61 | 62 | if (StringUtils.hasText(attrValue)) { 63 | envBeanDef.addConstructorArgReference(attrValue); 64 | } else { 65 | envBeanDef.addConstructorArgValue(supplier.get()); 66 | } 67 | registry.registerBeanDefinition(name, envBeanDef.getBeanDefinition()); 68 | } 69 | } 70 | 71 | @Override 72 | public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) { 73 | Map attrs = meta.getAnnotationAttributes(EnableReactor.class.getName()); 74 | 75 | registerReactorBean(registry, 76 | (String) attrs.get("timerSupplier"), 77 | DEFAULT_TIMER_SUPPLIER_NAME, 78 | TimedScheduler.class, 79 | DEFAULT_TIMER_SUPPLIER 80 | ); 81 | 82 | 83 | registerReactorBean(registry, 84 | (String) attrs.get("processorSupplier"), 85 | DEFAULT_SCHEDULER_GROUP_NAME, 86 | Processor.class, 87 | DEFAULT_SCHEDULER_GROUP 88 | ); 89 | 90 | 91 | // Create a ConsumerBeanAutoConfiguration 92 | if (!registry.containsBeanDefinition(ConsumerBeanAutoConfiguration.class.getName())) { 93 | BeanDefinitionBuilder autoConfigDef = BeanDefinitionBuilder.rootBeanDefinition 94 | (ConsumerBeanAutoConfiguration.class); 95 | registry.registerBeanDefinition(ConsumerBeanAutoConfiguration.class.getName(), autoConfigDef 96 | .getBeanDefinition()); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/selector/ExpressionSelector.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.selector; 2 | 3 | import org.springframework.beans.factory.BeanFactory; 4 | import org.springframework.context.expression.BeanFactoryResolver; 5 | import org.springframework.expression.EvaluationContext; 6 | import org.springframework.expression.Expression; 7 | import org.springframework.expression.spel.SpelCompilerMode; 8 | import org.springframework.expression.spel.SpelParserConfiguration; 9 | import org.springframework.expression.spel.standard.SpelExpressionParser; 10 | import org.springframework.expression.spel.support.StandardEvaluationContext; 11 | import reactor.bus.selector.ObjectSelector; 12 | import reactor.bus.selector.Selector; 13 | 14 | /** 15 | * An implementation of {@link reactor.bus.selector.Selector} that uses a SpEL expression to evaluate the match. 16 | * 17 | * @author Jon Brisbin 18 | */ 19 | public class ExpressionSelector extends ObjectSelector { 20 | 21 | private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); 22 | 23 | private final EvaluationContext evalCtx; 24 | 25 | public ExpressionSelector(Expression expr, EvaluationContext evalCtx) { 26 | super(expr); 27 | this.evalCtx = evalCtx; 28 | } 29 | 30 | @Override 31 | public boolean matches(Object key) { 32 | return getObject().getValue(evalCtx, key, Boolean.class); 33 | } 34 | 35 | /** 36 | * Shorthand helper method for creating an {@code ExpressionSelector}. 37 | * 38 | * @param expr 39 | * The expression to parse. 40 | * 41 | * @return A new {@link reactor.bus.selector.Selector} 42 | */ 43 | public static Selector E(String expr) { 44 | return expressionSelector(expr, (BeanFactory)null); 45 | } 46 | 47 | /** 48 | * Helper method for creating an {@code ExpressionSelector}. 49 | * 50 | * @param expr 51 | * The expression to parse. 52 | * 53 | * @return A new {@link reactor.bus.selector.Selector} 54 | */ 55 | public static Selector expressionSelector(String expr) { 56 | return expressionSelector(expr, (BeanFactory)null); 57 | } 58 | 59 | /** 60 | * Helper method for creating an {@code ExpressionSelector}. 61 | * 62 | * @param expr 63 | * The expression to parse. 64 | * @param beanFactory 65 | * The {@link org.springframework.beans.factory.BeanFactory} to use to resolve references in the expression. 66 | * 67 | * @return A new {@link reactor.bus.selector.Selector} 68 | */ 69 | public static Selector expressionSelector(String expr, BeanFactory beanFactory) { 70 | StandardEvaluationContext evalCtx = new StandardEvaluationContext(); 71 | if(null != beanFactory) { 72 | evalCtx.setBeanResolver(new BeanFactoryResolver(beanFactory)); 73 | } 74 | return expressionSelector(expr, evalCtx); 75 | } 76 | 77 | /** 78 | * Helper method for creating an {@code ExpressionSelector}. 79 | * 80 | * @param expr 81 | * The expression to parse. 82 | * @param evalCtx 83 | * The {@link org.springframework.expression.EvaluationContext} to use. 84 | * 85 | * @return A new {@link reactor.bus.selector.Selector} 86 | */ 87 | public static Selector expressionSelector(String expr, EvaluationContext evalCtx) { 88 | return new ExpressionSelector(SPEL_PARSER.parseExpression(expr), evalCtx); 89 | } 90 | 91 | /** 92 | * Helper method for creating an {@code ExpressionSelector}. 93 | * 94 | * @param expr 95 | * The expression to parse. 96 | * @param evalCtx 97 | * The {@link org.springframework.expression.EvaluationContext} to use. 98 | * @param mode 99 | * The {@link org.springframework.expression.spel.SpelCompilerMode} to use. 100 | * 101 | * @return A new {@link reactor.bus.selector.Selector} 102 | */ 103 | public static Selector expressionSelector(String expr, EvaluationContext evalCtx, SpelCompilerMode mode) { 104 | SpelParserConfiguration configuration = new SpelParserConfiguration(mode, null); 105 | SpelExpressionParser parser = new SpelExpressionParser(configuration); 106 | return new ExpressionSelector(parser.parseExpression(expr), evalCtx); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /reactor-spring-context/src/test/groovy/reactor/spring/context/config/ConsumerBeanAutoConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc., Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reactor.spring.context.config 17 | 18 | import groovy.transform.EqualsAndHashCode 19 | import org.reactivestreams.Processor 20 | import org.springframework.beans.factory.annotation.Autowired 21 | import org.springframework.context.annotation.AnnotationConfigApplicationContext 22 | import org.springframework.context.annotation.Bean 23 | import org.springframework.context.annotation.Configuration 24 | import org.springframework.core.convert.ConversionService 25 | import org.springframework.core.convert.converter.Converter 26 | import org.springframework.core.convert.support.DefaultConversionService 27 | import reactor.bus.Event 28 | import reactor.bus.EventBus 29 | import reactor.core.publisher.TopicProcessor 30 | import reactor.spring.context.annotation.Consumer 31 | import reactor.spring.context.annotation.ReplyTo 32 | import reactor.spring.context.annotation.Selector 33 | import reactor.spring.context.annotation.SelectorType 34 | import spock.lang.Specification 35 | 36 | import java.util.concurrent.CountDownLatch 37 | import java.util.concurrent.TimeUnit 38 | 39 | /** 40 | * @author Jon Brisbin 41 | * @author Stephane Maldini 42 | */ 43 | class ConsumerBeanAutoConfigurationSpec extends Specification { 44 | def "Annotated Consumer is wired to a Reactor"() { 45 | 46 | given: 47 | "an ApplicationContext with an annotated bean handler" 48 | def appCtx = new AnnotationConfigApplicationContext(AnnotatedHandlerConfig) 49 | def handlerBean = appCtx.getBean(HandlerBean) 50 | def reactor = appCtx.getBean(EventBus) 51 | 52 | when: 53 | "an Event is emitted onto the Reactor in context" 54 | reactor.notify('/a/b', Event.wrap("Hello World!")) 55 | 56 | then: 57 | "the method has been invoked" 58 | handlerBean.latch.await(1, TimeUnit.SECONDS) 59 | 60 | } 61 | 62 | def "Annotated Consumer with custom type is wired and the selector method is invoked after converting the value"() { 63 | 64 | given: 65 | "an ApplicationContext with an annotated bean handler" 66 | def appCtx = new AnnotationConfigApplicationContext(AnnotatedHandlerConfig) 67 | def handlerBean = appCtx.getBean(HandlerBean) 68 | def reactor = appCtx.getBean(EventBus) 69 | 70 | when: 71 | "an Event is emitted onto the Reactor in context" 72 | reactor.send('customEvent.sink', Event.wrap("Hello").setReplyTo('customEventReply.sink')) 73 | 74 | then: 75 | "the method has been invoked" 76 | handlerBean.latch.await(1, TimeUnit.SECONDS) 77 | 78 | } 79 | 80 | def "Annotated Consumer with Method that throws RuntimeException should use event's errorConsumer"() { 81 | given: 82 | "an ApplicationContext with an annotated bean handler" 83 | def appCtx = new AnnotationConfigApplicationContext(AnnotatedHandlerConfig) 84 | def reactor = appCtx.getBean(EventBus) 85 | final errorConsumedLatch = new CountDownLatch(1) 86 | 87 | when: 88 | "Event has an errorConsumer and event handler throws an error" 89 | Event ev = new Event(null, "Hello", { t -> 90 | errorConsumedLatch.countDown() 91 | }) 92 | reactor.notify('throws.exception', ev) 93 | 94 | then: 95 | "errorConsumer method has been invoked" 96 | errorConsumedLatch.await(30, TimeUnit.SECONDS) 97 | 98 | } 99 | 100 | } 101 | 102 | @EqualsAndHashCode 103 | class CustomEvent { 104 | String data 105 | } 106 | 107 | 108 | @Consumer 109 | class HandlerBean { 110 | @Autowired 111 | EventBus eventBus 112 | def latch = new CountDownLatch(1) 113 | 114 | @Selector(value = '/{a}/{b}', type = SelectorType.URI) 115 | void handleTest(Event ev) { 116 | //println "a=${ev.headers['a']}, b=${ev.headers['b']}" 117 | latch.countDown() 118 | } 119 | 120 | @Selector(value = 'customEvent.sink') 121 | @ReplyTo(value = 'customEventReply.sink') 122 | CustomEvent handleCustomEvent(CustomEvent customEvent) { 123 | return new CustomEvent(data: customEvent.data + " from custom event.") 124 | } 125 | 126 | @Selector('customEventReply.sink') 127 | void handleReplyToCustomEvent(Event ev) { 128 | println "Received response: ${ev.data}" 129 | latch.countDown() 130 | } 131 | 132 | @Selector(value = 'throws.exception') 133 | @ReplyTo 134 | String handleString(Event ev) { 135 | throw new CustomRuntimeException('This is an exception'); 136 | } 137 | } 138 | 139 | class EventToCustomEventConverter implements Converter { 140 | 141 | @Override 142 | CustomEvent convert(Event source) { 143 | return new CustomEvent(data: source.getData().toString()) 144 | } 145 | 146 | } 147 | 148 | class CustomEventToEventConverter implements Converter { 149 | 150 | @Override 151 | Event convert(CustomEvent source) { 152 | return Event.wrap(source.data) 153 | } 154 | 155 | } 156 | 157 | @Configuration 158 | class AnnotatedHandlerConfig { 159 | 160 | @Bean 161 | Processor, Event> env() { 162 | TopicProcessor.create() 163 | } 164 | 165 | @Bean 166 | EventBus eventBus(Processor, Event> env) { 167 | return EventBus.create(env) 168 | } 169 | 170 | @Bean 171 | ConsumerBeanAutoConfiguration consumerBeanAutoConfiguration() { 172 | return new ConsumerBeanAutoConfiguration() 173 | } 174 | 175 | @Bean 176 | HandlerBean handlerBean() { 177 | return new HandlerBean() 178 | } 179 | 180 | @Bean 181 | ConversionService reactorConversionService() { 182 | DefaultConversionService defaultConversionService = new DefaultConversionService() 183 | defaultConversionService.addConverter(Event, CustomEvent, new EventToCustomEventConverter()) 184 | defaultConversionService.addConverter(CustomEvent, Event, new CustomEventToEventConverter()) 185 | return defaultConversionService; 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /src/api/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* Javadoc style sheet */ 2 | 3 | /* 4 | Overall document style 5 | */ 6 | body { 7 | background-color: #ffffff; 8 | color: #353833; 9 | font-family: Arial, Helvetica, sans-serif; 10 | font-size: 76%; 11 | margin: 0; 12 | } 13 | 14 | a:link, a:visited { 15 | text-decoration: none; 16 | color: #4c6b87; 17 | } 18 | 19 | a:hover, a:focus { 20 | text-decoration: none; 21 | color: #bb7a2a; 22 | } 23 | 24 | a:active { 25 | text-decoration: none; 26 | color: #4c6b87; 27 | } 28 | 29 | a[name] { 30 | color: #353833; 31 | } 32 | 33 | a[name]:hover { 34 | text-decoration: none; 35 | color: #353833; 36 | } 37 | 38 | pre { 39 | font-size: 1.3em; 40 | } 41 | 42 | h1 { 43 | font-size: 1.8em; 44 | } 45 | 46 | h2 { 47 | font-size: 1.5em; 48 | } 49 | 50 | h3 { 51 | font-size: 1.4em; 52 | } 53 | 54 | h4 { 55 | font-size: 1.3em; 56 | } 57 | 58 | h5 { 59 | font-size: 1.2em; 60 | } 61 | 62 | h6 { 63 | font-size: 1.1em; 64 | } 65 | 66 | ul { 67 | list-style-type: disc; 68 | } 69 | 70 | code, tt { 71 | font-size: 1.2em; 72 | } 73 | 74 | dt code { 75 | font-size: 1.2em; 76 | } 77 | 78 | table tr td dt code { 79 | font-size: 1.2em; 80 | vertical-align: top; 81 | } 82 | 83 | sup { 84 | font-size: .6em; 85 | } 86 | 87 | /* 88 | Document title and Copyright styles 89 | */ 90 | .clear { 91 | clear: both; 92 | height: 0px; 93 | overflow: hidden; 94 | } 95 | 96 | .aboutLanguage { 97 | float: right; 98 | padding: 0px 21px; 99 | font-size: .8em; 100 | z-index: 200; 101 | margin-top: -7px; 102 | } 103 | 104 | .legalCopy { 105 | margin-left: .5em; 106 | } 107 | 108 | .bar a, .bar a:link, .bar a:visited, .bar a:active { 109 | color: #FFFFFF; 110 | text-decoration: none; 111 | } 112 | 113 | .bar a:hover, .bar a:focus { 114 | color: #bb7a2a; 115 | } 116 | 117 | .tab { 118 | background-color: #0066FF; 119 | background-position: left top; 120 | background-repeat: no-repeat; 121 | color: #ffffff; 122 | padding: 8px; 123 | width: 5em; 124 | font-weight: bold; 125 | } 126 | 127 | /* 128 | Navigation bar styles 129 | */ 130 | .bar { 131 | background-repeat: repeat-x; 132 | color: #FFFFFF; 133 | padding: .8em .5em .4em .8em; 134 | height: auto; /*height:1.8em;*/ 135 | font-size: 1em; 136 | margin: 0; 137 | } 138 | 139 | .topNav { 140 | float: left; 141 | padding: 0; 142 | width: 100%; 143 | clear: right; 144 | height: 2.8em; 145 | padding-top: 10px; 146 | overflow: hidden; 147 | } 148 | 149 | .bottomNav { 150 | margin-top: 10px; 151 | float: left; 152 | padding: 0; 153 | width: 100%; 154 | clear: right; 155 | height: 2.8em; 156 | padding-top: 10px; 157 | overflow: hidden; 158 | } 159 | 160 | .subNav { 161 | background-color: #dee3e9; 162 | border-bottom: 1px solid #9eadc0; 163 | float: left; 164 | width: 100%; 165 | overflow: hidden; 166 | } 167 | 168 | .subNav div { 169 | clear: left; 170 | float: left; 171 | padding: 0 0 5px 6px; 172 | } 173 | 174 | ul.navList, ul.subNavList { 175 | float: left; 176 | margin: 0 25px 0 0; 177 | padding: 0; 178 | } 179 | 180 | ul.navList li { 181 | list-style: none; 182 | float: left; 183 | padding: 3px 6px; 184 | } 185 | 186 | ul.subNavList li { 187 | list-style: none; 188 | float: left; 189 | font-size: 90%; 190 | } 191 | 192 | .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { 193 | color: #FFFFFF; 194 | text-decoration: none; 195 | } 196 | 197 | .topNav a:hover, .bottomNav a:hover { 198 | text-decoration: none; 199 | color: #bb7a2a; 200 | } 201 | 202 | .navBarCell1Rev { 203 | background-color: #a88834; 204 | color: #FFFFFF; 205 | margin: auto 5px; 206 | border: 1px solid #c9aa44; 207 | } 208 | 209 | /* 210 | Page header and footer styles 211 | */ 212 | .header, .footer { 213 | clear: both; 214 | margin: 0 20px; 215 | padding: 5px 0 0 0; 216 | } 217 | 218 | .indexHeader { 219 | margin: 10px; 220 | position: relative; 221 | } 222 | 223 | .indexHeader h1 { 224 | font-size: 1.3em; 225 | } 226 | 227 | .title { 228 | color: #2c4557; 229 | margin: 10px 0; 230 | } 231 | 232 | .subTitle { 233 | margin: 5px 0 0 0; 234 | } 235 | 236 | .header ul { 237 | margin: 0 0 25px 0; 238 | padding: 0; 239 | } 240 | 241 | .footer ul { 242 | margin: 20px 0 5px 0; 243 | } 244 | 245 | .header ul li, .footer ul li { 246 | list-style: none; 247 | font-size: 1.2em; 248 | } 249 | 250 | /* 251 | Heading styles 252 | */ 253 | div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { 254 | background-color: #dee3e9; 255 | border-top: 1px solid #9eadc0; 256 | border-bottom: 1px solid #9eadc0; 257 | margin: 0 0 6px -8px; 258 | padding: 2px 5px; 259 | } 260 | 261 | ul.blockList ul.blockList ul.blockList li.blockList h3 { 262 | background-color: #dee3e9; 263 | border-top: 1px solid #9eadc0; 264 | border-bottom: 1px solid #9eadc0; 265 | margin: 0 0 6px -8px; 266 | padding: 2px 5px; 267 | } 268 | 269 | ul.blockList ul.blockList li.blockList h3 { 270 | padding: 0; 271 | margin: 15px 0; 272 | } 273 | 274 | ul.blockList li.blockList h2 { 275 | padding: 0px 0 20px 0; 276 | } 277 | 278 | /* 279 | Page layout container styles 280 | */ 281 | .contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { 282 | clear: both; 283 | padding: 10px 20px; 284 | position: relative; 285 | } 286 | 287 | .indexContainer { 288 | margin: 10px; 289 | position: relative; 290 | font-size: 1.0em; 291 | } 292 | 293 | .indexContainer h2 { 294 | font-size: 1.1em; 295 | padding: 0 0 3px 0; 296 | } 297 | 298 | .indexContainer ul { 299 | margin: 0; 300 | padding: 0; 301 | } 302 | 303 | .indexContainer ul li { 304 | list-style: none; 305 | } 306 | 307 | .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { 308 | font-size: 1.1em; 309 | font-weight: bold; 310 | margin: 10px 0 0 0; 311 | color: #4E4E4E; 312 | } 313 | 314 | .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { 315 | margin: 10px 0 10px 20px; 316 | } 317 | 318 | .serializedFormContainer dl.nameValue dt { 319 | margin-left: 1px; 320 | font-size: 1.1em; 321 | display: inline; 322 | font-weight: bold; 323 | } 324 | 325 | .serializedFormContainer dl.nameValue dd { 326 | margin: 0 0 0 1px; 327 | font-size: 1.1em; 328 | display: inline; 329 | } 330 | 331 | /* 332 | List styles 333 | */ 334 | ul.horizontal li { 335 | display: inline; 336 | font-size: 0.9em; 337 | } 338 | 339 | ul.inheritance { 340 | margin: 0; 341 | padding: 0; 342 | } 343 | 344 | ul.inheritance li { 345 | display: inline; 346 | list-style: none; 347 | } 348 | 349 | ul.inheritance li ul.inheritance { 350 | margin-left: 15px; 351 | padding-left: 15px; 352 | padding-top: 1px; 353 | } 354 | 355 | ul.blockList, ul.blockListLast { 356 | margin: 10px 0 10px 0; 357 | padding: 0; 358 | } 359 | 360 | ul.blockList li.blockList, ul.blockListLast li.blockList { 361 | list-style: none; 362 | margin-bottom: 25px; 363 | } 364 | 365 | ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { 366 | padding: 0px 20px 5px 10px; 367 | border: 1px solid #9eadc0; 368 | background-color: #f9f9f9; 369 | } 370 | 371 | ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { 372 | padding: 0 0 5px 8px; 373 | background-color: #ffffff; 374 | border: 1px solid #9eadc0; 375 | border-top: none; 376 | } 377 | 378 | ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { 379 | margin-left: 0; 380 | padding-left: 0; 381 | padding-bottom: 15px; 382 | border: none; 383 | border-bottom: 1px solid #9eadc0; 384 | } 385 | 386 | ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { 387 | list-style: none; 388 | border-bottom: none; 389 | padding-bottom: 0; 390 | } 391 | 392 | table tr td dl, table tr td dl dt, table tr td dl dd { 393 | margin-top: 0; 394 | margin-bottom: 1px; 395 | } 396 | 397 | /* 398 | Table styles 399 | */ 400 | .contentContainer table, .classUseContainer table, .constantValuesContainer table { 401 | border-bottom: 1px solid #9eadc0; 402 | width: 100%; 403 | } 404 | 405 | .contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table { 406 | width: 100%; 407 | } 408 | 409 | .contentContainer .description table, .contentContainer .details table { 410 | border-bottom: none; 411 | } 412 | 413 | .contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td { 414 | vertical-align: top; 415 | padding-right: 20px; 416 | } 417 | 418 | .contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast, .constantValuesContainer ul li table th.colLast, 419 | .contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast, .constantValuesContainer ul li table td.colLast, 420 | .contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne, 421 | .contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne { 422 | padding-right: 3px; 423 | } 424 | 425 | .overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption { 426 | position: relative; 427 | text-align: left; 428 | background-repeat: no-repeat; 429 | color: #FFFFFF; 430 | font-weight: bold; 431 | clear: none; 432 | overflow: hidden; 433 | padding: 0px; 434 | margin: 0px; 435 | } 436 | 437 | caption a:link, caption a:hover, caption a:active, caption a:visited { 438 | color: #FFFFFF; 439 | } 440 | 441 | .overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span { 442 | white-space: nowrap; 443 | padding-top: 8px; 444 | padding-left: 8px; 445 | display: block; 446 | float: left; 447 | height: 18px; 448 | } 449 | 450 | .contentContainer ul.blockList li.blockList caption span.activeTableTab span { 451 | white-space: nowrap; 452 | padding-top: 8px; 453 | padding-left: 8px; 454 | display: block; 455 | float: left; 456 | height: 18px; 457 | } 458 | 459 | .contentContainer ul.blockList li.blockList caption span.tableTab span { 460 | white-space: nowrap; 461 | padding-top: 8px; 462 | padding-left: 8px; 463 | display: block; 464 | float: left; 465 | height: 18px; 466 | } 467 | 468 | .contentContainer ul.blockList li.blockList caption span.tableTab, .contentContainer ul.blockList li.blockList caption span.activeTableTab { 469 | padding-top: 0px; 470 | padding-left: 0px; 471 | background-image: none; 472 | float: none; 473 | display: inline; 474 | } 475 | 476 | .overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd { 477 | width: 10px; 478 | background-repeat: no-repeat; 479 | background-position: top right; 480 | position: relative; 481 | float: left; 482 | } 483 | 484 | .contentContainer ul.blockList li.blockList .activeTableTab .tabEnd { 485 | width: 10px; 486 | margin-right: 5px; 487 | background-repeat: no-repeat; 488 | background-position: top right; 489 | position: relative; 490 | float: left; 491 | } 492 | 493 | .contentContainer ul.blockList li.blockList .tableTab .tabEnd { 494 | width: 10px; 495 | margin-right: 5px; 496 | background-repeat: no-repeat; 497 | background-position: top right; 498 | position: relative; 499 | float: left; 500 | } 501 | 502 | ul.blockList ul.blockList li.blockList table { 503 | margin: 0 0 12px 0px; 504 | width: 100%; 505 | } 506 | 507 | .tableSubHeadingColor { 508 | background-color: #EEEEFF; 509 | } 510 | 511 | .altColor { 512 | background-color: #eeeeef; 513 | } 514 | 515 | .rowColor { 516 | background-color: #ffffff; 517 | } 518 | 519 | .overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td { 520 | text-align: left; 521 | padding: 3px 3px 3px 7px; 522 | } 523 | 524 | th.colFirst, th.colLast, th.colOne, .constantValuesContainer th { 525 | background: #dee3e9; 526 | border-top: 1px solid #9eadc0; 527 | border-bottom: 1px solid #9eadc0; 528 | text-align: left; 529 | padding: 3px 3px 3px 7px; 530 | } 531 | 532 | td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { 533 | font-weight: bold; 534 | } 535 | 536 | td.colFirst, th.colFirst { 537 | border-left: 1px solid #9eadc0; 538 | white-space: nowrap; 539 | } 540 | 541 | td.colLast, th.colLast { 542 | border-right: 1px solid #9eadc0; 543 | } 544 | 545 | td.colOne, th.colOne { 546 | border-right: 1px solid #9eadc0; 547 | border-left: 1px solid #9eadc0; 548 | } 549 | 550 | table.overviewSummary { 551 | padding: 0px; 552 | margin-left: 0px; 553 | } 554 | 555 | table.overviewSummary td.colFirst, table.overviewSummary th.colFirst, 556 | table.overviewSummary td.colOne, table.overviewSummary th.colOne { 557 | width: 25%; 558 | vertical-align: middle; 559 | } 560 | 561 | table.packageSummary td.colFirst, table.overviewSummary th.colFirst { 562 | width: 25%; 563 | vertical-align: middle; 564 | } 565 | 566 | /* 567 | Content styles 568 | */ 569 | .description pre { 570 | margin-top: 0; 571 | } 572 | 573 | .deprecatedContent { 574 | margin: 0; 575 | padding: 10px 0; 576 | } 577 | 578 | .docSummary { 579 | padding: 0; 580 | } 581 | 582 | /* 583 | Formatting effect styles 584 | */ 585 | .sourceLineNo { 586 | color: green; 587 | padding: 0 30px 0 0; 588 | } 589 | 590 | h1.hidden { 591 | visibility: hidden; 592 | overflow: hidden; 593 | font-size: .9em; 594 | } 595 | 596 | .block { 597 | display: block; 598 | margin: 3px 0 0 0; 599 | } 600 | 601 | .strong { 602 | font-weight: bold; 603 | } 604 | 605 | /* 606 | Spring 607 | */ 608 | 609 | pre.code { 610 | background-color: #F8F8F8; 611 | border: 1px solid #CCCCCC; 612 | border-radius: 3px 3px 3px 3px; 613 | overflow: auto; 614 | padding: 10px; 615 | margin: 4px 20px 2px 0px; 616 | } 617 | 618 | pre.code code, pre.code code * { 619 | font-size: 1em; 620 | } 621 | 622 | pre.code code, pre.code code * { 623 | padding: 0 !important; 624 | margin: 0 !important; 625 | } -------------------------------------------------------------------------------- /reactor-spring-core/src/main/java/reactor/spring/core/task/AbstractAsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package reactor.spring.core.task; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.concurrent.Callable; 23 | import java.util.concurrent.Delayed; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.concurrent.Future; 26 | import java.util.concurrent.FutureTask; 27 | import java.util.concurrent.ScheduledExecutorService; 28 | import java.util.concurrent.ScheduledFuture; 29 | import java.util.concurrent.TimeUnit; 30 | import java.util.concurrent.TimeoutException; 31 | import java.util.concurrent.atomic.AtomicBoolean; 32 | import java.util.concurrent.atomic.AtomicReference; 33 | 34 | import org.reactivestreams.Subscriber; 35 | import org.reactivestreams.Subscription; 36 | import org.slf4j.Logger; 37 | import org.slf4j.LoggerFactory; 38 | import reactor.core.Cancellation; 39 | import reactor.core.publisher.FluxProcessor; 40 | import reactor.core.scheduler.TimedScheduler; 41 | import reactor.core.Exceptions; 42 | 43 | import org.springframework.beans.factory.InitializingBean; 44 | import org.springframework.context.ApplicationEventPublisher; 45 | import org.springframework.context.ApplicationEventPublisherAware; 46 | import org.springframework.context.SmartLifecycle; 47 | import org.springframework.core.task.AsyncListenableTaskExecutor; 48 | import org.springframework.util.concurrent.ListenableFuture; 49 | import org.springframework.util.concurrent.ListenableFutureTask; 50 | 51 | /** 52 | * Abstract base class for {@link org.springframework.core.task.AsyncTaskExecutor} implementations that need some basic 53 | * metadata about how they should be configured. 54 | * 55 | * @author Jon Brisbin 56 | * @author Stephane Maldini 57 | * @since 1.1, 2.5 58 | */ 59 | public abstract class AbstractAsyncTaskExecutor implements ApplicationEventPublisherAware, 60 | ScheduledExecutorService, 61 | AsyncListenableTaskExecutor, 62 | InitializingBean, 63 | SmartLifecycle, 64 | Subscriber { 65 | 66 | private final Logger log = LoggerFactory.getLogger(getClass()); 67 | private final TimedScheduler timer; 68 | 69 | private final AtomicBoolean running = new AtomicBoolean(false); 70 | 71 | private String name = getClass().getSimpleName(); 72 | private int threads = Runtime.getRuntime().availableProcessors(); 73 | private int backlog = 2048; 74 | private boolean shared = true; 75 | 76 | private ApplicationEventPublisher eventPublisher; 77 | 78 | protected AbstractAsyncTaskExecutor(TimedScheduler timer) { 79 | this.timer = timer; 80 | } 81 | 82 | 83 | @Override 84 | public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { 85 | this.eventPublisher = eventPublisher; 86 | } 87 | 88 | @Override 89 | public boolean isAutoStartup() { 90 | return true; 91 | } 92 | 93 | @Override 94 | public void stop(Runnable callback) { 95 | if (running.compareAndSet(true, false)) { 96 | getProcessor().onComplete(); 97 | callback.run(); 98 | } 99 | } 100 | 101 | @Override 102 | public void start() { 103 | if (running.compareAndSet(false, true)) { 104 | for (int i = 0; i < getThreads(); i++) { 105 | getProcessor().subscribe(this); 106 | } 107 | getProcessor().connect(); 108 | } 109 | } 110 | 111 | @Override 112 | public void stop() { 113 | if (running.compareAndSet(true, false)){ 114 | getProcessor().onComplete(); 115 | } 116 | } 117 | 118 | @Override 119 | public boolean isRunning() { 120 | return running.get(); 121 | } 122 | 123 | @Override 124 | public int getPhase() { 125 | return 0; 126 | } 127 | 128 | @Override 129 | public void onSubscribe(Subscription s) { 130 | s.request(Long.MAX_VALUE); 131 | } 132 | 133 | @Override 134 | public void onNext(Runnable runnable) { 135 | try { 136 | runnable.run(); 137 | } catch (Throwable t) { 138 | Exceptions.throwIfFatal(t); 139 | onError(t); 140 | } 141 | } 142 | 143 | @Override 144 | public void onError(Throwable t) { 145 | if (null != eventPublisher) { 146 | eventPublisher.publishEvent(new AsyncTaskExceptionEvent(t)); 147 | } else { 148 | log.error(t.getMessage(), t); 149 | } 150 | } 151 | 152 | @Override 153 | public void onComplete() { 154 | timer.shutdown(); 155 | log.trace(getName() + " task executor has shutdown"); 156 | } 157 | 158 | /** 159 | * Get the name by which these threads will be known. 160 | * 161 | * @return name of the threads for this work queue 162 | */ 163 | public String getName() { 164 | return name; 165 | } 166 | 167 | /** 168 | * Set the name by which these threads are known. 169 | * 170 | * @param name name of the threads for this work queue 171 | */ 172 | public void setName(String name) { 173 | this.name = name; 174 | } 175 | 176 | /** 177 | * Get the number of threads being used by this executor. 178 | * 179 | * @return the number of threads being used 180 | */ 181 | public int getThreads() { 182 | return threads; 183 | } 184 | 185 | /** 186 | * Set the number of threads to use when creating this executor. 187 | * 188 | * @param threads the number of threads to use 189 | */ 190 | public void setThreads(int threads) { 191 | this.threads = threads; 192 | } 193 | 194 | /** Can this executor be called from multiple threads ? 195 | * 196 | * @return true if multithread input ready 197 | */ 198 | public boolean isShared() { 199 | return shared; 200 | } 201 | 202 | 203 | /** 204 | * Tells this executor if it will be accepting tasks from multiple threads 205 | * 206 | * @param shared True if should support multithread publishing 207 | */ 208 | public void setShared(boolean shared) { 209 | this.shared = shared; 210 | } 211 | 212 | /** 213 | * Get the number of pre-allocated tasks to keep in memory. Correlates directly to the size of the internal {@code 214 | * RingBuffer}. 215 | * 216 | * @return the backlog value 217 | */ 218 | public int getBacklog() { 219 | return backlog; 220 | } 221 | 222 | /** 223 | * Set the number of pre-allocated tasks to keep in memory. Correlates directly to the size of the internal {@code 224 | * RingBuffer}. 225 | * 226 | * @param backlog the backlog value 227 | */ 228 | public void setBacklog(int backlog) { 229 | this.backlog = backlog; 230 | } 231 | 232 | 233 | @Override 234 | public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 235 | getProcessor().onComplete(); 236 | return getProcessor().isTerminated(); 237 | } 238 | 239 | @Override 240 | public boolean isTerminated() { 241 | return getProcessor().isTerminated(); 242 | } 243 | 244 | @Override 245 | public boolean isShutdown() { 246 | return isTerminated(); 247 | } 248 | 249 | @Override 250 | public List shutdownNow() { 251 | shutdown(); 252 | return Collections.emptyList(); 253 | } 254 | 255 | @Override 256 | public void shutdown() { 257 | if(running.compareAndSet(true, false)) { 258 | getProcessor().onComplete(); 259 | } 260 | } 261 | 262 | @Override 263 | public T invokeAny(Collection> tasks, 264 | long timeout, 265 | TimeUnit unit) throws InterruptedException, 266 | ExecutionException, 267 | TimeoutException { 268 | List> submittedTasks = new ArrayList>(); 269 | for (Callable task : tasks) { 270 | FutureTask ft = new FutureTask(task); 271 | execute(ft); 272 | submittedTasks.add(ft); 273 | } 274 | 275 | T result = null; 276 | long start = System.currentTimeMillis(); 277 | for (; ; ) { 278 | for (FutureTask task : submittedTasks) { 279 | result = task.get(100, TimeUnit.MILLISECONDS); 280 | if (null != result || task.isDone()) { 281 | break; 282 | } 283 | } 284 | if (null != result || (System.currentTimeMillis() - start) > TimeUnit.MILLISECONDS.convert(timeout, 285 | unit)) { 286 | break; 287 | } 288 | } 289 | for (FutureTask task : submittedTasks) { 290 | if (!task.isDone()) { 291 | task.cancel(true); 292 | } 293 | } 294 | return result; 295 | } 296 | 297 | @Override 298 | public T invokeAny(Collection> tasks) throws InterruptedException, 299 | ExecutionException { 300 | try { 301 | return invokeAny(tasks, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); 302 | } catch (TimeoutException e) { 303 | throw new ExecutionException(e.getMessage(), e); 304 | } 305 | } 306 | 307 | @Override 308 | public List> invokeAll(Collection> tasks, 309 | long timeout, 310 | TimeUnit unit) throws InterruptedException { 311 | List> submittedTasks = new ArrayList>(); 312 | for (Callable task : tasks) { 313 | FutureTask ft = new FutureTask(task); 314 | execute(ft); 315 | submittedTasks.add(ft); 316 | } 317 | 318 | T result = null; 319 | long start = System.currentTimeMillis(); 320 | for (; ; ) { 321 | boolean allComplete = false; 322 | for (Future task : submittedTasks) { 323 | try { 324 | result = task.get(100, TimeUnit.MILLISECONDS); 325 | } catch (ExecutionException e) { 326 | log.error(e.getMessage(), e); 327 | } catch (TimeoutException e) { 328 | log.error(e.getMessage(), e); 329 | } 330 | if (allComplete = !allComplete && task.isDone()) { 331 | break; 332 | } 333 | } 334 | if (null != result || (System.currentTimeMillis() - start) > TimeUnit.MILLISECONDS.convert(timeout, 335 | unit)) { 336 | break; 337 | } 338 | } 339 | return submittedTasks; 340 | } 341 | 342 | @Override 343 | public List> invokeAll(Collection> tasks) throws InterruptedException { 344 | return invokeAll(tasks, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); 345 | } 346 | 347 | @Override 348 | public void execute(final Runnable task, long startTimeout) { 349 | timer.schedule( 350 | () -> execute(task), 351 | startTimeout, TimeUnit.MILLISECONDS 352 | ); 353 | } 354 | 355 | @Override 356 | public Future submit(Runnable task) { 357 | final FutureTask future = new FutureTask(task, null); 358 | execute(future); 359 | return future; 360 | } 361 | 362 | @Override 363 | public Future submit(Callable task) { 364 | final FutureTask future = new FutureTask(task); 365 | execute(future); 366 | return future; 367 | } 368 | 369 | @Override 370 | public Future submit(Runnable task, T result) { 371 | FutureTask future = new FutureTask(task, result); 372 | execute(future); 373 | return future; 374 | } 375 | 376 | @Override 377 | public ListenableFuture submitListenable(Runnable task) { 378 | ListenableFutureTask f = new ListenableFutureTask(task, null); 379 | submit(f); 380 | return f; 381 | } 382 | 383 | @Override 384 | public ListenableFuture submitListenable(Callable task) { 385 | ListenableFutureTask f = new ListenableFutureTask(task); 386 | submit(f); 387 | return f; 388 | } 389 | 390 | @Override 391 | public void execute(Runnable task) { 392 | getProcessor().onNext(task); 393 | } 394 | 395 | @Override 396 | public ScheduledFuture schedule(Runnable command, 397 | long delay, 398 | TimeUnit unit) { 399 | long initialDelay = convertToMillis(delay, unit); 400 | final ScheduledFutureTask future = new ScheduledFutureTask(command, null, initialDelay); 401 | timer.schedule( () -> execute(future), initialDelay, TimeUnit.MILLISECONDS); 402 | return future; 403 | } 404 | 405 | @Override 406 | public ScheduledFuture schedule(Callable callable, 407 | long delay, 408 | TimeUnit unit) { 409 | long initialDelay = convertToMillis(delay, unit); 410 | final ScheduledFutureTask future = new ScheduledFutureTask(callable, initialDelay); 411 | timer.schedule( () -> execute(future), initialDelay, TimeUnit.MILLISECONDS); 412 | return future; 413 | } 414 | 415 | @Override 416 | public ScheduledFuture scheduleAtFixedRate(final Runnable command, 417 | long initialDelay, 418 | long period, 419 | TimeUnit unit) { 420 | long initialDelayInMs = convertToMillis(initialDelay, unit); 421 | long periodInMs = convertToMillis(period, unit); 422 | final AtomicReference registration = new AtomicReference<>(); 423 | 424 | final Runnable task = () -> { 425 | try { 426 | command.run(); 427 | } catch (Throwable t) { 428 | log.error(t.getMessage(), t); 429 | Cancellation reg; 430 | if (null != (reg = registration.get())) { 431 | reg.dispose(); 432 | } 433 | } 434 | }; 435 | 436 | final Runnable consumer = () -> execute(task); 437 | 438 | final ScheduledFutureTask future = new ScheduledFutureTask(task, null, initialDelay); 439 | registration.set(timer.schedulePeriodically(consumer, initialDelayInMs, periodInMs, TimeUnit.MILLISECONDS)); 440 | return future; 441 | } 442 | 443 | @Override 444 | public ScheduledFuture scheduleWithFixedDelay(final Runnable command, 445 | long initialDelay, 446 | long delay, 447 | TimeUnit unit) { 448 | final long initialDelayInMs = convertToMillis(initialDelay, unit); 449 | final long delayInMs = convertToMillis(initialDelay, unit); 450 | final ScheduledFutureTask future = new ScheduledFutureTask(command, null, initialDelayInMs); 451 | 452 | final AtomicReference registration = new AtomicReference<>(); 453 | 454 | final Runnable consumer = new Runnable() { 455 | final Runnable self = this; 456 | 457 | @Override 458 | public void run() { 459 | execute(() -> { 460 | try { 461 | future.run(); 462 | timer.schedule(self, delayInMs, TimeUnit.MILLISECONDS); 463 | } catch (Throwable t) { 464 | log.error(t.getMessage(), t); 465 | Cancellation reg; 466 | if (null != (reg = registration.get())) { 467 | reg.dispose(); 468 | } 469 | } 470 | }); 471 | } 472 | }; 473 | 474 | registration.set(timer.schedule(consumer, initialDelayInMs, TimeUnit.MILLISECONDS)); 475 | return future; 476 | } 477 | 478 | protected abstract FluxProcessor getProcessor(); 479 | 480 | private static long convertToMillis(long l, TimeUnit timeUnit) { 481 | if (timeUnit == TimeUnit.MILLISECONDS) { 482 | return l; 483 | } else { 484 | return timeUnit.convert(l, TimeUnit.MILLISECONDS); 485 | } 486 | } 487 | 488 | private static class ScheduledFutureTask extends FutureTask implements ScheduledFuture { 489 | private final long delay; 490 | 491 | private ScheduledFutureTask(Runnable runnable, T result, long delay) { 492 | super(runnable, result); 493 | this.delay = delay; 494 | } 495 | 496 | private ScheduledFutureTask(Callable callable, long delay) { 497 | super(callable); 498 | this.delay = delay; 499 | } 500 | 501 | @Override 502 | public long getDelay(TimeUnit unit) { 503 | return convertToMillis(delay, unit); 504 | } 505 | 506 | @Override 507 | public int compareTo(Delayed d) { 508 | if (this == d) { 509 | return 0; 510 | } 511 | long diff = getDelay(TimeUnit.MILLISECONDS) - d.getDelay(TimeUnit.MILLISECONDS); 512 | return (diff == 0 ? 0 : ((diff < 0) ? -1 : 1)); 513 | } 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /reactor-spring-context/src/main/java/reactor/spring/context/config/ConsumerBeanAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package reactor.spring.context.config; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.LinkedHashSet; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.function.Consumer; 16 | import java.util.function.Function; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import reactor.bus.Bus; 21 | import reactor.bus.Event; 22 | import reactor.bus.selector.Selectors; 23 | import reactor.spring.context.annotation.ReplyTo; 24 | import reactor.spring.context.annotation.Selector; 25 | 26 | import org.springframework.beans.BeansException; 27 | import org.springframework.context.ApplicationContext; 28 | import org.springframework.context.ApplicationContextAware; 29 | import org.springframework.context.ApplicationListener; 30 | import org.springframework.context.event.ContextRefreshedEvent; 31 | import org.springframework.context.expression.BeanFactoryAccessor; 32 | import org.springframework.context.expression.BeanFactoryResolver; 33 | import org.springframework.context.expression.EnvironmentAccessor; 34 | import org.springframework.core.BridgeMethodResolver; 35 | import org.springframework.core.annotation.AnnotationUtils; 36 | import org.springframework.core.convert.ConversionService; 37 | import org.springframework.core.convert.TypeDescriptor; 38 | import org.springframework.expression.AccessException; 39 | import org.springframework.expression.BeanResolver; 40 | import org.springframework.expression.EvaluationContext; 41 | import org.springframework.expression.EvaluationException; 42 | import org.springframework.expression.PropertyAccessor; 43 | import org.springframework.expression.TypedValue; 44 | import org.springframework.expression.common.TemplateAwareExpressionParser; 45 | import org.springframework.expression.spel.standard.SpelExpressionParser; 46 | import org.springframework.expression.spel.support.ReflectivePropertyAccessor; 47 | import org.springframework.expression.spel.support.StandardEvaluationContext; 48 | import org.springframework.util.ClassUtils; 49 | import org.springframework.util.ReflectionUtils; 50 | import org.springframework.util.StringUtils; 51 | 52 | import static reactor.bus.selector.JsonPathSelector.jsonPathSelector; 53 | import static reactor.bus.selector.Selectors.*; 54 | 55 | /** 56 | * {@link org.springframework.context.ApplicationListener} implementation that finds beans registered in the current 57 | * {@link org.springframework.context.ApplicationContext} that look like a {@link reactor.spring.context.annotation 58 | * .Consumer} 59 | * bean and interrogates it for event handling methods. 60 | * 61 | * @author Jon Brisbin 62 | * @author Stephane Maldini 63 | */ 64 | public class ConsumerBeanAutoConfiguration implements ApplicationListener, 65 | ApplicationContextAware { 66 | 67 | public static final String REACTOR_CONVERSION_SERVICE_BEAN_NAME = "reactorConversionService"; 68 | 69 | public static final ReflectionUtils.MethodFilter CONSUMER_METHOD_FILTER = new ReflectionUtils.MethodFilter() { 70 | @Override 71 | public boolean matches(Method method) { 72 | return null != AnnotationUtils.findAnnotation(method, Selector.class); 73 | } 74 | }; 75 | 76 | private static final Logger LOG = LoggerFactory.getLogger(ConsumerBeanAutoConfiguration.class); 77 | 78 | private reactor.bus.selector.Selector defaultSelector = Selectors.anonymous(); 79 | private Map wiredBeans = new HashMap(); 80 | 81 | private ApplicationContext appCtx; 82 | private BeanResolver beanResolver; 83 | private ConversionService conversionService; 84 | private TemplateAwareExpressionParser expressionParser; 85 | private List expressionPropertyAccessors; 86 | 87 | public ConsumerBeanAutoConfiguration() { 88 | this.expressionParser = new SpelExpressionParser(); 89 | 90 | this.expressionPropertyAccessors = new ArrayList(); 91 | this.expressionPropertyAccessors.add(new EnvironmentAccessor()); 92 | this.expressionPropertyAccessors.add(new BeanFactoryAccessor()); 93 | this.expressionPropertyAccessors.add(new ReflectivePropertyAccessor()); 94 | this.expressionPropertyAccessors.add(new DirectFieldAccessPropertyAccessor()); 95 | } 96 | 97 | @Override 98 | public void setApplicationContext(ApplicationContext appCtx) throws BeansException { 99 | this.appCtx = appCtx; 100 | } 101 | 102 | @Override 103 | public void onApplicationEvent(ContextRefreshedEvent ev) { 104 | ApplicationContext ctx = ev.getApplicationContext(); 105 | if (ctx != appCtx) { 106 | return; 107 | } 108 | 109 | if (null == beanResolver) { 110 | beanResolver = new BeanFactoryResolver(ctx); 111 | } 112 | 113 | if (null == conversionService) { 114 | try { 115 | conversionService = ctx.getBean(REACTOR_CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); 116 | } catch (BeansException be) { 117 | if (LOG.isDebugEnabled()) { 118 | LOG.debug(REACTOR_CONVERSION_SERVICE_BEAN_NAME + " has not been found in the context. Skipping."); 119 | } 120 | } 121 | } 122 | 123 | for (String beanName : ctx.getBeanDefinitionNames()) { 124 | Set methods = new HashSet(); 125 | Class type = ctx.getType(beanName); 126 | if(type == null) continue; 127 | 128 | if (null == AnnotationUtils.findAnnotation(type, reactor.spring.context.annotation.Consumer.class)) { 129 | wiredBeans.put(beanName, Boolean.FALSE); 130 | continue; 131 | } 132 | if (wiredBeans.containsKey(beanName)) { 133 | continue; 134 | } 135 | 136 | try { 137 | if (Function.class.isAssignableFrom(type)) { 138 | methods.add(type.getDeclaredMethod("apply")); 139 | } else if (Consumer.class.isAssignableFrom(type)) { 140 | methods.add(type.getDeclaredMethod("accept")); 141 | } else { 142 | methods.addAll(findHandlerMethods(type, CONSUMER_METHOD_FILTER)); 143 | } 144 | wireBean(ctx.getBean(beanName), methods); 145 | wiredBeans.put(beanName, Boolean.TRUE); 146 | } catch (NoSuchMethodException ignored) { 147 | } 148 | } 149 | } 150 | 151 | @SuppressWarnings("unchecked") 152 | public void wireBean(final Object bean, final Set methods) { 153 | if (methods.isEmpty()) { 154 | return; 155 | } 156 | 157 | Consumer consumer; 158 | Bus reactor; 159 | Selector selectorAnno; 160 | ReplyTo replyToAnno; 161 | reactor.bus.selector.Selector selector; 162 | 163 | for (final Method method : methods) { 164 | //scanAnnotation method 165 | selectorAnno = AnnotationUtils.findAnnotation(method, Selector.class); 166 | replyToAnno = AnnotationUtils.findAnnotation(method, ReplyTo.class); 167 | reactor = fetchBus(selectorAnno, bean); 168 | selector = fetchSelector(selectorAnno, bean, method); 169 | 170 | //register [replyTo]consumer 171 | Object replyTo = replyToAnno != null ? parseReplyTo(replyToAnno, bean) : null; 172 | Invoker handler = new Invoker(method, bean, conversionService); 173 | consumer = null != replyToAnno ? 174 | new ReplyToServiceConsumer(reactor, replyTo, handler) : 175 | new ServiceConsumer(handler); 176 | 177 | if (LOG.isDebugEnabled()) { 178 | LOG.debug("Attaching Consumer to Reactor[" + reactor + "] using Selector[" + selector + "]"); 179 | } 180 | 181 | if (null == selector) { 182 | throw new IllegalArgumentException("Selector cannot be null"); 183 | } else { 184 | reactor.on(selector, consumer); 185 | } 186 | } 187 | } 188 | 189 | @SuppressWarnings("unchecked") 190 | private T expression(String selector, Object bean) { 191 | if (selector == null) { 192 | return null; 193 | } 194 | 195 | StandardEvaluationContext evalCtx = new StandardEvaluationContext(bean); 196 | evalCtx.setBeanResolver(beanResolver); 197 | evalCtx.setPropertyAccessors(expressionPropertyAccessors); 198 | 199 | return (T) expressionParser.parseExpression(selector).getValue(evalCtx); 200 | } 201 | 202 | private Bus fetchBus(Selector selectorAnno, Object bean) { 203 | return expression(selectorAnno.eventBus(), bean); 204 | } 205 | 206 | private Object parseSelector(Selector selector, Object bean, Method method) { 207 | if (!StringUtils.hasText(selector.value())) { 208 | return method.getName(); 209 | } 210 | 211 | try { 212 | return expression(selector.value(), bean); 213 | } catch (Exception e) { 214 | return selector.value(); 215 | } 216 | } 217 | 218 | private Object parseReplyTo(ReplyTo selector, Object bean) { 219 | if (StringUtils.isEmpty(selector.value())) { 220 | return null; 221 | } 222 | try { 223 | return expression(selector.value(), bean); 224 | } catch (EvaluationException ee) { 225 | return selector.value(); 226 | } 227 | } 228 | 229 | private reactor.bus.selector.Selector fetchSelector(Selector selectorAnno, Object bean, Method method) { 230 | Object sel = parseSelector(selectorAnno, bean, method); 231 | try { 232 | switch (selectorAnno.type()) { 233 | case OBJECT: 234 | return object(sel); 235 | case REGEX: 236 | return regex(sel.toString()); 237 | case URI: 238 | return uri(sel.toString()); 239 | case TYPE: 240 | try { 241 | return type(Class.forName(sel.toString())); 242 | } catch (ClassNotFoundException e) { 243 | throw new IllegalArgumentException(e.getMessage(), e); 244 | } 245 | case JSON_PATH: 246 | return jsonPathSelector(sel.toString()); 247 | } 248 | } catch (EvaluationException e) { 249 | if (LOG.isTraceEnabled()) { 250 | LOG.trace("Creating ObjectSelector for '" + sel + "' due to " + e.getMessage(), e); 251 | } 252 | } 253 | return object(sel); 254 | } 255 | 256 | private final static class ReplyToServiceConsumer implements Consumer { 257 | 258 | final private Bus reactor; 259 | final private Object replyToKey; 260 | final private Invoker handler; 261 | 262 | ReplyToServiceConsumer(Bus reactor, Object replyToKey, Invoker handler) { 263 | this.reactor = reactor; 264 | this.replyToKey = replyToKey; 265 | this.handler = handler; 266 | } 267 | 268 | public Bus getReactor() { 269 | return reactor; 270 | } 271 | 272 | public Object getReplyToKey() { 273 | return replyToKey; 274 | } 275 | 276 | public Invoker getHandler() { 277 | return handler; 278 | } 279 | 280 | @Override 281 | public void accept(Event ev) { 282 | Object result = null; 283 | try { 284 | result = handler.apply(ev); 285 | } catch (Exception ex) { 286 | if(ev.getErrorConsumer() != null) { 287 | ev.consumeError(ex); 288 | } 289 | } 290 | Object _replyToKey = replyToKey != null ? replyToKey : ev.getReplyTo(); 291 | if (_replyToKey != null) { 292 | reactor.notify(_replyToKey, Event.wrap(result)); 293 | } 294 | } 295 | } 296 | 297 | private final static class ServiceConsumer implements Consumer { 298 | final private Invoker handler; 299 | 300 | ServiceConsumer(Invoker handler) { 301 | this.handler = handler; 302 | } 303 | 304 | public Invoker getHandler() { 305 | return handler; 306 | } 307 | 308 | @Override 309 | public void accept(Event ev) { 310 | handler.apply(ev); 311 | } 312 | } 313 | 314 | private final static class Invoker implements Function { 315 | 316 | final private Method method; 317 | final private Object bean; 318 | final private Class[] argTypes; 319 | final private ConversionService conversionService; 320 | 321 | Invoker(Method method, Object bean, ConversionService conversionService) { 322 | this.method = method; 323 | this.bean = bean; 324 | this.conversionService = conversionService; 325 | this.argTypes = method.getParameterTypes(); 326 | } 327 | 328 | public Method getMethod() { 329 | return method; 330 | } 331 | 332 | public Object getBean() { 333 | return bean; 334 | } 335 | 336 | public Class[] getArgTypes() { 337 | return argTypes; 338 | } 339 | 340 | @Override 341 | public Object apply(Event ev) { 342 | if (argTypes.length == 0) { 343 | if (LOG.isDebugEnabled()) { 344 | LOG.debug("Invoking method[" + method + "] on " + bean.getClass() + " using " + ev); 345 | } 346 | return ReflectionUtils.invokeMethod(method, bean); 347 | } 348 | 349 | if (argTypes.length > 1) { 350 | throw new IllegalStateException("Multiple parameters not yet supported."); 351 | } 352 | 353 | if (Event.class.isAssignableFrom(argTypes[0])) { 354 | if (LOG.isDebugEnabled()) { 355 | LOG.debug("Invoking method[" + method + "] on " + bean.getClass() + " using " + ev); 356 | } 357 | return ReflectionUtils.invokeMethod(method, bean, ev); 358 | } 359 | 360 | if (null == ev.getData() || argTypes[0].isAssignableFrom(ev.getData().getClass())) { 361 | if (LOG.isDebugEnabled()) { 362 | LOG.debug("Invoking method[" + method + "] on " + bean.getClass() + " using " + ev.getData()); 363 | } 364 | return ReflectionUtils.invokeMethod(method, bean, ev.getData()); 365 | } 366 | 367 | if (!argTypes[0].isAssignableFrom(ev.getClass()) 368 | && conversionService.canConvert(ev.getClass(), argTypes[0])) { 369 | return ReflectionUtils.invokeMethod(method, bean, conversionService.convert(ev, argTypes[0])); 370 | } 371 | 372 | if (conversionService.canConvert(ev.getData().getClass(), argTypes[0])) { 373 | Object convertedObj = conversionService.convert(ev.getData(), argTypes[0]); 374 | if (LOG.isDebugEnabled()) { 375 | LOG.debug("Invoking method[" + method + "] on " + bean.getClass() + " using " + convertedObj); 376 | } 377 | return ReflectionUtils.invokeMethod(method, bean, convertedObj); 378 | } 379 | 380 | throw new IllegalArgumentException("Cannot invoke method " + method + " passing parameter " + ev.getData()); 381 | } 382 | } 383 | 384 | private static class DirectFieldAccessPropertyAccessor implements PropertyAccessor { 385 | private static final Map fieldCache = new ConcurrentHashMap(); 386 | 387 | @Override 388 | public Class[] getSpecificTargetClasses() { 389 | return null; 390 | } 391 | 392 | @Override 393 | public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { 394 | return null != findField(target, name); 395 | } 396 | 397 | @Override 398 | public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { 399 | Field fld = findField(target, name); 400 | try { 401 | Object obj = fld.get(target); 402 | return new TypedValue(obj, TypeDescriptor.forObject(obj)); 403 | } catch (IllegalAccessException e) { 404 | throw new AccessException(e.getMessage(), e); 405 | } 406 | } 407 | 408 | @Override 409 | public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { 410 | return null != findField(target, name); 411 | } 412 | 413 | @Override 414 | public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { 415 | Field fld = findField(target, name); 416 | try { 417 | fld.set(target, newValue); 418 | } catch (IllegalAccessException e) { 419 | throw new AccessException(e.getMessage(), e); 420 | } 421 | } 422 | 423 | private Field findField(Object target, final String name) { 424 | final int cacheKey = target.hashCode() ^ name.hashCode(); 425 | Field result = fieldCache.get(cacheKey); 426 | if (result==null || !result.getDeclaringClass().isInstance(target)) { 427 | // either the cache does not yet contain the field or we have the unlikely case 428 | // then the hash code of two objects of different classes is equal. 429 | // In this case, a re-lookup is done. 430 | ReflectionUtils.doWithFields( 431 | target.getClass(), 432 | new ReflectionUtils.FieldCallback() { 433 | @Override 434 | public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { 435 | if (name.equals(field.getName())) { 436 | ReflectionUtils.makeAccessible(field); 437 | fieldCache.put(cacheKey, field); 438 | } 439 | } 440 | } 441 | ); 442 | result = fieldCache.get(cacheKey); 443 | } 444 | return result; 445 | } 446 | } 447 | 448 | private static Set findHandlerMethods(Class handlerType, 449 | final ReflectionUtils.MethodFilter handlerMethodFilter) { 450 | final Set handlerMethods = new LinkedHashSet(); 451 | 452 | if (handlerType == null) { 453 | return handlerMethods; 454 | } 455 | 456 | Set> handlerTypes = new LinkedHashSet>(); 457 | Class specificHandlerType = null; 458 | if (!Proxy.isProxyClass(handlerType)) { 459 | handlerTypes.add(handlerType); 460 | specificHandlerType = handlerType; 461 | } 462 | handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); 463 | for (Class currentHandlerType : handlerTypes) { 464 | final Class targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); 465 | ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { 466 | @Override 467 | public void doWith(Method method) { 468 | Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 469 | Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); 470 | if (handlerMethodFilter.matches(specificMethod) && 471 | (bridgedMethod == specificMethod || !handlerMethodFilter.matches(bridgedMethod))) { 472 | handlerMethods.add(specificMethod); 473 | } 474 | } 475 | }, ReflectionUtils.USER_DECLARED_METHODS); 476 | } 477 | return handlerMethods; 478 | } 479 | 480 | } 481 | --------------------------------------------------------------------------------