/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009, Red Hat Middleware LLC, and others contributors as indicated
 * by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.soa.bpel.deployer;

import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.helpers.AbstractDeployer;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.deployers.vfs.spi.structure.VFSDeploymentUnit;

import org.jboss.logging.Logger;
import org.jboss.util.naming.NonSerializableFactory;
import org.jboss.virtual.VFSUtils;
import org.jboss.virtual.VirtualFile;

/**
 * This is the deployer for deploying BPEL (and associated artifacts) into
 * the runtime.
 * 
 * @author gbrown
 *
 */
public class BPELDeployer extends AbstractDeployer {

	public static final String BPELDeployerService="bpel/Deployer";

	public void deploy(DeploymentUnit du) throws DeploymentException {
				
		if (du instanceof VFSDeploymentUnit &&
					du.getSimpleName().equals(BPELDeploymentUnit.BPEL_DEPLOY_XML)) {		
			DeploymentUnit top=du;
			
			if (top.isTopLevel() == false) {
				top = du.getTopLevel();
			}
			
			logger.info("Deploy: "+top.getSimpleName()+" relativePath="+
					du.getRelativePath());
			
			VirtualFile root=((VFSDeploymentUnit)du).getRoot();
			
			logger.debug("Deployment descriptor file="+root);

			try {
				VirtualFile parent=((VFSDeploymentUnit)du).getRoot().getParent();
				
				logger.debug("Deployment unit dir="+parent);
			
				VirtualFile tmp=VFSUtils.explode(parent);
				
				java.net.URL url=VFSUtils.getCompatibleURL(tmp);
				
				logger.debug("Deployment exploded to dir="+tmp+" URL="+url);

				long lastModTime=0;
				
				if (top instanceof VFSDeploymentUnit) {
					lastModTime = ((VFSDeploymentUnit)top).getRoot().getLastModified();
				} else {
					logger.warn("Unable to determine last modified time for: "+top);
				}
				
				// TODO: Need to be a more unique name, as it is possible that more
				// than one deployment descriptor may exist in the same AS archive
				BPELDeploymentUnit unit=new BPELDeploymentUnit(top.getSimpleName(), lastModTime);
				
				java.io.File ddfile=new java.io.File(url.getFile(), BPELDeploymentUnit.BPEL_DEPLOY_XML);
				
				if (ddfile.exists() == false) {
					logger.error("Failed to deploy '"+ddfile+
							"', unable to locate deployment descriptor file");
				} else {
					unit.setDeploymentDescriptor(ddfile);
					
					synchronized(m_deploymentUnits) {
						m_deploymentUnits.put(unit.getName(), unit);
						
						if (m_listener != null) {
							m_listener.deploy(unit);
						}
					}
				}

			} catch (Exception e) {
				throw new DeploymentException("Failed to explode deployment unit '"+root+"'", e);
			}			
		}
	}
	
	@Override
	public void undeploy(DeploymentUnit du) {
		
		if (du instanceof VFSDeploymentUnit &&
				du.getSimpleName().equals(BPELDeploymentUnit.BPEL_DEPLOY_XML)) {		
			DeploymentUnit top=du;
			
			if (top.isTopLevel() == false) {
				top = du.getTopLevel();
			}
			
			logger.info("Undeploy: "+top.getSimpleName()+" relativePath="+
					du.getRelativePath());
			
			try {
				BPELDeploymentUnit prev=m_deploymentUnits.get(top.getSimpleName());
				
				// Undeploy if deployment unit no longer exists on the filesystem, OR
				// the last modified time of the cached deployment unit is older than
				// the last modified time of the current deployment unit, signifying
				// it has been updated and therefore must be undeployed first
				if (top instanceof VFSDeploymentUnit &&
						(((VFSDeploymentUnit)top).getRoot().exists() == false ||
								(prev != null && prev.getLastModified() < 
									((VFSDeploymentUnit)top).getRoot().getLastModified()))) {
			
					// TODO: Unique name - see deploy
					BPELDeploymentUnit unit=new BPELDeploymentUnit(top.getSimpleName(), 0);
					
					// NOTE: File required to provide path for use in ODE
					// process store impl. When this is replaced, this information
					// will not be required.
					java.io.File ddfile=new java.io.File(((VFSDeploymentUnit)du).getRoot().toURL().getFile());
					
					unit.setDeploymentDescriptor(ddfile);

					synchronized(m_deploymentUnits) {
						m_deploymentUnits.remove(unit.getName());
						
						if (m_listener != null) {
							m_listener.undeploy(unit);
						}
					}
				} else {
					logger.info("Not undeploying BPEL: "+top.getSimpleName()+" relativePath="+
							du.getRelativePath());
				}
			} catch(Exception e) {
				logger.error("Failed to undeploy '"+du+"'", e);
			}
		}
	}

	/**
	 * This method sets the deployment listener that will be
	 * informed when BPEL deployment units are detected.
	 * 
	 * @param l The BPEL deployment listener
	 */
	public void setDeploymentListener(BPELDeploymentListener l) {
		
		logger.debug("Set deployment listener: "+l);
		
		synchronized(m_deploymentUnits) {
			m_listener = l;
		
			// Notify listener of the currently deployed units
			java.util.Iterator<BPELDeploymentUnit> iter=m_deploymentUnits.values().iterator();
			while (iter.hasNext()) {
				l.deploy(iter.next());
			}
		}
	}
	
	/**
	 * This method returns the JNDI name for the BPEL deployer.
	 * 
	 * @return The JNDI name
	 */
    public String getJndiName() {
        return m_jndiName;
    }

    /**
     * This method sets the JNDI name for the BPEL deployer.
     * 
     * @param jndiName The JNDI name
     * @throws NamingException Failed to update JNDI with the supplied
     * 							name binding
     */
    public void setJndiName(String jndiName)throws NamingException {
        String oldName = m_jndiName;
        m_jndiName = jndiName;
        
        if (oldName != null) {
            unbind(oldName);
        }
        
        try {
            rebind();
        } catch(Exception e) {
            NamingException ne = new NamingException("Failed to update jndiName");
            ne.setRootCause(e);
            throw ne;
        }
    }
    
    private void rebind() throws NamingException {
        InitialContext rootCtx = new InitialContext();
        Name fullName = rootCtx.getNameParser("").parse(m_jndiName);
        log.debug("Binding BPEL deployer to name="+fullName);
        NonSerializableFactory.rebind(fullName, this, true);
    }

    private void unbind(String jndiName) {
        try {
            InitialContext rootCtx = new InitialContext();
            rootCtx.unbind(jndiName);
            NonSerializableFactory.unbind(jndiName);
        } catch(NamingException e) {
            log.error("Failed to unbind map", e);
        }
    }
    
	private final static Logger logger = Logger.getLogger(BPELDeployer.class);

	private String m_jndiName=null;
	private BPELDeploymentListener m_listener=null;
	private java.util.Map<String, BPELDeploymentUnit> m_deploymentUnits=
					new java.util.HashMap<String, BPELDeploymentUnit>();
}
