/*******************************************************************************
 * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.descriptors;

import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.*;
import java.io.*;

import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.core.descriptors.CoreDescriptorEventManager;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.sessions.SessionProfiler;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.internal.sessions.AbstractSession;

/**
 * <p><b>Purpose</b>: The event manager allows for a descriptor to specify that 
 * an object should be notified when a EclipseLink event occurs.  It also determines 
 * how the object will be notified. To specify an event a method name can be 
 * registered to be called on the object when the event occurs. Events can be 
 * used to extend the EclipseLink reading and writing behavior.
 * <p>
 * These events include:
 * <ul>
 * <li> pre/postWrite - occurs when an object is written (occurs even if no changes to the object).
 * <li> pre/postInsert - occurs when an object is inserted.
 * <li> pre/postUpdate - occurs when an object is updated (occurs even if no changes to the object).
 * <li> pre/postDeleted - occurs when an object is deleted.
 * <li> postBuild/postRefresh - occurs after a object has been built/refreshed from its database row.
 * <li> aboutTo/Insert/Update - occurs when an object is about to be inserted/update allows for row modification.
 * <li> postClone - occurs when an object is registered/cloned in a unit of work.
 * <li> postMerge - occurs when an object is merged with its original in a unit of work.
 * </ul>
 *
 * @see ClassDescriptor
 */
public class DescriptorEventManager extends CoreDescriptorEventManager<DescriptorEvent> implements Cloneable, Serializable {
    protected ClassDescriptor descriptor;
    protected Vector eventSelectors;
    protected transient Vector eventMethods;
    protected transient Vector eventListeners;
    
    // EJB 3.0 support for event listeners.
    protected transient Vector defaultEventListeners;
    protected transient Vector entityListenerEventListeners;
    protected transient DescriptorEventListener entityEventListener;
    /**
     * Listeners that are fired after all other listeners are fired
     */
    protected transient List<DescriptorEventListener>  internalListeners = new ArrayList<DescriptorEventListener>();
    
    // EJB 3.0 support - cache our parent event managers.
    protected transient Vector entityEventManagers;
    protected transient Vector entityListenerEventManagers;
    
    // EJB 3.0 support for event listener configuration flags.
    protected boolean excludeDefaultListeners;
    protected boolean excludeSuperclassListeners;

    //JPA project caching support.  Holds DescriptorEventListener representations for serialization/storage.
    protected java.util.List<SerializableDescriptorEventHolder> descriptorEventHolders;

    /** PERF: Cache if any events listener exist. */
    protected boolean hasAnyEventListeners;
    public static final int PreWriteEvent = 0;
    public static final int PostWriteEvent = 1;
    public static final int PreDeleteEvent = 2;
    public static final int PostDeleteEvent = 3;
    public static final int PreInsertEvent = 4;
    public static final int PostInsertEvent = 5;
    public static final int PreUpdateEvent = 6;
    public static final int PostUpdateEvent = 7;
    public static final int PostBuildEvent = 8;
    public static final int PostRefreshEvent = 9;
    public static final int PostCloneEvent = 10;
    public static final int PostMergeEvent = 11;
    public static final int AboutToInsertEvent = 12;
    public static final int AboutToUpdateEvent = 13;

    // CR#2660080 was missing aboutToDelete
    public static final int AboutToDeleteEvent = 14;
    
    // EJB 3.0 events
    public static final int PrePersistEvent = 15;
    public static final int PreRemoveEvent = 16;
    public static final int PreUpdateWithChangesEvent = 17;

   protected static final int NumberOfEvents = 18;
    /**
     * INTERNAL:
     * Returns a new DescriptorEventManager for the specified ClassDescriptor.
     */
    public DescriptorEventManager() {
        this.eventSelectors = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(NumberOfEvents);
        this.eventMethods = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(NumberOfEvents);
        this.hasAnyEventListeners = false;
        this.excludeDefaultListeners = false;
        this.excludeSuperclassListeners = false;

        for (int index = 0; index < NumberOfEvents; index++) {
            this.eventSelectors.addElement(null);
            this.eventMethods.addElement(null);
        }
    }

