/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.multitenant.internal;

import javax.servlet.Filter;

import org.ow2.easybeans.persistence.api.EZBPersistenceUnitManager;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.tenant.TenantIdInfo;
import org.ow2.jonas.lib.tenant.filter.HttpTenantIdFilter;
import org.ow2.jonas.lib.tenant.context.TenantContext;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.multitenant.MultitenantService;
import org.ow2.jonas.registry.RegistryService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployable.IDeployableInfo;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import java.lang.reflect.InvocationTargetException;


/**
 * Implements the multitenant service.
 * @see org.ow2.jonas.multitenant.MultitenantService
 *
 * @author Mohammed Boukada
 */
public class MultitenantServiceImpl extends AbsServiceImpl implements MultitenantService {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(MultitenantServiceImpl.class);

    /**
     * The JMX server.
     */
    private static JmxService jmxService = null;

    /**
     * Registry service.
     */
    private static RegistryService registryService = null;

    /**
     * Whether multitenant is enabled.
     */
    private boolean multitenantEnabled = true;

    /**
     * Default deployment policy to use when deploying a new version of a
     * versioned application, can be changed via JMX.
     */
    private String defaultPolicy = "Reserved";
    
    /**
     * @return Default policy, as set via JMX.
     */
    public String getDefaultDeploymentPolicy() {
        return defaultPolicy;
    }

    /**
     * @return Whether multitenant is started and enabled.
     */
    public boolean isMultitenantEnabled() {
        return isStarted() && multitenantEnabled;
    }

    /**
     * @param jmxService JMX service to set.
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param registryService Registry service to set.
     */
    public void setRegistryService(final RegistryService registryService) {
        this.registryService = registryService;
    }

    @Override
    protected void doStart() throws ServiceException {
        MultitenantVirtualJNDIBinding.initialize(jmxService, registryService, this);

        // Load mbeans-descriptor.xml into the modeler registry
        // the meta-data located in META-INF/mbeans-descriptors.xml
        jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        try {
            jmxService.registerModelMBean(this, JonasObjectName.multitenant(getDomainName()));
        } catch (Exception e) {
            throw new ServiceException("Cannot register 'multitenant' service MBean", e);
        }
        logger.info("Multitenant service management bean has been registered successfully");
    }

    @Override
    protected void doStop() throws ServiceException {
        jmxService.unregisterMBean(JonasObjectName.multitenant(getDomainName()));
        logger.info("Multitenant service management bean has been unregistered successfully");
    }

    /**
     * Creates an instance of a TenantId HTTP Filter
     * @param  tenantId the tenant identifier
     * @return an instance of a TenantId HTTP Filter
     */
    public Filter getTenantIdFilter(String tenantId){
        return new HttpTenantIdFilter(tenantId);
    }

    /**
     * Returns the default tenantId value
     * @return the default tenantId value
     */
    public String getDefaultTenantID(){
        return TenantContext.DEFAULT_TENANT_ID;
    }

    /**
     * Creates JNDI binding management beans for a given tenant identifier.
     * @param deployable JAR, WAR or EAR object.
     * @param tenantId tenant identifier of the application which will prefix JNDI names.
     */
    public void createJNDIBindingMBeans(final IDeployable<?> deployable, final String tenantId) {

        if (tenantId.equals(getDefaultTenantID())) {
            logger.warn("This application has not a specific tenant identifier. Default tenant identifier is used");
        }

        String applicationName = getOriginalDeployable(deployable).getModuleName();
        String prefix = tenantId;

        MultitenantVirtualJNDIBinding.createJNDIBindingMBeans(applicationName, prefix);
    }

    /**
     * @param deployable Deployable to get the original deployable from
     *        (recursive).
     * @return Original deployable.
     */
    private IDeployable<?> getOriginalDeployable(final IDeployable<?> deployable) {
        IDeployable<?> originalDeployable = deployable;
        while (originalDeployable.getOriginalDeployable() != null) {
            originalDeployable = originalDeployable.getOriginalDeployable();
        }
        return originalDeployable;
    }

    /**
     * Removes JNDI binding management beans that are not in the JNDI directory
     * anymore.
     */
    public void garbageCollectJNDIBindingMBeans() {
        MultitenantVirtualJNDIBinding.garbageCollectJNDIBindingMBeans();
    }

    /**
     * Add eclipselink properties
     * @param persistenceUnitManager persistence unit manager
     * @param tenantId tenant identifier
     */
    public void updatePersistenceUnitManager(EZBPersistenceUnitManager persistenceUnitManager, String tenantId){
        // This property will configure entities as multitenant
        // It is the equivalent of @Multitenant
        String sessionCustomizerProperty = "eclipselink.session.customizer";
        String sessionCustomizerClass = "org.ow2.easybeans.persistence.eclipselink.MultitenantEntitiesSessionCustomizer";
        persistenceUnitManager.addProperty(sessionCustomizerProperty, sessionCustomizerClass);

        // This property will propagate the tenantId to eclipselink
        // This value will be added to entities tables
        String tenantIdProperty = "eclipselink.tenant-id";
        String tenantIdValue = tenantId;
        persistenceUnitManager.addProperty(tenantIdProperty, tenantIdValue);

        logger.debug("Tenant id ''{0}'' was propagate to eclipselink", tenantId);
    }

    /**
     * Add tenantId as extension to the earDeployable
     * @param deployable application deployable
     * @param tenantId tenant identifier to add
     */
    public void addTenantIdDeployableInfo(IDeployable deployable, String tenantId) {
        TenantIdInfo tenantIdInfo = new TenantIdInfo();
        tenantIdInfo.setTenantIdInfo(tenantId);
        deployable.addExtension(tenantIdInfo);
    }

    /**
     * Gets tenantIdInfo stored in the deployable
     * @param deployable application deployable
     * @return tenantIdInfo
     */
    public String getTenantIdDeployableInfo(IDeployable deployable) {
        if (deployable == null ) {
            logger.debug("Deployable is null");
            return null;
        }

        IDeployableInfo tenantIdInfo = deployable.getExtension(TenantIdInfo.class);
        String tenantId = null;
        try {
            if (tenantIdInfo != null){
                tenantId = (String) tenantIdInfo.getClass().getDeclaredMethod("getTenantIdInfo").invoke(tenantIdInfo);
            } else {
                logger.debug("TenantIdInfo was not found in ''{0}''", deployable);
            }
        } catch (NoSuchMethodException e) {
            throw new ServiceException("Method was not found", e);
        } catch (InvocationTargetException e) {
            throw new ServiceException("Invocation method fail ",e);
        } catch (IllegalAccessException e) {
            throw new ServiceException("Method access fail", e);
        }
        return tenantId;
    }
}
