/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.container.component.deployment;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.slee.ComponentID;
import javax.slee.EventTypeID;
import javax.slee.SLEEException;
import javax.slee.SbbID;
import javax.slee.ServiceID;
import javax.slee.management.AlreadyDeployedException;
import javax.slee.management.DependencyException;
import javax.slee.management.DeployableUnitID;
import javax.slee.management.DeploymentException;
import javax.slee.management.LibraryID;
import javax.slee.profile.ProfileSpecificationID;
import javax.slee.resource.ResourceAdaptorID;
import javax.slee.resource.ResourceAdaptorTypeID;

import org.apache.log4j.Logger;
import org.mobicents.slee.container.component.AbstractSleeComponent;
import org.mobicents.slee.container.component.ComponentManagementImpl;
import org.mobicents.slee.container.component.ComponentRepository;
import org.mobicents.slee.container.component.EventTypeComponentImpl;
import org.mobicents.slee.container.component.ProfileSpecificationComponentImpl;
import org.mobicents.slee.container.component.ResourceAdaptorComponentImpl;
import org.mobicents.slee.container.component.ResourceAdaptorTypeComponentImpl;
import org.mobicents.slee.container.component.SbbComponentImpl;
import org.mobicents.slee.container.component.ServiceComponentImpl;
import org.mobicents.slee.container.component.SleeComponent;
import org.mobicents.slee.container.component.UsageParametersInterfaceDescriptor;
import org.mobicents.slee.container.component.deployment.classloading.ComponentClassLoaderImpl;
import org.mobicents.slee.container.component.deployment.classloading.URLClassLoaderDomainImpl;
import org.mobicents.slee.container.component.deployment.jaxb.descriptors.DeployableUnitDescriptorFactoryImpl;
import org.mobicents.slee.container.component.deployment.jaxb.descriptors.DeployableUnitDescriptorImpl;
import org.mobicents.slee.container.component.deployment.jaxb.descriptors.common.MUsageParametersInterface;
import org.mobicents.slee.container.component.du.DeployableUnitBuilder;
import org.mobicents.slee.container.component.sbb.SbbLocalInterfaceDescriptor;

public class DeployableUnitBuilderImpl implements DeployableUnitBuilder {

	private static final Logger logger = Logger
			.getLogger(DeployableUnitBuilderImpl.class);

	private final DeployableUnitJarComponentBuilder duComponentBuilder;
	private final DeployableUnitServiceComponentBuilder duServiceComponentBuilder;

	private final ComponentManagementImpl componentManagement;

	/**
	 *
	 */
	public DeployableUnitBuilderImpl(ComponentManagementImpl componentManagement) {
		this.componentManagement = componentManagement;
		this.duComponentBuilder = new DeployableUnitJarComponentBuilder(componentManagement);
		this.duServiceComponentBuilder = new DeployableUnitServiceComponentBuilder(componentManagement);
	}

