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

import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ManagedConnectionFactory;

import org.apache.aries.transaction.AriesTransactionManager;
import org.apache.geronimo.connector.outbound.GenericConnectionManager;
import org.apache.geronimo.connector.outbound.SubjectSource;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.LocalTransactions;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.NoPool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.NoTransactions;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.SinglePool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTrackingCoordinator;
import org.apache.geronimo.connector.outbound.connectiontracking.GeronimoTransactionListener;
import org.apache.geronimo.transaction.manager.TransactionManagerMonitor;
import org.tranql.connector.UserPasswordManagedConnectionFactory;

public class ConnectionManagerFactory {

    private AriesTransactionManager transactionManager;
    private ManagedConnectionFactory managedConnectionFactory;

    private String name;

    private TransactionSupport transactionSupport;
    private String transaction;

    private PoolingSupport poolingSupport;
    private boolean pooling = true;
    private String partitionStrategy; //: none, by-subject, by-connector-properties
    private int poolMaxSize = 10;
    private int poolMinSize = 0;
    private boolean allConnectionsEqual = true;
    private int connectionMaxWaitMilliseconds = 5000;
    private int connectionMaxIdleMinutes = 15;

    private boolean validateOnMatch = true;
    private boolean backgroundValidation = false;
    private int backgroundValidationMilliseconds = 600000;

    private SubjectSource subjectSource;

    private ConnectionTrackingCoordinator connectionTracker;
    private TransactionManagerMonitor transactionManagerMonitor;
    private GenericConnectionManager connectionManager;

    public ConnectionManager getConnectionManager() {
        return connectionManager;
    }

    public void init() throws Exception {
        if (transactionManager == null && ("xa".equals(transaction) || "local".equals(transaction))) {
            throw new IllegalArgumentException("transactionManager must be set");
        }
        if (managedConnectionFactory == null) {
            throw new IllegalArgumentException("managedConnectionFactory must be set");
        }
        if (validateOnMatch || backgroundValidation) {
            // Validation requires per-connection matching, see #ARIES-1790
            allConnectionsEqual = false;
        }
        // Apply the default value for property if necessary
        if (transactionSupport == null) {
            // No transaction
            if ("local".equalsIgnoreCase(transaction)) {
                transactionSupport = LocalTransactions.INSTANCE;
            } else if ("none".equalsIgnoreCase(transaction)) {
                transactionSupport = NoTransactions.INSTANCE;
            } else if ("xa".equalsIgnoreCase(transaction)) {
                transactionSupport = new XATransactions(true, false);
            } else {
                throw new IllegalArgumentException("Unknown transaction type " + transaction + " (must be local, none or xa)");
            }
        }
        if (poolingSupport == null) {
            // No pool
            if (!pooling) {
                poolingSupport = new NoPool();
            } else {
                if (partitionStrategy == null || "none".equalsIgnoreCase(partitionStrategy)) {

                    // unpartitioned pool
                    poolingSupport = new SinglePool(poolMaxSize,
                            poolMinSize,
                            connectionMaxWaitMilliseconds,
                            connectionMaxIdleMinutes,
                            allConnectionsEqual,
                            !allConnectionsEqual,
                            false);

                } else if ("by-connector-properties".equalsIgnoreCase(partitionStrategy)) {

                    // partition by connector properties such as username and password on a jdbc connection
                    poolingSupport = new PartitionedPool(poolMaxSize,
                            poolMinSize,
                            connectionMaxWaitMilliseconds,
                            connectionMaxIdleMinutes,
                            allConnectionsEqual,
                            !allConnectionsEqual,
                            false,
                            true,
                            false);
                } else if ("by-subject".equalsIgnoreCase(partitionStrategy)) {

                    // partition by caller subject
                    poolingSupport = new PartitionedPool(poolMaxSize,
                            poolMinSize,
                            connectionMaxWaitMilliseconds,
                            connectionMaxIdleMinutes,
                            allConnectionsEqual,
                            !allConnectionsEqual,
                            false,
                            false,
                            true);
                } else {
                    throw new IllegalArgumentException("Unknown partition strategy " + partitionStrategy + " (must be none, by-connector-properties or by-subject)");
                }
            }
        }
        if (connectionTracker == null) {
            connectionTracker = new ConnectionTrackingCoordinator();
        }
        if (transactionManagerMonitor == null && transactionManager != null) {
            transactionManagerMonitor = new GeronimoTransactionListener(connectionTracker);
            transactionManager.addTransactionAssociationListener(transactionManagerMonitor);
        }
        if (connectionManager == null) {
            if (validateOnMatch || backgroundValidation) {
                // Wrap the original ManagedConnectionFactory to add validation capability
                managedConnectionFactory = new ValidatingDelegatingManagedConnectionFactory((UserPasswordManagedConnectionFactory) managedConnectionFactory);
            }
            if (backgroundValidation) {
                // Instantiate the Validating Connection Manager
                connectionManager = new ValidatingGenericConnectionManager(
                        transactionSupport,
                        poolingSupport,
                        subjectSource,
                        connectionTracker,
                        transactionManager,
                        managedConnectionFactory,
                        name != null ? name : getClass().getName(),
                        getClass().getClassLoader(),
                        backgroundValidationMilliseconds);
            } else {
                // Instantiate the Geronimo Connection Manager
                connectionManager = new GenericConnectionManager(
                        transactionSupport,
                        poolingSupport,
                        subjectSource,
                        connectionTracker,
                        transactionManager,
                        managedConnectionFactory,
                        name != null ? name : getClass().getName(),
                        getClass().getClassLoader());
            }

            connectionManager.doStart();
        }
    }