    /**
     * PUBLIC:
     * EJB 3.0 support for default listeners.
     */
    public void addDefaultEventListener(DescriptorEventListener listener) {
        getDefaultEventListeners().addElement(listener);
    }
    
    /**
     * PUBLIC:
     * EJB 3.0 support for lifecycle callback events defined on an entity 
     * listener class.
     */
    public void addEntityListenerEventListener(DescriptorEventListener listener) {
        getEntityListenerEventListeners().addElement(listener);
    }

    /**
     * PUBLIC:
     * Listener objects can be registered with the event manager to be notified 
     * when an event occurs on any instance of the descriptor's class.
     */
    public void addListener(DescriptorEventListener listener) {
        getEventListeners().addElement(listener);
        setHasAnyEventListeners(true);
    }

    /**
     * INTERNAL:
     *
     */
    public void addInternalListener(DescriptorEventListener listener) {
        if (internalListeners==null) {
            internalListeners = new ArrayList<DescriptorEventListener>();
        }
        internalListeners.add(listener);
        setHasAnyEventListeners(true); // ensure that events are generated
    }

    /**
     * INTERNAL:
     *
     */
    public void addEntityListenerHolder(SerializableDescriptorEventHolder holder) {
        this.getDescriptorEventHolders().add(holder);
    }
    
    /**
     * INTERNAL:
     * Clone the manager and its private parts.
     */
    public Object clone() {
        DescriptorEventManager clone = null;

        try {
            clone = (DescriptorEventManager)super.clone();
            clone.setEventSelectors((Vector)getEventSelectors().clone());
            clone.setEventMethods((Vector)getEventMethods().clone());
            clone.setEventListeners(getEventListeners());
        } catch (Exception exception) {
            ;
        }

        return clone;
    }

    /**
     * INTERNAL:
     * This method was added to allow JPA project caching so that DescriptorEventListeners could be 
     * serialized and re-added to the EventManager using a SerializableDescriptorEventHolder.
     * @param classLoader 
     */
    public void processDescriptorEventHolders(AbstractSession session, ClassLoader classLoader) {
        if (this.descriptorEventHolders != null) {
            for (SerializableDescriptorEventHolder holder: descriptorEventHolders) {
                holder.addListenerToEventManager(getDescriptor(), session, classLoader);
            }
        }
    }

    /**
     * INTERNAL:
     * EJB 3.0 support. Returns true if this event manager should exclude the 
     * invocation of the default listeners for this descriptor.
     */
    public boolean excludeDefaultListeners() {
        return excludeDefaultListeners;
    }

    /**
     * INTERNAL:
     * EJB 3.0 support. Returns true is this event manager should exclude the 
     * invocation of the listeners defined by the entity listener classes for 
     * the superclasses of this descriptor.
     */
    public boolean excludeSuperclassListeners() {
        return excludeSuperclassListeners;
    }
    
