/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.services.organization.idm;

import javax.transaction.Status;

import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ObjectParameter;
import org.exoplatform.services.organization.BaseOrganizationService;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.common.transaction.JTAUserTransactionLifecycleService;
import org.picketlink.idm.api.Transaction;
import org.picocontainer.Startable;

/*
 * @author <a href="mailto:boleslaw.dawidowicz at redhat.com">Boleslaw Dawidowicz</a>
 */
public class PicketLinkIDMOrganizationServiceImpl extends BaseOrganizationService implements Startable,
        ComponentRequestLifecycle {

    // We may have several portal containers thus we need one PicketLinkIDMService per portal container
    // private static PicketLinkIDMService jbidmService_;
    private PicketLinkIDMServiceImpl idmService_;

    public static final String CONFIGURATION_OPTION = "configuration";

    private Config configuration = new Config();

    private JTAUserTransactionLifecycleService jtaTransactionLifecycleService;

    private static final Logger log = LoggerFactory.getLogger(PicketLinkIDMOrganizationServiceImpl.class);
    private static final boolean traceLoggingEnabled = log.isTraceEnabled();

    // Indicates whether any call to startRequest and endRequest is accepted
    private volatile boolean acceptComponentRequestCall;

    public PicketLinkIDMOrganizationServiceImpl(InitParams params, PicketLinkIDMService idmService,
            JTAUserTransactionLifecycleService jtaTransactionLifecycleService) throws Exception {
        groupDAO_ = new GroupDAOImpl(this, idmService);
        userDAO_ = new UserDAOImpl(this, idmService);
        userProfileDAO_ = new UserProfileDAOImpl(this, idmService);
        membershipDAO_ = new MembershipDAOImpl(this, idmService);
        membershipTypeDAO_ = new MembershipTypeDAOImpl(this, idmService);

        idmService_ = (PicketLinkIDMServiceImpl) idmService;

        this.jtaTransactionLifecycleService = jtaTransactionLifecycleService;

        if (params != null) {
            // Options
            ObjectParameter configurationParam = params.getObjectParam(CONFIGURATION_OPTION);

            if (configurationParam != null) {
                this.configuration = (Config) configurationParam.getObject();
            }

        }

    }

    public final org.picketlink.idm.api.Group getJBIDMGroup(String groupId) throws Exception {
        String[] ids = groupId.split("/");
        String name = ids[ids.length - 1];
        String parentId = null;
        if (groupId.contains("/")) {
            parentId = groupId.substring(0, groupId.lastIndexOf("/"));
        }

        String plGroupName = configuration.getPLIDMGroupName(name);

        return idmService_.getIdentitySession().getPersistenceManager()
                .findGroup(plGroupName, getConfiguration().getGroupType(parentId));
    }

    @Override
    public void start() {
        try {
            if (configuration.isUseJTA()) {
                jtaTransactionLifecycleService.registerListener(new IDMTransactionSyncListener(idmService_));
            }
            acceptComponentRequestCall = true;

            RequestLifeCycle.begin(this);

            super.start();

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            RequestLifeCycle.end();
        }

    }

    @Override
    public void stop() {
        // toto
    }

    /*
   */
    /**
     * Used to allow nested requests (as done by the authenticator during unit tests) and avoid to commit two times the same
     * transaction.
     */
    /*
     *
     * private ThreadLocal<AtomicInteger> currentRequestCount = new ThreadLocal<AtomicInteger>() {
     *
     * @Override protected AtomicInteger initialValue() { return new AtomicInteger(); } };
     */

    public void startRequest(ExoContainer container) {
        if (!acceptComponentRequestCall)
            return;
        try {
            if (configuration.isUseJTA()) {
                if (traceLoggingEnabled) {
                    log.trace("Starting UserTransaction in method startRequest");
                }
                jtaTransactionLifecycleService.beginJTATransaction();
            } else {

                if (!idmService_.getIdentitySession().getTransaction().isActive()) {
                    idmService_.getIdentitySession().beginTransaction();
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public void flush() {
        if (configuration.isUseJTA()) {
            if (traceLoggingEnabled) {
                log.trace("Flushing UserTransaction in method flush");
            }
            // Complete restart of JTA transaction don't have good performance. So we will only sync identitySession (same
            // as for non-jta environment)
            // finishJTATransaction();
            // beginJTATransaction();
            try {
                if (jtaTransactionLifecycleService.getUserTransaction().getStatus() == Status.STATUS_ACTIVE) {
                    idmService_.getIdentitySession().save();
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        } else {
            try {
                if (idmService_.getIdentitySession().getTransaction().isActive()) {
                    idmService_.getIdentitySession().save();
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                recoverFromIDMError(e);
            }
        }
    }

    public void endRequest(ExoContainer container) {
        if (!acceptComponentRequestCall)
            return;
        if (configuration.isUseJTA()) {
            if (traceLoggingEnabled) {
                log.trace("Finishing UserTransaction in method endRequest");
            }
            try {
                jtaTransactionLifecycleService.finishJTATransaction();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        } else {
            try {
                if (idmService_.getIdentitySession().getTransaction().isActive()) {
                    idmService_.getIdentitySession().getTransaction().commit();
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                recoverFromIDMError(e);
            }
        }
    }

    // Should be used only for non-JTA environment
    public void recoverFromIDMError(Exception e) {
        try {
            // We need to restart Hibernate transaction if it's available. First rollback old one and then start new one
            Transaction idmTransaction = idmService_.getIdentitySession().getTransaction();
            if (idmTransaction.isActive()) {
                idmTransaction.rollback();
                idmTransaction.start();
                log.info("IDM error recovery finished. Old transaction has been rolled-back and new transaction has been started");
            }
        } catch (Exception e1) {
            log.warn("Error during recovery of old error", e1);
        }
    }

    public Config getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Config configuration) {
        this.configuration = configuration;
    }

}
