package de.spring.webservices.rest.business.service;
+import java.io.IOException;
+
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public Car findById(long id);
- public Car create(Car resource);
+ public Car create(Car car);
+
+ public Car createThrowable(Car car) throws IOException;
}
public CompletableFuture<Car> create(Car resource);
+ public CompletableFuture<Car> createThrowable(Car car);
+
}
public Observable<Car> findById(long id);
- public Observable<Car> create(Car resource);
+ public Observable<Car> create(Car car);
+
+ public Observable<Car> createThrowable(Car car);
}
package de.spring.webservices.rest.business.service.impl;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
cars.add(new Car(counter.incrementAndGet(), String.format(TEMPLATE, 3)));
try {
- Thread.sleep(300000);
+ Thread.sleep(10000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
public Car findById(long id) {
try {
- Thread.sleep(300000);
+ Thread.sleep(10000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
@Override
- public Car create(Car resource) {
+ public Car create(Car car) {
long count = counter.incrementAndGet();
try {
- Thread.sleep(300000);
+ Thread.sleep(10000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
return new Car(count, String.format(TEMPLATE, count));
}
+
+ @Override
+ public Car createThrowable(Car car) throws IOException {
+
+ throw new IOException("createThrowable FATAL ERROR");
+ }
}
package de.spring.webservices.rest.business.service.impl;
+import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
import de.spring.webservices.rest.business.service.CompletableFutureBusinessLogic;
-/**
- *
- *
- * TODO: WHAT ABOUT EXCEPTIONS FROM awesomeBusinessLogic? RuntimeExceptions for example
- * I guess they will be caught by my adapter in controller layer but I must try it.
- *
- */
-
@Service("completableFutureBusinessLogic")
public class CompletablefutureBusinessLogicImpl implements CompletableFutureBusinessLogic {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CompletablefutureBusinessLogicImpl.class);
+
private final AwesomeBusinessLogic awesomeBusinessLogic;
@Inject
public CompletableFuture<Car> create(Car car) {
return CompletableFuture.supplyAsync(() -> awesomeBusinessLogic.create(car));
}
+
+ @Override
+ public CompletableFuture<Car> createThrowable(Car car) {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return awesomeBusinessLogic.createThrowable(car);
+ } catch (IOException ex) {
+
+ LOGGER.error("createThrowable error: ", ex);
+
+ throw new RuntimeException("Nested exception", ex);
+ }
+ });
+ }
}
package de.spring.webservices.rest.business.service.impl;
+import java.io.IOException;
+
import javax.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import de.spring.webservices.rest.business.service.AwesomeBusinessLogic;
import de.spring.webservices.rest.business.service.RxJavaBusinessLogic;
import rx.Observable;
-
-/**
- *
- *
- * TODO: WHAT ABOUT EXCEPTIONS FROM awesomeBusinessLogic? RuntimeExceptions for example
- * I guess they will be caught by my adapter in controller layer but I must try it.
- *
- */
+import rx.exceptions.Exceptions;
@Service("rxJavaBusinessLogic")
public class RxJavaBusinessLogicImpl implements RxJavaBusinessLogic {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaBusinessLogicImpl.class);
+
private final AwesomeBusinessLogic awesomeBusinessLogic;
@Inject
public Observable<Car> create(Car car) {
return Observable.create(observer -> observer.onNext(awesomeBusinessLogic.create(car)));
}
+
+ @Override
+ public Observable<Car> createThrowable(Car car) {
+ return Observable.create(observer -> {
+
+ try {
+ observer.onNext(awesomeBusinessLogic.createThrowable(car));
+ } catch (IOException ex) {
+ // I could use this implementation. Instead, I will wrap my exception because
+ // that is what you would be doing if you were using any other method from RxJava (like map() for example)
+ // observer.onError(ex);
+
+ LOGGER.error("createThrowable error: ", ex);
+
+ Exceptions.propagate(ex);
+ }
+
+ });
+ }
}
private CompletableFuture<ResponseEntity<Car>> createAsync(Car car) {
return completableFutureBusinessLogic
- .create(car)
+ .createThrowable(car)
.thenComposeAsync(newCar ->
CompletableFuture.supplyAsync(() -> createResponseCar(newCar))
private Observable<ResponseEntity<Car>> createAsync(Car car) {
return rxJavaBusinessLogic
- .create(car)
+ .createThrowable(car)
.map(this::createResponseCar);
}
- private ResponseEntity<Car> createResponseCar(Car car) {
+ private ResponseEntity<Car> createResponseCar(Car car) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.LOCATION, "/api/cars/" + car.getId());
return new ResponseEntity<>(car, headers, HttpStatus.CREATED);
public class CompletableFutureAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(CompletableFutureAdapter.class);
- /**
- *
- * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE)
- * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual)
- *
- */
-
// With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests.
// Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout.
private static final long ASYNC_TIMEOUT = 60000; /* milliseconds */
}
public static final <T> DeferredResult<T> deferredAdapter(CompletableFuture<T> completableFuture) {
-
+
DeferredResult<T> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
completableFuture
.thenAcceptAsync(deferredResult::setResult)
.exceptionally(exception -> {
-
- LOGGER.error("error: ", exception);
-
+ Throwable realException = launderException(exception);
+
+ LOGGER.error("error: ", realException);
+
deferredResult.setErrorResult(exception);
-
return null;
});
-
+
return deferredResult;
}
+ private static final Throwable launderException(Throwable exception) {
+ return exception.getCause() != null
+ ? exception.getCause()
+ : exception;
+ }
}
public class RxJavaAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaAdapter.class);
-
- /**
- *
- * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE)
- * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual)
- *
- */
// With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests.
// Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout.
}
public static final <T> DeferredResult<T> deferredAdapter(Observable<T> observable) {
-
+
DeferredResult<T> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT);
observable
.subscribeOn(Schedulers.io())
- .subscribe(deferredResult::setResult, exception -> LOGGER.error("error: ", exception));
-
+ .subscribe(deferredResult::setResult, exception -> {
+ Throwable realException = launderException(exception);
+
+ LOGGER.error("error: ", realException);
+
+ deferredResult.setErrorResult(realException);
+ });
+
return deferredResult;
}
+ private static final Throwable launderException(Throwable exception) {
+ return exception.getCause() != null
+ ? exception.getCause()
+ : exception;
+ }
}
--- /dev/null
+package de.spring.webservices.rest.controller.handler;
+
+import java.io.IOException;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+// using XML configuration @EnableWebMvc
+@ControllerAdvice
+public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
+
+ // 500
+ @ExceptionHandler({ IOException.class })
+ public ResponseEntity<Object> handleBadRequest(final Exception ex, final WebRequest request) {
+ return handleExceptionInternal(ex, null, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
+ }
+
+}
See: org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser
<mvc:annotation-driven/>
-->
-
+ <!-- Required for making work @ControllerAdvice -->
+ <mvc:annotation-driven/>
<context:annotation-config />