/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.hibernate.har;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.Attribute;
import javax.management.MBeanInfo;

import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.SubDeployerSupport;
import org.jboss.deployment.DeploymentException;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.hibernate.jmx.Hibernate;

/**
 * Deployer for Hibernate <tt>har</tt> archives. A Hibernate archive
 * is expected to have a .har extension and include:<ul>
 * <li> <tt>hibernate-service.xml</tt>
 * <li> persistent classes
 * <li> <tt>hbm.xml</tt> mapping documents.</ul>
 *
 * @jmx:mbean
 *      name="jboss.har:service=HARDeployer"
 *      extends="org.jboss.deployment.SubDeployerMBean"
 *
 * @version <tt>$Revision: 1.1.2.6 $</tt>
 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
 */
public class HARDeployer
   extends SubDeployerSupport
   implements HARDeployerMBean
{
   private static final ObjectName OBJECT_NAME = ObjectNameFactory.create("jboss.har:service=HARDeployer");

   private ServiceControllerMBean serviceController;

   /** A map of current deployments. */
   private HashMap deployments = new HashMap();

   /** A flag indicating if deployment descriptors should be validated */
   private boolean validateDTDs;

   /**
    * Returns the deployed applications.
    *
    * @jmx:managed-operation
    */
   public Iterator getDeployedApplications()
   {
      return deployments.values().iterator();
   }

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

   /**
    * Get a reference to the ServiceController
    */
   protected void startService() throws Exception
   {
      serviceController = (ServiceControllerMBean)
         MBeanProxyExt.create(ServiceControllerMBean.class,
            ServiceControllerMBean.OBJECT_NAME, server);

      // register with MainDeployer
      super.startService();
   }

   /**
    * Implements the template method in superclass. This method stops all the
    * applications in this server.
    */
   protected void stopService() throws Exception
   {

      for(Iterator modules = deployments.values().iterator();
          modules.hasNext();)
      {
         DeploymentInfo di = (DeploymentInfo)modules.next();
         stop(di);
      }

      // avoid concurrent modification exception
      for(Iterator modules = new ArrayList(deployments.values()).iterator(); modules.hasNext();)
      {
         DeploymentInfo di = (DeploymentInfo)modules.next();
         destroy(di);
      }
      deployments.clear();

      // deregister with MainDeployer
      super.stopService();

      serviceController = null;
   }

   /**
    * Get the flag indicating that ejb-jar.dtd, jboss.dtd &amp;
    * jboss-web.dtd conforming documents should be validated
    * against the DTD.
    *
    * @jmx:managed-attribute
    */
   public boolean getValidateDTDs()
   {
      return validateDTDs;
   }

   /**
    * Set the flag indicating that ejb-jar.dtd, jboss.dtd &amp;
    * jboss-web.dtd conforming documents should be validated
    * against the DTD.
    *
    * @jmx:managed-attribute
    */
   public void setValidateDTDs(boolean validate)
   {
      this.validateDTDs = validate;
   }

   public boolean accepts(DeploymentInfo di)
   {
      // To be accepted the deployment's root name must end in jar
      String urlStr = di.url.getFile();
      if(!urlStr.endsWith("har") && !urlStr.endsWith("har/"))
      {
         return false;
      }

      // However the jar must also contain at least one ejb-jar.xml
      boolean accepts = false;
      try
      {
         URL dd = di.localCl.findResource("META-INF/hibernate-service.xml");
         if(dd == null)
         {
            return false;
         }

         // If the DD url is not a subset of the urlStr then this is coming
         // from a jar referenced by the deployment jar manifest and the
         // this deployment jar it should not be treated as an ejb-jar
         if(di.localUrl != null)
         {
            urlStr = di.localUrl.toString();
         }

         String ddStr = dd.toString();
         if(ddStr.indexOf(urlStr) >= 0)
         {
            accepts = true;
         }
      }
      catch(Exception ignore)
      {
      }

      log.debug("accepts> url=" + di.url + ", accepted=" + accepts);

      return accepts;
   }

   public void init(DeploymentInfo di)
      throws DeploymentException
   {
      try
      {
         if( "file".equalsIgnoreCase( di.url.getProtocol() ) )
         {
            File file = new File(di.url.getFile());

            if(!file.isDirectory())
            {
               // If not directory we watch the package
               di.watch = di.url;
            }
            else
            {
               // If directory we watch the xml files
               di.watch = new URL(di.url, "hibernate-service.xml");
            }
         }
         else
         {
            // We watch the top only, no directory support
            di.watch = di.url;
         }
      }
      catch(Exception e)
      {
         if(e instanceof DeploymentException)
            throw (DeploymentException)e;
         throw new DeploymentException("failed to initialize", e);
      }

      // invoke super-class initialization
      super.init(di);
   }

   /**
    * This is here as a reminder that we may not want to allow ejb jars to
    * have arbitrary sub deployments. Currently we do.
    * It is also here as a temporary solution to get JSR-109 simultaneous
    * web service deployments going
    * @param di
    * @throws org.jboss.deployment.DeploymentException
    */
   protected void processNestedDeployments(DeploymentInfo di)
      throws DeploymentException
   {
      super.processNestedDeployments(di);
   }

   protected void deployUrl(DeploymentInfo di, URL url, String name)
      throws DeploymentException
   {
      super.deployUrl(di, url, name);
   }

   public boolean isDeployable(String name, URL url)
   {
      // super allows -service.xml but not in META-INF which is the case for HAR
      return name.endsWith("-service.xml") || super.isDeployable(name, url);
   }

   public synchronized void create(DeploymentInfo di)
      throws DeploymentException
   {
      super.create(di);

      for(Iterator iter = di.subDeployments.iterator(); iter.hasNext();)
      {
         DeploymentInfo nested = (DeploymentInfo)iter.next();
         if(nested.url.getFile().endsWith("hibernate-service.xml"))
         {
            for(Iterator serviceIter = nested.mbeans.iterator(); serviceIter.hasNext();)
            {
               ObjectName service = (ObjectName)serviceIter.next();
               if (isHibernateMBean(service))
               {
                  Attribute attr = new Attribute("HarUrl", di.url);
                  try
                  {
                     server.setAttribute(service, attr);
                  }
                  catch(Exception e)
                  {
                     throw new DeploymentException("Failed to set HarUrl attribute: " + e.getMessage(), e);
                  }
               }
            }
            // todo: why allow only one service file?
            break;
         }
      }
   }

   private boolean isHibernateMBean(ObjectName service)
   {
      try
      {
         MBeanInfo serviceInfo = server.getMBeanInfo(service);
         // todo: would really like this to be an isAssignableFrom check;
         // is it kosher to do Class.forName() on the MBean class name here?
         return Hibernate.class.getName().equals( serviceInfo.getClassName() );
      }
      catch(Throwable t)
      {
         log.warn("Unable to determine whether MBean [" + service + "] is Hibernate MBean");
         return false;
      }
   }

   public synchronized void start(DeploymentInfo di)
      throws DeploymentException
   {
      super.start(di);
   }

   public void stop(DeploymentInfo di)
      throws DeploymentException
   {
      try
      {
         serviceController.stop(di.deployedObject);
      }
      catch(Exception e)
      {
         throw new DeploymentException("problem stopping har module: " + di.url, e);
      }
      super.stop(di);
   }

   public void destroy(DeploymentInfo di)
      throws DeploymentException
   {
      // FIXME: If the put() is obsolete above, this is obsolete, too
      deployments.remove(di.url);

      try
      {
         serviceController.destroy(di.deployedObject);
         serviceController.remove(di.deployedObject);
      }
      catch(Exception e)
      {
         throw new DeploymentException("problem destroying har module: " + di.url, e);
      }
      super.destroy(di);
   }
}
