/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008
 * 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.services.bootstrap.repository;

import java.io.File;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.util.ConfigurationConstants;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.plan.bindings.exceptions.InvalidRepositoryException;
import org.ow2.util.plan.bindings.repository.ExtendedRepository;
import org.ow2.util.plan.bindings.repository.Repositories;
import org.ow2.util.plan.bindings.repository.Repository;
import org.ow2.util.plan.bindings.repository.RepositoryKind;
import org.ow2.util.plan.deployer.api.IRepositoryDeployer;
import org.ow2.util.plan.fetcher.api.Constants;
import org.ow2.util.plan.reader.repository.IRepositoryDataReader;
import org.ow2.util.plan.repository.api.IRepositoryManager;
import org.ow2.util.plan.repository.api.RepositoryIdCollisionException;

/**
 * This component is part of the bootstrap module and registers initial
 * repositories that will be used by the deployment plan.
 * @author Mickaël LEDUQUE
 * @author Francois Fornaciari
 */
public class JOnASRepositories implements JOnASRepositoriesMBean {

    /**
     * The logger.
     */
    private Log logger = LogFactory.getLog(JOnASRepositories.class);

    /**
     * ServerProperties service object.
     */
    private ServerProperties serverProperties = null;

    /**
     * The repository manager.
     */
    private IRepositoryManager repositoryManager = null;

    /**
     * Reference to the {@link ConfigurationAdmin} service.
     */
    private ConfigurationAdmin configurationAdmin = null;

    /**
     * JMX server (MBean server) instance.
     */
    private MBeanServer mbeanServer = null;

    /**
     * The repository deployer.
     */
    private IRepositoryDeployer repositoryDeployer;

    /**
     * The repository file reader.
     */
    private IRepositoryDataReader repositoryReader = null;

    /**
     * User specified user repository property name.
     */
    private static final String PROP_M2_LOCAL_REPOSITORY = "m2.repository";

    /**
     * Path to default M2 repository.
     */
    private static final String PATH_DEFAULT_REPOSITORY = ".m2/repository";

    /**
     * Configuration file for initial repositories.
     */
    private static final String CONFIG_FILE = "initial-repositories.xml";

    /**
     * This callback method is called during the service starting as soon as all
     * service dependencies are resolved.
     */
    public void start() throws Exception {
        // Set the local repositories base directory into the configuration
        Configuration configuration = configurationAdmin.getConfiguration(Constants.CONFIG_PID, null);
        if (configuration != null) {
            Dictionary<String, String> props = new Hashtable<String, String>();
            props.put(Constants.LOCAL_REPOSITORIES_BASE_DIR, JProp.getJonasBase() + "/repositories");
            configuration.update(props);
        }

        // Add the local repositories
        addLocalRepositories();

        // Initial repositories file
        Repositories initialRepositories = null;
        File configFile = new File(JProp.getConfDir(), CONFIG_FILE);
        if (configFile.exists()) {
            try {
                initialRepositories = repositoryReader.readRepositories(configFile);
            } catch (Exception e) {
                logger.error("Exception while parsing the initial repositories file : {0}", e);
            }
        }

        // Add the initial repositories in the repositories manager
        if (initialRepositories != null) {
            int repositoryCount = 0;
            for (Repository repository : initialRepositories.getRepositories()) {
                try {
                    repositoryManager.addRepository(repository);
                    repositoryCount++;
                } catch (RepositoryIdCollisionException e) {
                    logger.warn("Multiple repository with same id; id={0}", repository.getId());
                } catch (InvalidRepositoryException e) {
                    logger.warn("Invalid repository with id {0}", repository.getId());
                }
            }
            logger.debug("JOnAS started with {0} repositories.", repositoryCount);
        } else {
            logger.debug("JOnAS started with no repositories");
        }

        try {
            ObjectName objectName = JonasObjectName.repository(serverProperties.getDomainName());
            mbeanServer.registerMBean(this, objectName);
        } catch (Exception e) {
            logger.error("MBean registration error, {0}", e);
        }
    }