    public void destroy() throws Exception {
        if (connectionManager != null) {
            connectionManager.doStop();
            connectionManager = null;
        }
        if (transactionManagerMonitor != null && transactionManager != null) {
            transactionManager.removeTransactionAssociationListener(transactionManagerMonitor);
        }
    }

    public AriesTransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(AriesTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public ManagedConnectionFactory getManagedConnectionFactory() {
        return managedConnectionFactory;
    }

    public void setManagedConnectionFactory(ManagedConnectionFactory managedConnectionFactory) {
        this.managedConnectionFactory = managedConnectionFactory;
    }

    public TransactionSupport getTransactionSupport() {
        return transactionSupport;
    }

    public void setTransactionSupport(TransactionSupport transactionSupport) {
        this.transactionSupport = transactionSupport;
    }

    public String getTransaction() {
        return transaction;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setTransaction(String transaction) {
        this.transaction = transaction;
    }

    public PoolingSupport getPoolingSupport() {
        return poolingSupport;
    }

    public void setPoolingSupport(PoolingSupport poolingSupport) {
        this.poolingSupport = poolingSupport;
    }

    public boolean isPooling() {
        return pooling;
    }

    public void setPooling(boolean pooling) {
        this.pooling = pooling;
    }

    public String getPartitionStrategy() {
        return partitionStrategy;
    }

    public void setPartitionStrategy(String partitionStrategy) {
        this.partitionStrategy = partitionStrategy;
    }

    public int getPoolMaxSize() {
        return poolMaxSize;
    }

    public void setPoolMaxSize(int poolMaxSize) {
        this.poolMaxSize = poolMaxSize;
    }

    public int getPoolMinSize() {
        return poolMinSize;
    }

    public void setPoolMinSize(int poolMinSize) {
        this.poolMinSize = poolMinSize;
    }

    public boolean isAllConnectionsEqual() {
        return allConnectionsEqual;
    }

    public void setAllConnectionsEqual(boolean allConnectionsEqual) {
        this.allConnectionsEqual = allConnectionsEqual;
    }

    public int getConnectionMaxWaitMilliseconds() {
        return connectionMaxWaitMilliseconds;
    }

    public void setConnectionMaxWaitMilliseconds(int connectionMaxWaitMilliseconds) {
        this.connectionMaxWaitMilliseconds = connectionMaxWaitMilliseconds;
    }

    public int getConnectionMaxIdleMinutes() {
        return connectionMaxIdleMinutes;
    }

    public void setConnectionMaxIdleMinutes(int connectionMaxIdleMinutes) {
        this.connectionMaxIdleMinutes = connectionMaxIdleMinutes;
    }

    public boolean isValidateOnMatch() {
        return validateOnMatch;
    }

    public void setValidateOnMatch(boolean validateOnMatch) {
        this.validateOnMatch = validateOnMatch;
    }

    public boolean isBackgroundValidation() {
        return backgroundValidation;
    }

    public void setBackgroundValidation(boolean backgroundValidation) {
        this.backgroundValidation = backgroundValidation;
    }

    public int getBackgroundValidationMilliseconds() {
        return backgroundValidationMilliseconds;
    }

    public void setBackgroundValidationMilliseconds(int backgroundValidationMilliseconds) {
        this.backgroundValidationMilliseconds = backgroundValidationMilliseconds;
    }

    public SubjectSource getSubjectSource() {
        return subjectSource;
    }

    public void setSubjectSource(SubjectSource subjectSource) {
        this.subjectSource = subjectSource;
    }
}
