blob: 8974a40640fae94bb1d09528752f24d10932c1db [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.aries.transaction;
import java.lang.reflect.Method;
import java.util.Optional;
import javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.apache.aries.blueprint.Interceptor;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.coordinator.Coordination;
import org.osgi.service.coordinator.Coordinator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TxInterceptorImpl implements Interceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(TxInterceptorImpl.class);
private TransactionManager tm;
private Coordinator coordinator;
private ComponentTxData txData;
public TxInterceptorImpl(TransactionManager tm, Coordinator coordinator, ComponentTxData txData) {
this.tm = tm;
this.coordinator = coordinator;
this.txData = txData;
}
@Override
public int getRank() {
return 1; // Higher rank than jpa interceptor to make sure transaction is started first
}
@Override
public Object preCall(ComponentMetadata cm, Method m, Object... parameters) throws Throwable {
final Optional<TransactionalAnnotationAttributes> type = txData.getEffectiveType(m);
if (!type.isPresent()) {
// No transaction
return null;
}
TransactionAttribute txAttribute = TransactionAttribute.fromValue(type.get().getTxType());
LOGGER.debug("PreCall for bean {}, method {} with tx strategy {}.", getCmId(cm), m.getName(), txAttribute);
TransactionToken token = txAttribute.begin(tm);
String coordName = "txInterceptor." + m.getDeclaringClass().getName() + "." + m.getName();
Coordination coord = coordinator.begin(coordName , 0);
token.setCoordination(coord);
return token;
}
@Override
public void postCallWithException(ComponentMetadata cm, Method m, Throwable ex, Object preCallToken) {
if (!(preCallToken instanceof TransactionToken)) {
return;
}
LOGGER.debug("PostCallWithException for bean {}, method {}.", getCmId(cm), m.getName(), ex);
final TransactionToken token = (TransactionToken)preCallToken;
safeEndCoordination(token);
try {
Transaction tran = token.getActiveTransaction();
if (tran != null && isRollBackException(ex, m)) {
tran.setRollbackOnly();
LOGGER.debug("Setting transaction to rollback only because of exception ", ex);
}
token.getTransactionAttribute().finish(tm, token);
} catch (Exception e) {
// we do not throw the exception since there already is one, but we need to log it
LOGGER.warn("Exception during transaction cleanup", e);
}
}
@Override
public void postCallWithReturn(ComponentMetadata cm, Method m, Object returnType, Object preCallToken)
throws Exception {
LOGGER.debug("PostCallWithReturn for bean {}, method {}.", getCmId(cm), m);
// it is possible transaction is not involved at all
if (preCallToken == null) {
return;
}
if (!(preCallToken instanceof TransactionToken)) {
throw new IllegalStateException("Expected a TransactionToken from preCall but got " + preCallToken);
}
final TransactionToken token = (TransactionToken)preCallToken;
safeEndCoordination(token);
try {
token.getTransactionAttribute().finish(tm, token);
} catch (Exception e) {
// We are throwing an exception, so we don't error it out
LOGGER.debug("Exception while completing transaction.", e);
RollbackException rbe = new javax.transaction.RollbackException();
rbe.addSuppressed(e);
throw rbe;
}
}
private void safeEndCoordination(final TransactionToken token) {
try {
if (token != null && token.getCoordination() != null) {
token.getCoordination().end();
}
} catch (Exception e){
LOGGER.debug(e.getMessage(), e);
}
}
private static String getCmId(ComponentMetadata cm) {
return cm == null ? null : cm.getId();
}
private boolean isRollBackException(Throwable ex, Method m) {
if (m != null) {
Optional<TransactionalAnnotationAttributes> effectiveType = txData.getEffectiveType(m);
if (!effectiveType.isPresent()) {
return isUncheckedException(ex);
} else {
//check dontRollbackOn first, since according to spec it has precedence
for (Class dontRollbackClass : effectiveType.get().getDontRollbackOn()) {
if (dontRollbackClass.isInstance(ex)) {
LOGGER.debug("Current exception {} found in element dontRollbackOn.", ex.getClass());
return false;
}
}
//don't need to check further elements if ex is an unchecked exception
if (isUncheckedException(ex)) {
return true;
}
for (Class rollbackExceptionClass : effectiveType.get().getRollbackOn()) {
if (rollbackExceptionClass.isInstance(ex)) {
LOGGER.debug("Current exception {} found in element rollbackOn.", ex.getClass());
return true;
}
}
}
} else {
return isUncheckedException(ex);
}
return false;
}
private static boolean isUncheckedException(Throwable ex) {
return ex instanceof RuntimeException || ex instanceof Error;
}
}