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

import java.util.LinkedList;
import java.util.List;

import jakarta.transaction.NotSupportedException;
import jakarta.transaction.SystemException;
import jakarta.transaction.TransactionManager;

import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.InvalidStateException;

/**
 * Implementation of the {@link ManagedRuntime} interface that searches
 * through a set of known JNDI locations and method invocations to locate the
 * appropriate mechanism for obtaining a TransactionManager.
 *  Built in support is provided for the following Application Servers:
 * <ul>
 * <li>Bluestone</li>
 * <li>GlassFish</li>
 * <li>HP Application Server</li>
 * <li>JBoss</li>
 * <li>JRun</li>
 * <li>OpenEJB</li>
 * <li>Oracle Application Server</li>
 * <li>Orion</li>
 * <li>SunONE</li>
 * <li>Weblogic</li>
 * <li>Websphere</li>
 * </ul>
 *
 * @author Marc Prud'hommeaux
 */
public class AutomaticManagedRuntime extends AbstractManagedRuntime
    implements ManagedRuntime, Configurable {

    private static final String [] JNDI_LOCS = new String []{
        "jakarta.transaction.TransactionManager", // weblogic
        "java:/TransactionManager", // jboss, jrun, Geronimo
        "java:/DefaultDomain/TransactionManager", // jrun too
        "java:comp/pm/TransactionManager", // orion & oracle
        "java:comp/TransactionManager", // generic
        "java:appserver/TransactionManager", // GlassFish
        "java:pm/TransactionManager", // borland
        "aries:services/jakarta.transaction.TransactionManager", // Apache Aries
    };
    private static final String [] METHODS = new String[]{
        "com.arjuna.jta.JTA_TransactionManager.transactionManager", // hp
        "com.bluestone.jta.SaTransactionManagerFactory.SaGetTransactionManager",
        "org.openejb.OpenEJB.getTransactionManager",
        "com.sun.jts.jta.TransactionManagerImpl.getTransactionManagerImpl",
        "com.inprise.visitransact.jta.TransactionManagerImpl."
            + "getTransactionManagerImpl", // borland
    };

    private final ManagedRuntime REGISTRY;
    private final WLSManagedRuntime WLS;
    private final SunOneManagedRuntime SUNONE;
    private final WASManagedRuntime WAS;
    private final WASRegistryManagedRuntime WAS_REG;

    private static Localizer _loc = Localizer.forPackage
        (AutomaticManagedRuntime.class);

    public AutomaticManagedRuntime() {
        ManagedRuntime mr = null;

        mr = null;
        try {
            mr = (ManagedRuntime) Class.
                forName("org.apache.openjpa.ee.RegistryManagedRuntime").
                    newInstance();
        } catch (Throwable t) {
            // might be JTA version lower than 1.1, which doesn't have
            // TransactionSynchronizationRegistry
        }
        REGISTRY = mr;

        mr = null;
        try {
            mr = new WLSManagedRuntime();
        } catch (Throwable t) {
        }
        WLS = (WLSManagedRuntime) mr;

        mr = null;
        try {
            mr = new SunOneManagedRuntime();
        } catch (Throwable t) {
        }
        SUNONE = (SunOneManagedRuntime) mr;

        mr = null;
        try {
            mr = new WASManagedRuntime();
        } catch (Throwable t) {
        }
        WAS = (WASManagedRuntime) mr;

        mr = null;
        try {
            // In a WebSphere environment the thread's current classloader might
            // not have access to the WebSphere APIs. However the "runtime"
            // classloader will have access to them.

            // Should not need a doPriv getting this class' classloader
            ClassLoader cl = AutomaticManagedRuntime.class.getClassLoader();

            Class<WASRegistryManagedRuntime> mrClass =
                (Class<WASRegistryManagedRuntime>) J2DoPrivHelper
                        .getForNameAction(
                                WASRegistryManagedRuntime.class.getName(),
                                true, cl).run();
            mr = J2DoPrivHelper.newInstanceAction(mrClass).run();
        } catch (Throwable t) {
            // safe to ignore
        }
        WAS_REG = (WASRegistryManagedRuntime) mr;
    }

    private Configuration _conf = null;
    private ManagedRuntime _runtime = null;

    @Override
    public TransactionManager getTransactionManager()
        throws Exception {
        if (_runtime != null)
            return _runtime.getTransactionManager();

        List<Throwable> errors = new LinkedList<>();
        TransactionManager tm = null;

        // Try the registry extensions first so that any applicable vendor
        // specific extensions are used.
        if (WAS_REG != null) {
            try {
                tm = WAS_REG.getTransactionManager();
            } catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = WAS_REG;
                return tm;
            }
        }

        // Then try the registry, which is the official way to obtain
        // transaction synchronication in JTA 1.1
        if (REGISTRY != null) {
            try {
                tm = REGISTRY.getTransactionManager();
            } catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = REGISTRY;
                return tm;
            }
        }

        if (WLS != null) {
            try {
                tm = WLS.getTransactionManager();
            } catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = WLS;
                return tm;
            }
        }

        if (WAS != null) {
            try {
                WAS.setConfiguration(_conf);
                WAS.startConfiguration();
                WAS.endConfiguration();
                tm = WAS.getTransactionManager();
            } catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = WAS;
                return tm;
            }
        }

        // try to find a jndi runtime
        JNDIManagedRuntime jmr = new JNDIManagedRuntime();
        for (String jndiLoc : JNDI_LOCS) {
            jmr.setTransactionManagerName(jndiLoc);
            try {
                tm = jmr.getTransactionManager();
            }
            catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = jmr;
                return tm;
            }
        }

        // look for a method runtime
        InvocationManagedRuntime imr = new InvocationManagedRuntime();
        for (String method : METHODS) {
            imr.setConfiguration(_conf);
            imr.setTransactionManagerMethod(method);
            try {
                tm = imr.getTransactionManager();
            }
            catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = imr;
                return tm;
            }
        }

        if (SUNONE != null) {
            try {
                tm = SUNONE.getTransactionManager();
            } catch (Throwable t) {
                errors.add(t);
            }
            if (tm != null) {
                _runtime = SUNONE;
                return tm;
            }
        }

        Throwable[] t = (Throwable []) errors.toArray(
            new Throwable [errors.size()]);
        throw new InvalidStateException(_loc.get("tm-not-found")).
            setFatal(true).setNestedThrowables(t);
    }

    @Override
    public void setConfiguration(Configuration conf) {
        _conf = conf;
    }

    @Override
    public void startConfiguration() {
    }

    @Override
    public void endConfiguration() {
    }

    @Override
    public void setRollbackOnly(Throwable cause)
        throws Exception {
        // check to see if the runtime is cached
        if (_runtime == null)
            getTransactionManager();

        if (_runtime != null)
            _runtime.setRollbackOnly(cause);
    }

    @Override
    public Throwable getRollbackCause()
        throws Exception {
        // check to see if the runtime is cached
        if (_runtime == null)
            getTransactionManager();

        if (_runtime != null)
            return _runtime.getRollbackCause();

        return null;
    }

    @Override
    public Object getTransactionKey() throws Exception, SystemException {
        if(_runtime == null)
            getTransactionManager();

        if(_runtime != null )
            return _runtime.getTransactionKey();

        return null;
    }

    /**
     * Delegate nonTransactional work to the appropriate managed runtime. If no
     * managed runtime is found then delegate {@link AbstractManagedRuntime}.
     */
    @Override
    public void doNonTransactionalWork(Runnable runnable)
            throws NotSupportedException {
        // Obtain a transaction manager to initialize the runtime.
        try {
            getTransactionManager();
        } catch (Exception e) {
            NotSupportedException nse =
                new NotSupportedException(_loc
                        .get("tm-unavailable", _runtime).getMessage());
            nse.initCause(e);
            throw nse;
        }
        _runtime.doNonTransactionalWork(runnable);
    }
}