    /**
     * INTERNAL:
     * Execute the given selector with the event as argument.
     * @exception DescriptorException - the method cannot be found or executed
     */
    public void executeEvent(DescriptorEvent event) throws DescriptorException {
        try {
            event.getSession().startOperationProfile(SessionProfiler.DescriptorEvent);
            // CR#3467758, ensure the descriptor is set on the event.
            event.setDescriptor(getDescriptor());
            notifyListeners(event);
            notifyEJB30Listeners(event);

            if (event.getSource() instanceof DescriptorEventListener) {
                // Allow the object itself to implement the interface.
                notifyListener((DescriptorEventListener)event.getSource(), event);
                return;
            }

            Method eventMethod = (Method)getEventMethods().elementAt(event.getEventCode());
            if (eventMethod == null) {
                return;
            }

            // Now that I have the method, I need to invoke it
            try {
                Object[] runtimeParameters = new Object[1];
                runtimeParameters[0] = event;
                if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                    try {
                        AccessController.doPrivileged(new PrivilegedMethodInvoker(eventMethod, event.getSource(), runtimeParameters));
                    } catch (PrivilegedActionException exception) {
                        Exception throwableException = exception.getException();
                        if (throwableException instanceof IllegalAccessException) {
                            throw DescriptorException.illegalAccessWhileEventExecution(eventMethod.getName(), getDescriptor(), throwableException);
                        } else {
                            throw DescriptorException.targetInvocationWhileEventExecution(eventMethod.getName(), getDescriptor(), throwableException);
                        }
                    }
                } else {
                    PrivilegedAccessHelper.invokeMethod(eventMethod, event.getSource(), runtimeParameters);
                }
            } catch (IllegalAccessException exception) {
                throw DescriptorException.illegalAccessWhileEventExecution(eventMethod.getName(), getDescriptor(), exception);
            } catch (IllegalArgumentException exception) {
                throw DescriptorException.illegalArgumentWhileObsoleteEventExecute(eventMethod.getName(), getDescriptor(), exception);
            } catch (InvocationTargetException exception) {
                throw DescriptorException.targetInvocationWhileEventExecution(eventMethod.getName(), getDescriptor(), exception);
            }
        } finally {
            event.getSession().endOperationProfile(SessionProfiler.DescriptorEvent);
        }
    }

    /**
     * Find the method corresponding to the event selector. The method MUST take 
     * DescriptorEvent as argument, Session is also supported as argument for 
     * backward compatibility.
     */
    protected Method findMethod(int selector) throws DescriptorException {
        Class[] declarationParameters = new Class[1];
        declarationParameters[0] = ClassConstants.DescriptorEvent_Class;
        String methodName = (String)getEventSelectors().elementAt(selector);

        try {
            return Helper.getDeclaredMethod(getDescriptor().getJavaClass(), methodName, declarationParameters);
        } catch (NoSuchMethodException exception) {
            throw DescriptorException.noSuchMethodOnFindObsoleteMethod(methodName, getDescriptor(), exception);
        } catch (SecurityException exception) {
            throw DescriptorException.securityOnFindMethod(methodName, getDescriptor(), exception);
        }
    }

    /**
     * INTERNAL:
     * bug 251180 - Missing method org.eclipse.persistence.descriptors.DescriptorEventManager#setAboutToDeleteSelector 
     */
    public String getAboutToDeleteSelector() {
        return (String)getEventSelectors().elementAt(AboutToDeleteEvent);
    }

    /**
     * INTERNAL:
     */
    public String getAboutToInsertSelector() {
        return (String)getEventSelectors().elementAt(AboutToInsertEvent);
    }

    /**
     * INTERNAL:
     */
    public String getAboutToUpdateSelector() {
        return (String)getEventSelectors().elementAt(AboutToUpdateEvent);
    }

    /**
     * INTERNAL:
     * EJB 3.0 support. Returns the default listeners.
     */
    public Vector getDefaultEventListeners() {
        if (defaultEventListeners == null) {
            defaultEventListeners = new NonSynchronizedVector();
        }
        
        return defaultEventListeners;
    }
    
    /**
     * INTERNAL:
     */
    protected ClassDescriptor getDescriptor() {
        return descriptor;
    }

    /**
     * INTERNAL:
     * used by JPA project caching to store DescriptorEventListener representations that can build the underlying 
     * DescriptorEventListener and add it to the EventManager.
     */
    public java.util.List<SerializableDescriptorEventHolder> getDescriptorEventHolders() {
        if (descriptorEventHolders == null) {
            descriptorEventHolders = new java.util.ArrayList();
        }
        return descriptorEventHolders;
    }

    /**
     * INTERNAL:
     * used by JPA project caching to store DescriptorEventListener representations that can build the underlying 
     * DescriptorEventListener and add it to the EventManager.
     */
    public void setDescriptorEventHolders(java.util.List<SerializableDescriptorEventHolder> descriptorEventHolders) {
        this.descriptorEventHolders = descriptorEventHolders;
    }

    /**
     * INTERNAL:
     * EJB 3.0 support. Returns the entity event listener.
     */
    public DescriptorEventListener getEntityEventListener() {
        return entityEventListener;
    }
    
    /**
     * INTERNAL:
     * EJB 3.0 support. Returns the entity listener event listeners.
     */
    public Vector getEntityListenerEventListeners() {
        if (entityListenerEventListeners == null) {
            entityListenerEventListeners = new Vector();
        }
        
        return entityListenerEventListeners;
    }
    
    /**
     * PUBLIC:
     * Returns the Listener objects that have been added.
     *
     * @see #addListener(DescriptorEventListener)
     */
    public Vector getEventListeners() {
        // Lazy initialize to avoid unnecessary enumerations.
        if (eventListeners == null) {
            eventListeners = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
        }
        return eventListeners;
    }

    protected Vector getEventMethods() {
        //Lazy Initialized to prevent Null Pointer exception after serialization
        if (this.eventMethods == null) {
            this.eventMethods = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(NumberOfEvents);
            for (int index = 0; index < NumberOfEvents; ++index) {
                this.eventMethods.addElement(null);
            }
        }
        return eventMethods;
    }

    protected Vector getEventSelectors() {
        if (this.eventSelectors == null) {
            this.eventSelectors = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(NumberOfEvents);
            for (int index = 0; index < NumberOfEvents; ++index) {
                this.eventSelectors.addElement(null);
            }
        }
        return eventSelectors;
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is built
     */
    public String getPostBuildSelector() {
        return (String)getEventSelectors().elementAt(PostBuildEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is cloned
     */
    public String getPostCloneSelector() {
        return (String)getEventSelectors().elementAt(PostCloneEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is deleted
     */
    public String getPostDeleteSelector() {
        return (String)getEventSelectors().elementAt(PostDeleteEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is inserted
     */
    public String getPostInsertSelector() {
        return (String)getEventSelectors().elementAt(PostInsertEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is merged
     */
    public String getPostMergeSelector() {
        return (String)getEventSelectors().elementAt(PostMergeEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is refreshed
     */
    public String getPostRefreshSelector() {
        return (String)getEventSelectors().elementAt(PostRefreshEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is updated
     */
    public String getPostUpdateSelector() {
        return (String)getEventSelectors().elementAt(PostUpdateEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called after an object is written
     */
    public String getPostWriteSelector() {
        return (String)getEventSelectors().elementAt(PostWriteEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called before the create operation is applied to an object
     */
    public String getPrePersistSelector() {
        return (String)getEventSelectors().elementAt(PrePersistEvent);
    }

  /**
     * PUBLIC:
     *  The name of the method called before an object is deleted
     */
    public String getPreDeleteSelector() {
        return (String)getEventSelectors().elementAt(PreDeleteEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called before an object is inserted
     */
    public String getPreInsertSelector() {
        return (String)getEventSelectors().elementAt(PreInsertEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called before the remove operation is applied to an object
     */
    public String getPreRemoveSelector() {
        return (String)getEventSelectors().elementAt(PreRemoveEvent);
    }

  /**
     * PUBLIC:
     *  The name of the method called before an object is updated
     */
    public String getPreUpdateSelector() {
        return (String)getEventSelectors().elementAt(PreUpdateEvent);
    }

    /**
     * PUBLIC:
     *  The name of the method called before an object is written
     */
    public String getPreWriteSelector() {
        return (String)getEventSelectors().elementAt(PreWriteEvent);
    }

    /**
     * INTERNAL:
     * Return if the event manager has any event listeners, or event methods.
     * If nothing is listening to event they can be avoided.
     */
    public boolean hasAnyEventListeners() {
        // Check listeners in case of collection added to directly as occurs 
        // for aggregates that have a clone of the event manager but not the 
        // listeners.
        return hasAnyEventListeners || hasAnyListeners();
    }   
    
    protected boolean hasAnyListeners() {
        return (eventListeners != null) && (!eventListeners.isEmpty());
    }
    
    /**
     * INTERNAL:
     * This method will return true, if this event manager has default listeners
     * and does not exclude them. Default listeners are always added to every
     * event manager to allow users to turn them on a later time if so desired.
     */
    public boolean hasDefaultEventListeners() {
        return defaultEventListeners != null && ! defaultEventListeners.isEmpty() && ! excludeDefaultListeners;
    }
    
    /** 
     * INTERNAL:
     * EJB 3.0 support. Return true if this event manager has any entity event 
     * listeners.
     */
    public boolean hasEntityEventListener() {
        return entityEventListener != null;
    }
    
    /** 
     * INTERNAL:
     * Internal event support.  Return true if this event manager has any internal 
     * listener event listeners.
     */
    public boolean hasInternalEventListeners() {
        return internalListeners != null && internalListeners.size() > 0;
    }

    /** 
     * INTERNAL:
     * EJB 3.0 support. Return true if this event manager has any entity 
     * listener event listeners.
     */
    public boolean hasEntityListenerEventListeners() {
        return entityListenerEventListeners != null && entityListenerEventListeners.size() > 0;
    }

    /**
     * INTERNAL:
     * Configure inherited selectors.
     */
    public void initialize(AbstractSession session) {
        setHasAnyEventListeners(false);
        // Initialize the EJB 3.0 supported listeners.
        initializeEJB30EventManagers();
        if (hasEntityEventListener() || hasEntityListenerEventListeners() || hasDefaultEventListeners() || hasInternalEventListeners()) {
            setHasAnyEventListeners(true);
        }
        
        // Initialize if events are required at all.
        if (hasAnyListeners() || DescriptorEventListener.class.isAssignableFrom(getDescriptor().getJavaClass())) {
            setHasAnyEventListeners(true);
        }

        for (int index = 0; index < NumberOfEvents; index++) {
            if (getEventSelectors().elementAt(index) != null) {
                setHasAnyEventListeners(true);
                getEventMethods().setElementAt(findMethod(index), index);
            }
        }

        // Inherit all parent defined event method
        // Do NOT inherit the listener as the events are broadcast to the parent.
        if (getDescriptor().isChildDescriptor()) {
            DescriptorEventManager parentEventManager = getDescriptor().getInheritancePolicy().getParentDescriptor().getEventManager();
            if (parentEventManager.hasAnyEventListeners()) {
                setHasAnyEventListeners(true);                
            }

            for (int index = 0; index < NumberOfEvents; index++) {
                if ((getEventSelectors().get(index) == null) && (parentEventManager.getEventSelectors().get(index) != null)) {
                    setHasAnyEventListeners(true);
                    getEventSelectors().set(index, parentEventManager.getEventSelectors().get(index));
                    getEventMethods().set(index, parentEventManager.getEventMethods().get(index));
                }
            }            
        }
    }
    
    /**
     * INTERNAL:
     * EJB 3.0 support. Builds our chains of descriptor event managers that will 
     * need to be notified. The chains are cache so we only need to build them
     * once.
     */
    protected void initializeEJB30EventManagers() {
        entityEventManagers = new NonSynchronizedVector();
        entityListenerEventManagers = new NonSynchronizedVector();
             
        if (hasEntityEventListener()) {
            entityEventManagers.add(this);
        }
        
        if (hasEntityListenerEventListeners()) {
            entityListenerEventManagers.add(this);
        }
        
        ClassDescriptor currentDescriptor = getDescriptor();     
        boolean excludeEntityListeners = excludeSuperclassListeners();
            
        while (currentDescriptor.isChildDescriptor()) {
            currentDescriptor = currentDescriptor.getInheritancePolicy().getParentDescriptor();
            
            DescriptorEventManager eventManager = currentDescriptor.getEventManager();
            
            if (eventManager.hasEntityEventListener()) {
                entityEventManagers.add(eventManager);
            }
            
            if (eventManager.hasEntityListenerEventListeners()) {
                if (!excludeEntityListeners) {
                    entityListenerEventManagers.add(eventManager);
                }
            }
            
            excludeEntityListeners = eventManager.excludeSuperclassListeners();
        }
    }
    
    /**
     * INTERNAL:
     * Notify the EJB 3.0 event listeners.
     */
    protected void notifyEJB30Listeners(DescriptorEvent event) {
        // Step 1 - notify our default listeners.
        if (hasDefaultEventListeners()) {
            for (int i = 0; i < getDefaultEventListeners().size(); i++) {
                DescriptorEventListener listener = (DescriptorEventListener) getDefaultEventListeners().get(i);
                notifyListener(listener, event);
            }
        }

        // Step 2 - Notify the Entity Listener's first, top -> down.
        for (int index = entityListenerEventManagers.size() - 1; index >= 0; index--) {
            Vector entityListenerEventListeners = ((DescriptorEventManager) entityListenerEventManagers.get(index)).getEntityListenerEventListeners();
                 
            for (int i = 0; i < entityListenerEventListeners.size(); i++) {
                DescriptorEventListener listener = (DescriptorEventListener) entityListenerEventListeners.get(i);
                notifyListener(listener, event);
            }
        }

        // Step 3 - Notify the Entity event listeners. top -> down, unless
        // they are overridden in a subclass.
        for (int index = entityEventManagers.size() - 1; index >= 0; index--) {
            DescriptorEventListener entityEventListener = ((DescriptorEventManager) entityEventManagers.get(index)).getEntityEventListener();

            if (! entityEventListener.isOverriddenEvent(event, entityEventManagers)) {
                notifyListener(entityEventListener, event);
            }
        }

        // Step 4 - Notify internal listeners.
        if (internalListeners != null) { // could be null after serialization
            for (DescriptorEventListener listener : internalListeners) {
                notifyListener(listener, event);
            }
        }
    }
    
    /**
     * INTERNAL:
     * Big ugly case statement to notify listeners.
     */
    protected void notifyListener(DescriptorEventListener listener, DescriptorEvent event) throws DescriptorException {
        switch (event.getEventCode()) {
        case PreWriteEvent:
            listener.preWrite(event);
            break;
        case PostWriteEvent:
            listener.postWrite(event);
            break;
        case PreDeleteEvent:
            listener.preDelete(event);
            break;
        case PostDeleteEvent:
            listener.postDelete(event);
            break;
        case PreInsertEvent:
            listener.preInsert(event);
            break;
        case PostInsertEvent:
            listener.postInsert(event);
            break;
        case PreUpdateEvent:
            listener.preUpdate(event);
            break;
        case PostUpdateEvent:
            listener.postUpdate(event);
            break;
        case PostMergeEvent:
            listener.postMerge(event);
            break;
        case PostCloneEvent:
            listener.postClone(event);
            break;
        case PostBuildEvent:
            listener.postBuild(event);
            break;
        case PostRefreshEvent:
            listener.postRefresh(event);
            break;
        case AboutToInsertEvent:
            listener.aboutToInsert(event);
            break;
        case AboutToUpdateEvent:
            listener.aboutToUpdate(event);
            break;
        case AboutToDeleteEvent:
            listener.aboutToDelete(event);
            break;
        case PrePersistEvent:
            listener.prePersist(event);
            break;
        case PreRemoveEvent:
            listener.preRemove(event);
            break;
        case PreUpdateWithChangesEvent:
            listener.preUpdateWithChanges(event);
            break;
        default:
            throw DescriptorException.invalidDescriptorEventCode(event, getDescriptor());
        }
    }

    /**
     * INTERNAL:
     * Notify the event listeners.
     */
    public void notifyListeners(DescriptorEvent event) {
        if (hasAnyListeners()) {
            for (int index = 0; index < getEventListeners().size(); index++) {
                DescriptorEventListener listener = (DescriptorEventListener)getEventListeners().get(index);
                notifyListener(listener, event);
            }
        }

        // Also must notify any inherited listeners.
        if (getDescriptor().isChildDescriptor()) {
            getDescriptor().getInheritancePolicy().getParentDescriptor().getEventManager().notifyListeners(event);
        }
    }

    /**
     * INTERNAL:
     * Used to initialize a remote DescriptorEventManager.
     */
    public void remoteInitialization(AbstractSession session) {
        this.eventMethods = new Vector(NumberOfEvents);

        for (int index = 0; index < NumberOfEvents; index++) {
            this.eventMethods.addElement(null);
        }

        initialize(session);
    }

    /**
     * PUBLIC:
     * Remove a event listener.
     */
    public void removeListener(DescriptorEventListener listener) {
        getEventListeners().removeElement(listener);
    }
    
    /**
     * PUBLIC:
     * A method can be registered to be called when an object's row it about to 
     * be inserted. This uses the optional event argument of the DatabaseRow.
     * This is different from pre/postInsert because it occurs after the row has 
     * already been built. This event can be used to modify the row before 
     * insert, such as adding a user inserted by.
     */
    //bug 251180: Missing method org.eclipse.persistence.descriptors.DescriptorEventManager#setAboutToDeleteSelector 
    public void setAboutToDeleteSelector(String aboutToDeleteSelector) {
        getEventSelectors().setElementAt(aboutToDeleteSelector, AboutToDeleteEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called when an object's row it about to 
     * be inserted. This uses the optional event argument of the DatabaseRow.
     * This is different from pre/postInsert because it occurs after the row has 
     * already been built. This event can be used to modify the row before 
     * insert, such as adding a user inserted by.
     */
    public void setAboutToInsertSelector(String aboutToInsertSelector) {
        getEventSelectors().setElementAt(aboutToInsertSelector, AboutToInsertEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called when an object's row it about to 
     * be updated. This uses the optional event argument of the DatabaseRow.
     * This is different from pre/postUpdate because it occurs after the row has 
     * already been built, and it ONLY called if the update is required (changed 
     * within a unit of work), as the other occur ALWAYS. This event can be used 
     * to modify the row before insert, such as adding a user inserted by.
     */
    public void setAboutToUpdateSelector(String aboutToUpdateSelector) {
        getEventSelectors().setElementAt(aboutToUpdateSelector, AboutToUpdateEvent);
    }

    /**
     * INTERNAL:
     * Set the descriptor.
     */
    public void setDescriptor(ClassDescriptor descriptor) {
        this.descriptor = descriptor;
    }
    
    /**
     * PUBLIC:
     * EJB 3.0 support for lifecycle callback events defined on an entity class.
     */
    public void setEntityEventListener(DescriptorEventListener listener) {
        this.entityEventListener = listener;
    }

    protected void setEventListeners(Vector eventListeners) {
        this.eventListeners = eventListeners;
    }

    protected void setEventMethods(Vector eventMethods) {
        this.eventMethods = eventMethods;
    }

    protected void setEventSelectors(Vector eventSelectors) {
        this.eventSelectors = eventSelectors;
    }
    
    /**
     * INTERNAL:
     * EJB 3.0 support. Default listeners apply to all entities in a persistence 
     * unit. Set this flag to true to exclude the invocation of the default 
     * listeners for this descriptor.
     */
    public void setExcludeDefaultListeners(boolean excludeDefaultListeners) {
        this.excludeDefaultListeners = excludeDefaultListeners;
    }

    /**
     * INTERNAL:
     * EJB 3.0 support. If multiple entity classes in an inheritance hierarchy 
     * define entity listeners, the listeners defined for a superclass are 
     * invoked before the listeners defined for its subclasses. Set this flag 
     * to true to exclude the invocation of the listeners defined by the entity 
     * listener classes for the superclasses of this descriptor.
     */
    public void setExcludeSuperclassListeners(boolean excludeSuperclassListeners) {
        this.excludeSuperclassListeners = excludeSuperclassListeners;
    }
    
    /**
     * INTERNAL:
     * Set if the event manager has any event listeners, or event methods.
     * If nothing is listening to event they can be avoided.
     */
    protected void setHasAnyEventListeners(boolean hasAnyEventListeners) {
        this.hasAnyEventListeners = hasAnyEventListeners;
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * built from the database. This uses the optional event argument for the 
     * DatabaseRow. This event can be used to correctly initialize an object's 
     * non-persistent attributes or to perform complex optimizations or 
     * mappings. This event is called whenever an object is built.
     */
    public void setPostBuildSelector(String postBuildSelector) {
        getEventSelectors().setElementAt(postBuildSelector, PostBuildEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * cloned into a unit of work. This uses the optional event argument for the 
     * original object (the source object is the clone). This event can be used 
     * to correctly initialize an object's non-persistent attributes.
     */
    public void setPostCloneSelector(String postCloneSelector) {
        getEventSelectors().setElementAt(postCloneSelector, PostCloneEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * deleted from the database. This event can notify/remove any dependents 
     * on the object.
     */
    public void setPostDeleteSelector(String postDeleteSelector) {
        getEventSelectors().setElementAt(postDeleteSelector, PostDeleteEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * inserted into the database. This event can be used to notify any 
     * dependent on the object, or to update information not accessible until 
     * the object has been inserted.
     */
    public void setPostInsertSelector(String postInsertSelector) {
        getEventSelectors().setElementAt(postInsertSelector, PostInsertEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * merge from a unit of work. This uses the optional event argument of the 
     * original object which is the object being merged from, the source object 
     * is the object being merged into. This event can be used to correctly 
     * initialize an object's non-persistent attributes.
     */
    public void setPostMergeSelector(String postMergeSelector) {
        getEventSelectors().setElementAt(postMergeSelector, PostMergeEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * refreshed from the database. This uses the optional event argument of 
     * the DatabaseRow. This event can be used to correctly initialize an 
     * object's non-persistent attributes or to perform complex optimizations or 
     * mappings. This event is only called on refreshes of existing objects.
     */
    public void setPostRefreshSelector(String postRefreshSelector) {
        getEventSelectors().setElementAt(postRefreshSelector, PostRefreshEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * updated into the database.
     */
    public void setPostUpdateSelector(String postUpdateSelector) {
        getEventSelectors().setElementAt(postUpdateSelector, PostUpdateEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that has just been 
     * written to the database. This event is raised on any registered object 
     * in a unit of work, even if it has not changed, refer to the 
     * "aboutToUpdate" selector if it is required for the event to be raised 
     * only when the object has been changed. This will be called on all inserts 
     * and updates, after the "postInsert/Update" event has been raised. This 
     * event can be used to notify any dependent on the object.
     */
    public void setPostWriteSelector(String postWriteSelector) {
        getEventSelectors().setElementAt(postWriteSelector, PostWriteEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that is going to be 
     * deleted from the database. This event can notify/remove any dependents 
     * on the object.
     */
    public void setPreDeleteSelector(String preDeleteSelector) {
        getEventSelectors().setElementAt(preDeleteSelector, PreDeleteEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that is going to be 
     * inserted into the database. This event can be used to notify any 
     * dependent on the object or acquire the object's id through a custom 
     * mechanism.
     */
    public void setPreInsertSelector(String preInsertSelector) {
        getEventSelectors().setElementAt(preInsertSelector, PreInsertEvent);
    }
    
    /**
     * PUBLIC:
     * A method can be registered to be called on a object when that object has 
     * the create operation applied to it.
     */
    public void setPrePersistSelector(String prePersistSelector) {
        getEventSelectors().setElementAt(prePersistSelector, PrePersistEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object when that object has 
     * the remove operation applied to it.
     */
    public void setPreRemoveSelector(String preRemoveSelector) {
        getEventSelectors().setElementAt(preRemoveSelector, PreRemoveEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that is going to be 
     * updated into the database. This event is raised on any registered object 
     * in a unit of work, even if it has not changed, refer to the 
     * "aboutToUpdate" selector if it is required for the event to be raised 
     * only when the object has been changed. This event can be used to notify 
     * any dependent on the object.
     */
    public void setPreUpdateSelector(String preUpdateSelector) {
        getEventSelectors().setElementAt(preUpdateSelector, PreUpdateEvent);
    }

    /**
     * PUBLIC:
     * A method can be registered to be called on a object that is going to be 
     * written to the database. This event is raised on any registered object 
     * in a unit of work, even if it has not changed, refer to the 
     * "aboutToUpdate" selector if it is required for the event to be raised 
     * only when the object has been changed. This will be called on all inserts 
     * and updates, before the "preInsert/Update" event has been raised. This 
     * event can be used to notify any dependent on the object.
     */
    public void setPreWriteSelector(String preWriteSelector) {
        getEventSelectors().setElementAt(preWriteSelector, PreWriteEvent);
    }
}
