/*
 * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2021 IBM Corporation. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sessions;

import java.util.*;
import java.io.*;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.core.sessions.CoreSessionEventManager;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.sessions.*;
import org.eclipse.persistence.sessions.broker.SessionBroker;
import org.eclipse.persistence.sessions.server.ClientSession;
import org.eclipse.persistence.sessions.SessionProfiler;

/**
 * <p><b>Purpose</b>: Used to support session events.
 * To register for events notification an event listener must be registered with the session.
 *
 * @see Session#getEventManager()
 * @see SessionEvent
 */
public class SessionEventManager extends CoreSessionEventManager<SessionEventListener> implements Cloneable, Serializable {
    protected List<SessionEventListener> listeners;
    protected Session session;

    /**
     * INTERNAL:
     * Default constructor.
     */
    public SessionEventManager() {
        this.listeners = new ArrayList<SessionEventListener>();
    }

    /**
     * PUBLIC:
     * Create a new session event manager for a session
     */
    public SessionEventManager(Session session) {
        this.listeners = new ArrayList<SessionEventListener>();
        this.session = session;
    }

    /**
     * PUBLIC:
     * Add the event listener to the session.
     * The listener will receive all events raised by this session.
     * Also unit of works acquire from this session will inherit the listeners.
     * If session is a broker then its members add the listener, too.
     */
    public void addListener(SessionEventListener listener) {
        if (this.session != null) {
            if (this.session.isConnected()) {
                synchronized(this) {
                    if (this.listeners != null) {
                        ArrayList<SessionEventListener> listenersCopy = new ArrayList(listeners);
                        listenersCopy.add(listener);
                        this.listeners = listenersCopy;
                    } else {
                        this.listeners = new ArrayList();
                        this.listeners.add(listener);
                    }
                }
            } else {
                getListeners().add(listener);
            }
            if (this.session.isSessionBroker()) {
                // add listener to member sessions
                Iterator<AbstractSession> memberSessions = ((SessionBroker)this.session).getSessionsByName().values().iterator();
                while (memberSessions.hasNext()) {
                    AbstractSession memberSession = memberSessions.next();
                    memberSession.getEventManager().addListener(listener);
                }
            }
        } else {
            getListeners().add(listener);
        }
    }

