/*
 * 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.apache.ode.store;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.compiler.api.CompilationException;
//import org.apache.ode.bpel.extension.ExtensionValidator;
import org.apache.ode.bpel.iapi.ContextException;
import org.apache.ode.bpel.iapi.EndpointReferenceContext;
import org.apache.ode.bpel.iapi.ProcessState;
import org.apache.ode.bpel.iapi.ProcessStoreEvent;
import org.apache.ode.il.config.OdeConfigProperties;
import org.jboss.soa.bpel.deployer.BPELDeployer;
import org.jboss.soa.bpel.deployer.BPELDeploymentUnit;

public class RiftSawProcessStore extends ProcessStoreImpl
    implements org.jboss.soa.bpel.deployer.BPELDeploymentListener {

	private static final Log __log = LogFactory.getLog(RiftSawProcessStore.class);
	private DeployScheduler m_deployScheduler=new DeployScheduler();
	//private Map<QName, ExtensionValidator> m_extensionValidators = new HashMap<QName, ExtensionValidator>();

	public RiftSawProcessStore(EndpointReferenceContext eprContext, DataSource ds, String persistenceType, OdeConfigProperties props, boolean createDatamodel) {
		super(eprContext, ds, persistenceType, props, createDatamodel);
	}

	public void loadAll() {

		// Register listener with BPEL deployer
		try {
			InitialContext context=new InitialContext();

			BPELDeployer deployer=(BPELDeployer)context.lookup(BPELDeployer.BPELDeployerService);

			if (deployer != null) {
				deployer.setDeploymentListener(this);
			} else {
				__log.error("Deploy not found in JNDI with name '"+BPELDeployer.BPELDeployerService+"'");
			}
		} catch(Exception e) {
			__log.error("Failed to obtain BPEL deployer", e);
		}
	}

	public void deploy(final BPELDeploymentUnit bdu) {
		__log.debug("Deploy "+bdu);
		m_deployScheduler.add(bdu);
	}

	public void undeploy(BPELDeploymentUnit bdu) {
		__log.debug("Undeploy "+bdu);
		undeploy(bdu.getDeploymentDescriptor().getParentFile());
	}

	/*
	public void setExtensionValidators(Map<QName, ExtensionValidator> extensionValidators) {
		m_extensionValidators = extensionValidators;
		super.setExtensionValidators(extensionValidators);
	}
	
	public Map<QName, ExtensionValidator> getExtensionValidators() {
		return(m_extensionValidators);
	}
	*/
	
	public class DeployScheduler extends Thread {

		public DeployScheduler() {
		      setDaemon(true);
		      start();
		}

		public void run() {

			while (true) {

				try {
					doDeploy(m_units.take());
				} catch(Exception e) {
					__log.error("Unknown error", e);
				}
			}
		}

		protected void doDeploy(final BPELDeploymentUnit bdu) {
			__log.debug("Deploy scheduled: "+bdu.getDeploymentDescriptor().getParentFile());

			/* 
			 * NOTE: When using this approach to deployment, where the
			 * BPEL deployment is redeployed each time the server is started,
			 * it is necessary to 'disable' checking of the GUID associated
			 * with the process definitions - otherwise ODE will detect the
			 * different GUID, and remove the process definition and instances.
			 * This can be achieved using the 'ode.process.checkguid' boolean
			 * property.
			 */
			final ArrayList<ProcessConfImpl> loaded = new ArrayList<ProcessConfImpl>();

			// Check for the deployment unit associated with the name
			boolean deploy=exec(new ProcessStoreImpl.Callable<Boolean>() {
				public Boolean call(ConfStoreConnection conn) {
					boolean ret=false;
					DeploymentUnitDAO dudao = conn.getDeploymentUnit(bdu.getName());
					if (dudao == null)
						return true;

					try {
						String dir=bdu.getDeploymentDescriptor().getParentFile().getCanonicalPath();

						if (dudao.getDeploymentUnitDir() != null &&
								dudao.getDeploymentUnitDir().equals(dir) == false) {
							__log.debug("Updating deployunit directory from: "+dudao.getDeploymentUnitDir()+" to: "+dir);
							dudao.setDeploymentUnitDir(dir);
						}

						// Check if process has changed, and needs to be redeployed
						if (bdu.getLastModified() > dudao.getDeployDate().getTime()) {
							ret = true;
						} else {
							__log.debug("Re-compiling: "+bdu.getDeploymentDescriptor().getParentFile());

							DeploymentUnitDir du=new DeploymentUnitDir(bdu.getDeploymentDescriptor().getParentFile());

							// Create the DU and compile it before acquiring lock.
							//du.setExtensionValidators(getExtensionValidators());
							try {
								du.compile();
							} catch (CompilationException ce) {
								String errmsg = "Failed to compile deployment unit '"+
										bdu.getDeploymentDescriptor().getParentFile()+"'";
								__log.error(errmsg, ce);
								throw new ContextException(errmsg, ce);
							}

							loaded.addAll(load(dudao));
						}

					} catch(Throwable e) {
						__log.error("Failed to update deployment unit dir", e);
					}

					return ret;
				}
			});

			if (deploy) {
				__log.debug("Deploy new version: "+bdu.getDeploymentDescriptor().getParentFile());

				deploy(bdu.getDeploymentDescriptor().getParentFile());

			} else {
				__log.debug("Trigger Integration Layer to use existing version: "+bdu.getDeploymentDescriptor().getParentFile());

				// Just load and notify IL
				for (ProcessConfImpl p : loaded) {
					try {
						fireStateChange(p.getProcessId(), p.getState(), p.getDeploymentUnit().getName());
					} catch (Exception except) {
						__log.error("Error while activating process: pid=" + p.getProcessId() + " package="+p.getDeploymentUnit().getName(), except);
					}
				}
			}
		}

		/**
		 * This method is copied from the ODE ProcessStoreImpl, as its implementation is private.
		 * 
		 * @param processId
		 * @param state
		 * @param duname
		 */
		private void fireStateChange(QName processId, ProcessState state, String duname) {
			switch (state) {
			case ACTIVE:
				fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.ACTVIATED, processId, duname));
				break;
			case DISABLED:
				fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.DISABLED, processId, duname));
				break;
			case RETIRED:
				fireEvent(new ProcessStoreEvent(ProcessStoreEvent.Type.RETIRED, processId, duname));
				break;
			}
		}
    
		public void add(BPELDeploymentUnit bdu) {
			try {
				m_units.put(bdu);
			} catch(Exception e) {
				__log.error(e);
			}
		}

		private java.util.concurrent.SynchronousQueue<BPELDeploymentUnit> m_units=
			new java.util.concurrent.SynchronousQueue<BPELDeploymentUnit>();
	}
}
