/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * 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.jboss.system.server.jmx;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.jboss.bootstrap.api.as.config.JBossASBasedServerConfig;
import org.jboss.bootstrap.api.config.InvalidConfigurationException;
import org.jboss.bootstrap.api.lifecycle.LifecycleEventException;
import org.jboss.bootstrap.api.lifecycle.LifecycleEventHandler;
import org.jboss.bootstrap.api.lifecycle.LifecycleState;
import org.jboss.bootstrap.spi.Bootstrap;
import org.jboss.bootstrap.spi.as.server.JBossASBasedServerProvider;
import org.jboss.bootstrap.spi.config.ConfigurationInitializer;
import org.jboss.bootstrap.spi.config.ConfigurationValidator;
import org.jboss.bootstrap.spi.server.ServerInitializer;
import org.jboss.classloader.spi.ClassLoaderSystem;
import org.jboss.classloading.spi.RealClassLoader;
import org.jboss.classloading.spi.metadata.ExportAll;
import org.jboss.classloading.spi.vfs.policy.VFSClassLoaderPolicy;
import org.jboss.kernel.Kernel;
import org.jboss.kernel.spi.deployment.KernelDeployment;
import org.jboss.logging.Logger;
import org.jboss.mx.loading.RepositoryClassLoader;
import org.jboss.mx.server.ServerConstants;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.system.ServiceController;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.system.server.ServerConfigImpl;
import org.jboss.system.server.ServerConfigImplMBean;
import org.jboss.system.server.ServerImplMBean;
import org.jboss.system.server.ServerInfoMBean;
import org.jboss.util.JBossObject;
import org.jboss.util.file.FileSuffixFilter;
import org.jboss.virtual.VFS;
import org.jboss.virtual.VirtualFile;

/**
 * A pojo that creates a legacy jmx kernel ala the jboss-4.x server bootstrap.
 * This is used to support the SARDeployer and mbean integration.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 97479 $
 */
