/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.runtime.config.builders;

import static java.util.Arrays.asList;
import static org.apache.commons.io.FilenameUtils.getBaseName;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.mule.runtime.deployment.model.api.application.ApplicationDescriptor.DEFAULT_CONFIGURATION_RESOURCE;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.config.spring.internal.SpringXmlConfigurationBuilder;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.config.ConfigurationException;
import org.mule.runtime.core.api.config.MuleProperties;
import org.mule.runtime.core.api.context.MuleContextBuilder;
import org.mule.runtime.core.api.context.MuleContextFactory;
import org.mule.runtime.core.api.config.DefaultMuleConfiguration;
import org.mule.runtime.core.api.context.DefaultMuleContextBuilder;
import org.mule.runtime.core.api.context.DefaultMuleContextFactory;

import java.io.File;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;

/**
 * <code>MuleXmlBuilderContextListener</code> is a bootstrap listener used to construct a
 * {@link org.mule.runtime.core.api.MuleContext} instance. This listener delegates to the <i>MuleXmlConfigurationBuilder</i>.
 * <p>
 * The location of the configuration file can be specified in a init parameter called <i>org.mule.config</i>, the value can be a
 * path on the local file system or on the classpath. If a config parameter is not specified a default <i>mule-config.xml</i> will
 * be used.
 * </p>
 * 
 * @see SpringXmlConfigurationBuilder
 */

public class MuleXmlBuilderContextListener implements ServletContextListener {

  /**
   * One or more Mule config files.
   */
  public static final String INIT_PARAMETER_MULE_CONFIG = "org.mule.config";

  public static final String INIT_PARAMETER_MULE_APP_CONFIG = "org.mule.app.config";

  /**
   * Name of the temp dir param as per the servlet spec. The object will be a java.io.File.
   */
  public static final String ATTR_JAVAX_SERVLET_CONTEXT_TEMPDIR = "javax.servlet.context.tempdir";

  protected MuleContext muleContext;

  protected transient final Logger logger = LoggerFactory.getLogger(MuleXmlBuilderContextListener.class);

  @Override
  public void contextInitialized(ServletContextEvent event) {
    initialize(event.getServletContext());
  }

  public void initialize(ServletContext context) {
    String config = context.getInitParameter(INIT_PARAMETER_MULE_CONFIG);
    if (config == null) {
      config = getDefaultConfigResource();
      if (logger.isDebugEnabled()) {
        logger.debug("No Mule config file(s) specified, using default: " + config);
      }
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Mule config file(s): " + config);
      }
    }

    try {
      muleContext = createMuleContext(config, context);
      context.setAttribute(MuleProperties.MULE_CONTEXT_PROPERTY, muleContext);
      muleContext.start();
    } catch (MuleException ex) {
      context.log(ex.getMessage(), ex);
      // Logging is not configured OOTB for Tomcat, so we'd better make a
      // start-up failure plain to see.
      ex.printStackTrace();
    } catch (Error error) {
      // WSAD doesn't always report the java.lang.Error, log it
      context.log(error.getMessage(), error);
      // Logging is not configured OOTB for Tomcat, so we'd better make a
      // start-up failure plain to see.
      error.printStackTrace();
      throw error;
    }
  }

  /**
   * Creates the MuleContext based on the configuration resource(s) and possibly init parameters for the Servlet.
   */
  protected MuleContext createMuleContext(String configResource, ServletContext servletContext)
      throws ConfigurationException, InitialisationException {
    String serverId = defaultIfEmpty(servletContext.getInitParameter("mule.serverId"), null);

    // serverId will be used as a sub-folder in Mule working directory (.mule)

    if (serverId == null) {
      // guess this app's context name from the temp/work dir the container created us
      // Servlet 2.5 has servletContext.getContextPath(), but we can't force users to upgrade yet
      final File tempDir = (File) servletContext.getAttribute(ATTR_JAVAX_SERVLET_CONTEXT_TEMPDIR);
      final String contextName = getBaseName(tempDir.toString());
      serverId = contextName;
    }

    WebappMuleXmlConfigurationBuilder builder = new WebappMuleXmlConfigurationBuilder(servletContext, configResource);
    MuleContextFactory muleContextFactory = new DefaultMuleContextFactory();

    String muleAppConfig = servletContext.getInitParameter(INIT_PARAMETER_MULE_APP_CONFIG) != null
        ? servletContext.getInitParameter(INIT_PARAMETER_MULE_APP_CONFIG)
        : PropertiesMuleConfigurationFactory.getMuleAppConfiguration(configResource);

    DefaultMuleConfiguration muleConfiguration = new PropertiesMuleConfigurationFactory(muleAppConfig).createConfiguration();

    /*
     * We deliberately enable container mode here to allow for multi-tenant environment (multiple WARs embedding Mule instance
     * each). See property javadocs for more info.
     */
    muleConfiguration.setContainerMode(true);

    if (serverId != null) {
      muleConfiguration.setId(serverId);
    }
    MuleContextBuilder muleContextBuilder = new DefaultMuleContextBuilder();
    muleContextBuilder.setMuleConfiguration(muleConfiguration);

    // Support Spring-first configuration in webapps
    final ApplicationContext parentContext =
        (ApplicationContext) servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    if (parentContext != null) {
      builder.setParentContext(parentContext);
    }
    return muleContextFactory.createMuleContext(asList(new BasicRuntimeServicesConfigurationBuilder(), builder),
                                                muleContextBuilder);
  }

  /**
   * If no config location resource is configured on the servlet context, the value returned from this method will be used to
   * initialise the MuleManager.
   * 
   * @return the default config resource location
   */
  protected String getDefaultConfigResource() {
    return DEFAULT_CONFIGURATION_RESOURCE;
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    destroy();
  }

  public void destroy() {
    if (muleContext != null) {
      if (!muleContext.isDisposing() || !muleContext.isDisposed()) {
        muleContext.dispose();
      }
    }
  }
}
