diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandler.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandler.java new file mode 100644 index 00000000..3f9bce94 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.rx; + +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.WebAsyncUtils; +import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; +import rx.Observable; +import rx.functions.Action1; + +/** + * Handles return values of type {@link rx.Observable}. + * + * @author Spencer Gibb + */ +public class ObservableReturnValueHandler implements AsyncHandlerMethodReturnValueHandler { + + @Override + public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) { + return returnValue != null && returnValue instanceof Observable; + } + + @Override + public boolean supportsReturnType(MethodParameter returnType) { + return Observable.class.isAssignableFrom(returnType.getParameterType()); + } + + @Override + public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { + if (returnValue == null) { + mavContainer.setRequestHandled(true); + return; + } + + Observable observable = Observable.class.cast(returnValue); + + final DeferredResult deferredResult = new DeferredResult<>(); + + observable.subscribe(new Action1() { + @Override + public void call(Object o) { + deferredResult.setResult(o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + deferredResult.setErrorResult(throwable); + } + }); + + WebAsyncUtils.getAsyncManager(webRequest) + .startDeferredResultProcessing(deferredResult, mavContainer); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/RxJavaAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/RxJavaAutoConfiguration.java new file mode 100644 index 00000000..62be1dc5 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/rx/RxJavaAutoConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.rx; + +import java.util.List; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import rx.Observable; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnClass(Observable.class) +public class RxJavaAutoConfiguration { + + @Bean + public ObservableReturnValueHandler observableReturnValueHandler() { + return new ObservableReturnValueHandler(); + } + + @Bean + @ConditionalOnWebApplication + public WebMvcConfigurerAdapter observableMVCConfiguration() { + return new WebMvcConfigurerAdapter() { + @Override + public void addReturnValueHandlers(List returnValueHandlers) { + returnValueHandlers.add(observableReturnValueHandler()); + } + }; + } +} diff --git a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories index 7e84ee29..8e19fd89 100644 --- a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories @@ -8,6 +8,7 @@ org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\ +org.springframework.cloud.netflix.rx.RxJavaAutoConfiguration,\ org.springframework.cloud.netflix.servo.ServoMetricsAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandlerTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandlerTests.java new file mode 100644 index 00000000..210b6cf3 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/rx/ObservableReturnValueHandlerTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.rx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import rx.Observable; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ObservableReturnValueHandlerTests.Application.class) +@WebAppConfiguration +@IntegrationTest({ "server.port=0" }) +@DirtiesContext +public class ObservableReturnValueHandlerTests { + + @Value("${local.server.port}") + private int port = 0; + + @Configuration + @EnableAutoConfiguration + @RestController + protected static class Application { + @RequestMapping(method = RequestMethod.GET, value = "/") + public Observable hi() { + return Observable.just("hello world"); + } + } + + @Test + public void observableReturns() { + ResponseEntity response = new TestRestTemplate().getForEntity("http://localhost:" + port, String.class); + assertNotNull("response was null", response); + assertEquals("response code was wrong", HttpStatus.OK, response.getStatusCode()); + assertEquals("response was wrong", "hello world", response.getBody()); + } +}