/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, 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.aop.deployment;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import javassist.ByteArrayClassPath;
import javassist.scopedpool.ScopedClassPoolFactory;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.jboss.aop.AspectManager;
import org.jboss.aop.AspectNotificationHandler;
import org.jboss.aop.AspectXmlLoader;
import org.jboss.aop.ClassLoaderValidation;
import org.jboss.aop.Deployment;
import org.jboss.aop.hook.JDK14Transformer;
import org.jboss.aop.hook.JDK14TransformerManager;
import org.jboss.aop.instrument.InstrumentorFactory;
import org.jboss.aop.instrument.TransformerCommon;
import org.jboss.mx.loading.HeirarchicalLoaderRepository3;
import org.jboss.mx.server.ServerConstants;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.server.ServerConfig;

/**
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 60554 $
 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
 */
public class AspectManagerService
        extends ServiceMBeanSupport
        implements AspectManagerServiceMBean, AspectNotificationHandler
{
   static {
      //pre-load necessary classes so that we avoid NoClassDefFoundErrors on JRockit when using the RepositoryClassloader hook
      //When AspectManager.translate() is called the first time, these classes have not been loaded yet, and this is what causes
      //JRockit to get confused
      Class clazz = TransformerCommon.class;
      clazz = HeirarchicalLoaderRepository3.class;
      clazz = ByteArrayClassPath.class;
   }

   public static final ObjectName DEFAULT_LOADER_REPOSITORY = ObjectNameFactory.create(ServerConstants.DEFAULT_LOADER_NAME);

   // Attributes ---------------------------------------------------

   boolean created = false;
   protected File tmpClassesDir;
   protected boolean enableTransformer = false;
   protected boolean enableLoadtimeWeaving = false;
   protected boolean suppressTransformationErrors = true;
   protected boolean suppressReferenceErrors = true;
   protected String exclude;
   protected String include;
   protected String ignore;

   // Static -------------------------------------------------------

   // Constructors -------------------------------------------------
   public AspectManagerService()
   {
   }

   // Public -------------------------------------------------------

   protected ScopedClassPoolFactory createFactory() throws Exception
   {
      return new JBossClassPoolFactory(tmpClassesDir);
   }

   protected ClassLoaderValidation createClassLoaderValidation()
   {
      return new JBossClassLoaderValidator();
   }

   protected void createService()
           throws Exception
   {
      // Set a default tmp classes dir to the jboss tmp dir/aopclasses
      if (tmpClassesDir == null)
      {
         String jbossTmpDir = System.getProperty(ServerConfig.SERVER_TEMP_DIR);
         if (jbossTmpDir == null)
            jbossTmpDir = System.getProperty("java.io.tmpdir");
         tmpClassesDir = new File(jbossTmpDir, "aopdynclasses");
      }
      // Validate the the tmp dir exists
      if (tmpClassesDir.exists() == false && tmpClassesDir.mkdirs() == false)
         throw new FileNotFoundException("Failed to create tmpClassesDir: " + tmpClassesDir.getAbsolutePath());
      AspectManager.setClassPoolFactory(createFactory());

      AspectManager.classLoaderValidator = createClassLoaderValidation();
      // Add the tmp classes dir to our UCL classpath

      Deployment.searchClasspath = false; // turn off dynamic finding of DDs
      AspectManager.suppressTransformationErrors = suppressTransformationErrors;
      if (enableTransformer && enableLoadtimeWeaving) throw new RuntimeException("Cannot set both EnableTransformer and EnableLoadtimeWeaving");
      if (enableTransformer)
      {
         attachDeprecatedTranslator();
      }
      if (enableLoadtimeWeaving)
      {
         attachTranslator();
      }
      created = true;
      AspectManager.notificationHandler = this;

      AspectManager.scopedCLHelper = new JBossScopedClassLoaderHelper();

      baseAop();
   }

   protected void baseAop()
   {
      try
      {
         ClassLoader cl = Thread.currentThread().getContextClassLoader();
         URL base = cl.getResource("base-aop.xml");
         AspectXmlLoader.deployXML(base);
      }
      catch (Exception ex)
      {
         ex.printStackTrace();
      }
   }

   protected void attachDeprecatedTranslator()
   {
      log.warn("EnableTransformer has been deprecated, please use EnableLoadtimeWeaving.  See docs for more details");
      AspectManager mgr = AspectManager.instance();
      try
      {
         server.setAttribute(DEFAULT_LOADER_REPOSITORY, new Attribute("Translator", mgr));
      }
      catch (InstanceNotFoundException e)
      {
         throw new RuntimeException(e);
      }
      catch (AttributeNotFoundException e)
      {
         throw new RuntimeException(e);
      }
      catch (InvalidAttributeValueException e)
      {
         throw new RuntimeException(e);
      }
      catch (MBeanException e)
      {
         throw new RuntimeException(e);
      }
      catch (ReflectionException e)
      {
         throw new RuntimeException(e);
      }
   }

   protected void detachDeprecatedTranslator()
   {
      try
      {
         server.setAttribute(DEFAULT_LOADER_REPOSITORY, new Attribute("Translator", null));
      }
      catch (InstanceNotFoundException e)
      {
         throw new RuntimeException(e);
      }
      catch (AttributeNotFoundException e)
      {
         throw new RuntimeException(e);
      }
      catch (InvalidAttributeValueException e)
      {
         throw new RuntimeException(e);
      }
      catch (MBeanException e)
      {
         throw new RuntimeException(e);
      }
      catch (ReflectionException e)
      {
         throw new RuntimeException(e);
      }
   }

   protected void attachTranslator()
   {
      JDK14TransformerManager.transformer = new JDK14Transformer()
      {
         public byte[] transform(ClassLoader loader, String classname, byte[] classBytes)
         {
            try
            {
               //Make sure that we use the correct classloader, in order to get the correct domain if it is a scoped loader
               return AspectManager.instance(loader).translate(classname, loader, classBytes);
            }
            catch (Exception e)
            {
               throw new RuntimeException("Error converting " + classname + " on " + loader, e);
            }
         }
      };
   }

   protected void detachTranslator()
   {
      JDK14TransformerManager.transformer = null;
   }

   public void attachClass(String classname)
   {
      Notification msg = new Notification("AOP class attached", this, getNextNotificationSequenceNumber());
      msg.setUserData(classname);
      sendNotification(msg);
   }

   protected void startService()
           throws Exception
   {
   }

   protected void stopService()
   {
   }

   public boolean getPrune()
   {
      return AspectManager.getPrune();
   }

   public void setPrune(boolean prune)
   {
      AspectManager.setPrune(prune);
   }

   public String getExclude()
   {
      return exclude;
   }

   public void setExclude(String exclude)
   {
      this.exclude = exclude;
      ArrayList list = new ArrayList();
      if (exclude != null)
      {
         StringTokenizer tokenizer = new StringTokenizer(exclude, ",");
         while (tokenizer.hasMoreTokens())
         {
            list.add(tokenizer.nextToken().trim());
         }
      }
      AspectManager.instance().setExclude(list);
   }

   public String getInclude()
   {
      return include;
   }

   public void setInclude(String include)
   {
      this.include = include;
      ArrayList list = new ArrayList();
      if (include != null)
      {
         StringTokenizer tokenizer = new StringTokenizer(include, ",");
         while (tokenizer.hasMoreTokens())
         {
            list.add(tokenizer.nextToken().trim());
         }
      }
      AspectManager.instance().setInclude(list);
   }

   public String getIgnore()
   {
      return ignore;
   }

   public void setIgnore(String ignore)
   {
      this.ignore = ignore;
      ArrayList list = new ArrayList();
      if (ignore != null)
      {
         StringTokenizer tokenizer = new StringTokenizer(ignore, ",");
         while (tokenizer.hasMoreTokens())
         {
            list.add(tokenizer.nextToken().trim());
         }
      }
      AspectManager.instance().setIgnore(list);
   }


   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-attribute
    */
   public File getTmpClassesDir()
   {
      return tmpClassesDir;
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-attribute
    */
   public void setTmpClassesDir(File tmpClassesDir)
   {
      this.tmpClassesDir = tmpClassesDir;
   }

   /**
    * Set the verbosity of aop logging.  It doesn't use log4j
    *
    * @jmx:managed-attribute
    */
   public boolean getVerbose()
   {
      return AspectManager.verbose;
   }

   /**
    * Set the verbosity of aop logging.  It doesn't use log4j
    *
    * @jmx:managed-attribute
    */
   public void setVerbose(boolean verbose)
   {
      AspectManager.verbose = verbose;
   }

   /**
    * Use aop optimizations.  Optional just in case there is a bug
    *
    * @jmx:managed-attribute
    */
   public boolean getOptimized()
   {
      return AspectManager.optimize;
   }

   /**
    * Use aop optimizations.  Optional just in case there is a bug
    *
    * @jmx:managed-attribute
    */
   public void setOptimized(boolean verbose)
   {
      AspectManager.optimize = verbose;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean getSuppressTransformationErrors()
   {
      return suppressTransformationErrors;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setSuppressTransformationErrors(boolean suppressTransformationErrors)
   {
      this.suppressTransformationErrors = suppressTransformationErrors;
      AspectManager.suppressTransformationErrors = suppressTransformationErrors;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean getSuppressReferenceErrors()
   {
      return suppressReferenceErrors;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setSuppressReferenceErrors(boolean suppressReferenceErrors)
   {
      this.suppressReferenceErrors = suppressReferenceErrors;
      AspectManager.suppressReferenceErrors = suppressReferenceErrors;
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-attribute
    */
   public boolean getEnableTransformer()
   {
      return enableTransformer;
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-operation
    */
   public String interceptorFactories()
   {
      Map factories = AspectManager.instance().getInterceptorFactories();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-operation
    */
   public String aspectDefinitions()
   {
      Map factories = AspectManager.instance().getAspectDefinitions();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * @jmx:managed-operation
    */
   public String introductions()
   {
      Map factories = AspectManager.instance().getInterfaceIntroductions();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-operation
    */
   public String stacks()
   {
      Map factories = AspectManager.instance().getInterceptorStacks();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-operation
    */
   public String bindings()
   {
      Map factories = AspectManager.instance().getBindings();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-operation
    */
   public String registeredClassLoaders()
   {
      Map factories = AspectManager.instance().getRegisteredCLs();
      Iterator it = factories.keySet().iterator();
      StringBuffer buffer = new StringBuffer("");
      while (it.hasNext())
      {
         buffer.append(it.next() + "<br>");
      }
      return buffer.toString();
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-attribute
    */
   public void setEnableTransformer(boolean enableTransformer)
   {
      // Testsuite uses enableTransformer, we may be testing new loadtime features though.

      if (enableLoadtimeWeaving)
      {
         log.warn("enabledLoadtimeWeaving alread set");
         return;
      }
      if (this.enableTransformer == enableTransformer) return;
      if (this.getState() == STARTED)
      {
         if (enableTransformer)
         {
            attachDeprecatedTranslator();
         }
         else
         {
            detachDeprecatedTranslator();
         }
      }
      this.enableTransformer = enableTransformer;
   }

   public boolean getEnableLoadtimeWeaving()
   {
      return enableLoadtimeWeaving;
   }

   /**
    * The temporary directory to which dyn class files are written
    *
    * @jmx:managed-attribute
    */
   public void setEnableLoadtimeWeaving(boolean enableTransformer)
   {
      if (this.enableLoadtimeWeaving == enableTransformer) return;
      if (this.getState() == STARTED)
      {
         if (enableTransformer)
         {
            attachTranslator();
         }
         else
         {
            detachTranslator();
         }
      }
      this.enableLoadtimeWeaving = enableTransformer;
   }

   public String getInstrumentor()
   {
      return InstrumentorFactory.getInstrumentorName();
   }

   public void setInstrumentor(String instrumentor)
   {
      InstrumentorFactory.initialise(instrumentor);
   }

}