    /**
     * This callback method is called during the service stopping. The
     * unregistration of one of the required service can trigger the method
     * invocation.
     */
    public void stop() throws Exception {
        // MBean unregistration
        if (mbeanServer != null) {
            try {
                ObjectName objectName = JonasObjectName.repository(serverProperties.getDomainName());
                mbeanServer.unregisterMBean(objectName);
            } catch (Exception e) {
                logger.error("MBean unregistration error, {0}", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public Integer getRepositoryDeployablesCount() {
        return this.repositoryDeployer.getDeployablesCount();
    }

    /**
     * {@inheritDoc}
     */
    public List<String> getRepositoriesDescriptions() {
        List<String> repositoriesDescriptions = new ArrayList<String>();
        for (Repository repository : this.repositoryManager) {
            repositoriesDescriptions.add(repository.toString());
        }
        return repositoriesDescriptions;
    }

    /**
     * {@inheritDoc}
     */
    public Integer getRepositoriesNumber() {
        return this.repositoryManager.getRepositoryCount();
    }

    /**
     * Set the server properties service.
     * @param serverProperties the serverProperties to set
     */
    public void setServerProperties(final ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    /**
     * Set the repository manager used by this service.
     * @param repositoryManager the new repository manager.
     */
    public void setRepositoryManager(final IRepositoryManager repositoryManager) {
        this.repositoryManager = repositoryManager;
    }

    /**
     * Set the repository deployer.
     * @param repositoryDeployer the new repository deployer.
     */
    public void setRepositoryDeployer(final IRepositoryDeployer repositoryDeployer) {
        this.repositoryDeployer = repositoryDeployer;
    }

    /**
     * Set the MBean Server.
     * @param mbeanServer the new JMX service.
     */
    public void setMBeanServer(final MBeanServer mbeanServer) {
        this.mbeanServer = mbeanServer;
    }

    /**
     * Set the repository reader.
     * @param repositoryReader the new repository reader.
     */
    public void setRepositoryReader(final IRepositoryDataReader repositoryReader) {
        this.repositoryReader = repositoryReader;
    }

    /**
     * @param configurationAdmin the configurationAdmin to set
     */
    public void setConfigurationAdmin(final ConfigurationAdmin configurationAdmin) {
        this.configurationAdmin = configurationAdmin;
    }

    /**
     * Add local repositories.
     */
    private void addLocalRepositories() {
        // True if the "jonas.developer" system property has been set
        boolean developer = Boolean.getBoolean(ConfigurationConstants.JONAS_DEVELOPER_PROP);

        // Registers maven2 repositories
        if (developer) {
            addRepository("Maven2 local repository", getMaven2RepositoryLocation(), RepositoryKind.MAVEN_2);
        } else {
            addRepository("Maven2 internal base repository", getMaven2InternalBaseRepository(), RepositoryKind.MAVEN_2);
            addRepository("Maven2 internal root repository", getMaven2InternalRootRepository(), RepositoryKind.MAVEN_2);
        }

        // Registers the url internal repository
        File base = new File(getURLInternalBaseRepositoryLocation());
        File root = new File(getURLInternalRootRepositoryLocation());
        if (!root.equals(base) && base.exists()) {
            addRepository("Base URL internal", base.getPath(), RepositoryKind.URL);
        }
        addRepository("Root URL internal", root.getPath(), RepositoryKind.URL);
    }

    /**
     * Add a new repository to the repository manager.
     * @param id Repository id
     * @param location Repository location
     * @param repositoryKind King of repository
     */
    private void addRepository(final String id, final String location, final RepositoryKind repositoryKind) {
        Repository repository = new ExtendedRepository();
        repository.setType(repositoryKind);
        repository.setId(id);

        if (location.startsWith("/")) {
            repository.setUrl("file:" + location);
        } else {
            repository.setUrl("file:/" + location);
        }

        try {
            repositoryManager.addRepository(repository);
        } catch (RepositoryIdCollisionException e) {
            logger.error("Cannot add repository {0}, it already exists", repository);
        } catch (InvalidRepositoryException e) {
            logger.error("Invalid repository {0}", repository);
        }
    }

    /**
     * @return Location pointing to $JONAS_ROOT/repositories/maven2-internal
     */
    private String getMaven2InternalRootRepository() {
        return JProp.getRepositoriesRootDir() + File.separator + "maven2-internal";
    }

    /**
     * @return Location pointing to $JONAS_BASE/repositories/maven2-internal
     */
    private String getMaven2InternalBaseRepository() {
        return JProp.getRepositoriesBaseDir() + File.separator + "maven2-internal";
    }

    /**
     * @return Location pointing to $JONAS_ROOT/repositories/url-internal
     */
    private String getURLInternalRootRepositoryLocation() {
        return JProp.getRepositoriesRootDir() + File.separator + "url-internal";
    }

    /**
     * @return Location pointing to $JONAS_BASE/repositories/url-internal
     */
    private String getURLInternalBaseRepositoryLocation() {
        return JProp.getRepositoriesBaseDir() + File.separator + "url-internal";
    }

    /**
     * Get the local maven2 repository location
     * @return a {@link String} to a usable Maven2 repository.
     */
    private String getMaven2RepositoryLocation() {
        String m2Repository = System.getProperty(PROP_M2_LOCAL_REPOSITORY);

        // Priority to a user defined repository
        if (m2Repository == null) {
            // Nothing, so try a reasonable default
            // Assume that local Maven2 repository is in ~/.m2/repository
            String userHome = System.getProperty("user.home");
            m2Repository = userHome + File.separator + PATH_DEFAULT_REPOSITORY;
        }

        // Return the detected repository
        return m2Repository;
    }
}
