All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.bonitasoft.engine.work.DefaultExceptionRetryabilityEvaluator Maven / Gradle / Ivy
/**
* Copyright (C) 2020 Bonitasoft S.A.
* Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation
* version 2.1 of the License.
* This library 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 Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.bonitasoft.engine.work;
import static javax.transaction.xa.XAException.XA_RBTIMEOUT;
import static javax.transaction.xa.XAException.XA_RBTRANSIENT;
import static org.bonitasoft.engine.work.ExceptionRetryabilityEvaluator.Retryability.NOT_RETRYABLE;
import static org.bonitasoft.engine.work.ExceptionRetryabilityEvaluator.Retryability.RETRYABLE;
import static org.bonitasoft.engine.work.ExceptionRetryabilityEvaluator.Retryability.UNCERTAIN_COMPLETION_OF_COMMIT;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import javax.transaction.xa.XAException;
import org.bonitasoft.engine.transaction.STransactionCommitException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.RecoverableDataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
/**
* @author Baptiste Mesta.
*/
public class DefaultExceptionRetryabilityEvaluator implements ExceptionRetryabilityEvaluator {
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionRetryabilityEvaluator.class);
private static final List XA_RETRYABLE = Arrays.asList(XA_RBTRANSIENT, XA_RBTIMEOUT);
private HashSet> exceptionClassesToRetry;
private HashSet> exceptionClassesToNotRetry;
public DefaultExceptionRetryabilityEvaluator(List exceptionClassesToRetry,
List exceptionClassesToNotRetry) {
this.exceptionClassesToRetry = toSetOfClasses(exceptionClassesToRetry);
this.exceptionClassesToNotRetry = toSetOfClasses(exceptionClassesToNotRetry);
}
private HashSet> toSetOfClasses(List exceptionClassesToRetry) {
ClassLoader classLoader = this.getClass().getClassLoader();
HashSet> classSet = new HashSet<>();
for (String className : exceptionClassesToRetry) {
classSet.add(getCheckedThrowable(classLoader, className));
}
return classSet;
}
private Class extends Throwable> getCheckedThrowable(ClassLoader classLoader, String className) {
Class> throwable;
try {
throwable = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(String.format("The class %s is not found", className), e);
}
if (!Throwable.class.isAssignableFrom(throwable)) {
throw new IllegalArgumentException(String.format("The class %s is not a Throwable", className));
}
return (Class extends Throwable>) throwable;
}
@Override
public Retryability evaluateRetryability(Throwable thrown) {
List exceptions = getAllCauses(thrown);
Optional notRetryableException = exceptions.stream().filter(this::isKnownAsNotRetryable).findFirst();
if (notRetryableException.isPresent()) {
logger.debug("Exception is known as not retryable: {}", print(exceptions, notRetryableException.get()));
return NOT_RETRYABLE;
}
Optional retryableException = exceptions.stream().filter(this::isRetryable).findFirst();
if (!retryableException.isPresent()) {
logger.debug("No retryable exception found: {}", print(exceptions));
return NOT_RETRYABLE;
}
Throwable theRetryableException = retryableException.get();
if (isSqlConnectionIssue(theRetryableException)
&& exceptions.stream().anyMatch(e -> e instanceof STransactionCommitException)) {
logger.debug(
"Retryable exception found but exception happened on commit, uncertain completion of the transaction: {}",
print(exceptions, theRetryableException));
return UNCERTAIN_COMPLETION_OF_COMMIT;
} else {
logger.debug("Retryable exception found: {}", print(exceptions, theRetryableException));
return RETRYABLE;
}
}
private boolean isRetryable(Throwable thrown) {
if (isInListedRetriableException(thrown)) {
return true;
}
if (thrown instanceof SQLException) {
DataAccessException dataAccessException = new SQLErrorCodeSQLExceptionTranslator().translate("", "",
(SQLException) thrown);
if (dataAccessException instanceof RecoverableDataAccessException
|| dataAccessException instanceof TransientDataAccessException) {
return true;
}
}
if (thrown instanceof XAException) {
int errorCode = ((XAException) thrown).errorCode;
if (XA_RETRYABLE.contains(errorCode)) {
return true;
}
}
return isSqlConnectionIssue(thrown);
}
private List getAllCauses(Throwable thrown) {
List allExceptions = new ArrayList<>();
while (thrown != null) {
allExceptions.add(thrown);
List exceptions = getExceptions(thrown, "getExceptions", "getHandlerExceptions");
if (!exceptions.isEmpty()) {
for (Exception exception : exceptions) {
allExceptions.addAll(getAllCauses(exception));
}
}
thrown = thrown.getCause();
}
return allExceptions;
}
private boolean isSqlConnectionIssue(Throwable thrown) {
String name = thrown.getClass().getSimpleName().toLowerCase();
String message = thrown.getMessage() != null ? thrown.getMessage().toLowerCase() : "";
return (name.contains("sql") || name.contains("jdbc"))
&& (message.contains("connection") || message.contains("timeout") || message.contains("i/o error"));
}
private String print(List throwables, Throwable highlighted) {
StringBuilder stringBuilder = new StringBuilder("Exceptions:\n");
for (Throwable throwable : throwables) {
stringBuilder.append(" ↳");
stringBuilder.append(throwable.getClass().getName());
if (highlighted != null && throwable.getClass().equals(highlighted.getClass())) {
stringBuilder.append(": ");
stringBuilder.append(throwable.getMessage());
stringBuilder.append(" ☚");
}
stringBuilder.append('\n');
}
return stringBuilder.toString();
}
private String print(List throwables) {
return print(throwables, null);
}
private boolean isInListedRetriableException(Throwable e) {
return isListedIn(e, exceptionClassesToRetry);
}
private boolean isKnownAsNotRetryable(Throwable e) {
return isListedIn(e, exceptionClassesToNotRetry);
}
private boolean isListedIn(Throwable e, HashSet> exceptions) {
for (Class extends Throwable> throwable : exceptions) {
if (throwable.isInstance(e)) {
return true;
}
}
return false;
}
private List getExceptions(Throwable thrown, String... methodNames) {
for (String methodName : methodNames) {
try {
//for bitronix PhaseException and FireEventExceptions
Method getExceptions = thrown.getClass().getDeclaredMethod(methodName);
return (List) getExceptions.invoke(thrown);
} catch (Throwable ignored) {
}
}
return Collections.emptyList();
}
}