/*
 * 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 javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.Transactional.TxType;

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 TxType type = txData.getEffectiveType(m);
        if (type == null) {
            // No transaction
            return null;
        }
        TransactionAttribute txAttribute = TransactionAttribute.fromValue(type);

        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)) {
                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 static boolean isRollBackException(Throwable ex) {
        return ex instanceof RuntimeException || ex instanceof Error;
    }

}

@@ -997,24 +997,64 @@
 ionManager;%0A
+import javax.transaction.Transactional;%0A
 import javax
@@ -1088,16 +1088,16 @@
 TxType;%0A
-
 %0Aimport 
@@ -3249,16 +3249,19 @@
 ption(ex
+, m
 )) %7B%0A   
@@ -5167,23 +5167,16 @@
 private 
-static 
 boolean 
@@ -5177,32 +5177,1461 @@
 olean isRollBack
+Exception(Throwable ex, Method m) %7B%0A        if (m != null) %7B%0A            Transactional annotation = m.getAnnotation(Transactional.class);%0A            if (annotation == null) %7B%0A                return isUncheckedException(ex);%0A            %7D else %7B%0A                //check dontRollbackOn first, since according to spec it has precedence%0A                Class%5B%5D dontRollbackOn = annotation.dontRollbackOn();%0A                for (Class dontRollbackClass : dontRollbackOn) %7B%0A                    if (dontRollbackClass.isInstance(ex)) %7B%0A                        LOGGER.debug(%22Current exception %7B%7D found in element dontRollbackOn.%22, ex.getClass());%0A                        return false;%0A                    %7D%0A                %7D%0A                //don't need to check further elements if ex is an unchecked exception%0A                if (isUncheckedException(ex)) %7B%0A                    return true;%0A                %7D%0A                Class%5B%5D rollbackOn = annotation.rollbackOn();%0A                for (Class rollbackExceptionClass : rollbackOn) %7B%0A                    if (rollbackExceptionClass.isInstance(ex)) %7B%0A                        LOGGER.debug(%22Current exception %7B%7D found in element rollbackOn.%22, ex.getClass());%0A                        return true;%0A                    %7D%0A                %7D%0A            %7D%0A        %7D else %7B%0A%09        return isUncheckedException(ex);%0A        %7D%0A        return false;%0A    %7D%0A%0A    private static boolean isUnchecked
 Exception(Throwa

Add Transactional dontRollbackOn/rollbackOn evaluation. Closes #72