/*
 * 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.internal;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;

import org.apache.geronimo.transaction.manager.TransactionLog;
import org.apache.geronimo.transaction.manager.TransactionManagerMonitor;
import org.apache.geronimo.transaction.manager.XidFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 */
public class AriesPlatformTransactionManager extends AriesTransactionManagerImpl implements PlatformTransactionManager {

    private final PlatformTransactionManager platformTransactionManager;
    private final Map<Transaction, SuspendedResourcesHolder> suspendedResources = new ConcurrentHashMap<Transaction, SuspendedResourcesHolder>();

    public AriesPlatformTransactionManager() throws XAException {
        platformTransactionManager = new JtaTransactionManager(this, this);
        registerTransactionAssociationListener();
    }

    public AriesPlatformTransactionManager(int defaultTransactionTimeoutSeconds) throws XAException {
        super(defaultTransactionTimeoutSeconds);
        platformTransactionManager = new JtaTransactionManager(this, this);
        registerTransactionAssociationListener();
    }

    public AriesPlatformTransactionManager(int defaultTransactionTimeoutSeconds, TransactionLog transactionLog) throws XAException {
        super(defaultTransactionTimeoutSeconds, transactionLog);
        platformTransactionManager = new JtaTransactionManager(this, this);
        registerTransactionAssociationListener();
    }

    public AriesPlatformTransactionManager(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
        super(defaultTransactionTimeoutSeconds, xidFactory, transactionLog);
        platformTransactionManager = new JtaTransactionManager(this, this);
        registerTransactionAssociationListener();
    }

    public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        return platformTransactionManager.getTransaction(definition);
    }

    public void commit(TransactionStatus status) throws TransactionException {
        platformTransactionManager.commit(status);
    }

    public void rollback(TransactionStatus status) throws TransactionException {
        platformTransactionManager.rollback(status);
    }

    protected void registerTransactionAssociationListener() {
        addTransactionAssociationListener(new TransactionManagerMonitor() {
            public void threadAssociated(Transaction transaction) {
                try {
                    if (transaction.getStatus() == Status.STATUS_ACTIVE) {
                        SuspendedResourcesHolder holder = suspendedResources.remove(transaction);
                        if (holder != null && holder.getSuspendedSynchronizations() != null) {
                            TransactionSynchronizationManager.setActualTransactionActive(true);
                            TransactionSynchronizationManager.setCurrentTransactionReadOnly(holder.isReadOnly());
                            TransactionSynchronizationManager.setCurrentTransactionName(holder.getName());
                            TransactionSynchronizationManager.initSynchronization();
                            for (Iterator<?> it = holder.getSuspendedSynchronizations().iterator(); it.hasNext();) {
                                TransactionSynchronization synchronization = (TransactionSynchronization) it.next();
                                synchronization.resume();
                                TransactionSynchronizationManager.registerSynchronization(synchronization);
                            }
                        }
                    }
                } catch (SystemException e) {
                    return;
                }
            }
            public void threadUnassociated(Transaction transaction) {
                try {
                    if (transaction.getStatus() == Status.STATUS_ACTIVE) {
                        if (TransactionSynchronizationManager.isSynchronizationActive()) {
                            List<?> suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations();
                            for (Iterator<?> it = suspendedSynchronizations.iterator(); it.hasNext();) {
                                ((TransactionSynchronization) it.next()).suspend();
                            }
                            TransactionSynchronizationManager.clearSynchronization();
                            String name = TransactionSynchronizationManager.getCurrentTransactionName();
                            TransactionSynchronizationManager.setCurrentTransactionName(null);
                            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
                            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
                            TransactionSynchronizationManager.setActualTransactionActive(false);
                            SuspendedResourcesHolder holder = new SuspendedResourcesHolder(null, suspendedSynchronizations, name, readOnly);
                            suspendedResources.put(transaction, holder);
                        }
                    }
                } catch (SystemException e) {
                    return;
                }
            }
        });
    }

    /**
     * Holder for suspended resources.
     * Used internally by <code>suspend</code> and <code>resume</code>.
     */
    private static class SuspendedResourcesHolder {

        private final Object suspendedResources;

        private final List<?> suspendedSynchronizations;

        private final String name;

        private final boolean readOnly;

        public SuspendedResourcesHolder(
                Object suspendedResources, List<?> suspendedSynchronizations, String name, boolean readOnly) {

            this.suspendedResources = suspendedResources;
            this.suspendedSynchronizations = suspendedSynchronizations;
            this.name = name;
            this.readOnly = readOnly;
        }

        @SuppressWarnings("unused")
        public Object getSuspendedResources() {
            return suspendedResources;
        }

        public List<?> getSuspendedSynchronizations() {
            return suspendedSynchronizations;
        }

        public String getName() {
            return name;
        }

        public boolean isReadOnly() {
            return readOnly;
        }
    }

}