public class JMXKernel<K extends JBossASBasedServerProvider<K, T>, T extends JBossASBasedServerConfig<T>> extends JBossObject
   implements JMXKernelMBean<K,T>, NotificationEmitter
{
   private final static ObjectName DEFAULT_LOADER_NAME =
      ObjectNameFactory.create(ServerConstants.DEFAULT_LOADER_NAME);

   private static final Logger log = Logger.getLogger(JMXKernel.class);

   /** The JMX MBeanServer which will serve as our communication bus. */
   private MBeanServer mbeanServer;
   private K serverImpl;
   private ServiceController controller;
   private T serverConfig;
   private ServerConfigImplMBean<T> serverConfigMBean;
   private ServerInfoMBean serverInfo;
   /** The kernel */
   private Kernel kernel;
   /** The serverImpl cast as an emitter */
   private NotificationEmitter notificationEmitter;
   
   /** The bootstrap UCL class loader ObjectName */
   private ObjectName bootstrapUCLName;
   private boolean started;
   /** Whether to use the old classloader */
   private boolean oldClassLoader;

   public JMXKernel()
   {
      super(log);
   }

   public K getServerImpl()
   {
      return serverImpl;
   }
   public void setServerImpl(K serverImpl)
   {
      this.serverImpl = serverImpl;
      this.notificationEmitter = (NotificationEmitter) serverImpl;
   }

   public ServiceControllerMBean getServiceController()
   {
      return this.controller;
   }
   public MBeanServer getMbeanServer()
   {
      return mbeanServer;
   }

   public ServerInfoMBean getServerInfo()
   {
      return serverInfo;
   }
   public void setServerInfo(ServerInfoMBean serverInfo)
   {
      this.serverInfo = serverInfo;
   }

   /**
    * Set the kernel.
    * 
    * @param kernel the kernel.
    */
   public void setKernel(Kernel kernel)
   {
      this.kernel = kernel;
   }
   
   public boolean isOldClassLoader()
   {
      return oldClassLoader;
   }

   public void setOldClassLoader(boolean oldClassLoader)
   {
      this.oldClassLoader = oldClassLoader;
   }
   
   public void start() throws Exception
   {
      ClassLoader tcl = Thread.currentThread().getContextClassLoader();
      // Create the MBeanServer
      String builder = System.getProperty(ServerConstants.MBEAN_SERVER_BUILDER_CLASS_PROPERTY,
                                          ServerConstants.DEFAULT_MBEAN_SERVER_BUILDER_CLASS);
      System.setProperty(ServerConstants.MBEAN_SERVER_BUILDER_CLASS_PROPERTY, builder);

      serverConfig = serverImpl.getConfiguration();
      serverConfigMBean = new ServerConfigImpl(serverConfig);
      // Check if we'll use the platform MBeanServer or instantiate our own
      if (serverConfig.isUsePlatformMBeanServer() == true)
      {
         // jdk1.5+
         ClassLoader cl = Thread.currentThread().getContextClassLoader();
         Class<?> clazz = cl.loadClass("java.lang.management.ManagementFactory");
         Class<?>[] sig = null;
         Method method = clazz.getMethod("getPlatformMBeanServer", sig);
         Object[] args = null;
         mbeanServer = (MBeanServer) method.invoke(null, args);
         // Tell the MBeanServerLocator to point to this mbeanServer
         MBeanServerLocator.setJBoss(mbeanServer);
         /* If the LazyMBeanServer was used, we need to reset to the jboss
         MBeanServer to use our implementation for the jboss services.
         */
         mbeanServer = LazyMBeanServer.resetToJBossServer(mbeanServer);
      }
      else
      {
         // Create our own MBeanServer
         mbeanServer = MBeanServerFactory.createMBeanServer("jboss");
      }
      log.debug("Created MBeanServer: " + mbeanServer);      

      // Register mbeanServer components
      mbeanServer.registerMBean(this, ServerImplMBean.OBJECT_NAME);
      mbeanServer.registerMBean(serverConfigMBean, ServerConfigImplMBean.OBJECT_NAME);
      
      // Initialize spine boot libraries
      ClassLoader cl;
      if (oldClassLoader)
         cl = initBootLibrariesOld();
      else
         cl = initBootLibraries();
      
      // Set ServiceClassLoader as classloader for the construction of
      // the basic system
      try
      {
         Thread.currentThread().setContextClassLoader(cl);
   
         // General Purpose Architecture information -- 
         mbeanServer.registerMBean(serverInfo, new ObjectName("jboss.system:type=ServerInfo"));

         // Service Controller
         controller = new ServiceController();
         controller.setKernel(kernel);
         controller.setMBeanServer(mbeanServer);
         mbeanServer.registerMBean(controller, new ObjectName("jboss.system:service=ServiceController"));
   
         log.info("Legacy JMX core initialized");
         started = true;
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(tcl);
      }
   }

   /**
    * Stop the mbeans
    *
    * @throws IllegalStateException - if not started.
    */
   public void stop() throws IllegalStateException
   {
      if (log.isTraceEnabled())
         log.trace("stop caller:", new Throwable("Here"));

      if (!started)
         throw new IllegalStateException("Server not started");

      log.debug("Shutting down all services");
      shutdownServices();

      // Make sure all mbeans are unregistered
      removeMBeans();
   }

   ///////////////////////////////////////////////////////////////////////////
   //                          NotificationEmitter                          //
   ///////////////////////////////////////////////////////////////////////////
   
   public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
   {
      notificationEmitter.addNotificationListener(listener, filter, handback);
   }
   
   public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException
   {
      notificationEmitter.removeNotificationListener(listener);
   }
   
   public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
      throws ListenerNotFoundException
   {
      notificationEmitter.removeNotificationListener(listener, filter, handback);
   }
      
   public MBeanNotificationInfo[] getNotificationInfo()
   {
      return notificationEmitter.getNotificationInfo();
   }

   // ServerImplMBean delegation
   public void init(Properties props) throws Exception
   {
      serverImpl.initialize();
   }

   // AS isn't always going to be a standalone process
//   public void exit()
//   {
//      serverImpl.exit();
//   }
//
//   public void exit(int exitcode)
//   {
//      serverImpl.exit(exitcode);
//   }

   public T getConfig()
   {
      return serverConfig;
   }
   /**
    * Get the optional server configuration metadata
    * @return a possibly empty map of configuration metadata.
    */
   public Map<String, Object> getMetaData()
   {
      return Collections.emptyMap();
   }
   public String getBuildDate()
   {
      return serverImpl.getBuildDate();
   }

   public String getBuildID()
   {
      return serverImpl.getBuildID();
   }

   public String getBuildJVM()
   {
      return serverImpl.getBuildJVM();
   }

   public String getBuildNumber()
   {
      return serverImpl.getBuildNumber();
   }

   public String getBuildOS()
   {
      return serverImpl.getBuildOS();
   }

   public Date getStartDate()
   {
      return serverImpl.getStartDate();
   }

   public String getVersion()
   {
      return serverImpl.getVersion();
   }

   public String getVersionName()
   {
      return serverImpl.getVersionName();
   }

   public String getVersionNumber()
   {
      return serverImpl.getVersionNumber();
   }

   // AS isn't always going to be a standalone process
//   public void halt()
//   {
//      serverImpl.halt();
//   }
//
//   public void halt(int exitcode)
//   {
//      serverImpl.halt(exitcode);
//   }

   public boolean isInShutdown()
   {
      return serverImpl.getState().equals(LifecycleState.STOPPING);
   }

   public boolean isStarted()
   {
      return serverImpl.getState().equals(LifecycleState.STARTED);
   }

   public void runFinalization()
   {
      
   }

   public void runGarbageCollector()
   {
      
   }

   public void traceInstructions(Boolean flag)
   {
      
   }

   public void traceMethodCalls(Boolean flag)
   {
      
   }
   
   public void shutdown()
   {
      // JBAS-6916
      // We'll shut down the server via the Main shutdown hook...
//      if (log.isTraceEnabled())
//         log.trace("Shutdown caller:", new Throwable("Here"));
//      try{
//         serverImpl.shutdown();
//      }
//      catch(Throwable t)
//      {
//         throw new RuntimeException("Error in shutdown of server: " + serverImpl,t);
//      }
      
      // JBAS-6916:
      //TODO This must be removed, VM exit is not a feature of server shutdown.
      // Now exit the Runtime, because AS has non-daemon Threads still
      // chugging away. 
      new Thread()
      {
         public void run()
         {
            log.info("Server exit called, exiting the JVM now!");
            Runtime.getRuntime().exit(1);
         }
      }.start();
   }

   /**
    * The <code>shutdownServices</code> method calls the one and only
    * ServiceController to shut down all the mbeans registered with it.
    */
   protected void shutdownServices()
   {
      try
      {
         // get the deployed objects from ServiceController
         controller.shutdown();
      }
      catch (Exception e)
      {
         Throwable t = JMXExceptionDecoder.decode(e);
         log.error("Failed to shutdown services", t);
      }
   }

   /**
    * The <code>removeMBeans</code> method uses the mbean mbeanServer to unregister
    * all the mbeans registered here.
    */
   protected void removeMBeans()
   {
      try
      {
         mbeanServer.unregisterMBean(ServerConfigImplMBean.OBJECT_NAME);
      }
      catch (Exception e)
      {
         Throwable t = JMXExceptionDecoder.decode(e);
         log.error("Failed to unregister mbeans", t);
      }
      try
      {
         mbeanServer.unregisterMBean(ServerImplMBean.OBJECT_NAME);
      }
      catch (Exception e)
      {
         Throwable t = JMXExceptionDecoder.decode(e);
         log.error("Failed to unregister mbeans", t);
      }
      try
      {
         MBeanServer registeredServer = mbeanServer;
         if (serverConfig.isUsePlatformMBeanServer() == true)
            registeredServer = LazyMBeanServer.getRegisteredMBeanServer(mbeanServer);
         MBeanServerFactory.releaseMBeanServer(registeredServer);
      }
      catch (Exception e)
      {
         Throwable t = JMXExceptionDecoder.decode(e);
         log.error("Failed to release mbean mbeanServer", t);
      }
   }


   /**
    * Initialize the boot libraries using the old classloader
    * 
    * @return the classloader
    * @throws Exception for any error
    */
   private List<URL> getBootURLs() throws Exception
   {
      // Build the list of URL for the spine to boot
      List<URL> list = new ArrayList<URL>();

      // Add the patch URL.  If the url protocol is file, then
      // add the contents of the directory it points to
      URL patchURL = null; //serverConfig.getPatchURL(); //TODO Supply a patchURL?
      if (patchURL != null)
      {
         if (patchURL.getProtocol().equals("file"))
         {
            File dir = new File(patchURL.getFile());
            if (dir.exists())
            {
               // Add the local file patch directory
               list.add(dir.toURI().toURL());

               // Add the contents of the directory too
               File[] jars = dir.listFiles(new FileSuffixFilter(new String[] { ".jar", ".zip" }, true));

               for (int j = 0; jars != null && j < jars.length; j++)
               {
                  list.add(jars[j].getCanonicalFile().toURI().toURL());
               }
            }
         }
         else
         {
            list.add(patchURL);
         }
      }

      // Add the mbeanServer configuration directory to be able to load serverConfig files as resources
      list.add(serverConfig.getServerConfLocation());
      log.debug("Boot url list: " + list);
      
      return list;
   }

   /**
    * Initialize the boot libraries using the old classloader
    * 
    * @return the classloader
    * @throws Exception for any error
    */
   private ClassLoader initBootLibrariesOld() throws Exception
   {
      List<URL> list = getBootURLs();
      
      // Create loaders for each URL
      RepositoryClassLoader loader = null;
      for (URL url : list)
      {
         log.debug("Creating loader for URL: " + url);

         // This is a boot URL, so key it on itself.
         Object[] args = {url, Boolean.TRUE};
         String[] sig = {"java.net.URL", "boolean"};
         loader = (RepositoryClassLoader) mbeanServer.invoke(DEFAULT_LOADER_NAME, "newClassLoader", args, sig);
      }
      bootstrapUCLName = loader.getObjectName();
      mbeanServer.registerMBean(loader, bootstrapUCLName);
      return loader;
   }

   /**
    * Initialize the boot libraries using the new classloader
    * 
    * @return the classloader
    * @throws Exception for any error
    */
   private ClassLoader initBootLibraries() throws Exception
   {
      ClassLoaderSystem system = ClassLoaderSystem.getInstance();
      mbeanServer.registerMBean(system, new ObjectName("jboss.classloader:service=ClassLoaderSystem"));
      
      List<URL> list = getBootURLs();

      VirtualFile[] files = new VirtualFile[list.size()];
      for (int i = 0; i < list.size(); ++i)
      {
         URL url = list.get(i);
         files[i] = VFS.getRoot(url);
      }
      
      VFSClassLoaderPolicy policy = new VFSClassLoaderPolicy(files);
      policy.setExportAll(ExportAll.NON_EMPTY);
      policy.setImportAll(true);
      ClassLoader classLoader = system.registerClassLoaderPolicy(policy);
      if (classLoader instanceof RealClassLoader)
         bootstrapUCLName = ((RealClassLoader) classLoader).getObjectName();
      return classLoader;
   }
   
   /*
    * Below this line are delegate methods only to
    * fulfill the contract of JBossASServer
    */

   public Map<String, KernelDeployment> getDeployments()
   {
      return serverImpl.getDeployments();
   }

   public Kernel getKernel()
   {
      return serverImpl.getKernel();
   }

   public void addBootstrap(final Bootstrap<K, T> arg0) throws IllegalArgumentException
   {
      serverImpl.addBootstrap(arg0);
   }

   public ConfigurationInitializer<T> getConfigInitializer()
   {
      return serverImpl.getConfigInitializer();
   }

   public T getConfiguration()
   {
      return serverImpl.getConfiguration();
   }

   public ServerInitializer<K, T> getServerInitializer()
   {
      return serverImpl.getServerInitializer();
   }

   public LifecycleState getState()
   {
      return serverImpl.getState();
   }

   public ConfigurationValidator<T> getValidator()
   {
      return serverImpl.getValidator();
   }

   public void initialize() throws IllegalStateException, InvalidConfigurationException, LifecycleEventException
   {
      serverImpl.initialize();
   }

   public void registerEventHandler(final LifecycleEventHandler arg0, final EnumSet<LifecycleState> arg1)
         throws IllegalArgumentException
   {
      serverImpl.registerEventHandler(arg0, arg1);
   }

   public void registerEventHandler(final LifecycleEventHandler arg0, final LifecycleState... arg1)
         throws IllegalArgumentException
   {
      serverImpl.registerEventHandler(arg0, arg1);

   }

   public void registerEventHandler(final LifecycleState arg0, final LifecycleEventHandler arg1)
         throws IllegalArgumentException
   {
      serverImpl.registerEventHandler(arg0, arg1);
   }

   public void registerEventHandlers(final LifecycleState arg0, final LifecycleEventHandler... arg1)
         throws IllegalArgumentException
   {
      serverImpl.registerEventHandlers(arg0, arg1);
   }

   public void removeBootstrap(final Bootstrap<K, T> arg0)
         throws IllegalArgumentException
   {
      serverImpl.removeBootstrap(arg0);

   }

   public void setConfigInitializer(final ConfigurationInitializer<T> arg0)
         throws IllegalStateException
   {
      serverImpl.setConfigInitializer(arg0);

   }

   public void setConfiguration(final T arg0)
   {
      serverImpl.setConfiguration(arg0);
   }

   public void setServerInitializer(final ServerInitializer<K, T> arg0)
         throws IllegalStateException
   {
      serverImpl.setServerInitializer(arg0);

   }

   public void setValidator(final ConfigurationValidator<T> arg0) throws IllegalStateException
   {
      serverImpl.setValidator(arg0);

   }

   public boolean unregisterEventHandler(final LifecycleEventHandler arg0, final LifecycleState arg1)
         throws IllegalArgumentException
   {
      return serverImpl.unregisterEventHandler(arg0, arg1);
   }
}

