ExceptionMapper.java
/**
* Powerunit - A JDK1.8 test framework
* Copyright (C) 2014 Mathieu Boretti.
*
* This file is part of Powerunit
*
* Powerunit is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Powerunit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.powerunit.extensions.exceptions;
import static java.util.Arrays.stream;
import java.util.Collection;
import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
/**
* Interface to help create mapper function for exception wrapping.
*
* @since 2.0.0
*/
public interface ExceptionMapper extends Function<Exception, RuntimeException> {
/**
* Exception wrapper, that may be used to copy error code and sql state to the
* {@code WrappedException} message.
* <p>
* <b>This mapper will only works correctly if the module <i>java.sql</i> is
* available.</b>
* <p>
*
* @return the Mapper for {@code SQLException}.
* @throws NoClassDefFoundError
* In case the {@code SQLException} is not available (java.sql
* module missing).
*/
static ExceptionMapper sqlExceptionMapper() {
return Optional.ofNullable(Constants.SQL_EXCEPTION_MAPPER)
.orElseThrow(() -> new NoClassDefFoundError("Unable to find the sqlException"));
}
/**
* Exception wrapper, that may be used to copy jaxb information to the
* {@code WrappedException} message.
* <p>
* <b>This mapper will only works correctly if the class <i>JAXBException</i> is
* available.</b>
* <p>
*
* @return the Mapper for {@code JAXBException}.
* @throws NoClassDefFoundError
* In case the {@code JAXBException} is not available.
* @since 2.1.0
*/
static ExceptionMapper jaxbExceptionMapper() {
return Optional.ofNullable(Constants.JAXBEXCEPTION_EXCEPTION_MAPPER)
.orElseThrow(() -> new NoClassDefFoundError("Unable to find the JAXBException"));
}
/**
* Exception wrapper, that may be used to copy sax information to the
* {@code WrappedException} message.
* <p>
* <b>This mapper will only works correctly if the class <i>SAXException</i> is
* available.</b>
* <p>
*
* @return the Mapper for {@code SAXException}.
* @throws NoClassDefFoundError
* In case the {@code SAXException} is not available (java.xml
* module missing).
* @since 2.1.0
*/
static ExceptionMapper saxExceptionMapper() {
return Optional.ofNullable(Constants.SAXEXCEPTION_EXCEPTION_MAPPER)
.orElseThrow(() -> new NoClassDefFoundError("Unable to find the SAXException"));
}
/**
* Exception wrapper, that may be used to copy transformer exception information
* to the {@code WrappedException} message.
* <p>
* <b>This mapper will only works correctly if the class <i>SAXException</i> is
* available.</b>
* <p>
*
* @return the Mapper for {@code TransformerException}.
* @throws NoClassDefFoundError
* In case the {@code TransformerException} is not available
* (java.xml module missing).
* @since 2.1.0
*/
static ExceptionMapper transformerExceptionMapper() {
return Optional.ofNullable(Constants.TRANSFORMEREXCEPTION_EXCEPTION_MAPPER)
.orElseThrow(() -> new NoClassDefFoundError("Unable to find the TransformerException"));
}
Class<? extends Exception> targetException();
private boolean accept(Exception e) {
return targetException().isInstance(e);
}
/**
* This method is used when ExceptionMapper are registered as default Mapper to
* defines the right order to select then.
*
* @return an ordering key, by default 0.
* @since 2.2.0
*/
default int order() {
return 0;
}
/**
* Helper method to create exception wrapper that check the exception class.
*
* @param clazz
* the class of the exception to be wrapped.
* @param mapper
* the exception mapper.
* @param <E>
* the type of the exception.
* @return A new exception mapper, which use the one received as parameter or
* for the other exception just create a {@code WrappedException}.
*/
static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper) {
return forException(clazz, mapper, 0);
}
/**
* Helper method to create exception wrapper that check the exception class.
*
* @param clazz
* the class of the exception to be wrapped.
* @param mapper
* the exception mapper.
* @param order
* the order
* @param <E>
* the type of the exception.
* @return A new exception mapper, which use the one received as parameter or
* for the other exception just create a {@code WrappedException}.
* @since 2.2.0
*/
static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper,
int order) {
return new ExceptionMapper() {
@Override
public RuntimeException apply(Exception t) {
return clazz.isInstance(t) ? mapper.apply(clazz.cast(t)) : new WrappedException(t);
}
@Override
public Class<E> targetException() {
return clazz;
}
@Override
public int order() {
return order;
}
};
}
/**
* Helper method to create exception wrapper to use the first mapper if
* applicable or else the second.
*
* @param mapper1
* the first mapper to be used.
* @param mapper2
* the second mapper to used.
* @return the mapping function.
*/
static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2) {
return e -> mapper1.accept(e) ? mapper1.apply(e) : mapper2.apply(e);
}
/**
* Helper method to create exception wrapper to use the first mapper if
* applicable or else the second or else the third
*
* @param mapper1
* the first mapper to be used.
* @param mapper2
* the second mapper to used.
* @param mapper3
* the third mapper to used.
* @return the mapping function.
*/
static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2,
ExceptionMapper mapper3) {
return e -> (mapper1.accept(e) ? mapper1 : forExceptions(mapper2, mapper3)).apply(e);
}
/**
* Helper method to create exception wrapper that use the first one that is
* applicable.
*
* @param mappers
* the mapper to be tried
* @return the mapping function.
*/
static Function<Exception, RuntimeException> forExceptions(ExceptionMapper... mappers) {
if (mappers.length == 0) {
return WrappedException::new;
}
return e -> stream(mappers).sequential().filter(m -> m.accept(e)).limit(1).map(m -> m.apply(e)).findFirst()
.orElseGet(() -> new WrappedException(e));
}
/**
* Helper method to create exception wrapper that use the first one that is
* applicable, but having them sorted using the {@link #order()} method.
*
* @param mappers
* the mapper to be tried
* @return the mapping function.
* @since 2.2.0
*/
static Function<Exception, RuntimeException> forOrderedExceptions(Collection<ExceptionMapper> mappers) {
if (mappers.isEmpty()) {
return WrappedException::new;
}
return forExceptions(mappers.stream().sorted(Comparator.comparingInt(ExceptionMapper::order))
.toArray(ExceptionMapper[]::new));
}
}