/*
 * 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.container.util;

import org.exoplatform.commons.utils.ClassLoading;
import org.exoplatform.commons.utils.PropertiesLoader;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.commons.utils.Tools;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.multitenancy.bridge.TenantsContainerContext;
import org.exoplatform.container.xml.Component;
import org.exoplatform.container.xml.ComponentLifecyclePlugin;
import org.exoplatform.container.xml.ContainerLifecyclePlugin;
import org.exoplatform.container.xml.Deserializer;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.picocontainer.defaults.ConstructorInjectionComponentAdapter;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Tuan Nguyen (tuan08@users.sourceforge.net)
 * @since Oct 28, 2004
 * @version $Id: ContainerUtil.java 9894 2006-10-31 02:52:41Z tuan08 $
 */
@SuppressWarnings("unchecked")
public class ContainerUtil
{
   /** The logger. */
   private static final Log LOG = ExoLogger.getExoLogger(ContainerUtil.class);

   static public Constructor<?>[] getSortedConstructors(Class<?> clazz) throws NoClassDefFoundError
   {
      Constructor<?>[] constructors = clazz.getConstructors();
      for (int i = 0; i < constructors.length; i++)
      {
         for (int j = i + 1; j < constructors.length; j++)
         {
            if (constructors[i].getParameterTypes().length < constructors[j].getParameterTypes().length)
            {
               Constructor<?> tmp = constructors[i];
               constructors[i] = constructors[j];
               constructors[j] = tmp;
            }
         }
      }
      return constructors;
   }

   static public Collection<URL> getConfigurationURL(final String configuration) throws Exception
   {
      final ClassLoader cl = Thread.currentThread().getContextClassLoader();

      Collection c = SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Collection>()
      {
         public Collection run() throws IOException
         {
            return Collections.list(cl.getResources(configuration));
         }
      });

      Map<String, URL> map = new HashMap<String, URL>();
      Iterator i = c.iterator();
      String forbiddenSuffix = "WEB-INF/" + configuration;
      while (i.hasNext())
      {
         URL url = (URL)i.next();
         String key = url.toString();
         // The content of the WEB-INF folder is part of the CL in JBoss AS 6 so 
         // we have to get rid of it in order to prevent any boot issues
         if (key.endsWith(forbiddenSuffix))
         {
            continue;
         }
         // jboss bug, jboss has a very weird behavior. It copy all the jar files
         // and
         // deploy them to a temp dir and include both jars, the one in sar and tmp
         // dir,
         // in the class path. It cause the configuration run twice
         int index1 = key.lastIndexOf("exo-");
         int index2 = key.lastIndexOf("exo.");
         int index = index1 < index2 ? index2 : index1;
         if (index >= 0)
            key = key.substring(index);
         map.put(key, url);
      }