    /**
     * INTERNAL:
     * Shallow clone the event manager.
     */
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException exception) {
            return new InternalError(exception.toString());
        }
    }

    /**
     * INTERNAL:
     * Clone the event manager for the new session.
     */
    public SessionEventManager clone(Session newSession) {
        SessionEventManager newManager = (SessionEventManager)clone();
        newManager.setSession(newSession);
        if (this.listeners != null) {
            newManager.setListeners(new ArrayList<SessionEventListener>(this.listeners));
        }
        return newManager;
    }

    /**
     * PUBLIC:
     * The event listeners will receive all events raised by this session.
     * Also unit of works acquire from this session will inherit the listeners.
     */
    public List<SessionEventListener> getListeners() {
        if (listeners == null) {
            listeners = new ArrayList<SessionEventListener>();
        }
        return listeners;
    }

    /**
     * INTERNAL:
     * Get the session for this session event manager
     */
    public Session getSession() {
        return session;
    }

    /**
     * PUBLIC:
     * Check if there are any event listeners.
     */
    public boolean hasListeners() {
        return (this.listeners != null) && (!this.listeners.isEmpty());
    }

    /**
     * INTERNAL:
     * Raised for missing descriptors for lazy registration.
     */
    public void missingDescriptor(Class missingClass) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.MissingDescriptor, getSession());
        event.setResult(missingClass);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).missingDescriptor(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised for stored proc output parameters.
     */
    public void moreRowsDetected(DatabaseCall call) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.MoreRowsDetected, getSession());
        event.setResult(call);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).moreRowsDetected(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised for stored proc output parameters.
     */
    public void noRowsModified(ModifyQuery query, Object object) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.NoRowsModified, getSession());
        event.setQuery(query);
        event.setResult(object);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).noRowsModified(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised for stored proc output parameters.
     */
    public void outputParametersDetected(Record outputRow, DatasourceCall call) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.OutputParametersDetected, getSession());
        event.setResult(outputRow);
        event.setProperty("call", call);
        event.setQuery(call.getQuery());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).outputParametersDetected(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post acquire client session.
     */
    public void postAcquireClientSession() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostAcquireClientSession, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postAcquireClientSession(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised after acquire a connection from a connection pool.
     */
    public void postAcquireConnection(Accessor accessor) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostAcquireConnection, getSession());
        event.setResult(accessor);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postAcquireConnection(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised after acquire a connection from a connection pool.
     */
    public void postAcquireExclusiveConnection(ClientSession clientSession, Accessor accessor) {
        if (!hasListeners()) {
            return;
        }

        SessionEvent event = new SessionEvent(SessionEvent.PostAcquireExclusiveConnection, clientSession);
        event.setResult(accessor);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postAcquireExclusiveConnection(event);
        }
    }

    /**
     * INTERNAL:
     * Post acquire unit of work.
     */
    public void postAcquireUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostAcquireUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postAcquireUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post begin transaction.
     */
    public void postBeginTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostBeginTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postBeginTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post commit transaction.
     */
    public void postCommitTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostCommitTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postCommitTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post commit unit of work.
     */
    public void postCommitUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostCommitUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postCommitUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised after connecting.
     */
    public void postConnect(Accessor accessor) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostConnect, getSession());
        event.setResult(accessor);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postConnect(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post execute call.
     */
    public void postExecuteCall(Call call, Object result) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostExecuteCall, getSession());
        event.setCall(call);
        event.setResult(result);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            this.listeners.get(index).postExecuteCall(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post execute query.
     */
    public void postExecuteQuery(DatabaseQuery query, Object result) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostExecuteQuery, getSession());
        event.setQuery(query);
        event.setResult(result);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            this.listeners.get(index).postExecuteQuery(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post release client session.
     */
    public void postReleaseClientSession() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostReleaseClientSession, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postReleaseClientSession(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post release unit of work.
     */
    public void postReleaseUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostReleaseUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postReleaseUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post resume unit of work.
     */
    public void postResumeUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostResumeUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postResumeUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post rollback transaction.
     */
    public void postRollbackTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostRollbackTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postRollbackTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre execute query.
     */
    public void postDistributedMergeUnitOfWorkChangeSet(UnitOfWorkChangeSet changeSet) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostDistributedMergeUnitOfWorkChangeSet, getSession());
        event.setProperty("UnitOfWorkChangeSet", changeSet);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postDistributedMergeUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre execute query.
     */
    public void postMergeUnitOfWorkChangeSet(UnitOfWorkChangeSet changeSet) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostMergeUnitOfWorkChangeSet, getSession());
        event.setProperty("UnitOfWorkChangeSet", changeSet);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postMergeUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre begin transaction.
     */
    public void preBeginTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreBeginTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preBeginTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre calculate UnitOfWork Change Set.
     */
    public void preCalculateUnitOfWorkChangeSet() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreCalculateUnitOfWorkChangeSet, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preCalculateUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Post calculate UnitOfWork Change Set.
     */
    public void postCalculateUnitOfWorkChangeSet(UnitOfWorkChangeSet changeSet) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostCalculateUnitOfWorkChangeSet, getSession());
        event.setProperty("UnitOfWorkChangeSet", changeSet);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postCalculateUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
       * INTERNAL:
       * Pre commit transaction.
       */
    public void preCommitTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreCommitTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preCommitTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre commit unit of work.
     */
    public void preCommitUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreCommitUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preCommitUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre execute call.
     */
    public void preExecuteCall(Call call) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreExecuteCall, getSession());
        event.setCall(call);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preExecuteCall(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre execute query.
     */
    public void preExecuteQuery(DatabaseQuery query) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreExecuteQuery, getSession());
        event.setQuery(query);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preExecuteQuery(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre login to the session.
     */
    public void preLogin(Session session) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreLogin, session);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preLogin(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * post login to the session.
     */
    public void postLogin(Session session) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostLogin, session);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postLogin(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre logout to the session.
     */
    public void preLogout(Session session) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreLogout, session);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preLogout(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * post logout to the session.
     */
    public void postLogout(Session session) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PostLogout, session);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).postLogout(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Prepare unit of work.
     */
    public void prepareUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PrepareUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).prepareUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre release client session.
     */
    public void preReleaseClientSession() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreReleaseClientSession, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preReleaseClientSession(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Raised before release a connection to a connection pool.
     */
    public void preReleaseConnection(Accessor accessor) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreReleaseConnection, getSession());
        event.setResult(accessor);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preReleaseConnection(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * This event is fired just before a Client Session, with isolated data,
     * releases its Exclusive Connection
     */
    public void preReleaseExclusiveConnection(ClientSession clientSession, Accessor accessor) {
        if (!hasListeners()) {
            return;
        }

        SessionEvent event = new SessionEvent(SessionEvent.PreReleaseExclusiveConnection, clientSession);
        event.setResult(accessor);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preReleaseExclusiveConnection(event);
        }
    }

    /**
     * INTERNAL:
     * Pre release unit of work.
     */
    public void preReleaseUnitOfWork() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreReleaseUnitOfWork, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preReleaseUnitOfWork(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre rollback transaction.
     */
    public void preRollbackTransaction() {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreRollbackTransaction, getSession());
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preRollbackTransaction(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre merge Distributed UnitOfWorkChangeSet
     */
    public void preDistributedMergeUnitOfWorkChangeSet(UnitOfWorkChangeSet changeSet) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreDistributedMergeUnitOfWorkChangeSet, getSession());
        event.setProperty("UnitOfWorkChangeSet", changeSet);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preDistributedMergeUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
     * INTERNAL:
     * Pre merge UnitOfWorkChangeSet
     */
    public void preMergeUnitOfWorkChangeSet(UnitOfWorkChangeSet changeSet) {
        if (!hasListeners()) {
            return;
        }
        startOperationProfile();
        SessionEvent event = new SessionEvent(SessionEvent.PreMergeUnitOfWorkChangeSet, getSession());
        event.setProperty("UnitOfWorkChangeSet", changeSet);
        List<SessionEventListener> listeners = this.listeners;
        int size = listeners.size();
        for (int index = 0; index < size; index++) {
            listeners.get(index).preMergeUnitOfWorkChangeSet(event);
        }
        endOperationProfile();
    }

    /**
     * PUBLIC:
     * Remove the event listener from the session.
     * If session is a broker and the listener was in its list, then its members remove the listener, too.
     */
    public void removeListener(SessionEventListener listener) {
        if (this.session != null) {
            boolean isRemoved = false;
            if (this.session.isConnected()) {
                synchronized(this) {
                    if (this.listeners != null) {
                        ArrayList<SessionEventListener> listenersCopy = new ArrayList(listeners);
                        isRemoved = listenersCopy.remove(listener);
                        if (isRemoved) {
                            this.listeners = listenersCopy;
                        }
                    }
                }
            } else {
                isRemoved = getListeners().remove(listener);
            }
            if (this.session.isSessionBroker() && isRemoved) {
                // remove listener from member sessions
                Iterator<AbstractSession> memberSessions = ((SessionBroker)this.session).getSessionsByName().values().iterator();
                while (memberSessions.hasNext()) {
                    AbstractSession memberSession = memberSessions.next();
                    memberSession.getEventManager().removeListener(listener);
                }
            }
        } else {
            getListeners().remove(listener);
        }
    }

    /**
     * The event listeners will receive all events raised by this session.
     * Also unit of works acquire from this session will inherit the listeners.
     */
    protected void setListeners(List<SessionEventListener> listeners) {
        this.listeners = listeners;
    }

    /**
     * INTERNAL:
     * Set the session for this session event manager
     */
    public void setSession(Session session) {
        this.session = session;
    }

    /**
     * INTERNAL:
     * Start call
     */
    protected void startOperationProfile() {
        if (getSession().isInProfile()) {
            getSession().getProfiler().startOperationProfile(SessionProfiler.SessionEvent);
        }
    }

    /**
     * INTERNAL:
     * End call
     */
    protected void endOperationProfile() {
        if (getSession().isInProfile()) {
            getSession().getProfiler().endOperationProfile(SessionProfiler.SessionEvent);
        }
    }
}
