The scenario
- you have some DAO that can enrich a DTO with different pieces of information
- the call site can specify which information
options
should be added to the DTO
- there are many different types of
options
that could potentially be added
- you hate switch/case but you love java 8 streams
- you want to play with java 8
The problem
- we want to collect enrichment-methods in a
Map
and execute them dynamically
- our enrichment methods throw
SQLException
and (for some crazy reason) cannot be changed
- we still want (for some other crazy reason) to propagate any
SQLException
up to the call site
The solution
- we create a
@FunctionalInterface
representing something that consumes a T
and throws a SQLException
- we allow that interface to transform itself into a
Function
that takes a T
and returns an Optional<SQLException>
.
Thus we can remove the checked
Exception
that would otherwise have stopped us from using method references in
options.stream()
.
import java.sql.SQLException;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import static java.util.Arrays.asList;
public class SomeDao<T> {
private final Map<With, OptionHandler<T>> optionHandlers;
public SomeDao() {
this.optionHandlers = ImmutableMap.of(
With.THIS, this::enrichWithThis,
With.THAT, this::enrichWithThat
);
}
public void enrich(final T someDto, final With... options)
throws SQLException {
final Optional<SQLException> e = asList(options).stream()
.map(optionHandlers::get)
.map(OptionHandler::toFunction)
.map(function -> function.apply(someDto))
.filter(Optional::isPresent)
.map(Optional::get)
.findAny(); // this will short circuit execution
// in case a SQLException occurs
if (e.isPresent()) {
throw e.get();
}
}
@FunctionalInterface
private interface OptionHandler<T> {
void accept(final T t) throws SQLException;
default Function<T, Optional<SQLException>> toFunction() {
return argument -> {
try {
accept(argument);
return Optional.empty();
} catch (SQLException e) {
return Optional.of(e);
}
};
}
}
public enum With {
THIS, THAT
}
private void enrichWithThis(final T dto) throws SQLException {
// something
}
private void enrichWithThat(final T dto) throws SQLException {
// something
}
}
The call site would typically look something like this:
private final SomeDao<MyDto> someDao;
...
private void prepareMyDto(final MyDto myDto) {
someDao.enrich(myDto, With.THIS, With.THAT);
}
}