| /* |
| * Copyright (c) 2011, 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: |
| // July 13, 2011 - Andrei Ilitchev (Oracle) - initial API and implementation |
| package org.eclipse.persistence.testing.framework; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl; |
| import org.eclipse.persistence.sessions.Session; |
| import org.eclipse.persistence.sessions.SessionEvent; |
| import org.eclipse.persistence.sessions.SessionEventListener; |
| import org.eclipse.persistence.sessions.SessionEventManager; |
| |
| /** |
| * <p><b>Purpose</b>: Used to test handling of session events. |
| * |
| * Create several instances of the class; |
| * name them to distinguish from each other; |
| * add them to different sessions' EventManagers. |
| * |
| * In the test: |
| * // clear the previously logged event handlings |
| * SessionEventTracker.clearLog(); |
| * // define which events should be tracked, for instance: |
| * // first clear all the previously set events, |
| * SessionEventTracker.noneEvents(); |
| * // then add events to be tracked: |
| * SessionEventTracker.addEvent(SessionEvent.PreLogin); |
| * SessionEventTracker.addEvent(SessionEvent.PostLogin); |
| * // start tracking |
| * SessionEventTracker.startTracking(); |
| * |
| * // do something |
| * |
| * SessionEventTracker.stopTracking(); |
| * // analyze listeners and events Lists. |
| * |
| * clean up (optional) |
| * SessionEventTracker.clearLog(); |
| * // return back to the default setting - handling all events |
| * SessionEventTracker.allEvents(); |
| * |
| * In static handlings list contains instances of Handling class - |
| * each holds an event and a listener that has handled it. |
| * |
| * It's possible to set error on a Handling (see examples preLogin and postLogin) - |
| * that sends the erroneous Handling to errors list (still kept on handlings list, too). |
| * |
| * @see SessionEventManager#addListener(SessionEventListener) |
| * @see Session#getEventManager() |
| * @see SessionEvent |
| */ |
| public class SessionEventTracker implements SessionEventListener { |
| |
| // in order from 1 to (currently) 35 (no holes!). |
| // should be kept in sync with SessionEvent codes. |
| public static final String[] eventNames = { |
| "PreExecuteQuery ", |
| "PostExecuteQuery", |
| "PreBeginTransaction", |
| "PostBeginTransaction", |
| "PreCommitTransaction", |
| "PostCommitTransaction", |
| "PreRollbackTransaction", |
| "PostRollbackTransaction", |
| "PostAcquireUnitOfWork", |
| "PreCommitUnitOfWork", |
| "PostCommitUnitOfWork", |
| "PreReleaseUnitOfWork", |
| "PostReleaseUnitOfWork", |
| "PrepareUnitOfWork", |
| "PostResumeUnitOfWork", |
| "PostAcquireClientSession", |
| "PreReleaseClientSession", |
| "PostReleaseClientSession", |
| "OutputParametersDetected", |
| "MoreRowsDetected", |
| "PostConnect", |
| "PostAcquireConnection", |
| "PreReleaseConnection", |
| "PreLogin", |
| "PostLogin", |
| "PreMergeUnitOfWorkChangeSet", |
| "PreDistributedMergeUnitOfWorkChangeSet", |
| "PostMergeUnitOfWorkChangeSet", |
| "PostDistributedMergeUnitOfWorkChangeSet", |
| "PreCalculateUnitOfWorkChangeSet", |
| "PostCalculateUnitOfWorkChangeSet", |
| "MissingDescriptor", |
| "PostAcquireExclusiveConnection", |
| "PreReleaseExclusiveConnection", |
| "NoRowsModified" |
| }; |
| |
| public static int nEvents = eventNames.length; |
| |
| public static boolean isTracking; |
| public static boolean[] shouldTrackEvent = new boolean[nEvents]; |
| static { |
| allEvents(); |
| } |
| |
| public static class Handling { |
| public Handling(SessionEventTracker listener, SessionEvent event) { |
| this.time = System.currentTimeMillis(); |
| this.listener = listener; |
| this.event = event; |
| } |
| long time; |
| SessionEventTracker listener; |
| SessionEvent event; |
| String error = ""; |
| public String toString() { |
| return listener.name + " -> " + eventToString(event) + (error.length()==0 ? "" : " Error: " + error); |
| } |
| public void setError(String error) { |
| this.error = error; |
| if (error.length() > 0) { |
| synchronized (errors) { |
| errors.add(this); |
| } |
| } else { |
| synchronized (errors) { |
| errors.remove(this); |
| } |
| } |
| } |
| public String getError() { |
| return error; |
| } |
| public SessionEvent getEvent() { |
| return event; |
| } |
| public SessionEventTracker getListener() { |
| return listener; |
| } |
| } |
| |
| protected static List<Handling> handlings = new ArrayList(); |
| protected static List<Handling> errors = new ArrayList(); |
| |
| protected String name = ""; |
| |
| public SessionEventTracker() { |
| super(); |
| } |
| |
| public SessionEventTracker(String name) { |
| this.name = name; |
| } |
| |
| public static void startTracking() { |
| isTracking = true; |
| } |
| |
| public static void stopTracking() { |
| isTracking = false; |
| } |
| |
| public static boolean isTracking() { |
| return isTracking; |
| } |
| |
| public static boolean isTrackingEvent(SessionEvent event) { |
| if (isTracking) { |
| return shouldTrackEvent[event.getEventCode()]; |
| } else { |
| return false; |
| } |
| } |
| |
| public static int size() { |
| return handlings.size(); |
| } |
| |
| public static void clearLog() { |
| handlings = new ArrayList(); |
| errors = new ArrayList(); |
| } |
| |
| public static void allEvents() { |
| for (int i=1; i<nEvents; i++) { |
| shouldTrackEvent[i] = true; |
| } |
| } |
| |
| public static void noneEvents() { |
| for (int i=1; i<nEvents; i++) { |
| shouldTrackEvent[i] = false; |
| } |
| } |
| |
| public static void addEvent(int eventCode) { |
| if (eventCode <= 0) { |
| throw new TestErrorException("event code " + eventCode + " is wrong - should be a positive number"); |
| } else if (eventCode > eventNames.length) { |
| throw new TestErrorException("event code " + eventCode + " is unknown - add event name for it to SessionEventTracker.eventNames array"); |
| } |
| shouldTrackEvent[eventCode] = true; |
| } |
| |
| public static void removeEvent(int eventCode) { |
| if (eventCode <= 0) { |
| throw new TestErrorException("event code " + eventCode + " is wrong - should be a positive number"); |
| } else if (eventCode > eventNames.length) { |
| throw new TestErrorException("event code " + eventCode + " is unknown - add event name for it to SessionEventTracker.eventNames array"); |
| } |
| shouldTrackEvent[eventCode] = false; |
| } |
| |
| public static List<Handling> getHandlings() { |
| return handlings; |
| } |
| |
| public static List<Handling> getErrors() { |
| return errors; |
| } |
| |
| public String getName() { |
| return this.name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the session if a descriptor is missing for a class being persisted. |
| * This can be used to lazy register the descriptor or set of descriptors. |
| */ |
| @Override |
| public void missingDescriptor(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the session after read object query detected more than a single row back from the database. |
| * The "result" of the event will be the call. Some applications may want to interpret this as an error or warning condition. |
| */ |
| @Override |
| public void moreRowsDetected(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the session after update or delete SQL has been sent to the database |
| * but a row count of zero was returned. |
| */ |
| @Override |
| public void noRowsModified(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the session after a stored procedure call has been executed that had output parameters. |
| * If the proc was used to override an insert/update/delete operation then EclipseLink will not be expecting any return value. |
| * This event mechanism allows for a listener to be registered before the proc is call to process the output values. |
| * The event "result" will contain a Record of the output values, and property "call" will be the StoredProcedureCall. |
| */ |
| @Override |
| public void outputParametersDetected(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the client session after creation/acquiring. |
| */ |
| @Override |
| public void postAcquireClientSession(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on when using the server/client sessions. |
| * This event is raised after a connection is acquired from a connection pool. |
| */ |
| @Override |
| public void postAcquireConnection(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised when a ClientSession, with Isolated data, acquires |
| * an exclusive connection. The event will contain the ClientSession that |
| * is being acquired. Users can set properties within the ConnectionPolicy |
| * of that ClientSession for access within this event. |
| */ |
| @Override |
| public void postAcquireExclusiveConnection(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work after creation/acquiring. |
| * This will be raised on nest units of work. |
| */ |
| @Override |
| public void postAcquireUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after a database transaction is started. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void postBeginTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after the commit has begun on the UnitOfWork but before |
| * the changes are calculated. |
| */ |
| @Override |
| public void preCalculateUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after the commit has begun on the UnitOfWork and |
| * after the changes are calculated. The UnitOfWorkChangeSet, at this point, |
| * will contain changeSets without the version fields updated and without |
| * IdentityField type primary keys. These will be updated after the insert, or |
| * update, of the object |
| */ |
| @Override |
| public void postCalculateUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after a database transaction is commited. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void postCommitTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work after commit. |
| * This will be raised on nest units of work. |
| */ |
| @Override |
| public void postCommitUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after the session connects to the database. |
| * In a server session this event is raised on every new connection established. |
| */ |
| @Override |
| public void postConnect(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after the execution of every query against the session. |
| * The event contains the query and query result. |
| */ |
| @Override |
| public void postExecuteCall(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after the execution of every query against the session. |
| * The event contains the query and query result. |
| */ |
| @Override |
| public void postExecuteQuery(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the client session after releasing. |
| */ |
| @Override |
| public void postReleaseClientSession(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work after release. |
| * This will be raised on nest units of work. |
| */ |
| @Override |
| public void postReleaseUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work after resuming. |
| * This occurs after pre/postCommit. |
| */ |
| @Override |
| public void postResumeUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised after a database transaction is rolledback. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void postRollbackTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This even will be raised after a UnitOfWorkChangeSet has been merged |
| * When that changeSet has been received from a distributed session |
| */ |
| @Override |
| public void postDistributedMergeUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This even will be raised after a UnitOfWorkChangeSet has been merged |
| */ |
| @Override |
| public void postMergeUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised before a database transaction is started. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void preBeginTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised before a database transaction is commited. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void preCommitTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work before commit. |
| * This will be raised on nest units of work. |
| */ |
| @Override |
| public void preCommitUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised before the execution of every query against the session. |
| * The event contains the query to be executed. |
| */ |
| @Override |
| public void preExecuteCall(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised before the execution of every query against the session. |
| * The event contains the query to be executed. |
| */ |
| @Override |
| public void preExecuteQuery(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work after the SQL has been flushed, but the commit transaction has not been executed. |
| * It is similar to the JTS prepare phase. |
| */ |
| @Override |
| public void prepareUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the client session before releasing. |
| */ |
| @Override |
| public void preReleaseClientSession(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on when using the server/client sessions. |
| * This event is raised before a connection is released into a connection pool. |
| */ |
| @Override |
| public void preReleaseConnection(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is fired just before a Client Session, with isolated data, |
| * releases its Exclusive Connection |
| */ |
| @Override |
| public void preReleaseExclusiveConnection(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised on the unit of work before release. |
| * This will be raised on nest units of work. |
| */ |
| @Override |
| public void preReleaseUnitOfWork(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This event is raised before a database transaction is rolledback. |
| * It is not raised for nested transactions. |
| */ |
| @Override |
| public void preRollbackTransaction(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This even will be raised before a UnitOfWorkChangeSet has been merged |
| * When that changeSet has been received from a distributed session |
| */ |
| @Override |
| public void preDistributedMergeUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This even will be raised before a UnitOfWorkChangeSet has been merged |
| */ |
| @Override |
| public void preMergeUnitOfWorkChangeSet(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This Event is raised before the session logs in. |
| */ |
| @Override |
| public void preLogin(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| Handling handling = log(event); |
| if (((DatabaseSessionImpl)event.getSession()).isLoggedIn()) { |
| handling.setError("session is already logged in"); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * This Event is raised after the session logs out. |
| */ |
| @Override |
| public void preLogout(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This Event is raised after the session logs out. |
| */ |
| @Override |
| public void postLogout(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| log(event); |
| } |
| |
| /** |
| * PUBLIC: |
| * This Event is raised after the session logs in. |
| */ |
| @Override |
| public void postLogin(SessionEvent event) { |
| if (!isTrackingEvent(event)) { |
| return; |
| } |
| Handling handling = log(event); |
| String errorMsg = ""; |
| Iterator<ClassDescriptor> it = event.getSession().getDescriptors().values().iterator(); |
| while (it.hasNext()) { |
| ClassDescriptor descriptor = it.next(); |
| if (!descriptor.isAggregateDescriptor()) { |
| if (!descriptor.isFullyInitialized()) { |
| errorMsg += descriptor.getJavaClass().getName() + "; "; |
| } |
| } |
| } |
| if (errorMsg.length() > 0) { |
| errorMsg = "Some descriptors are not initialized: " + errorMsg; |
| handling.setError(errorMsg); |
| } |
| } |
| |
| protected Handling log(SessionEvent event) { |
| Handling handling = new Handling(this, event); |
| synchronized (handlings) { |
| handlings.add(handling); |
| } |
| return handling; |
| } |
| |
| public String toString() { |
| return Helper.getShortClassName(this) + "(" + (name != null ? name : "") + ")"; |
| } |
| |
| public static String eventToString(SessionEvent event) { |
| return getEventName(event.getEventCode()) + "[" + sessionToString(event.getSession()) + "]"; |
| } |
| |
| public static String getEventName(int eventCode) { |
| if (eventCode <= 0) { |
| throw new TestErrorException("event code " + eventCode + " is wrong - should be a positive number"); |
| } else if (eventCode > eventNames.length) { |
| throw new TestErrorException("event code " + eventCode + " is unknown - add event name for it to SessionEventTracker.eventNames array"); |
| } |
| return eventNames[eventCode - 1]; |
| } |
| |
| public static String sessionToString(Session session) { |
| return Helper.getShortClassName(session) + "(" + session.getName() + ")"; |
| } |
| } |