      i = map.values().iterator();
      // while(i.hasNext()) {
      // URL url = (URL) i.next() ;
      // System.out.println("==> Add " + url);
      // }
      return map.values();
   }

   static public void addContainerLifecyclePlugin(ExoContainer container, ConfigurationManager conf)
   {
      Iterator i = conf.getConfiguration().getContainerLifecyclePluginIterator();
      while (i.hasNext())
      {
         ContainerLifecyclePlugin plugin = (ContainerLifecyclePlugin)i.next();
         addContainerLifecyclePlugin(container, plugin);
      }
   }
      
   private static void addContainerLifecyclePlugin(ExoContainer container, ContainerLifecyclePlugin plugin)
   {
      try
      {
         Class<?> clazz = ClassLoading.forName(plugin.getType(), ContainerUtil.class);
         org.exoplatform.container.ContainerLifecyclePlugin cplugin =
            (org.exoplatform.container.ContainerLifecyclePlugin)container
               .createComponent(clazz, plugin.getInitParams());
         cplugin.setName(plugin.getName());
         cplugin.setDescription(plugin.getDescription());
         container.addContainerLifecylePlugin(cplugin);
      }
      catch (Exception ex)
      {
         LOG.error("Failed to instanciate plugin " + plugin.getType() + ": " + ex.getMessage(), ex);
      }
   }

   public static void addComponentLifecyclePlugin(ExoContainer container, ConfigurationManager conf)
   {
      Collection plugins = conf.getConfiguration().getComponentLifecyclePlugins();
      Iterator i = plugins.iterator();
      while (i.hasNext())
      {
         ComponentLifecyclePlugin plugin = (ComponentLifecyclePlugin)i.next();
         try
         {
            Class<?> classType = ClassLoading.loadClass(plugin.getType(), ContainerUtil.class);
            org.exoplatform.container.component.ComponentLifecyclePlugin instance =
               (org.exoplatform.container.component.ComponentLifecyclePlugin)classType.newInstance();
            container.addComponentLifecylePlugin(instance);
         }
         catch (Exception ex)
         {
            LOG.error("Failed to instanciate plugin " + plugin.getType() + ": " + ex.getMessage(), ex);
         }
      }
   }

   public static void addComponents(ExoContainer container, ConfigurationManager conf)
   {
      Collection components = conf.getComponents();
      if (components == null)
         return;
      Iterator i = components.iterator();
      while (i.hasNext())
      {
         Component component = (Component)i.next();
         String type = component.getType();
         String key = component.getKey();
         try
         {
            Class<?> classType = ClassLoading.loadClass(type, ContainerUtil.class);
            if (key == null)
            {
               if (component.isMultiInstance())
               {
                  container.registerComponent(new ConstructorInjectionComponentAdapter(classType, classType));
                  LOG.debug("===>>> Thread local component " + classType.getName() + " registered.");
               }
               else
               {
                  container.registerComponentImplementation(classType);
               }
            }
            else
            {
               try
               {
                  Class<?> keyType = ClassLoading.loadClass(key, ContainerUtil.class);
                  if (component.isMultiInstance())
                  {
                     container.registerComponent(new ConstructorInjectionComponentAdapter(keyType, classType));
                     LOG.debug("===>>> Thread local component " + classType.getName() + " registered.");
                  }
                  else
                  {
                     container.registerComponentImplementation(keyType, classType);
                  }
               }
               catch (Exception ex)
               {
                  container.registerComponentImplementation(key, classType);
               }
            }
         }
         catch (ClassNotFoundException ex)
         {
            LOG.error("Cannot register the component corresponding to key = '" + key + "' and type = '" + type + "'", ex);
         }
      }
   }

   public static TenantsContainerContext createTenantsContext(ExoContainer container, ConfigurationManager conf)
   {
      try
      {
         Component component = conf.getComponent(TenantsContainerContext.class.getName());
         if (component != null)
         {
            String key = component.getKey();
            String type = component.getType();
            InitParams params = component.getInitParams();
            try
            {
               Class<?> typeClass = ClassLoading.loadClass(type, ContainerUtil.class);
               Constructor<TenantsContainerContext> constructor =
                  (Constructor<TenantsContainerContext>)typeClass.getConstructor(ExoContainer.class, InitParams.class);
               return constructor.newInstance(container, params);
            }
            catch (ClassNotFoundException e)
            {
               LOG.error("Cannot register the component with key '" + key + "' and type '" + type + "'", e);
            }
            catch (InstantiationException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "'", e);
            }
            catch (IllegalAccessException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "'", e);
            }
            catch (SecurityException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "'", e);
            }
            catch (NoSuchMethodException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "' constructor with parameter '"
                  + ExoContainer.class.getName() + "' not found", e);
            }
            catch (IllegalArgumentException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "'", e);
            }
            catch (InvocationTargetException e)
            {
               LOG.error("Cannot instantiate new instance of '" + type + "'", e);
            }
         }
      }
      catch (Exception e)
      {
         LOG.error("Error getting TenantContainerContext component", e);
      }
      finally
      {
         // finally remove context from configurations
         conf.getConfiguration().removeConfiguration(TenantsContainerContext.class.getName());
      }
      return null;
   }

   /**
    * Loads the properties file corresponding to the given url
    * @param url the url of the properties file
    * @return a {@link Map} of properties
    */
   public static Map<String, String> loadProperties(URL url)
   {
      return loadProperties(url, true);
   }

   /**
    * Loads the properties file corresponding to the given url
    * @param url the url of the properties file
    * @param resolveVariables indicates if the variables must be resolved
    * @return a {@link Map} of properties
    */
   public static Map<String, String> loadProperties(URL url, boolean resolveVariables)
   {
      LinkedHashMap<String, String> props = null;
      String path = null;
      InputStream in = null;
      try
      {
         //
         if (url != null)
         {
            path = url.getPath();
            in = url.openStream();
         }

         //
         if (in != null)
         {
            String fileName = url.getFile();
            if (Tools.endsWithIgnoreCase(path, ".properties"))
            {
               if (LOG.isDebugEnabled())
                  LOG.debug("Attempt to load property file " + path);
               props = PropertiesLoader.load(in);
            }
            else if (Tools.endsWithIgnoreCase(fileName, ".xml"))
            {
               if (LOG.isDebugEnabled())
                  LOG.debug("Attempt to load property file " + path + " with XML format");
               props = PropertiesLoader.loadFromXML(in);
            }
            else if (LOG.isDebugEnabled())
            {
               LOG.debug("Will not load property file" + path + " because its format is not recognized");
            }
            if (props != null && resolveVariables)
            {
               // Those properties are used for variables resolution
               final Map<String, Object> currentProps = new HashMap<String, Object>();
               for (Map.Entry<String, String> entry : props.entrySet())
               {
                  String propertyName = entry.getKey();
                  String propertyValue = entry.getValue();
                  propertyValue = Deserializer.resolveVariables(propertyValue, currentProps);
                  props.put(propertyName, propertyValue);
                  currentProps.put(propertyName, propertyValue);
               }
            }
         }
         else
         {
            LOG.error("Could not load property file " + path);
         }
      }
      catch (Exception e)
      {
         LOG.error("Cannot load property file " + path, e);
      }
      finally
      {
         if (in != null)
         {
            try
            {
               in.close();
            }
            catch (IOException ignore)
            {
               if (LOG.isTraceEnabled())
               {
                  LOG.trace("An exception occurred: " + ignore.getMessage());
               }
            }
         }
      }
      return props;
   }   
}
