/*
 * 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.deployment.services;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.enterprise.deploy.shared.ModuleType;
import javax.enterprise.deploy.spi.exceptions.TargetException;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;

import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.DeploymentState;
import org.jboss.deployment.MainDeployerMBean;
import org.jboss.deployment.spi.SerializableTargetModuleID;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.file.Files;

/**
 * A service that supports the JSR-88 DeploymentManager operations.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class DeploymentManagerService extends ServiceMBeanSupport implements DeploymentManagerServiceMBean
{
   private Map moduleMap = Collections.synchronizedMap(new HashMap());
   /**
    * The name of the MainDeployer
    */
   private ObjectName mainDeployer;
   private ObjectName carDeployer;
   private ObjectName earDeployer;
   private ObjectName ejbDeployer;
   private ObjectName rarDeployer;
   private ObjectName warDeployer;

   /**
    * The local directory for uploaded content.
    */
   File uploadDir;

   public ObjectName getMainDeployer()
   {
      return mainDeployer;
   }

   public void setMainDeployer(ObjectName mainDeployer)
   {
      this.mainDeployer = mainDeployer;
   }

   /**
    * @return The EARDeployer mbean name
    * @jmx:managed-attribute
    */
   public ObjectName getEARDeployer()
   {
      return earDeployer;
   }

   /**
    * @param name The EARDeployer mbean name
    * @jmx:managed-attribute
    */
   public void setEARDeployer(ObjectName name)
   {
      this.earDeployer = name;
   }

   /**
    * @return The EJBDeployer mbean name
    * @jmx:managed-attribute
    */
   public ObjectName getEJBDeployer()
   {
      return ejbDeployer;
   }

   /**
    * @param name The EJBDeployer mbean name
    * @jmx:managed-attribute
    */
   public void setEJBDeployer(ObjectName name)
   {
      this.ejbDeployer = name;
   }

   /**
    * @return The RARDeployer mbean name
    * @jmx:managed-attribute
    */
   public ObjectName getRARDeployer()
   {
      return rarDeployer;
   }

   /**
    * @param name The RARDeployer mbean name
    * @jmx:managed-attribute
    */
   public void setRARDeployer(ObjectName name)
   {
      this.rarDeployer = name;
   }

   /**
    * @return The WARDeployer mbean name
    * @jmx:managed-attribute
    */
   public ObjectName getWARDeployer()
   {
      return warDeployer;
   }

   /**
    * @param name The WARDeployer mbean name
    * @jmx:managed-attribute
    */
   public void setWARDeployer(ObjectName name)
   {
      this.warDeployer = name;
   }

   /**
    * @return The CARDeployer mbean name
    * @jmx:managed-attribute
    */
   public ObjectName getCARDeployer()
   {
      return carDeployer;
   }

   /**
    * @param name The CARDeployer mbean name
    * @jmx:managed-attribute
    */
   public void setCARDeployer(ObjectName name)
   {
      this.carDeployer = name;
   }

   public File getUploadDir()
   {
      return this.uploadDir;
   }

   public void setUploadDir(File uploadDir)
   {
      this.uploadDir = uploadDir;
   }

   public Map getModuleMap()
   {
      return Collections.unmodifiableMap(moduleMap);
   }

   public void deploy(SerializableTargetModuleID moduleID) throws Exception
   {
      String url = moduleID.getModuleID();

      // Create a status object
      URL deployURL = new URL(url);
      URLConnection conn = deployURL.openConnection();
      int contentLength = conn.getContentLength();
      log.debug("Begin deploy, url: " + deployURL + ", contentLength: " + contentLength);

      // Create the local path
      File path = new File(deployURL.getFile());
      String archive = path.getName();
      File deployFile = new File(uploadDir, archive);
      if (deployFile.exists() == true)
         throw new IOException("deployURL(" + deployURL + ") collides with: " + deployFile.getPath());

      File parentFile = deployFile.getParentFile();
      if (parentFile.exists() == false)
      {
         if (parentFile.mkdirs() == false)
            throw new IOException("Failed to create local path: " + parentFile);
      }

      InputStream is = conn.getInputStream();
      BufferedInputStream bis = new BufferedInputStream(is);
      byte[] buffer = new byte[4096];
      FileOutputStream fos = new FileOutputStream(deployFile);
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      int count = 0;
      while ((count = bis.read(buffer)) > 0)
      {
         bos.write(buffer, 0, count);
      }
      bis.close();
      bos.close();

      moduleMap.put(url, moduleID);
   }

   public void start(String url) throws Exception
   {
      SerializableTargetModuleID moduleID = (SerializableTargetModuleID)moduleMap.get(url);
      if (moduleID == null)
         throw new IOException("deployURL(" + url + ") has not been distributed");

      URL deployURL = new URL(url);
      // Build the local path
      File path = new File(deployURL.getFile());
      String archive = path.getName();
      File deployFile = new File(uploadDir, archive);
      if (deployFile.exists() == false)
         throw new IOException("deployURL(" + url + ") has no local archive");

      MainDeployerMBean proxy = (MainDeployerMBean)MBeanServerInvocationHandler.newProxyInstance(server, mainDeployer, MainDeployerMBean.class, false);
      proxy.deploy(deployFile.toURL());
      moduleID.setRunning(true);
      moduleID.clearChildModuleIDs();
      // Repopulate the child modules
      DeploymentInfo info = proxy.getDeployment(deployFile.toURL());
      fillChildrenTargetModuleID(moduleID, info);
   }

   public void stop(String url) throws Exception
   {
      SerializableTargetModuleID moduleID = (SerializableTargetModuleID)moduleMap.get(url);
      if (moduleID == null)
         throw new IOException("deployURL(" + url + ") has not been distributed");

      URL deployURL = new URL(url);
      // Build the local path
      File path = new File(deployURL.getFile());
      String archive = path.getName();
      File deployFile = new File(uploadDir, archive);
      if (deployFile.exists() == false)
         throw new IOException("deployURL(" + url + ") has no local archive");

      MainDeployerMBean proxy = (MainDeployerMBean)MBeanServerInvocationHandler.newProxyInstance(server, mainDeployer, MainDeployerMBean.class, false);
      proxy.undeploy(deployFile.toURL());
      moduleID.setRunning(false);
   }

   public void undeploy(String url) throws Exception
   {
      SerializableTargetModuleID moduleID = (SerializableTargetModuleID)moduleMap.get(url);
      if (moduleID == null)
         return;

      // If the module is still running, stop it
      if (moduleID.isRunning())
         stop(url);

      URL deployURL = new URL(url);
      // Build the local path
      File path = new File(deployURL.getFile());
      String archive = path.getName();
      File deployFile = new File(uploadDir, archive);
      if (deployFile.exists() == false)
         throw new IOException("deployURL(" + url + ") has not been distributed");
      Files.delete(deployFile);

      moduleMap.remove(url);
   }

   public SerializableTargetModuleID[] getAvailableModules(int moduleType) throws TargetException
   {
      ArrayList matches = new ArrayList();
      Iterator modules = moduleMap.values().iterator();
      while (modules.hasNext())
      {
         SerializableTargetModuleID module = (SerializableTargetModuleID)modules.next();
         if (module.getModuleType() == moduleType)
            matches.add(module);
      }

      SerializableTargetModuleID[] ids = new SerializableTargetModuleID[matches.size()];
      matches.toArray(ids);
      return ids;
   }

   private void fillChildrenTargetModuleID(SerializableTargetModuleID moduleID, DeploymentInfo info)
   {
      Iterator it = info.subDeployments.iterator();
      while (it.hasNext())
      {
         DeploymentInfo sdi = (DeploymentInfo)it.next();
         try
         {
            ModuleType type = getModuleType(sdi);
            // Discard unknown ModuleTypes
            if (type != null)
            {
               String module = sdi.url.toString();
               boolean isRunning = (DeploymentState.STARTED == sdi.state);
               SerializableTargetModuleID child = new SerializableTargetModuleID(moduleID, module, type.getValue(), isRunning);
               moduleID.addChildTargetModuleID(child);
               fillChildrenTargetModuleID(child, sdi);
            }
         }
         catch (UnsupportedOperationException e)
         {
            if (log.isTraceEnabled())
               log.trace("Ignoring", e);
         }
      }
   }

   private ModuleType getModuleType(DeploymentInfo info)
   {
      if (info.deployer == null)
         return null;

      ModuleType type = null;
      ObjectName deployer = info.deployer.getServiceName();

      // Validate an ejb vs car based on the watch url
      if (deployer.equals(carDeployer))
      {
         type = ModuleType.CAR;
      }
      else if (deployer.equals(ejbDeployer))
      {
         type = ModuleType.EJB;
      }
      else if (deployer.equals(earDeployer))
      {
         type = ModuleType.EAR;
      }
      else if (deployer.equals(rarDeployer))
      {
         type = ModuleType.RAR;
      }
      else if (deployer.equals(warDeployer))
      {
         type = ModuleType.WAR;
      }

      return type;
   }

}
