/*
 * 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 jakarta.transaction.TransactionManager;

import org.apache.openjpa.util.InternalException;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;

/**
 * <p>Implementation of the {@link ManagedRuntime} interface that listens
 * for an OSGi service with interface <code>jakarta.transaction.TransactionManager</code>
 * to create a {@link TransactionManager} for controlling transactions.</p>
 *
 * <p>The support for a transaction manager is usually activated inside an OSGi
 * {@link BundleActivator} implementation using {@link #registerServiceListener(BundleContext)}
 * and {@link #deregisterServiceListener(BundleContext)}.
 * </p>
 */
public class OSGiManagedRuntime extends AbstractManagedRuntime {

    /**
     * a static instance, which is managed by the ServiceListener added to the bundle
     * context by {@link #registerServiceListener(BundleContext)}.
     */
    private static TransactionManager transactionManager;
    private static ServiceReference serviceReference;
    private static ServiceListener listener;

    private static final class Listener implements ServiceListener {

        final BundleContext bundleContext;
        // avoid the garbage collection of the OSGiManagedRuntime class itself,
        // by holding a reference to it.
        final Class<OSGiManagedRuntime> clazzRef;

        public Listener(BundleContext bundleContext) {
            this.bundleContext = bundleContext;
            this.clazzRef = OSGiManagedRuntime.class;
        }

        @Override
        public void serviceChanged(ServiceEvent event) {

            synchronized (this.clazzRef) {

                switch (event.getType()) {

                    case ServiceEvent.REGISTERED:
                        OSGiManagedRuntime.serviceReference = event.getServiceReference();
                        OSGiManagedRuntime.transactionManager =
                            (TransactionManager) this.bundleContext.getService(OSGiManagedRuntime.serviceReference);
                        break;

                    case ServiceEvent.UNREGISTERING:
                        OSGiManagedRuntime.transactionManager = null;
                        OSGiManagedRuntime.serviceReference = null;
                        this.bundleContext.ungetService(event.getServiceReference());
                        break;
                }
            }
        }
    }

    /**
     * <p>Register a service listener to the given bundle context by
     * {@link BundleContext#addServiceListener(ServiceListener,String)} with a filter
     *  expression of <code>(objectClass=jakarta.transaction.TransactionManager)</code>.</p>
     *
     *  <p>The caller is responsible for calling
     *  {@link #deregisterServiceListener(BundleContext)}, when
     *  the bundle context is being stopped.</p>
     *
     * @param bundle The bundle, which is currently being started.
     * @throws InvalidSyntaxException When the filter expression is invalid.
     */
    public static synchronized void registerServiceListener(BundleContext bundleContext)
    throws InvalidSyntaxException {

        if (listener != null) {
            throw new InternalException("Another OSGi service listener has already been registered.");
        }

        listener = new Listener(bundleContext);

        bundleContext.addServiceListener(listener, "(" + Constants.OBJECTCLASS
            + "=jakarta.transaction.TransactionManager)");

        serviceReference = bundleContext.getServiceReference("jakarta.transaction.TransactionManager");

        if (serviceReference != null) {
            transactionManager = (TransactionManager)bundleContext.getService(serviceReference);
        }
    }

    /**
     * Remove a service listener previously started inside {@link #registerServiceListener(BundleContext)}.
     *
     * @param bundleContext The bundle context to call
     *          {@link BundleContext#removeServiceListener(ServiceListener)} on.
     */
    public static synchronized void deregisterServiceListener(BundleContext bundleContext) {

        try {
            if (serviceReference != null) {
                bundleContext.ungetService(serviceReference);
                transactionManager = null;
                serviceReference = null;
            }
        }
        finally {
            // assure, that the service listener is removed, whatever happens above.
            if (listener != null) {
                bundleContext.removeServiceListener(listener);
                listener = null;
            }
        }
    }

    /* (non-Javadoc)
     * @see org.apache.openjpa.ee.ManagedRuntime#getTransactionManager()
     */
    @Override
    public TransactionManager getTransactionManager() throws Exception {
        synchronized (OSGiManagedRuntime.class) {

            if (transactionManager == null) {
                throw new InternalException("No jakarta.transaction.TransactionManager " +
                		"service is currently registered as an OSGi service.");
            }

            return transactionManager;
        }
    }

    /* (non-Javadoc)
     *
     * @see org.apache.openjpa.ee.ManagedRuntime#setRollbackOnly(java.lang.Throwable)
     */
    @Override
    public void setRollbackOnly(Throwable cause) throws Exception {

        // there is no generic support for setting the rollback cause
        getTransactionManager().getTransaction().setRollbackOnly();
    }

    /* (non-Javadoc)
     * @see org.apache.openjpa.ee.ManagedRuntime#getRollbackCause()
     */
    @Override
    public Throwable getRollbackCause() throws Exception {
        // there is no generic support for setting the rollback cause
        return null;
    }

}
