001/** 002 * Powerunit - A JDK1.8 test framework 003 * Copyright (C) 2014 Mathieu Boretti. 004 * 005 * This file is part of Powerunit 006 * 007 * Powerunit is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation, either version 3 of the License, or 010 * (at your option) any later version. 011 * 012 * Powerunit is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>. 019 */ 020package ch.powerunit.extensions.exceptions; 021 022import static java.util.Arrays.stream; 023 024import java.util.Collection; 025import java.util.Comparator; 026import java.util.Optional; 027import java.util.function.Function; 028 029/** 030 * Interface to help create mapper function for exception wrapping. 031 * 032 * @since 2.0.0 033 */ 034public interface ExceptionMapper extends Function<Exception, RuntimeException> { 035 036 /** 037 * Exception wrapper, that may be used to copy error code and sql state to the 038 * {@code WrappedException} message. 039 * <p> 040 * <b>This mapper will only works correctly if the module <i>java.sql</i> is 041 * available.</b> 042 * <p> 043 * 044 * @return the Mapper for {@code SQLException}. 045 * @throws NoClassDefFoundError 046 * In case the {@code SQLException} is not available (java.sql 047 * module missing). 048 */ 049 static ExceptionMapper sqlExceptionMapper() { 050 return Optional.ofNullable(Constants.SQL_EXCEPTION_MAPPER) 051 .orElseThrow(() -> new NoClassDefFoundError("Unable to find the sqlException")); 052 } 053 054 /** 055 * Exception wrapper, that may be used to copy jaxb information to the 056 * {@code WrappedException} message. 057 * <p> 058 * <b>This mapper will only works correctly if the class <i>JAXBException</i> is 059 * available.</b> 060 * <p> 061 * 062 * @return the Mapper for {@code JAXBException}. 063 * @throws NoClassDefFoundError 064 * In case the {@code JAXBException} is not available. 065 * @since 2.1.0 066 */ 067 static ExceptionMapper jaxbExceptionMapper() { 068 return Optional.ofNullable(Constants.JAXBEXCEPTION_EXCEPTION_MAPPER) 069 .orElseThrow(() -> new NoClassDefFoundError("Unable to find the JAXBException")); 070 } 071 072 /** 073 * Exception wrapper, that may be used to copy sax information to the 074 * {@code WrappedException} message. 075 * <p> 076 * <b>This mapper will only works correctly if the class <i>SAXException</i> is 077 * available.</b> 078 * <p> 079 * 080 * @return the Mapper for {@code SAXException}. 081 * @throws NoClassDefFoundError 082 * In case the {@code SAXException} is not available (java.xml 083 * module missing). 084 * @since 2.1.0 085 */ 086 static ExceptionMapper saxExceptionMapper() { 087 return Optional.ofNullable(Constants.SAXEXCEPTION_EXCEPTION_MAPPER) 088 .orElseThrow(() -> new NoClassDefFoundError("Unable to find the SAXException")); 089 } 090 091 /** 092 * Exception wrapper, that may be used to copy transformer exception information 093 * to the {@code WrappedException} message. 094 * <p> 095 * <b>This mapper will only works correctly if the class <i>SAXException</i> is 096 * available.</b> 097 * <p> 098 * 099 * @return the Mapper for {@code TransformerException}. 100 * @throws NoClassDefFoundError 101 * In case the {@code TransformerException} is not available 102 * (java.xml module missing). 103 * @since 2.1.0 104 */ 105 static ExceptionMapper transformerExceptionMapper() { 106 return Optional.ofNullable(Constants.TRANSFORMEREXCEPTION_EXCEPTION_MAPPER) 107 .orElseThrow(() -> new NoClassDefFoundError("Unable to find the TransformerException")); 108 } 109 110 Class<? extends Exception> targetException(); 111 112 private boolean accept(Exception e) { 113 return targetException().isInstance(e); 114 } 115 116 /** 117 * This method is used when ExceptionMapper are registered as default Mapper to 118 * defines the right order to select then. 119 * 120 * @return an ordering key, by default 0. 121 * @since 2.2.0 122 */ 123 default int order() { 124 return 0; 125 } 126 127 /** 128 * Helper method to create exception wrapper that check the exception class. 129 * 130 * @param clazz 131 * the class of the exception to be wrapped. 132 * @param mapper 133 * the exception mapper. 134 * @param <E> 135 * the type of the exception. 136 * @return A new exception mapper, which use the one received as parameter or 137 * for the other exception just create a {@code WrappedException}. 138 */ 139 static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper) { 140 return forException(clazz, mapper, 0); 141 } 142 143 /** 144 * Helper method to create exception wrapper that check the exception class. 145 * 146 * @param clazz 147 * the class of the exception to be wrapped. 148 * @param mapper 149 * the exception mapper. 150 * @param order 151 * the order 152 * @param <E> 153 * the type of the exception. 154 * @return A new exception mapper, which use the one received as parameter or 155 * for the other exception just create a {@code WrappedException}. 156 * @since 2.2.0 157 */ 158 static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper, 159 int order) { 160 return new ExceptionMapper() { 161 162 @Override 163 public RuntimeException apply(Exception t) { 164 return clazz.isInstance(t) ? mapper.apply(clazz.cast(t)) : new WrappedException(t); 165 } 166 167 @Override 168 public Class<E> targetException() { 169 return clazz; 170 } 171 172 @Override 173 public int order() { 174 return order; 175 } 176 }; 177 } 178 179 /** 180 * Helper method to create exception wrapper to use the first mapper if 181 * applicable or else the second. 182 * 183 * @param mapper1 184 * the first mapper to be used. 185 * @param mapper2 186 * the second mapper to used. 187 * @return the mapping function. 188 */ 189 static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2) { 190 return e -> mapper1.accept(e) ? mapper1.apply(e) : mapper2.apply(e); 191 } 192 193 /** 194 * Helper method to create exception wrapper to use the first mapper if 195 * applicable or else the second or else the third 196 * 197 * @param mapper1 198 * the first mapper to be used. 199 * @param mapper2 200 * the second mapper to used. 201 * @param mapper3 202 * the third mapper to used. 203 * @return the mapping function. 204 */ 205 static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2, 206 ExceptionMapper mapper3) { 207 return e -> (mapper1.accept(e) ? mapper1 : forExceptions(mapper2, mapper3)).apply(e); 208 } 209 210 /** 211 * Helper method to create exception wrapper that use the first one that is 212 * applicable. 213 * 214 * @param mappers 215 * the mapper to be tried 216 * @return the mapping function. 217 */ 218 static Function<Exception, RuntimeException> forExceptions(ExceptionMapper... mappers) { 219 if (mappers.length == 0) { 220 return WrappedException::new; 221 } 222 return e -> stream(mappers).sequential().filter(m -> m.accept(e)).limit(1).map(m -> m.apply(e)).findFirst() 223 .orElseGet(() -> new WrappedException(e)); 224 } 225 226 /** 227 * Helper method to create exception wrapper that use the first one that is 228 * applicable, but having them sorted using the {@link #order()} method. 229 * 230 * @param mappers 231 * the mapper to be tried 232 * @return the mapping function. 233 * @since 2.2.0 234 */ 235 static Function<Exception, RuntimeException> forOrderedExceptions(Collection<ExceptionMapper> mappers) { 236 if (mappers.isEmpty()) { 237 return WrappedException::new; 238 } 239 return forExceptions(mappers.stream().sorted(Comparator.comparingInt(ExceptionMapper::order)) 240 .toArray(ExceptionMapper[]::new)); 241 } 242 243}