	/**
	 * Installs a JAIN SLEE DU.
	 *
	 * @param sourceUrl
	 *            the original URL of the deployable unit jar
	 * @param deploymentRoot
	 *            the root dir where this du will be installed
	 * @param componentRepository
	 *            the repository to retrieve components
	 * @return
	 * @throws DeploymentException
	 */
	public DeployableUnitImpl build(String url, File deploymentRoot,
			ComponentRepository componentRepository)
			throws DeploymentException, AlreadyDeployedException,
			MalformedURLException {

		if (logger.isDebugEnabled()) {
			logger.debug("Building DU from " + url);
		}

		DeployableUnitID deployableUnitID = new DeployableUnitID(url);

		if (deploymentRoot == null) {
			throw new NullPointerException("null deploymentRoot");
		}

		URL sourceUrl = new URL(url);

		// support remote deployment
		if(sourceUrl.getProtocol().equals("http") || sourceUrl.getProtocol().equals("https")) {
		  try {
		    // Fetch the remote file to a temporary location
		    File downloadedFile = downloadRemoteDU(sourceUrl, deploymentRoot);

		    // Update the pointers from URL and String
		    sourceUrl = downloadedFile.toURI().toURL();
		    url = sourceUrl.toString();
		  }
		  catch (Exception e) {
		    throw new DeploymentException("Failed to retrieve remote DU file : " + sourceUrl.getFile(), e);
		  }
		}

		// create jar file
		JarFile deployableUnitJar = null;
		try {
			deployableUnitJar = new JarFile(sourceUrl.getFile());
		} catch (IOException e) {
			throw new DeploymentException(
					"Failed to open DU file as JAR file: "
							+ sourceUrl.getFile(), e);
		}

		// get and parse du descriptor
		JarEntry duXmlEntry = deployableUnitJar
				.getJarEntry("META-INF/deployable-unit.xml");
		if (duXmlEntry == null) {
			throw new DeploymentException(
					"META-INF/deployable-unit.xml was not found in "
							+ deployableUnitJar.getName());
		}
		DeployableUnitDescriptorFactoryImpl descriptorFactory = componentManagement.getComponentDescriptorFactory().getDeployableUnitDescriptorFactory();
		DeployableUnitDescriptorImpl deployableUnitDescriptor = null;
		try {
			deployableUnitDescriptor = descriptorFactory
					.parse(deployableUnitJar.getInputStream(duXmlEntry));
		} catch (IOException e) {
			try {
				deployableUnitJar.close();
			} catch (IOException e1) {
				logger.error(e.getMessage(), e);
			}
			throw new DeploymentException(
					"Failed to get DU descriptor DU inputstream from JAR file "
							+ sourceUrl.getFile(), e);
		}

		// create the du dir
		File deploymentDir = createTempDUDeploymentDir(deploymentRoot,
				deployableUnitID);

		// build du object
		DeployableUnitImpl deployableUnit = null;

		try {

			deployableUnit = new DeployableUnitImpl(deployableUnitID,
					deployableUnitDescriptor, componentRepository,
					deploymentDir);

			// build each du jar component
			for (String jarFileName : deployableUnitDescriptor.getJarEntries()) {
				for (SleeComponent sleeComponent : duComponentBuilder
						.buildComponents(jarFileName, deployableUnitJar,
								deployableUnit.getDeploymentDir())) {
					sleeComponent.setDeployableUnit(deployableUnit);
					if (componentRepository.isInstalled(sleeComponent
							.getComponentID())) {
						throw new AlreadyDeployedException("Component "
								+ sleeComponent.getComponentID()
								+ " already deployed");
					}
					sleeComponent.setDeploymentUnitSource(jarFileName);
				}
			}

			// build each du service component
			for (String serviceDescriptorFileName : deployableUnitDescriptor
					.getServiceEntries()) {
				for (ServiceComponentImpl serviceComponent : duServiceComponentBuilder
						.buildComponents(serviceDescriptorFileName,
								deployableUnitJar)) {
					serviceComponent.setDeployableUnit(deployableUnit);
					if (componentRepository.isInstalled(serviceComponent
							.getComponentID())) {
						throw new AlreadyDeployedException("Component "
								+ serviceComponent.getComponentID()
								+ " already deployed");
					}
					// set the direct reference to the sbb component
					serviceComponent.setRootSbbComponent(deployableUnit
							.getDeployableUnitRepository().getComponentByID(
									serviceComponent.getDescriptor()
											.getRootSbbID()));
					serviceComponent
							.setDeploymentUnitSource(serviceDescriptorFileName);
				}
			}

			// get a set with all components of the DU
			Set<SleeComponent> duComponentsSet = deployableUnit
					.getDeployableUnitComponents();

			// now that all components are built we need to
			for (SleeComponent sleeComponent : duComponentsSet) {
				// check if all
				// dependencies are available
				checkDependencies(sleeComponent, deployableUnit);
				// build its class loader
				createClassLoader((AbstractSleeComponent) sleeComponent);
			}

			// load the provided classes for the component
			for (SleeComponent sleeComponent : duComponentsSet) {
				loadAndSetNonGeneratedComponentClasses((AbstractSleeComponent) sleeComponent);
			}

			//boolean secEnabled = SleeContainer.isSecurityEnabled();
			// validate each component
			for (SleeComponent sleeComponent : duComponentsSet) {
				ClassLoader componentClassLoader = sleeComponent
				.getClassLoader();
				ClassLoader oldClassLoader = Thread.currentThread()
				.getContextClassLoader();
				try {
					if (componentClassLoader != null) {
						Thread.currentThread().setContextClassLoader(
								componentClassLoader);
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Validating " + sleeComponent);
					}
					if (!sleeComponent.validate()) {
						throw new DeploymentException(
								sleeComponent.toString()
								+ " validation failed, check logs for errors found");
					}
					//Make permissions object, this instruments codebase etc, and store POJOs in component.
					//if(secEnabled)
					//{
						sleeComponent.processSecurityPermissions();
					//}
				} catch (Throwable e) {
					throw new DeploymentException("failed to validate "
							+ sleeComponent, e);
				} finally {
					if (componentClassLoader != null) {
						Thread.currentThread().setContextClassLoader(
								oldClassLoader);
					}
				}
			}

			try {
				deployableUnitJar.close();
			} catch (IOException e) {
				logger.error("failed to close deployable jar from " + url, e);
			}

			return deployableUnit;
		} catch (Throwable e) {
			if (deployableUnit == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Deleting deployable unit temp dir "
							+ deploymentDir);
				}
				deploymentDir.delete();
			}
			if (e instanceof DeploymentException) {
				throw (DeploymentException) e;
			} else if (e instanceof AlreadyDeployedException) {
				throw (AlreadyDeployedException) e;
			} else {
				throw new DeploymentException(
						"failed to build deployable unit", e);
			}
		}
	}

	public static void createClassLoader(AbstractSleeComponent component) {

		URLClassLoaderDomainImpl classLoaderDomain = component.getClassLoaderDomain();
		if (classLoaderDomain != null) {
			// add all dependency domains to the component domain
			for (ComponentID componentID : component.getDependenciesSet()) {
				AbstractSleeComponent dependency = null;
				if (componentID instanceof EventTypeID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(EventTypeID) componentID);
				} else if (componentID instanceof LibraryID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(LibraryID) componentID);
                                        //fixes https://github.com/RestComm/jain-slee/issues/49
                                        //add same preferences to dependency, so order is respected
                                        Set<String> preferredPackages1 = dependency.getClassLoaderDomain().getPreferredPackages();
                                        for (String pack : classLoaderDomain.getPreferredPackages()) {
                                            preferredPackages1.add(pack);
                                            if (logger.isTraceEnabled())  {
                                                logger.debug("adding preferred package to dependecy:" + pack);
                                            }
                                        }
				} else if (componentID instanceof ProfileSpecificationID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(ProfileSpecificationID) componentID);
				} else if (componentID instanceof ResourceAdaptorID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(ResourceAdaptorID) componentID);
				} else if (componentID instanceof ResourceAdaptorTypeID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(ResourceAdaptorTypeID) componentID);
				} else if (componentID instanceof SbbID) {
					dependency = (AbstractSleeComponent) component.getDeployableUnit()
							.getDeployableUnitRepository().getComponentByID(
									(SbbID) componentID);
				}
				if (dependency != null && dependency.getClassLoaderDomain() != null) {
					classLoaderDomain.addDirectDependency(dependency.getClassLoaderDomain());
					if (logger.isTraceEnabled()) {
						logger.trace(classLoaderDomain+" added "+dependency.getClassLoaderDomain()+ " in dependencies");
					}
				}
				else {
					throw new SLEEException(component.toString()+" dependency "+componentID+" not found or doesn't have class loading domain");
				}
			}
			// create class loader
			component.setClassLoader(new ComponentClassLoaderImpl(component.getComponentID(),classLoaderDomain));
		}

	}

	/**
	 * Loads all non SLEE generated classes from the component class loader to
	 * the component, those will be needed for validation or runtime purposes
	 *
	 * @param sleeComponent
	 * @throws DeploymentException
	 */
	private void loadAndSetNonGeneratedComponentClasses(
			AbstractSleeComponent sleeComponent) throws DeploymentException {

		if (logger.isDebugEnabled()) {
			logger.debug("Loading classes for component " + sleeComponent);
		}
		ClassLoader oldClassLoader = Thread.currentThread()
				.getContextClassLoader();
		try {
			ComponentClassLoaderImpl componentClassLoader = sleeComponent
					.getClassLoader();
			if (componentClassLoader != null) {
				// change class loader
				Thread.currentThread().setContextClassLoader(
						componentClassLoader);
				// load and set non generated component classes
				if (sleeComponent instanceof EventTypeComponentImpl) {
					EventTypeComponentImpl component = (EventTypeComponentImpl) sleeComponent;
					Class<?> eventTypeClass = componentClassLoader
							.loadClass(component.getDescriptor()
									.getEventClassName());
					component.setEventTypeClass(eventTypeClass);
				} else if (sleeComponent instanceof ProfileSpecificationComponentImpl) {
					ProfileSpecificationComponentImpl component = (ProfileSpecificationComponentImpl) sleeComponent;
					Class<?> profileCmpInterfaceClass = componentClassLoader
							.loadClass(component.getDescriptor()
									.getProfileCMPInterface()
									.getProfileCmpInterfaceName());
					component
							.setProfileCmpInterfaceClass(profileCmpInterfaceClass);
					if (component.getDescriptor()
							.getProfileLocalInterface() != null) {
						Class<?> profileLocalInterfaceClass = componentClassLoader
								.loadClass(component.getDescriptor()
										.getProfileLocalInterface()
										.getProfileLocalInterfaceName());
						component
								.setProfileLocalInterfaceClass(profileLocalInterfaceClass);
					}
					if (component.getDescriptor()
							.getProfileManagementInterface() != null) {
						Class<?> profileManagementInterfaceClass = componentClassLoader
								.loadClass(component.getDescriptor()
										.getProfileManagementInterface());
						component
								.setProfileManagementInterfaceClass(profileManagementInterfaceClass);
					}
					if (component.getDescriptor()
							.getProfileAbstractClass() != null) {
						boolean decoratedClass = new ProfileAbstractClassDecorator(component).decorateAbstractClass();
						Class<?> profileAbstractClass = null;
						if (decoratedClass) {
							// need to ensure we load the class from disk, not one coming from SLEE shared class loading domain
							profileAbstractClass = componentClassLoader
							.loadClassLocally(component.getDescriptor()
									.getProfileAbstractClass()
									.getProfileAbstractClassName());
						}
						else {
							profileAbstractClass = componentClassLoader
							.loadClass(component.getDescriptor()
									.getProfileAbstractClass()
									.getProfileAbstractClassName());
						}
						component.setProfileAbstractClass(profileAbstractClass);
					}
					String mProfileTableInterface = component
							.getDescriptor()
							.getProfileTableInterface();
					if (mProfileTableInterface != null) {
						component
								.setProfileTableInterfaceClass(componentClassLoader
										.loadClass(mProfileTableInterface));
					}
					UsageParametersInterfaceDescriptor mUsageParametersInterface = component
							.getDescriptor()
							.getProfileUsageParameterInterface();
					if (mUsageParametersInterface != null) {
						component
								.setUsageParametersInterface(componentClassLoader
										.loadClass(mUsageParametersInterface
												.getUsageParametersInterfaceName()));
					}
				} else if (sleeComponent instanceof ResourceAdaptorComponentImpl) {
					ResourceAdaptorComponentImpl component = (ResourceAdaptorComponentImpl) sleeComponent;
					Class<?> resourceAdaptorClass = componentClassLoader
							.loadClass(component.getDescriptor()
									.getResourceAdaptorClassName());
					component.setResourceAdaptorClass(resourceAdaptorClass);
					MUsageParametersInterface mUsageParametersInterface = component
							.getDescriptor()
							.getResourceAdaptorUsageParametersInterface();
					if (mUsageParametersInterface != null) {
						component
								.setUsageParametersInterface(componentClassLoader
										.loadClass(mUsageParametersInterface
												.getUsageParametersInterfaceName()));
					}
				} else if (sleeComponent instanceof ResourceAdaptorTypeComponentImpl) {
					ResourceAdaptorTypeComponentImpl component = (ResourceAdaptorTypeComponentImpl) sleeComponent;
					if (component.getDescriptor()
							.getActivityContextInterfaceFactoryInterface() != null) {
						Class<?> activityContextInterfaceFactoryInterface = componentClassLoader
								.loadClass(component
										.getDescriptor()
										.getActivityContextInterfaceFactoryInterface());
						component
								.setActivityContextInterfaceFactoryInterface(activityContextInterfaceFactoryInterface);
					}
				} else if (sleeComponent instanceof SbbComponentImpl) {
					SbbComponentImpl component = (SbbComponentImpl) sleeComponent;
					// before loading the abstract class, we may have to decorate it
					boolean decoratedClass = new SbbAbstractClassDecorator(component).decorateAbstractSbb();
					Class<?> abstractSbbClass = null;
					if (decoratedClass) {
						// need to ensure we load the class from disk, not one coming from SLEE shared class loading domain
						 abstractSbbClass = componentClassLoader
							.loadClassLocally(component.getDescriptor()
									.getSbbAbstractClass()
									.getSbbAbstractClassName());
					}
					else {
						 abstractSbbClass = componentClassLoader
							.loadClass(component.getDescriptor()
									.getSbbAbstractClass()
									.getSbbAbstractClassName());
					}
					component.setAbstractSbbClass(abstractSbbClass);
					SbbLocalInterfaceDescriptor mSbbLocalInterface = component
							.getDescriptor()
							.getSbbLocalInterface();
					if (mSbbLocalInterface != null) {
						component
								.setSbbLocalInterfaceClass(componentClassLoader
										.loadClass(mSbbLocalInterface
												.getSbbLocalInterfaceName()));
					}
					String mSbbActivityContextInterface = component
							.getDescriptor()
							.getSbbActivityContextInterface();
					if (mSbbActivityContextInterface != null) {
						component
								.setActivityContextInterface(componentClassLoader
										.loadClass(mSbbActivityContextInterface));
					}
					UsageParametersInterfaceDescriptor mUsageParametersInterface = component
							.getDescriptor()
							.getSbbUsageParametersInterface();
					if (mUsageParametersInterface != null) {
						component
								.setUsageParametersInterface(componentClassLoader
										.loadClass(mUsageParametersInterface
												.getUsageParametersInterfaceName()));
					}
				}
			}
		} catch (NoClassDefFoundError e) {
			throw new DeploymentException("Component "
					+ sleeComponent.getComponentID()
					+ " requires a class that was not found", e);
		} catch (ClassNotFoundException e) {
			throw new DeploymentException("Component "
					+ sleeComponent.getComponentID()
					+ " requires a class that was not found", e);
		} finally {
			Thread.currentThread().setContextClassLoader(oldClassLoader);
		}
	}

	/**
	 * Checks if all dependencies of a DU component exists
	 *
	 * @param sleeComponent
	 * @param deployableUnit
	 * @throws DependencyException
	 *             if a dependency is missing
	 */
	private void checkDependencies(SleeComponent sleeComponent,
			DeployableUnitImpl deployableUnit) throws DependencyException {
		for (ComponentID componentID : sleeComponent.getDependenciesSet()) {
			if (componentID instanceof EventTypeID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((EventTypeID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof LibraryID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((LibraryID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof ProfileSpecificationID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((ProfileSpecificationID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof ResourceAdaptorID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((ResourceAdaptorID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof ResourceAdaptorTypeID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((ResourceAdaptorTypeID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof SbbID) {
				if (deployableUnit.getDeployableUnitRepository()
						.getComponentByID((SbbID) componentID) == null) {
					throw new DependencyException(
							"Component "
									+ sleeComponent.getComponentID()
									+ " depends on "
									+ componentID
									+ " which is not available in the component repository or in the deployable unit");
				}
			} else if (componentID instanceof ServiceID) {
				throw new SLEEException(
						"Component "
								+ sleeComponent.getComponentID()
								+ " depends on a service component "
								+ componentID
								+ " which is not available in the component repository or in the deployable unit");
			}
		}
	}

	/**
	 * Creates the directory that will be used for unpacking the child jars for
	 * a given DU.
	 *
	 * @param rootDir
	 * @param sourceUrl
	 * @throws SLEEException
	 *             if the dir can't be created
	 * @return
	 */
	private File createTempDUDeploymentDir(File deploymentRoot,
			DeployableUnitID deployableUnitID) {
		try {
			// first create a dummy file to gurantee uniqueness. I would have
			// been nice if the File class had a createTempDir() method
			// IVELIN -- do not use jarName here because windows cannot see the
			// path (exceeds system limit)
			File tempFile = File.createTempFile("restcomm-slee-du-", "",
					deploymentRoot);
			File tempDUDeploymentDir = new File(tempFile.getAbsolutePath()
					+ "-contents");
			if (!tempDUDeploymentDir.exists()) {
				tempDUDeploymentDir.mkdirs();
			} else {
				throw new SLEEException(
						"Dir "
								+ tempDUDeploymentDir
								+ " already exists, unable to create deployment dir for DU "
								+ deployableUnitID);
			}
			tempFile.delete();
			return tempDUDeploymentDir;
		} catch (IOException e) {
			throw new SLEEException("Failed to create deployment dir for DU "
					+ deployableUnitID, e);
		}
	}

	private File downloadRemoteDU(URL duURL, File deploymentRoot) throws Exception {
	  InputStream in = null;
	  OutputStream out = null;

	  try {
	    String fileWithPath = duURL.getFile();
	    int start = fileWithPath.lastIndexOf('/') + 1;
	    String filename = Math.abs((long)duURL.hashCode()) + "_" + fileWithPath.substring(start, fileWithPath.length());

	    File tempFile = new File(deploymentRoot, filename);

	    out = new BufferedOutputStream(new FileOutputStream(tempFile));
	    URLConnection conn = duURL.openConnection();
	    in = conn.getInputStream();

	    // Get the data
	    byte[] buffer = new byte[1024];
	    int numRead;
	    while ((numRead = in.read(buffer)) != -1) {
	      out.write(buffer, 0, numRead);
	    }
	    // Done! Successful.
	    return tempFile;
	  }
	  finally {
	    try {
	      if (in != null) {
	        in.close();
	        in = null;
	      }
	      if (out != null) {
	        out.close();
	        out = null;
	      }
	    }
	    catch (IOException ioe) {
	      // Shouldn't happen, let's ignore.
	    }
	  }
	}
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.container.event;

import java.util.concurrent.ConcurrentHashMap;

import javax.slee.Address;
import javax.slee.EventTypeID;
import javax.slee.ServiceID;

import org.apache.log4j.Logger;
import org.mobicents.slee.container.activity.ActivityContext;

/**
 * @author martins
 *
 */
public class DefaultEventContextFactoryDataSource implements EventContextFactoryDataSource {

	private final static Logger logger = Logger.getLogger(DefaultEventContextFactoryDataSource.class);

	private final ConcurrentHashMap<EventContextHandle, EventContext> dataSource = new ConcurrentHashMap<EventContextHandle, EventContext>();

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContextFactoryDataSource#addEventContext(org.mobicents.slee.container.event.EventContextHandle, org.mobicents.slee.container.event.EventContext)
	 */
	public void addEventContext(EventContextHandle handle,
			EventContext eventContext) {
		if(logger.isDebugEnabled()) {
			logger.debug("Adding event context "+eventContext+" to datasource. Event context handle is "+handle);
		}
		dataSource.put(handle, eventContext);
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContextFactoryDataSource#getEventContext(org.mobicents.slee.container.event.EventContextHandle)
	 */
	public EventContext getEventContext(EventContextHandle handle) {
		return dataSource.get(handle);
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContextFactoryDataSource#newEventContextData(javax.slee.EventTypeID, java.lang.Object, org.mobicents.slee.container.activity.ActivityContext, javax.slee.Address, javax.slee.ServiceID, org.mobicents.slee.container.event.EventProcessingSucceedCallback, org.mobicents.slee.container.event.EventProcessingFailedCallback, org.mobicents.slee.container.event.EventUnreferencedCallback, org.mobicents.slee.container.event.EventReferencesHandler)
	 */
	public EventContextData newEventContextData(EventTypeID eventTypeId,
			Object event, ActivityContext ac, Address address,
			ServiceID serviceID,
			EventProcessingSucceedCallback succeedCallback,
			EventProcessingFailedCallback failedCallback,
			EventUnreferencedCallback unreferencedCallback,
			ReferencesHandler referencesHandler) {
		return new DefaultEventContextData(eventTypeId, event, ac, address, serviceID, succeedCallback, failedCallback, unreferencedCallback, referencesHandler);
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContextFactoryDataSource#removeEventContext(org.mobicents.slee.container.event.EventContextHandle)
	 */
	public void removeEventContext(EventContextHandle handle) {
		if(logger.isDebugEnabled()) {
			logger.debug("Removing event context from datasource. Event context handle is "+handle);
		}
		dataSource.remove(handle);
	}

	@Override
	public String toString() {
		return "DefaultEventContextFactoryDataSource[ "+(dataSource.size() > 20 ? dataSource.size() : dataSource.values())+" ]";
	}

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.runtime.eventrouter.routingtask;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;

import javax.slee.EventTypeID;
import javax.slee.SLEEException;
import javax.slee.resource.FailureReason;
import javax.transaction.SystemException;
import javax.transaction.Transaction;

import org.apache.log4j.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.SleeThreadLocals;
import org.mobicents.slee.container.activity.ActivityContext;
import org.mobicents.slee.container.activity.LocalActivityContext;
import org.mobicents.slee.container.component.event.EventTypeComponent;
import org.mobicents.slee.container.component.service.ServiceComponent;
import org.mobicents.slee.container.event.EventContext;
import org.mobicents.slee.container.eventrouter.EventRoutingTask;
import org.mobicents.slee.container.eventrouter.SbbInvocationState;
import org.mobicents.slee.container.sbb.SbbObject;
import org.mobicents.slee.container.sbbentity.SbbEntity;
import org.mobicents.slee.container.sbbentity.SbbEntityID;
import org.mobicents.slee.container.transaction.SleeTransactionManager;
import org.mobicents.slee.container.transaction.TransactionalAction;

public class EventRoutingTaskImpl implements EventRoutingTask {

	private final static Logger logger = Logger.getLogger(EventRoutingTaskImpl.class);

	private final SleeContainer container;

	/**
	 *
	 * @author martins
	 *
	 */
	private static enum RoutingPhase { DELIVERING , DELIVERED };

	/**
	 * processing logic to handle a tx rollback
	 */
	private static final HandleRollback handleRollback = new HandleRollback();

	/**
	 * processing logic to handle a sbb tx rollback
	 */
	private static final HandleSbbRollback handleSbbRollback = new HandleSbbRollback();

	/**
	 * processing logic to handle an event as initial
	 */
	private static final InitialEventProcessor initialEventProcessor = new InitialEventProcessor();

	/**
	 * processing logic to retrieve next sbb entity to handle an event on an activity
	 */
	private static final NextSbbEntityFinder nextSbbEntityFinder = new NextSbbEntityFinder();

	/**
	 *
	 */
	private final EventContext eventContext;

	/**
	 * indicates which phase we are in routing of event
	 */
	private RoutingPhase routingPhase = RoutingPhase.DELIVERING;

	/**
	 *
	 * @param eventContext
	 */
	public EventRoutingTaskImpl(EventContext eventContext, SleeContainer sleeContainer) {
		this.eventContext = eventContext;
		this.container = sleeContainer;
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.eventrouter.EventRoutingTask#getEventContext()
	 */
	public EventContext getEventContext() {
		return eventContext;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged(
				new PrivilegedAction<Object>() {
					public Object run()	{
						routeQueuedEvent();
						return null;
					}
				}
			);
		}
		else {
			routeQueuedEvent();
		}
	}

	/**
	 * Delivers to SBBs an event off the top of the queue for an activity
	 * context
	 *
	 * @param de
	 * @return true if the event processing suceeds
	 */
	private void routeQueuedEvent() {

		boolean debugLogging = logger.isDebugEnabled();

		final SleeTransactionManager txMgr = container.getTransactionManager();

		ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();

		try {
			final LocalActivityContext lac = eventContext.getLocalActivityContext();
			final EventRoutingTask activityCurrentEventRoutingTask = lac.getCurrentEventRoutingTask();
			EventContext activityCurrentEventContext = activityCurrentEventRoutingTask == null ? null : activityCurrentEventRoutingTask.getEventContext();
			if (activityCurrentEventContext == null) {
				if (debugLogging)
					logger.debug("\n\n\nStarting routing for "+eventContext);

				activityCurrentEventContext = eventContext;
				// activity has no event routing task set
				lac.setCurrentEventRoutingTask(this);

				EventTypeComponent eventTypeComponent = container.getComponentRepository().getComponentByID(eventContext.getEventTypeId());
				if (eventTypeComponent == null) {
					logger.error("Unable to route event, the related component is not installed");
					eventContext.eventProcessingFailed(FailureReason.OTHER_REASON);
					return;
				}

				// setup initial event processing
				if (eventContext.getService() != null) {
					ServiceComponent serviceComponent = container.getComponentRepository().getComponentByID(eventContext.getService());
					if (eventTypeComponent.getActiveServicesWhichDefineEventAsInitial().contains(serviceComponent)) {
						activityCurrentEventContext.getActiveServicesToProcessEventAsInitial().add(serviceComponent);
					}
				}
				else {
					Set<ServiceComponent> services = eventTypeComponent.getActiveServicesWhichDefineEventAsInitial();
					if (services != null) {
						activityCurrentEventContext.getActiveServicesToProcessEventAsInitial().addAll(services);
					}
				}
			}
			else {
				if (activityCurrentEventContext.isSuspendedNotTransacted()) {
					if (debugLogging)
						logger.debug("\n\n\nFreezing (due to suspended context) the routing for "+eventContext);
					activityCurrentEventContext.barrierEvent(eventContext);
					return;
				}
				else {
					if (debugLogging)
						logger.debug("\n\n\nResuming the routing for"+eventContext);
					// needed to ensure tests/events/eventcontext/Test1108039Test.xml passes
					// the test must be fixed
					Thread.sleep(10);
				}
			}

			LinkedList<ServiceComponent> serviceComponents = activityCurrentEventContext.getActiveServicesToProcessEventAsInitial();
			if (debugLogging)
				logger.debug("Active services which define "+eventContext.getEventTypeId()+" as initial: "
						+ serviceComponents);

			boolean finished;
			SbbEntityID rootSbbEntityId;
			ClassLoader invokerClassLoader;
			SbbEntity sbbEntity;
			SbbObject sbbObject;
			ServiceComponent serviceComponent;
			boolean keepSbbEntityIfTxRollbacks;
			NextSbbEntityFinder.Result nextSbbEntityFinderResult;
			ActivityContext ac = null;
			Exception caught = null;
			Set<SbbEntityID> sbbEntitiesThatHandledCurrentEvent;
			boolean deliverEvent;
			boolean rollbackTx;
			boolean rollbackOnlySet;

			boolean sbbHandledEvent = false;

			do {

				// For each SBB that is attached to this activity context and active service to process event as initial

				rootSbbEntityId = null;
				invokerClassLoader = null;
				sbbEntity = null;
				sbbObject = null;
				serviceComponent = null;
				keepSbbEntityIfTxRollbacks = false;
				nextSbbEntityFinderResult = null;
				finished = false;
				ac = null;
				caught = null;
				deliverEvent = true;
				rollbackTx = true;
				rollbackOnlySet = false;

				try {

					/*
					 * Start of SLEE Originated Invocation Sequence
					 * ============================================== This
					 * sequence consists of either: 1) One "Op Only" SLEE
					 * Originated Invocation - in the case that it's a
					 * straightforward event routing for the sbb entity. 2) One
					 * "Op and Remove" SLEE Originated Invocation - in the case
					 * it's an event routing to a root sbb entity that ends up
					 * in a remove to the same entity since the attachment count
					 * goes to zero after the event invocation 3) One "Op Only"
					 * followed by one "Remove Only" SLEE Originated Invocation -
					 * in the case it's an event routing to a non-root sbb
					 * entity that ends up in a remove to the corresponding root
					 * entity since the root attachment count goes to zero after
					 * the event invocation Each Invocation Sequence is handled
					 * in it's own transaction. All exception handling for each
					 * invocation sequence is handled here. Any exceptions that
					 * propagate up aren't necessary to be caught. -Tim
					 */

					// If this fails then we propagate up since there's nothing to roll-back anyway

					txMgr.begin();

					sbbEntitiesThatHandledCurrentEvent = activityCurrentEventContext.getSbbEntitiesThatHandledEvent();

					try {

						// load ac
						ac = container.getActivityContextFactory().getActivityContext(eventContext.getActivityContextHandle(),true);
						if (ac == null) {
							logger.error("Unable to route event "+eventContext+". The activity context is gone");
							try {
								eventContext.eventProcessingFailed(FailureReason.OTHER_REASON);
								txMgr.commit();
							}
							catch (Throwable e) {
								logger.error(e.getMessage(),e);
							}
							return;
						}

						if (routingPhase == RoutingPhase.DELIVERING) {

							// calculate highest priority attached sbb entity that needs to handle the event
							try {
								nextSbbEntityFinderResult = nextSbbEntityFinder.next(ac, eventContext, sbbEntitiesThatHandledCurrentEvent,container);
							} catch (Exception e) {
								logger.warn("Failed to find next sbb entity to deliver the event "+eventContext+" in "+ac.getActivityContextHandle(), e);
							}

							// calculate highest priority service to process event as initial
							if (!serviceComponents.isEmpty()) {
								serviceComponent = serviceComponents.getFirst();
							}

							// compare highest priority sbb entity already attached with highest priority service to process event as initial
							if (nextSbbEntityFinderResult == null) {
								if (serviceComponent != null) {
									if (debugLogging)
										logger.debug("No sbb entities attached, which didn't already route the event, but "+serviceComponent+" defines the event type as initial, starting initial event processing");
									// let the service process event as initial
									serviceComponents.removeFirst();
									sbbEntity = initialEventProcessor.processInitialEvent(serviceComponent, eventContext, container, ac);
									// if service returned no sbb entity and there are no more service components we are done
									if (sbbEntity == null && serviceComponents.isEmpty()) {
										finished = true;
									}
								}
								else {
									// nothing else to route
									finished = true;
									if (debugLogging)
										logger.debug("No sbb entities attached, which didn't already route the event, and no services left to process the event as initial");

								}
							} else {
								if (serviceComponent != null && serviceComponent.getDescriptor().getDefaultPriority() >= nextSbbEntityFinderResult.sbbEntity.getPriority()) {
									if (debugLogging)
										logger.debug("Found an sbb entity attached, which didn't already route the event, but "+serviceComponent+" defines the event type as initial and has the same or higher priority, starting initial event processing");
									// the service has higher or equal priority as the sbb entity, let the service process the eventas initial
									serviceComponents.removeFirst();
									sbbEntity = initialEventProcessor.processInitialEvent(serviceComponent, eventContext, container, ac);
								}
								else {
									if (debugLogging)
										logger.debug("Found an sbb entity attached, which didn't already route the event, and either there are no more services, which defines the event type as initial, or their priorities is lower than the attached sbb entity found");
									sbbEntity = nextSbbEntityFinderResult.sbbEntity;
									// in this case it needs to find out if the sbb entity should receive the event or not
									// it may be an activity end event and the attached sbb entity does not receives such event
									deliverEvent = nextSbbEntityFinderResult.deliverEvent;
								}
							}
						}

						if (sbbEntity != null) {

							sbbEntitiesThatHandledCurrentEvent.add(sbbEntity.getSbbEntityId());

							if (debugLogging) {
								logger
										.debug("Highest priority SBB entity, which is attached to the ac "+eventContext.getActivityContextHandle()+" , to deliver the event: "
												+ sbbEntity.getSbbEntityId());
							}

							// CHANGE CLASS LOADER
							invokerClassLoader = sbbEntity.getSbbComponent().getClassLoader();
							Thread.currentThread().setContextClassLoader(invokerClassLoader);

							if (!sbbEntity.getSbbEntityId().isRootSbbEntity()) {
								rootSbbEntityId = sbbEntity.getSbbEntityId().getRootSBBEntityID();
							}

							SleeThreadLocals.setInvokingService(sbbEntity.getSbbEntityId().getServiceID());

							// we deliver the event in case it is an initial
							// event processing or if the highest priority
							// attached sbb entity should do it (in activity
							// end event it may not if the service id was
							// set in the event firing, or if the sbb entity
							// does not declares the event in sbb descriptor

							if (deliverEvent) {

								sbbEntity.assignSbbObject();
								sbbObject = sbbEntity.getSbbObject();
								if (sbbEntity.isCreated() && !txMgr.getRollbackOnly()) {
									keepSbbEntityIfTxRollbacks = true;
								}

								// GET AND CHECK EVENT MASK FOR THIS SBB ENTITY
								Set<EventTypeID> eventMask = sbbEntity.getMaskedEventTypes(eventContext.getActivityContextHandle());
								if (eventMask == null || !eventMask.contains(eventContext.getEventTypeId())) {

									// TIME TO INVOKE THE EVENT HANDLER METHOD
									sbbObject.setSbbInvocationState(SbbInvocationState.INVOKING_EVENT_HANDLER);
									ac.beforeDeliveringEvent(eventContext);

									if (debugLogging) {
										logger
										.debug("---> Invoking event handler: ac="+eventContext.getActivityContextHandle()+" , sbbEntity="+sbbEntity.getSbbEntityId()+" , sbbObject="+sbbObject);
									}

									sbbEntity.invokeEventHandler(eventContext,ac,activityCurrentEventContext);

									sbbHandledEvent = true;

									if (debugLogging) {
										logger
										.debug("<--- Invoked event handler: ac="+eventContext.getActivityContextHandle()+" , sbbEntity="+sbbEntity.getSbbEntityId()+" , sbbObject="+sbbObject);
									}

									// check to see if the transaction is marked for
									// rollback if it is then we need to get out of
									// here soon as we can.
									rollbackOnlySet = txMgr.getRollbackOnly();
									if (!rollbackOnlySet) {
										// TODO understand why the invoking state is not changed if rollback is set
										sbbObject.setSbbInvocationState(SbbInvocationState.NOT_INVOKING);
									}

								} else {
									if (debugLogging) {
										logger
										.debug("Not invoking event handler since event is masked");
									}
								}
							}

							if (!rollbackOnlySet) {
								// IF IT'S AN ACTIVITY END EVENT DETACH SBB ENTITY HERE
								if (eventContext.isActivityEndEvent() && activityCurrentEventContext.getSbbEntitiesThatHandledEvent().contains(sbbEntity.getSbbEntityId())) {
									if (debugLogging) {
										logger
										.debug("The event is an activity end event, detaching ac="+eventContext.getActivityContextHandle()+" , sbbEntity="+sbbEntity.getSbbEntityId());
									}
									ac.detachSbbEntity(sbbEntity.getSbbEntityId());
									sbbEntity.afterACDetach(eventContext.getActivityContextHandle());
								}

								// CHECK IF WE CAN CLAIM THE ROOT SBB ENTITY
								if (rootSbbEntityId != null) {
									SbbEntity rootSbbEntity = container.getSbbEntityFactory().getSbbEntity(rootSbbEntityId,false);
									if (rootSbbEntity == null || rootSbbEntity.getAttachmentCount() != 0) {
										if (debugLogging) {
											logger
											.debug("Not removing sbb entity "+sbbEntity.getSbbEntityId()+" , the attachment count is not 0");
										}
										// the root sbb entity is not be claimed
										rootSbbEntityId = null;
									}
								} else {
									// it's a root sbb
									if (!sbbEntity.isRemoved() && sbbEntity.getAttachmentCount() == 0) {
										if (debugLogging) {
											logger
											.debug("Removing sbb entity "+sbbEntity.getSbbEntityId()+" , the attachment count is not 0");
										}
										// If it's the same entity then this is an
										// "Op and
										// Remove Invocation Sequence"
										// so we do the remove in the same
										// invocation
										// sequence as the Op
										container.getSbbEntityFactory().removeSbbEntity(sbbEntity,true);
									}
								}
							}
						}

					} catch (Exception e) {
						logger.error("Caught exception while routing "+eventContext, e);
						if (sbbEntity != null) {
							sbbObject = sbbEntity.getSbbObject();
						}
						caught = e;
					} catch (Throwable e) {
						// not an exception, wrap it in exception so sbb learns about it
						logger.error("Caught throwable while routing "+eventContext, e);
						if (sbbEntity != null) {
							sbbObject = sbbEntity.getSbbObject();
						}
						caught = new SLEEException("Caught throwable!",e);
					}

					// do a final check to see if there is another SBB to
					// deliver.
					// We don't want to waste another loop. Note that
					// rollback
					// will not has any impact on this because the
					// ac.DeliveredSet
					// is not in the cache.
					if (!finished) {
						if (serviceComponents.isEmpty()) {
							// no more services to process event as initial
							try {
								if (nextSbbEntityFinder.next(ac, eventContext,sbbEntitiesThatHandledCurrentEvent,container) == null) {
								//if (nextSbbEntityFinder.next(ac, de.getEventTypeId(),de.getService(),sbbEntitiesThatHandledCurrentEvent) == null && sbbEntitiesThatHandledCurrentEvent.contains(sbbEntity.getSbbEntityID())) {
									// no more attached sbb entities to route the event
									finished = true;
								}
							} catch (Throwable e) {
								if (debugLogging) {
									logger.debug("failed to get next attached sbb entity to handle event",e);
								}
							}
						}
					}

					boolean invokeSbbRolledBack = handleRollback.handleRollback(sbbObject, caught, invokerClassLoader,txMgr);

					boolean invokeSbbRolledBackRemove = false;
					ClassLoader rootInvokerClassLoader = null;
					SbbEntity rootSbbEntity = null;

					if (!invokeSbbRolledBack && rootSbbEntityId != null) {
						/*
						 * If we get here this means that we need to do a
						 * cascading remove of the root sbb entity - since the
						 * original invocation was done on a non-root sbb entity
						 * then this is done in a different SLEE originated
						 * invocation, but inside the same SLEE originated
						 * invocation sequence. Confused yet? This is case 3) in
						 * my previous comment - the SLEE originated invocation
						 * sequence contains two SLEE originated invocations:
						 * One "Op Only" and One "Remove Only" This is the
						 * "Remove Only" part. We don't bother doing this if we
						 * already need to rollback
						 */

						caught = null;

						try {
							rootSbbEntity = container.getSbbEntityFactory().getSbbEntity(rootSbbEntityId,false);
							if (rootSbbEntity != null) {
								container.getSbbEntityFactory().removeSbbEntity(rootSbbEntity,false);
							}
						} catch (Exception e) {
							logger.error("Failure while routing event; third phase. Event Posting ["+ eventContext + "]", e);
							caught = e;
						}

						// We have no target sbb object in a Remove Only SLEE
						// originated invocation
						// FIXME emmartins review
						invokeSbbRolledBackRemove = handleRollback.handleRollback(null, caught, rootSbbEntity.getSbbComponent().getClassLoader(),txMgr);
					}

					/*
					 * We are now coming to the end of the SLEE originated
					 * invocation sequence We may need to run sbbRolledBack This
					 * is done in the same tx if there is no target sbb entity
					 * in any of the SLEE originated invocations making up this
					 * SLEE originated invocation sequence Otherwise we do it in
					 * a separate tx for each SLEE originated invocation that
					 * has a target sbb entity. In other words we might have a
					 * maximum of 2 sbbrolledback callbacks invoked in separate
					 * tx in the case this SLEE Originated Invocation Sequence
					 * contained an Op Only and a Remove Only (since these have
					 * different target sbb entities) Pretty obvious really! ;)
					 */
					if (invokeSbbRolledBack && sbbEntity == null) {
						// We do it in this tx
						handleSbbRollback.handleSbbRolledBack(null, sbbObject, null, null, invokerClassLoader, false, container,false);
					} else if (sbbEntity != null && !txMgr.getRollbackOnly()
							&& sbbEntity.getSbbObject() != null) {
						sbbEntity.passivateAndReleaseSbbObject();

						//fixes https://github.com/RestComm/jain-slee/issues/53
						/* add new tx action before tx commit in which
						 * get all sbb entities in the tx context except target sbb entity
						 * and invoke passivateAndReleaseSbbObject method if the sbb object is not null
						 */
						final Collection coll = txMgr.getTransactionContext().getData().keySet();
						final SbbEntityID ongoingEntID = sbbEntity.getSbbEntityId();
						TransactionalAction removeLoadedSBBAction = new TransactionalAction() {
							@Override
							public void execute() {
								for (Object obj : coll) {
									if (obj instanceof SbbEntityID) {
										if (!obj.equals(ongoingEntID)) {
											SbbEntity currentEntity = (SbbEntity) txMgr.getTransactionContext().getData().get(obj);
											if (currentEntity.getSbbObject() != null) {
												currentEntity.passivateAndReleaseSbbObject();
											}
										}
									}
								}
							}
						};
						txMgr.getTransactionContext().getBeforeCommitPriorityActions().add(removeLoadedSBBAction);
					}

					if (txMgr.getRollbackOnly()) {
						if (debugLogging) {
							logger
									.trace("Rolling back SLEE Originated Invocation Sequence");
						}
						txMgr.rollback();
					} else {

						if (finished) {
							switch (routingPhase) {
							case DELIVERING:
								// last tx for this event delivering and it is going to commit (hopefully)
								// if it has a unrefrenced callback which is tx aware try to take advantage of that
								// and do post processing in this tx, in the worst (and unexpected) scenario
								// the tx will rollback and we will do another spin, due to setting gotSbb as true
								if (eventContext.routedRequiresTransaction()) {
									finished = false;
									routingPhase = RoutingPhase.DELIVERED;
									eventContext.routed();
								}
								break;
							case DELIVERED:
								if (eventContext.routedRequiresTransaction()) {
									// we had bad luck and last tx rollbacked, repeat action
									finished = false;
									eventContext.routed();
								}
								break;
							default:
								logger.error("Unknown routing phase!!!");
								break;
							}
						}

						if (debugLogging) {
							logger
									.trace("Committing SLEE Originated Invocation Sequence");
						}
						txMgr.commit();

						// if we are not in delivering mode anymore and tx commits then we allow the loop to exit
						if (routingPhase != RoutingPhase.DELIVERING) {
							finished = true;
						}
					}

					/*
					 * Now we invoke sbbRolledBack for each SLEE originated
					 * invocation that had a target sbb entity in a new tx - the
					 * new tx creating is handled inside the handleSbbRolledBack
					 * method
					 */
					if (invokeSbbRolledBack && sbbEntity != null) {
						// Firstly for the "Op only" or "Op and Remove" part
						if (debugLogging) {
							logger
									.trace("Invoking sbbRolledBack for Op Only or Op and Remove");

						}
						//FIXME: baranowb: de is passed for test: tests/sbb/abstractclass/SbbRolledBackNewTransaction.xml
						handleSbbRollback.handleSbbRolledBack(sbbEntity, null, eventContext, ac, invokerClassLoader, false, container, keepSbbEntityIfTxRollbacks);
					}
					if (invokeSbbRolledBackRemove) {
						// Now for the "Remove Only" if appropriate
						handleSbbRollback.handleSbbRolledBack(rootSbbEntity, null, null, null, rootInvokerClassLoader, true, container,keepSbbEntityIfTxRollbacks);
					}

					/*
					 * A note on exception handling here- Any exceptions thrown
					 * further further up that need to be caught in order to
					 * handle rollback or otherwise maintain consistency of the
					 * SLEE state are all handled further up I.e. We *do not*
					 * need to call rollback here. So any exceptions that get
					 * here do not result in the SLEE being in an inconsistent
					 * state, therefore we just log them and carry on.
					 */

					rollbackTx = false;
				} catch (RuntimeException e) {
					logger.error(
							"Unhandled RuntimeException in event router: ", e);
				} catch (Exception e) {
					logger.error("Unhandled Exception in event router: ", e);
				} catch (Error e) {
					logger.error("Unhandled Error in event router: ", e);
					throw e; // Always rethrow errors
				} catch (Throwable t) {
					logger.error("Unhandled Throwable in event router: ", t);
				} finally {
					try {
						// FIXME this should not be possible, check if this is ever called by tck!!!
						final Transaction forgottenTx = txMgr.getTransaction();
						if (forgottenTx != null) {
							logger
							.error("HOUSTON WE HAVE A PROBLEM! Transaction "+forgottenTx+" left open in event routing.");
							if (rollbackTx) {
								txMgr.rollback();
							} else {
								txMgr.commit();
							}
						}
					} catch (SystemException se) {
						logger.error(se.getMessage(), se);
					}
					if (sbbEntity != null) {
						if (debugLogging) {
							logger
									.debug("Finished routing for "
											+ eventContext
											+ "\n\n\n");
						}
					}
				}

				SleeThreadLocals.setInvokingService(null);

				if (activityCurrentEventContext.isSuspendedNotTransacted()) {
					if (debugLogging)
						logger.debug("Suspended routing for "
							+ eventContext+"\n\n\n");
					return;
				}

			} while (!finished);

			/*
			 * End of SLEE Originated Invocation Sequence
			 * ==========================================
			 *
			 */

			eventContext.eventProcessingSucceed(sbbHandledEvent);

			// we got to the end of the event routing, remove from local ac
			lac.setCurrentEventRoutingTask(null);

			if (!eventContext.routedRequiresTransaction()) {
				eventContext.routed();
			}

		} catch (Exception e) {
			logger.error("Unhandled Exception in event router top try", e);
		}

		Thread.currentThread().setContextClassLoader(
				oldClassLoader);
	}

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.resource;

import org.infinispan.remoting.transport.Address;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.activity.ActivityType;
import org.mobicents.slee.container.resource.ResourceAdaptorActivityContextHandle;
import org.mobicents.slee.container.resource.ResourceAdaptorEntity;

import javax.slee.resource.ActivityHandle;
import javax.slee.resource.Marshaler;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
 * @author martins
 *
 */
public class ResourceAdaptorActivityContextHandleImpl implements ResourceAdaptorActivityContextHandle {

	/**
	 *
	 */
	private static final long serialVersionUID = 1L;

	private ActivityHandle activityHandle;
	private ResourceAdaptorEntity raEntity;

	/**
	 * not to be used, needed due to externalizable
	 */
	public ResourceAdaptorActivityContextHandleImpl() {

	}

	/**
	 *
	 */
	public ResourceAdaptorActivityContextHandleImpl(ResourceAdaptorEntity raEntity, ActivityHandle activityHandle) {
		this.raEntity = raEntity;
		this.activityHandle = activityHandle;
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.runtime.activity.ActivityContextHandle#getActivityObject()
	 */
	public Object getActivityObject() {
		return raEntity.getResourceAdaptorObject().getActivity(activityHandle);
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.runtime.activity.ActivityContextHandle#getActivityHandle()
	 */
	public ActivityHandle getActivityHandle() {
		return activityHandle;
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.resource.ResourceAdaptorActivityContextHandle#getResourceAdaptorEntity()
	 */
	public ResourceAdaptorEntity getResourceAdaptorEntity() {
		return raEntity;
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.runtime.activity.ActivityContextHandle#getActivityType()
	 */
	public ActivityType getActivityType() {
		return ActivityType.RA;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (obj.getClass() == this.getClass()) {
			final ResourceAdaptorActivityContextHandleImpl other = (ResourceAdaptorActivityContextHandleImpl) obj;
			return other.activityHandle.equals(this.activityHandle) && other.raEntity.equals(this.raEntity);
		} else {
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return activityHandle.hashCode() * 31 + raEntity.hashCode();
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return new StringBuilder ("RA:").append(getResourceAdaptorEntity().getName()).append(':').append(activityHandle).toString();
	}

	/*
	 * (non-Javadoc)
	 * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
	 */
	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		// read ra entity name
		final String raEntityName = in.readUTF();
		// read activity handle
		this.raEntity = SleeContainer.lookupFromJndi().getResourceManagement()
		.getResourceAdaptorEntity(raEntityName);
		if (raEntity == null) {
			throw new IOException("RA Entity with name " + raEntityName
					+ " not found.");
		}
		// read activity handle
		boolean handleReference = in.readBoolean();
		if (handleReference) {
			// a reference
			activityHandle = new ActivityHandleReference(null, (Address) in.readObject(), in.readUTF());
		} else {
			final Marshaler marshaler = raEntity.getMarshaler();
			if (marshaler != null) {
				activityHandle = marshaler.unmarshalHandle(in);
			} else {
				throw new IOException("marshaller from RA is null");
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
	 */
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		// write ra entity name
		out.writeUTF(raEntity.getName());
		// write activity handle
		if (activityHandle.getClass() == ActivityHandleReference.class) {
			// a reference
			out.writeBoolean(true);
			final ActivityHandleReference reference = (ActivityHandleReference) activityHandle;
			out.writeObject(reference.getAddress());
			out.writeUTF(reference.getId());
		}
		else {
			out.writeBoolean(false);
			final Marshaler marshaler = raEntity.getMarshaler();
			if (marshaler != null) {
				marshaler.marshalHandle(activityHandle, out);
			}
			else {
				throw new IOException("marshaller from RA is null");
			}
		}
	}

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.container.deployment.profile.jpa;

import java.beans.Introspector;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.slee.SLEEException;

import org.apache.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.mobicents.slee.container.component.ClassPool;
import org.mobicents.slee.container.component.profile.ProfileAttribute;
import org.mobicents.slee.container.component.profile.ProfileSpecificationComponent;
import org.mobicents.slee.container.component.profile.ProfileSpecificationDescriptor;
import org.mobicents.slee.container.component.profile.cmp.ProfileCMPInterfaceDescriptor;
import org.mobicents.slee.container.deployment.profile.ClassGeneratorUtils;
import org.mobicents.slee.container.profile.entity.ProfileEntity;

/**
 *
 * Generates the concrete profile entity and attribute array value classes, which hold the profile persistent attributes
 *
 *
Project:  mobicents
 *
11:16:57 AM Mar 23, 2009
 *

 * @author  Alexandre Mendonca
 * @author  Bartosz Baranowski
 * @author martins
 */
public class ConcreteProfileEntityGenerator {

  private static final Logger logger = Logger.getLogger(ConcreteProfileEntityGenerator.class);

  private ProfileSpecificationComponent profileComponent;
  private final JPAProfileEntityFramework jpaProfileDataSource;

  public ConcreteProfileEntityGenerator(ProfileSpecificationComponent profileComponent,JPAProfileEntityFramework jpaProfileDataSource)
  {
	  this.profileComponent = profileComponent;
	  this.jpaProfileDataSource = jpaProfileDataSource;

	  ClassGeneratorUtils.setClassPool( this.profileComponent.getClassPool().getClassPool() );
  }

  /**
   * Generates the concrete profile entity class and a profile entity array
   * attr value class for each attribute that is an array
   */
  public void generateClasses() {

    try {

      ProfileSpecificationDescriptor profileDescriptor = profileComponent.getDescriptor();
      String deployDir = profileComponent.getDeploymentDir().getAbsolutePath();

      ProfileCMPInterfaceDescriptor cmpInterface = profileDescriptor.getProfileCMPInterface();

      // define the concrete profile entity class name
      String concreteProfileEntityClassName = cmpInterface.getProfileCmpInterfaceName() + "_PE";

      // create javassist class
      CtClass concreteProfileEntityClass = ClassGeneratorUtils.createClass(concreteProfileEntityClassName, new String[]{cmpInterface.getProfileCmpInterfaceName(), Serializable.class.getName()});

      // set inheritance
      ClassGeneratorUtils.createInheritanceLink(concreteProfileEntityClass, ProfileEntity.class.getName());

      // annotate with @Entity
      ClassGeneratorUtils.addAnnotation( Entity.class.getName(), new LinkedHashMap<String, Object>(), concreteProfileEntityClass );

      // annotate the @IdClass
      LinkedHashMap<String, Object> idClassMVs = new LinkedHashMap<String, Object>();
      idClassMVs.put( "value", JPAProfileId.class );
      ClassGeneratorUtils.addAnnotation( IdClass.class.getName(), idClassMVs, concreteProfileEntityClass );

      Set<String> uniqueConstraints = new HashSet<String>();

      // override @id & @basic getter methods
      String getProfileNameMethodSrc = "public String getProfileName() { return super.getProfileName(); }";
      CtMethod getProfileNameMethod = CtNewMethod.make(getProfileNameMethodSrc, concreteProfileEntityClass);
      ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getProfileNameMethod);
      concreteProfileEntityClass.addMethod(getProfileNameMethod);
      String getTableNameMethodSrc = "public String getTableName() { return super.getTableName(); }";
      CtMethod getTableNameMethod = CtNewMethod.make(getTableNameMethodSrc, concreteProfileEntityClass);
      ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getTableNameMethod);
      concreteProfileEntityClass.addMethod(getTableNameMethod);

      // generate the getters/setters in the profile entity
      // gather the fieldNames of array type attributes
      ClassPool pool = profileComponent.getClassPool();
      CtClass cmpInterfaceClass = pool.get(cmpInterface.getProfileCmpInterfaceName());
      CtClass listClass = pool.get(List.class.getName());

      Map<String,Class<?>> profileEntityArrayAttrValueClassMap = new HashMap<String, Class<?>>();

      for(CtMethod method : cmpInterfaceClass.getMethods()) {
    	  if(!method.getDeclaringClass().getName().equals(Object.class.getName()) && method.getName().startsWith( "get" )) {
    		  String fieldName = Introspector.decapitalize(method.getName().replaceFirst( "get", "" ));
    		  boolean array = method.getReturnType().isArray();
    		  CtClass returnType = array ? listClass : method.getReturnType();
    		  CtField genField = ClassGeneratorUtils.addField( returnType, fieldName, concreteProfileEntityClass );

    		  // see issue #23: [Profiles] getting correct fieldName from method of ProfileCMP interface
    		  fieldName = genField.getName();

    		  String pojoCmpAccessorSufix = ClassGeneratorUtils.getPojoCmpAccessorSufix(genField.getName());
    		  // create the getter
    		  CtMethod ctMethod = CtNewMethod.getter( "get" + pojoCmpAccessorSufix, genField );
    		  ProfileAttribute profileAttribute = profileComponent.getProfileAttributes().get(fieldName);
    		  concreteProfileEntityClass.addMethod(ctMethod);
    		  if (array) {
    			  // we need to generate a class for this attribute, to hold the one to many relation
    			  Class<?> profileAttributeArrayValueClass = generateProfileAttributeArrayValueClass(concreteProfileEntityClass,fieldName,profileAttribute.isUnique());
    			  profileEntityArrayAttrValueClassMap.put(fieldName, profileAttributeArrayValueClass);
    			  // add the annotations of one to many association with array attr value class
    			  LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
    			  map.put("targetEntity", profileAttributeArrayValueClass);
    			  // FIXME see comment on generation of avv, it is possible to work wituout a join table but needs more work
    			  // THE MAPPEDBY IS REQUIRED FOR THE RELATION WITHOUT A JOIN TABLE
    			  map.put("mappedBy", "owner");
    			  map.put("cascade",new CascadeType[]{CascadeType.ALL});
    			  ClassGeneratorUtils.addAnnotation( OneToMany.class.getName(), map, ctMethod);
    			  // we need to add a special hibernate annotation because the jpa cascade delete only deletes from join table, not the orphan row at the PEAAV table
    			  map = new LinkedHashMap<String, Object>();
    			  map.put("value",new org.hibernate.annotations.CascadeType[]{org.hibernate.annotations.CascadeType.DELETE_ORPHAN});
    			  ClassGeneratorUtils.addAnnotation( Cascade.class.getName(), map, ctMethod);
    			  // make setter from src
    			  /*String setterSrc =
    				  "public void set"+ pojoCmpAccessorSufix + "("+List.class.getName()+" value) {" +
    				  "  System.out.println(\"PEAAV setter: "+genField.getName()+" = \"+this."+genField.getName()+"+\" value = \"+value);" +
    				  "  if (this."+genField.getName()+" != null) { " +
    				  "    this."+genField.getName()+".clear(); " +
    				  "    if (value != null) { " +
					  "      for ("+Iterator.class.getName()+" i = value.iterator(); i.hasNext();) { " +
					  "        " + profileAttributeArrayValueClass.getName()+" otherArrayValue = ("+profileAttributeArrayValueClass.getName()+") i.next(); " +
					  "        " + profileAttributeArrayValueClass.getName()+" thisArrayValue = new "+profileAttributeArrayValueClass.getName()+"(); " +
					  "        thisArrayValue.setString( otherArrayValue.getString() ); " +
					  "        thisArrayValue.setSerializable( ("+Serializable.class.getName()+") "+ObjectCloner.class.getName()+".makeDeepCopy(otherArrayValue.getSerializable()) ); " +
					  "        this."+genField.getName()+".add(thisArrayValue); " +
					  "      }" +
					  "    }" +
    		          "  } else { " +
    		          "    this."+genField.getName()+" = value; " +
    		          "  }" +
					  "}";
    			  ctMethod = CtMethod.make(setterSrc, concreteProfileEntityClass);
    			  concreteProfileEntityClass.addMethod(ctMethod);
    			  */
    		  }
    		  else {
    			  // not an array, just add column annotation with or without unique constraint
	    		  if (profileAttribute.isUnique()) {
	    			  // just collect uniqueConstraints attributtes
	    			  uniqueConstraints.add(Introspector.decapitalize(pojoCmpAccessorSufix));
        		  }

	    		  //String , primitive types , Array , Date will not be modified , only serialized data
	    		  LinkedHashMap<String,Object> map = new LinkedHashMap<String, Object>();
	    		  if (!returnType.isPrimitive() && ! returnType.getName().equals("java.lang.String"))
    				  map.put("length", 512);

    			  ClassGeneratorUtils.addAnnotation(Column.class.getName(), map, ctMethod);
    		  }
    		// add usual setter
    		  ctMethod = CtNewMethod.setter( "set" + pojoCmpAccessorSufix, genField );
    		  concreteProfileEntityClass.addMethod(ctMethod);

    	  }
      }

      String tableName = "SLEE_PE_"+profileComponent.getProfileCmpInterfaceClass().getSimpleName() + "_" + Math.abs((long)profileComponent.getComponentID().hashCode());
      addTableAnnotation(tableName, uniqueConstraints, concreteProfileEntityClass);

      jpaProfileDataSource.setProfileEntityArrayAttrValueClassMap(profileEntityArrayAttrValueClassMap);

      // write and load profile entity class
	  if (logger.isDebugEnabled())
    	  logger.debug( "Writing PROFILE ENTITY CONCRETE CLASS ( "+concreteProfileEntityClass.getName()+" ) to: " + deployDir );
      concreteProfileEntityClass.writeFile( deployDir );
      jpaProfileDataSource.setProfileEntityClass(Thread.currentThread().getContextClassLoader().loadClass(concreteProfileEntityClass.getName()));
      concreteProfileEntityClass.defrost();

    }
    catch ( Throwable e ) {
      throw new SLEEException(e.getMessage(),e);
    }

  }

  private void addTableAnnotation(String name, Set<String> uniqueAttributes,CtClass ctClass) {

	  ClassFile cf = ctClass.getClassFile();
	  ConstPool cp = cf.getConstPool();

	  AnnotationsAttribute attr = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
	  if (attr == null) {
		  attr = new AnnotationsAttribute(cp,AnnotationsAttribute.visibleTag);
	  }

	  // create table annotation and set name attribute
	  Annotation table = new Annotation(Table.class.getName(), cp);
	  table.addMemberValue("name", new StringMemberValue(name,cp));

	  if (!uniqueAttributes.isEmpty()) {
		  // we have unique attributes, this in fact transforms in unique constraints of both table name key and the unique attribute
		  // since in one entity there are multiple tables "merged"
		  ArrayMemberValue uniqueConstraintsArrayMemberValue = new ArrayMemberValue(cp);
		  MemberValue[] uniqueConstraintsMemberValues = new MemberValue[uniqueAttributes.size()];
		  int i = 0;
		  for(String uniqueAttr : uniqueAttributes) {
			  // create UniqueConstraint annotation
			  Annotation uniqueConstraint = new Annotation(UniqueConstraint.class.getName(), cp);
			  MemberValue[] uniqueConstraintMemberValues = new MemberValue[2];
			  uniqueConstraintMemberValues[0] = new StringMemberValue(uniqueAttr, cp);
			  uniqueConstraintMemberValues[1] = new StringMemberValue("tableName", cp);
			  ArrayMemberValue uniqueConstraintArrayMemberValue = new ArrayMemberValue(cp);
			  uniqueConstraintArrayMemberValue.setValue(uniqueConstraintMemberValues);
			  // set columnNames field with value {uniqueAttr,"tableName"}
			  uniqueConstraint.addMemberValue("columnNames", uniqueConstraintArrayMemberValue);
			  // add to array of UniqueConstraint annotations
			  uniqueConstraintsMemberValues[i] = new AnnotationMemberValue(uniqueConstraint,cp);
		  }
		  // add to Table the field uniqueConstraints with UniqueConstraint[] value
		  uniqueConstraintsArrayMemberValue.setValue(uniqueConstraintsMemberValues);
		  table.addMemberValue("uniqueConstraints", uniqueConstraintsArrayMemberValue);
	  }

	  // add Table annotation to class
	  attr.addAnnotation(table);
  }

  /**
   * Generates a class that extends {@link ProfileEntityArrayAttributeValue} for a specific entity attribute of array type value
   * @param concreteProfileEntityClass
   * @param profileAttributeName
   * @return
   */
  private Class<?> generateProfileAttributeArrayValueClass(CtClass concreteProfileEntityClass, String profileAttributeName, boolean unique) {

	  CtClass concreteArrayValueClass = null;

	  try {

		  // define the concrete profile attribute array value class name
		  String concreteArrayValueClassName = profileComponent.getProfileCmpInterfaceClass().getName() + "PEAAV_"+ClassGeneratorUtils.capitalize(profileAttributeName);

		  // create javassist class
		  concreteArrayValueClass = ClassGeneratorUtils.createClass(concreteArrayValueClassName, new String[]{Serializable.class.getName()});

		  // set inheritance
		  ClassGeneratorUtils.createInheritanceLink(concreteArrayValueClass, ProfileEntityArrayAttributeValue.class.getName());

		  // annotate class with @Entity
		  ClassGeneratorUtils.addAnnotation( Entity.class.getName(), new LinkedHashMap<String, Object>(), concreteArrayValueClass );

		  // generate a random table name
		  addTableAnnotationToPEAAV("SLEE_PEAAV_"+profileComponent.getProfileCmpInterfaceClass().getSimpleName() + "_" + Math.abs((long)profileComponent.getComponentID().hashCode()) + profileAttributeName,unique,concreteArrayValueClass);

		  // override @id
		  String getIdNameMethodSrc = "public long getId() { return super.getId(); }";
		  CtMethod getIdNameMethod = CtNewMethod.make(getIdNameMethodSrc, concreteArrayValueClass);
		  ClassGeneratorUtils.addAnnotation( Id.class.getName(), new LinkedHashMap<String, Object>(), getIdNameMethod);
		  ClassGeneratorUtils.addAnnotation( GeneratedValue.class.getName(), new LinkedHashMap<String, Object>(), getIdNameMethod);
		  concreteArrayValueClass.addMethod(getIdNameMethod);

		  // override getter methods
		  String getSerializableMethodSrc = "public "+Serializable.class.getName()+" getSerializable() { return super.getSerializable(); }";
		  CtMethod getSerializableMethod = CtNewMethod.make(getSerializableMethodSrc, concreteArrayValueClass);
		  LinkedHashMap<String,Object> map = new LinkedHashMap<String, Object>();
		  map.put("name", "serializable");
		  map.put("length", 512);
		  //if (unique)map.put("unique", true);
		  ClassGeneratorUtils.addAnnotation(Column.class.getName(), map, getSerializableMethod);
		  concreteArrayValueClass.addMethod(getSerializableMethod);
		  String getStringMethodSrc = "public String getString() { return super.getString(); }";
		  CtMethod getStringMethod = CtNewMethod.make(getStringMethodSrc, concreteArrayValueClass);
		  map = new LinkedHashMap<String, Object>();
		  map.put("name", "string");
		  //if (unique)map.put("unique", true);
		  ClassGeneratorUtils.addAnnotation(Column.class.getName(), map, getStringMethod);
		  concreteArrayValueClass.addMethod(getStringMethod);

		  // FIXME add join columns here or in profile entity class to make
		  // the relation without a join table, atm if this is changed, the
		  // inserts on this table go with profile and table name as null %)
		  // THE PROFILENTITY FIELD IN AAV CLASS IS REQUIRED FOR THE RELATION WITH PROFILE ENTITY CLASS WITHOUT A JOIN TABLE
		  // add join column regarding the relation from array attr value to profile entity
		  CtField ctField = ClassGeneratorUtils.addField(concreteProfileEntityClass, "owner", concreteArrayValueClass);
		  ClassGeneratorUtils.generateSetter(ctField,null);
		  CtMethod getter = ClassGeneratorUtils.generateGetter(ctField,null);
		  //ClassGeneratorUtils.addAnnotation(ManyToOne.class.getName(), new LinkedHashMap<String, Object>(), getter);
		  // ----
		  ConstPool cp = getter.getMethodInfo().getConstPool();
		  AnnotationsAttribute attr = (AnnotationsAttribute) getter.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag);
		  if (attr == null) {
			  attr = new AnnotationsAttribute(cp,AnnotationsAttribute.visibleTag);
		  }

		  Annotation manyToOne = new Annotation(ManyToOne.class.getName(), cp);
		  manyToOne.addMemberValue("optional", new BooleanMemberValue(false,cp));
		  attr.addAnnotation(manyToOne);

		  Annotation joinColumns = new Annotation(JoinColumns.class.getName(), cp);
		  Annotation joinColumn1 = new Annotation(JoinColumn.class.getName(), cp);
		  joinColumn1.addMemberValue("name", new StringMemberValue("owner_tableName", cp));
		  joinColumn1.addMemberValue("referencedColumnName", new StringMemberValue("tableName", cp));
		  Annotation joinColumn2 = new Annotation(JoinColumn.class.getName(), cp);
		  joinColumn2.addMemberValue("name", new StringMemberValue("owner_profileName", cp));
		  joinColumn2.addMemberValue("referencedColumnName", new StringMemberValue("profileName", cp));
		  ArrayMemberValue joinColumnsMemberValue = new ArrayMemberValue(cp);
    	  joinColumnsMemberValue.setValue(new MemberValue[] { new AnnotationMemberValue(joinColumn1,cp), new AnnotationMemberValue(joinColumn2,cp)});
    	  joinColumns.addMemberValue("value", joinColumnsMemberValue);
    	  attr.addAnnotation(joinColumns);

    	  getter.getMethodInfo().addAttribute(attr);

    	  // generate concrete setProfileEntity method
    	  String setProfileEntityMethodSrc = "public void setProfileEntity("+ProfileEntity.class.getName()+" profileEntity){ setOwner(("+concreteProfileEntityClass.getName()+")profileEntity); }";
    	  CtMethod setProfileEntityMethod = CtMethod.make(setProfileEntityMethodSrc, concreteArrayValueClass);
    	  concreteArrayValueClass.addMethod(setProfileEntityMethod);

		  // write and load the attr array value class
		  String deployDir = profileComponent.getDeploymentDir().getAbsolutePath();
		  if (logger.isDebugEnabled()) {
			  logger.debug( "Writing PROFILE ATTR ARRAY VALUE CONCRETE CLASS ( "+concreteArrayValueClass.getName()+" ) to: " + deployDir );
		  }
		   concreteArrayValueClass.writeFile( deployDir );

		  return Thread.currentThread().getContextClassLoader().loadClass(concreteArrayValueClass.getName());
	  }
	  catch (Throwable e) {
		  throw new SLEEException(e.getMessage(),e);
	  }
	  finally {
		  if (concreteArrayValueClass != null) {
			  concreteArrayValueClass.defrost();
		  }
	  }
  }

  private void addTableAnnotationToPEAAV(String tableName, boolean unique, CtClass ctClass) {

	  if (logger.isTraceEnabled()) {
		 logger.trace("Adding PEAAV table with name "+tableName+" for "+profileComponent+", attribute is unique? "+unique);
	  }

	  ConstPool cp = ctClass.getClassFile().getConstPool();
	  AnnotationsAttribute attr = (AnnotationsAttribute) ctClass.getClassFile().getAttribute(AnnotationsAttribute.visibleTag);
	  if (attr == null) {
		  attr = new AnnotationsAttribute(cp,AnnotationsAttribute.visibleTag);
	  }

	  Annotation table = new Annotation(Table.class.getName(), cp);

	  table.addMemberValue("name", new StringMemberValue(tableName,cp));

	  if (unique) {
		  ArrayMemberValue columnNamesMemberValue = new ArrayMemberValue(cp);
		  columnNamesMemberValue.setValue(new MemberValue[] { new StringMemberValue("owner_tableName",cp) , new StringMemberValue("string",cp) });
		  Annotation uniqueContraint = new Annotation(UniqueConstraint.class.getName(), cp);
		  uniqueContraint.addMemberValue("columnNames", columnNamesMemberValue);

		  ArrayMemberValue uniqueConstraintsMemberValue = new ArrayMemberValue(cp);
		  uniqueConstraintsMemberValue.setValue(new MemberValue[] { new AnnotationMemberValue(uniqueContraint,cp)});

		  table.addMemberValue("uniqueConstraints", uniqueConstraintsMemberValue);
	  }

	  attr.addAnnotation(table);
	  ctClass.getClassFile().addAttribute(attr);

  }

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * 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.mobicents.slee.runtime.sbbentity;

import java.util.Comparator;
import java.util.LinkedList;

import org.mobicents.slee.container.sbbentity.SbbEntityID;

/**
 * Comparator used to sort a set of {@link SbbEntityID} by priority.
 *
 * @author martins
 *
 */
public class SbbEntityIDComparator implements Comparator<SbbEntityID> {

	private final SbbEntityFactoryImpl sbbEntityFactory;

	public SbbEntityIDComparator(SbbEntityFactoryImpl sbbEntityFactory) {
		this.sbbEntityFactory = sbbEntityFactory;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
	 */
	public int compare(SbbEntityID sbbEntityID1, SbbEntityID sbbEntityID2) {
		if (sbbEntityID1 == sbbEntityID2) {
			return 0;
		}
		if (sbbEntityID1 == null) {
			return 1;
		}
		if (sbbEntityID2 == null) {
			return -1;
		}
		return higherPrioritySbb(sbbEntityID1, sbbEntityID2);
	}

	private int higherPrioritySbb(SbbEntityID sbbEntityID1, SbbEntityID sbbEntityID2) {

		final LinkedList<Byte> stack1 = priorityOfSbb(sbbEntityID1);
		final LinkedList<Byte> stack2 = priorityOfSbb(sbbEntityID2);

		// stacks may be null if sbb entity is removed concurrently
		if (stack1 == null) {
			if (stack2 == null) {
				return 0;
			} else {
				return 1;
			}
		}
		else {
			if (stack2 == null) {
				return -1;
			}
		}

		// both are non null
		byte priority1 = 0;
		byte priority2 = 0;
		while (true) {
			priority1 = stack1.removeFirst();
			priority2 = stack2.removeFirst();
			if (priority1 == priority2) {
				// sbb entities have the same ancestor.
				if (stack1.isEmpty()) {
					if (stack2.isEmpty()) {
						// compare id as string
						return sbbEntityID1.toString().compareTo(sbbEntityID2.toString());
					}
					else {
						return -1;
					}
				} else if (stack2.isEmpty()) {
					return 1;
				}
			} else if (priority1 > priority2) {
				return -1;
			}
			else {
				return 1;
			}
		}
	}

	private LinkedList<Byte> priorityOfSbb(SbbEntityID sbbEntityID) {
		final LinkedList<Byte> list = new LinkedList<Byte>();
		// push priority of all non root sbb entities
		SbbEntityCacheData sbbEntityCacheData = null;
		while (!sbbEntityID.isRootSbbEntity()) {
			sbbEntityCacheData = new SbbEntityCacheData(sbbEntityID,sbbEntityFactory.getSleeContainer().getCluster().getMobicentsCache());
			if(!sbbEntityCacheData.exists()) {
				// edge case where a sbb entity was concurrently removed, ignore this sbb entity
				return null;
			}
			list.addFirst(Byte.valueOf(sbbEntityCacheData.getPriority()));
			sbbEntityID = sbbEntityID.getParentSBBEntityID();
		}
		// push the root one
		sbbEntityCacheData = new SbbEntityCacheData(sbbEntityID,sbbEntityFactory.getSleeContainer().getCluster().getMobicentsCache());
		if(!sbbEntityCacheData.exists()) {
			// edge case where a sbb entity was concurrently removed, ignore this sbb entity
			return null;
		}
		list.addFirst(Byte.valueOf(sbbEntityCacheData.getPriority()));
		return list;
	}

}