/*
 * 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.net.URL;
import java.util.Iterator;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.ObjectName;

import org.jboss.aop.AspectAnnotationLoader;
import org.jboss.aop.AspectManager;
import org.jboss.aop.AspectXmlLoader;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.DeploymentState;
import org.jboss.deployment.SubDeployer;
import org.jboss.deployment.SubDeployerSupport;
import org.jboss.mx.loading.HeirarchicalLoaderRepository3;
import org.jboss.mx.loading.LoaderRepository;
import org.jboss.util.file.ArchiveBrowser;
import org.jboss.util.file.ClassFileFilter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Deployer for Aspects
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 * @version $Revision: 60560 $
 * @jmx:mbean name="jboss.aspect:AspectDeployer"
 * extends="org.jboss.deployment.SubDeployerMBean"
 */
public class AspectDeployer
extends SubDeployerSupport
implements SubDeployer, AspectDeployerMBean
{

   /**
    * Default CTOR used to set default values to the Suffixes and RelativeOrder
    * attributes. Those are read at subdeployer registration time by the MainDeployer
    * to alter its SuffixOrder.
    */
   public AspectDeployer()
   {
      initializeMainDeployer();
   }

   /**
    * Set the suffixes and relative order attributes.
    * 
    * Those are read at subdeployer registration time by the MainDeployer
    * to update its SuffixOrder list.
    */
   protected void initializeMainDeployer()
   {
      setSuffixes(new String[]{".aop", "-aop.xml"});
      setRelativeOrder(100);
   }

   /**
    * Returns true if this deployer can deploy the given DeploymentInfo.
    *
    * @return True if this deployer can deploy the given DeploymentInfo.
    * @jmx:managed-operation
    */
   public boolean accepts(DeploymentInfo di)
   {
      String urlStr = di.url.toString();
      return urlStr.endsWith(".aop") || urlStr.endsWith(".aop/") ||
      urlStr.endsWith("-aop.xml");
   }

   /**
    * Describe <code>init</code> method here.
    *
    * @param di a <code>DeploymentInfo</code> value
    * @throws DeploymentException if an error occurs
    * @jmx:managed-operation
    */
   public void init(DeploymentInfo di)
   throws DeploymentException
   {
      try
      {
         if (di.watch == null)
         {
            // resolve the watch
            if (di.url.getProtocol().equals("file"))
            {
               File file = new File(di.url.getFile());

               // If not directory we watch the package
               if (!file.isDirectory())
               {
                  di.watch = di.url;
               }
               // If directory we watch the xml files
               else
               {
                  di.watch = new URL(di.url, "META-INF/jboss-aop.xml");
               }
            }
            else
            {
               // We watch the top only, no directory support
               di.watch = di.url;
            }
         }
      }
      catch (Exception e)
      {
         log.error("failed to parse AOP document: ", e);
         throw new DeploymentException(e);
      }
      super.init(di);
   }

   /**
    * Describe <code>create</code> method here.
    *
    * @param di a <code>DeploymentInfo</code> value
    * @throws DeploymentException if an error occurs
    * @jmx:managed-operation
    */
   public void create(DeploymentInfo di)
   throws DeploymentException
   {
      ClassLoader old = Thread.currentThread().getContextClassLoader();
      try
      {
         URL docURL = getDocUrl(di);
         ClassLoader scl = getScopedClassLoader(di, docURL);

         if (scl != null)
         {
            log.info("AOP deployment is scoped using classloader " + scl);   
         }
         
         Thread.currentThread().setContextClassLoader(di.ucl);
         if (!di.isXML)
         {
            Iterator it = ArchiveBrowser.getBrowser(di.localUrl, new ClassFileFilter());
            AspectManager manager = (scl != null) ? AspectManager.instance(scl) : AspectManager.instance();
            AspectAnnotationLoader loader = new AspectAnnotationLoader(manager);
            loader.setClassLoader(scl);
            loader.deployInputStreamIterator(it);
         }
         
         AspectXmlLoader.deployXML(docURL, scl);
         Notification msg = new Notification("AOP Deploy", this, getNextNotificationSequenceNumber());
         sendNotification(msg);
         log.debug("Deployed AOP: " + di.url);
      }
      catch (Exception ex)
      {
         ex.printStackTrace();
         throw new DeploymentException(ex);
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(old);
      }
   }

   /**
    * The <code>start</code> method starts all the mbeans in this DeploymentInfo..
    *
    * @param di a <code>DeploymentInfo</code> value
    * @throws DeploymentException if an error occurs
    * @jmx:managed-operation
    */
   public void start(DeploymentInfo di) throws DeploymentException
   {
   }

   /**
    * Undeploys the package at the url string specified. This will: Undeploy
    * packages depending on this one. Stop, destroy, and unregister all the
    * specified mbeans Unload this package and packages this package deployed
    * via the classpath tag. Keep track of packages depending on this one that
    * we undeployed so that they can be redeployed should this one be
    * redeployed.
    *
    * @param di the <code>DeploymentInfo</code> value to stop.
    * @jmx:managed-operation
    */
   public void stop(DeploymentInfo di)
   //throws DeploymentException
   {
      // Can happen on shutdown with a nested .aop module
      // which is first stopped then undeployed.
      if (di.state != DeploymentState.STARTED)
      {
         log.debug("Ignoring request to stop '" + di.url + "', current state: " + di.state);
         return;
      }
      
      log.debug("undeploying document " + di.url);
      ClassLoader old = Thread.currentThread().getContextClassLoader();
      try
      {
         Thread.currentThread().setContextClassLoader(di.ucl);
         if (!di.isXML)
         {
            Iterator it = ArchiveBrowser.getBrowser(di.localUrl, new ClassFileFilter());
            AspectAnnotationLoader loader = new AspectAnnotationLoader(AspectManager.instance());
            loader.undeployInputStreamIterator(it);

         }

         URL docURL = getDocUrl(di);
         //long start = System.currentTimeMillis();
         AspectXmlLoader.undeployXML(docURL);
         AspectManager.instance().unregisterClassLoader(di.ucl);

         /*
         System.out.println("************************");
         System.out.println("undeploy took: " + (System.currentTimeMillis() - start));
         System.out.println("************************");
         */
         Notification msg = new Notification("AOP Undeploy", this, getNextNotificationSequenceNumber());
         sendNotification(msg);
      }
      catch (Exception ex)
      {
         log.error("failed to stop", ex);
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(old);
      }
   }

   /**
    * Describe <code>destroy</code> method here.
    *
    * @param di a <code>DeploymentInfo</code> value
    * @jmx:managed-operation
    */
   public void destroy(DeploymentInfo di)
   //throws DeploymentException
   {
   }

   /**
    * The startService method gets the mbeanProxies for MainDeployer
    * and ServiceController, used elsewhere.
    *
    * @throws Exception if an error occurs
    */
   protected void startService() throws Exception
   {
      super.startService();
   }

   protected ObjectName getObjectName(MBeanServer server, ObjectName name)
   throws MalformedObjectNameException
   {
      return name == null ? OBJECT_NAME : name;
   }

   private URL getDocUrl(DeploymentInfo di) throws DeploymentException
   {
      URL docURL = di.localUrl;
      if (di.isXML == false)
         docURL = di.localCl.findResource("META-INF/jboss-aop.xml");
      // Validate that the descriptor was found
      if (docURL == null)
         throw new DeploymentException("Failed to find META-INF/jboss-aop.xml");
      return docURL;
   }
   
   private ClassLoader getScopedClassLoader(DeploymentInfo di, URL docUrl) throws Exception
   {
      //Scoped AOP deployments are only available when deployed as part of a scoped sar, ear etc.
      //It can contain an aop.xml file, or it can be part of a .aop file
      //Linking a standalone -aop.xml file onto a scoped deployment is not possible at the moment
      if (JBossScopedClassLoaderHelper.isScopedClassLoader(di.ucl))
      {
         return di.ucl;
      }
      
      LoaderRepository attachToRepository = getLoaderRepositoryIfAttaching(di, docUrl);
      if (attachToRepository != null)
      {
         di.ucl.setRepository(attachToRepository);
         attachToRepository.addClassLoader(di.ucl);
         return di.ucl;
      }
      
      return null;
   }

   
   private LoaderRepository getLoaderRepositoryIfAttaching(DeploymentInfo di, URL docUrl) throws Exception
   {
      if (di.parent == null)
      {
         //We are a top-level deployment, check if we are meant to be attaching to a scoped repository
         
         //TODO Use AspectXmlLoader.loadURL once AOP is added to repository 
         Document doc = AspectXmlLoader.loadURL(docUrl);
         Element top = doc.getDocumentElement();
         NodeList children = top.getChildNodes();
         int childlength = children.getLength();
         for (int i = 0; i < childlength ; i++)
         {
            Node child = children.item(i); 
            if (child.getNodeType() == Node.ELEMENT_NODE)
            {
               Element element = (Element)child;  
               if (element.getTagName().equals("loader-repository"))
               {
                  String repositoryName = null;
                  NodeList nestedChildren = child.getChildNodes();
                  int nestedChildLength = nestedChildren.getLength();
                  for (int j = 0 ; j < nestedChildLength ; j++)
                  {
                     Node nestedChild = nestedChildren.item(j);
                     if (nestedChild.getNodeType() == Node.TEXT_NODE || nestedChild.getNodeType() == Node.CDATA_SECTION_NODE)
                     {
                        repositoryName = nestedChild.getNodeValue().trim();
                        break;
                     }
                  }
                  log.info("Should attach deployment to loader repository " + repositoryName);
                  ObjectName on;
                  try
                  {
                     on = new ObjectName(repositoryName);
                  }
                  catch (MalformedObjectNameException e)
                  {
                     throw new RuntimeException("loader-repository='" + repositoryName + "' is not a valid object name", e);
                  }
                  
                  try
                  {
                     MBeanInfo info = server.getMBeanInfo(on);
                  }
                  catch (InstanceNotFoundException e)
                  {
                     log.warn("No scoped loader repository exists with the name " + on);
                  }
                  
                  LoaderRepository repository = (LoaderRepository)server.getAttribute(on, "Instance");
                  if (repository instanceof HeirarchicalLoaderRepository3)
                  {
                     return repository;
                  }
                  else
                  {
                     log.warn("Loader repository " + on + " is not a isolated loader repository. Deploying aspects in default domain.");
                  }
               }
            }            
         }
      }
      
      return null;
   }
}