All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.sequencing.SequencingManager Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 1998, 2024 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 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.internal.sequencing;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.MultitenantPolicy;
import org.eclipse.persistence.descriptors.SchemaPerMultitenantPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.helper.ConcurrencyManager;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sequencing.DefaultSequence;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sequencing.SequencingControl;
import org.eclipse.persistence.sessions.Login;
import org.eclipse.persistence.sessions.server.ConnectionPool;
import org.eclipse.persistence.sessions.server.ExternalConnectionPool;
import org.eclipse.persistence.sessions.server.ServerSession;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SequencingManager is private to EclipseLink.
 * It provides most of sequencing functionality.
 * It's accessed by DatabaseSession through getSequencingHome() method.
 * 

* Here's the lifecycle of SequencingManager. * InitialState: SequencingManager doesn't exist. * Action: SequencingManager created -{@literal >} Not connected State. * State: Not connected. * isConnected() returns false; * getSequencingControl() could be used; * getSequencing() == getSequencingServer() == getSequencingCallbackFactory() == null; * Action: onConnect is called -{@literal >} Connected State. * State: Connected. * isConnected() returns true; * getSequencingControl() could be used; * getSequencing() could be used; * in case ownwerSession is a ServerSession getSequencingServer() could be used; * Action: onDisconnect is called -{@literal >} Not connected State. *

* Here's a sketch of SequencingManager architecture. * The main 4 objects comprising SessionManager are: * valueGenarationPolicy; * preallocationHandler; * connectionHandler; * state; *

* That's how they evolve during lifetime of SequencingManager object: * Not connected State: * preallocationHandler doesn't have any preallocated sequencing values. * connectionHandler == null; * state == null; *

* Connected State: * preallocationHandler may contain preallocated sequencing values. * valueGenarationPolicy != null; * state != null; *

* The most important method of the class is onConnect(): * that's where, using values of the attributes'(accessible through SequencingControl): * shouldUseSeparateConnection; * login; * minPoolSize; * maxPoolSize; * as well as boolean flags returned by valueGenerationPolicy methods: * shouldAcquireValueAfterInsert(); * shouldUsePreallocation(); * shouldUseSeparateConnection(); * shouldUseTransaction(); * one of implementors of inner interface State is created. *

* Once in Connected State, neither changes to attributes, nor to returns of valueGenerationPolicy's * four should... methods can change the state object. * To change the state object, onDisconnect(), than onConnect() should be called. * There is no need to do it directly: each of the following methods * available through SequencingControl does that: * setValueGenerationPolicy; * setShouldUseNativeSequencing; * setShouldUseTableSequencing; * resetSequencing; */ class SequencingManager implements SequencingHome, SequencingServer, SequencingControl { private final DatabaseSessionImpl ownerSession; private SequencingConnectionHandler connectionHandler; private Map preallocationHandler; private int whenShouldAcquireValueForAll; private Vector connectedSequences; boolean atLeastOneSequenceShouldUseTransaction; boolean atLeastOneSequenceShouldUsePreallocation; // state ids private static final int NOPREALLOCATION = 0; private static final int PREALLOCATION_NOTRANSACTION = 1; private static final int PREALLOCATION_TRANSACTION_NOACCESSOR = 2; private static final int PREALLOCATION_TRANSACTION_ACCESSOR = 3; private static final int NUMBER_OF_STATES = 4; private State[] states; private Map locks; private SequencingCallbackFactory callbackFactory; private SequencingServer server; private Sequencing seq; private boolean shouldUseSeparateConnection; private Login login; private int minPoolSize = -1; private int maxPoolSize = -1; private int initialPoolSize = -1; private ConnectionPool connectionPool; public SequencingManager(DatabaseSessionImpl ownerSession) { this.ownerSession = ownerSession; } protected DatabaseSessionImpl getOwnerSession() { return ownerSession; } protected void createConnectionHandler() { boolean isServerSession = getOwnerSession().isServerSession(); if (getLogin() == null) { Login login; if (isServerSession) { login = ((ServerSession)getOwnerSession()).getReadConnectionPool().getLogin(); } else { login = getOwnerSession().getDatasourceLogin(); } setLogin(login); } if (getLogin() != null) { if (getLogin().shouldUseExternalTransactionController()) { throw ValidationException.invalidSequencingLogin(); } } if (isServerSession) { ConnectionPool pool = null; if (this.connectionPool == null) { if (getLogin().shouldUseExternalConnectionPooling()) { pool = new ExternalConnectionPool("sequencing", getLogin(), (ServerSession)getOwnerSession()); } else { if (getMinPoolSize() == -1) { setMinPoolSize(2); } if (getMaxPoolSize() == -1) { setMinPoolSize(2); } if (getInitialPoolSize() == -1) { setInitialPoolSize(1); } pool = new ConnectionPool("sequencing", getLogin(), getInitialPoolSize(), getMinPoolSize(), getMaxPoolSize(), (ServerSession)getOwnerSession()); } } else { pool = this.connectionPool; } setConnectionHandler(new ServerSessionConnectionHandler(pool)); } else { setConnectionHandler(new DatabaseSessionConnectionHandler(getOwnerSession(), getLogin())); } } @Override public SequencingControl getSequencingControl() { return this; } protected void setSequencing(Sequencing sequencing) { this.seq = sequencing; } @Override public Sequencing getSequencing() { return seq; } protected void setSequencingServer(SequencingServer server) { this.server = server; } @Override public SequencingServer getSequencingServer() { return server; } protected void setSequencingCallbackFactory(SequencingCallbackFactory callbackFactory) { this.callbackFactory = callbackFactory; } @Override public boolean isSequencingCallbackRequired() { return this.callbackFactory != null; } @Override public boolean shouldUseSeparateConnection() { return shouldUseSeparateConnection; } @Override public void setShouldUseSeparateConnection(boolean shouldUseSeparateConnection) { this.shouldUseSeparateConnection = shouldUseSeparateConnection; } @Override public boolean isConnectedUsingSeparateConnection() { return isConnected() && (getConnectionHandler() != null); } @Override public Login getLogin() { return login; } @Override public void setLogin(Login login) { this.login = login; } @Override public int getMinPoolSize() { return minPoolSize; } @Override public void setMinPoolSize(int size) { this.minPoolSize = size; } @Override public int getMaxPoolSize() { return maxPoolSize; } @Override public void setMaxPoolSize(int size) { this.maxPoolSize = size; } public int getInitialPoolSize() { return this.initialPoolSize; } @Override public void setInitialPoolSize(int size) { this.initialPoolSize = size; } @Override public boolean isConnected() { return states != null; } // SequencingSetup protected SequencingConnectionHandler getConnectionHandler() { return connectionHandler; } protected void setConnectionHandler(SequencingConnectionHandler handler) { this.connectionHandler = handler; } @Override public ConnectionPool getConnectionPool() { if ((getConnectionHandler() != null) && (getConnectionHandler() instanceof ServerSessionConnectionHandler)) { return ((ServerSessionConnectionHandler)getConnectionHandler()).getPool(); } return this.connectionPool; } @Override public Object getNextValue(Class cls) { return getNextValue(getOwnerSession(), cls); } @Override public void initializePreallocated() { if (preallocationHandler != null) { for (PreallocationHandler handler : preallocationHandler.values()) { handler.initializePreallocated(); } } } @Override public void initializePreallocated(String seqName) { if (preallocationHandler != null) { for (PreallocationHandler handler : preallocationHandler.values()) { handler.initializePreallocated(seqName); } } } protected void setLocks(Map locks) { this.locks = locks; } protected Map getLocks() { return locks; } /** * Acquire a lock for the sequence name. * A lock should be, and only be, acquired when allocating new sequences from the database. */ protected ConcurrencyManager acquireLock(String sequenceName) { ConcurrencyManager manager = getLocks().get(sequenceName); if (manager == null) { synchronized (getLocks()) { manager = getLocks().get(sequenceName); if (manager == null) { manager = new ConcurrencyManager(); getLocks().put(sequenceName, manager); } } } manager.acquire(); return manager; } protected Sequence getSequence(Class cls) { //** should check here that sequencing is used? String seqName = getOwnerSession().getDescriptor(cls).getSequenceNumberName(); return getSequence(seqName); } protected void logDebugPreallocation(String seqName, Object firstSequenceValue, Vector sequences) { if (getOwnerSession().shouldLog(SessionLog.FINEST, SessionLog.SEQUENCING)) { // the first value has been already removed from sequences vector Object[] args = { seqName, sequences.size() + 1, firstSequenceValue, sequences.lastElement() }; getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_preallocation", args); } } protected void logDebugLocalPreallocation(AbstractSession writeSession, String seqName, Vector sequences, Accessor accessor) { if (writeSession.shouldLog(SessionLog.FINEST, SessionLog.SEQUENCING)) { Object[] args = { seqName, sequences.size(), sequences.get(0), sequences.lastElement() }; writeSession.log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_localPreallocation", args, accessor); } } static abstract class State { abstract Object getNextValue(Sequence sequence, AbstractSession writeSession); SequencingCallbackFactory getSequencingCallbackFactory() { return null; } @Override public String toString() { String name = getClass().getName(); return name.substring(name.lastIndexOf('$') + 1); } } /** * Uses preallocation, uses transaction, no separate connection. * This is used for a DatabaseSession, or a ServerSession not using native sequencing, * and not using a sequence connection pool. * This is used by default for table sequencing, unless a sequence connection pool is specified, * however it should only be used if there is no non-JTA login available. * This will use the writeConnection, but use individual transactions per sequence allocation, * unless the unit of work is in an early transaction, or the connection is JTA (this may deadlock). */ class Preallocation_Transaction_NoAccessor_State extends State implements SequencingCallbackFactory { final class SequencingCallbackImpl implements SequencingCallback { Map> localSequences = new HashMap<>(); String context; /** * INTERNAL: * Called after transaction has committed (commit in non-jta case; after completion - jta case). * Should not be called after rollback. */ @Override public void afterCommit(Accessor accessor) { afterCommitInternal(context, localSequences, accessor); } public Map> getPreallocatedSequenceValues() { return localSequences; } } @Override SequencingCallbackFactory getSequencingCallbackFactory() { return this; } /** * INTERNAL: * Creates SequencingCallback. */ @Override public SequencingCallback createSequencingCallback() { return new SequencingCallbackImpl(); } /** * Release any locally allocated sequence back to the global sequence pool. */ void afterCommitInternal(String context, Map> localSequences, Accessor accessor) { Iterator>> it = localSequences.entrySet().iterator(); while(it.hasNext()) { Map.Entry> entry = it.next(); String seqName = entry.getKey(); Vector localSequenceForName = entry.getValue(); if (!localSequenceForName.isEmpty()) { getPreallocationHandler(context).setPreallocated(seqName, localSequenceForName); // clear all localSequencesForName localSequenceForName.clear(); } } if(accessor != null) { getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_afterTransactionCommitted", null, accessor); } else { getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_afterTransactionCommitted", null); } } SequencingCallbackImpl getCallbackImpl(AbstractSession writeSession, Accessor accessor) { SequencingCallbackImpl seqCallbackImpl; if(writeSession.hasExternalTransactionController()) { // note that controller obtained from writeSession (not from ownerSession) - // the difference is important in case of ownerSession being a member of SessionBroker: // in that case only writeSession (which is either ClientSession or DatabaseSession) always has // the correct controller. seqCallbackImpl = (SequencingCallbackImpl)writeSession.getExternalTransactionController().getActiveSequencingCallback(getOwnerSession(), getSequencingCallbackFactory()); } else { seqCallbackImpl = (SequencingCallbackImpl)accessor.getSequencingCallback(getSequencingCallbackFactory()); } seqCallbackImpl.context = getContext(writeSession); return seqCallbackImpl; } /** * Return the next sequence value. * First check the global pool, if empty then allocate new sequences locally. */ @Override public Object getNextValue(Sequence sequence, AbstractSession writeSession) { String seqName = sequence.getName(); if(sequence.getPreallocationSize() > 1) { Queue sequencesForName = getPreallocationHandler(getContext(writeSession)).getPreallocated(seqName); // First grab the first sequence value without locking, a lock is only required if empty. Object sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } // KeepLocked indicates whether the sequence lock should be kept for the whole duration of this method. // Of course the lock should be released in any case when the method returns or throws an exception. // This is only used if a sequence transaction was begun by the unit of work, // and will be committed before the unit of work commit. boolean keepLocked = false; ConcurrencyManager lock = null; if (!getOwnerSession().getDatasourceLogin().shouldUseExternalTransactionController() && !writeSession.isInTransaction()) { // To prevent several threads from simultaneously allocating a separate bunch of // sequencing numbers each. With keepLocked==true the first thread locks out others // until it copies the obtained sequence numbers to the global storage. // Note that this optimization possible only in non-jts case when there is no transaction. lock = acquireLock(seqName); try { sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } writeSession.beginTransaction();//write accessor is set in begin keepLocked = true; } finally { if (!keepLocked) { lock.release(); } } } Accessor accessor; Vector localSequencesForName; if (!keepLocked) { writeSession.beginTransaction();//write accessor is set in begin } try { accessor = writeSession.getAccessor(); SequencingCallbackImpl seqCallbackImpl = getCallbackImpl(writeSession, accessor); Map> localSequences = seqCallbackImpl.getPreallocatedSequenceValues(); localSequencesForName = localSequences.get(seqName); if ((localSequencesForName == null) || localSequencesForName.isEmpty()) { localSequencesForName = sequence.getGeneratedVector(null, writeSession); localSequences.put(seqName, localSequencesForName); logDebugLocalPreallocation(writeSession, seqName, localSequencesForName, accessor); } } catch (RuntimeException ex) { if (keepLocked) { lock.release(); } try { // make sure to rollback the transaction we've begun writeSession.rollbackTransaction(); } catch (Exception rollbackException) { // ignore rollback exception } // don't eat the original exception throw ex; } try { try { // commitTransaction may copy preallocated sequence numbers // from localSequences to preallocationHandler: that happens // if it isn't a nested transaction, and sequencingCallback.afterCommit // method has been called. // In this case: // 1. localSequences corresponding to the accessor // has been removed from accessorToPreallocated; // 2. All its members are empty (therefore localSequenceForName is empty). writeSession.commitTransaction(); } catch (DatabaseException ex) { try { // make sure to rollback the transaction we've begun writeSession.rollbackTransaction(); } catch (Exception rollbackException) { // ignore rollback exception } // don't eat the original exception throw ex; } if (!localSequencesForName.isEmpty()) { // localSeqencesForName is not empty, that means // sequencingCallback has not been called. sequenceValue = localSequencesForName.remove(0); return sequenceValue; } else { // localSeqencesForName is empty, that means // sequencingCallback has been called. sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } return getNextValue(sequence, writeSession); } } finally { if(keepLocked) { lock.release(); } } } else { writeSession.beginTransaction(); try { // preallocation size is 1 - just return the first (and only) element of the allocated vector. Object sequenceValue = sequence.getGeneratedVector(null, writeSession).get(0); writeSession.commitTransaction(); return sequenceValue; } catch (RuntimeException ex) { try { // make sure to rollback the transaction we've begun writeSession.rollbackTransaction(); } catch (Exception rollbackException) { // ignore rollback exception } // don't eat the original exception throw ex; } } } } /** * Uses preallocation, uses transaction, and acquires an accessor. * This is used in a ServerSession with a sequence connection pool. * This is typically the default behavior. */ class Preallocation_Transaction_Accessor_State extends State { @Override public Object getNextValue(Sequence sequence, AbstractSession writeSession) { String seqName = sequence.getName(); if(sequence.getPreallocationSize() > 1) { PreallocationHandler handler = getPreallocationHandler(getContext(writeSession)); Queue sequencesForName = handler.getPreallocated(seqName); // First try to get the next sequence value without locking. Object sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } // Sequences are empty, so must lock and allocate next batch of sequences. ConcurrencyManager lock = acquireLock(seqName); try { sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } // note that accessor.getLogin().shouldUseExternalTransactionController() // should be set to false Accessor accessor = getConnectionHandler().acquireAccessor(); try { accessor.beginTransaction(writeSession); try { Vector sequences = sequence.getGeneratedVector(accessor, writeSession); accessor.commitTransaction(writeSession); // Remove the first value before adding to the global cache to ensure this thread gets one. sequenceValue = sequences.remove(0); // copy remaining values to global cache. handler.setPreallocated(seqName, sequences); logDebugPreallocation(seqName, sequenceValue, sequences); } catch (RuntimeException ex) { try { // make sure to rollback the transaction we've begun accessor.rollbackTransaction(writeSession); } catch (Exception rollbackException) { // ignore rollback exception } // don't eat the original exception throw ex; } } finally { getConnectionHandler().releaseAccessor(accessor); } } finally { lock.release(); } return sequenceValue; } else { // note that accessor.getLogin().shouldUseExternalTransactionController() // should be set to false Accessor accessor = getConnectionHandler().acquireAccessor(); try { accessor.beginTransaction(writeSession); try { // preallocation size is 1 - just return the first (and only) element of the allocated vector. Object sequenceValue = sequence.getGeneratedVector(accessor, writeSession).get(0); accessor.commitTransaction(writeSession); return sequenceValue; } catch (RuntimeException ex) { try { // make sure to rollback the transaction we've begun accessor.rollbackTransaction(writeSession); } catch (Exception rollbackException) { // ignore rollback exception } // don't eat the original exception throw ex; } } finally { getConnectionHandler().releaseAccessor(accessor); } } } } /** * Using preallocation, NoTransaction, NoAccessor. * This is used by native sequence objects. * No transaction is required as sequence objects are non-transactional. */ class Preallocation_NoTransaction_State extends State { @Override public Object getNextValue(Sequence sequence, AbstractSession writeSession) { String seqName = sequence.getName(); if(sequence.getPreallocationSize() > 1) { PreallocationHandler handler = getPreallocationHandler(getContext(writeSession)); Queue sequencesForName = handler.getPreallocated(seqName); // First try to get the next sequence value without locking. Object sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } // Sequences are empty, so must lock and allocate next batch of sequences. ConcurrencyManager lock = acquireLock(seqName); try { sequenceValue = sequencesForName.poll(); if (sequenceValue != null) { return sequenceValue; } Vector sequences = sequence.getGeneratedVector(null, writeSession); // Remove the first value before adding to the global cache to ensure this thread gets one. sequenceValue = sequences.remove(0); // copy remaining values to global cache. handler.setPreallocated(seqName, sequences); logDebugPreallocation(seqName, sequenceValue, sequences); } finally { lock.release(); } return sequenceValue; } else { // preallocation size is 1 - just return the first (and only) element of the allocated vector. return sequence.getGeneratedVector(null, writeSession).get(0); } } } /** * Using NoPreallocation, no transaction, no Accessor. * This is only used for identity sequencing when preallocation is not possible. * The writeSession is always in a transaction, so a transaction is never required. * Table or sequence object with preallocation size 1 still goes through the preallocation state. */ static class NoPreallocation_State extends State { @Override public Object getNextValue(Sequence sequence, AbstractSession writeSession) { return sequence.getGeneratedValue(null, writeSession); } } @Override public void resetSequencing() { if (isConnected()) { onDisconnect(); onConnect(); } } /** * Initialize the sequences on login. */ @Override public void onConnect() { if (isConnected()) { return; } if (!getOwnerSession().getProject().usesSequencing()) { return; } onConnectInternal(null); } /** * If sequencing is connected initialize the sequences used by descriptors, otherwise connect. */ @Override public void onAddDescriptors(Collection descriptors) { if (!isConnected()) { onConnect(); return; } if (descriptors == null || descriptors.isEmpty()) { return; } onConnectInternal(descriptors); } /** * Initialize the sequences on login. * @param descriptors class descriptors for which to initialize sequencing */ protected void onConnectInternal(Collection descriptors) { // This method is called in two distinct cases. // // Connect case. // If descriptors == null then the sequencing has not been connected yet // and this method by onConnect method. // Nothing is allocated yet (connectedSequences, etc) and // therefore nAlreadyConnectedSequences = 0 // // AddDescriptors case. // If descriptors is not null then sequencing is already connected and this method // is called by onAddDescriptors method. // connectedSequences (and the rest of stuff allocated by onConnect) already exists. // Typically in this case nAlreadyConnectedSequences > 0 // (unless none sequences were connected by onConnect. int nAlreadyConnectedSequences = 0; if (connectedSequences != null) { nAlreadyConnectedSequences = connectedSequences.size(); } // These flags saved here to rollback the state of sequencing in case of failure. int whenShouldAcquireValueForAllOriginal = whenShouldAcquireValueForAll; boolean atLeastOneSequenceShouldUseTransactionOriginal = atLeastOneSequenceShouldUseTransaction; boolean atLeastOneSequenceShouldUsePreallocationOriginal = atLeastOneSequenceShouldUsePreallocation; onConnectSequences(descriptors); if (nAlreadyConnectedSequences == connectedSequences.size()) { // no sequences connected by onConnectSequences method - nothing to do return; } boolean onExceptionDisconnectPreallocationHandler = false; boolean onExceptionDisconnectConnectionHandler = false; boolean hasConnectionHandler = getConnectionHandler() != null; boolean hasPreallocationHandler = getPreallocationHandler(null) != null; try { // In AddDescriptors case the handler may have been already created if (!hasConnectionHandler) { if (!shouldUseSeparateConnection()) { setConnectionHandler(null); } else if (atLeastOneSequenceShouldUseTransaction) { if (getConnectionHandler() == null) { createConnectionHandler(); } if (getConnectionHandler() != null) { getConnectionHandler().onConnect(); onExceptionDisconnectConnectionHandler = true; } } } // In AddDescriptors case the handler may have been already created if (!hasPreallocationHandler) { if (atLeastOneSequenceShouldUsePreallocation) { String context = getContext(null); if (getPreallocationHandler(context) == null) { createPreallocationHandler(context); } getPreallocationHandler(context).onConnect(); onExceptionDisconnectPreallocationHandler = true; } } initializeStates(nAlreadyConnectedSequences); } catch (RuntimeException ex) { try { onDisconnectSequences(nAlreadyConnectedSequences); } catch (Exception ex2) { // Ignore } finally { whenShouldAcquireValueForAll = whenShouldAcquireValueForAllOriginal; atLeastOneSequenceShouldUseTransaction = atLeastOneSequenceShouldUseTransactionOriginal; atLeastOneSequenceShouldUsePreallocation = atLeastOneSequenceShouldUsePreallocationOriginal; } if (!hasConnectionHandler && getConnectionHandler() != null) { if (onExceptionDisconnectConnectionHandler) { getConnectionHandler().onDisconnect(); } setConnectionHandler(null); } if (!hasPreallocationHandler && getPreallocationHandler(null) != null) { if (onExceptionDisconnectPreallocationHandler) { getPreallocationHandler(null).onDisconnect(); } clearPreallocationHandler(null); } throw ex; } // In AddDescriptors case locks may have been already created if (atLeastOneSequenceShouldUsePreallocation && getLocks() == null) { setLocks(new ConcurrentHashMap<>(20)); } // In AddDescriptors case the factory may have been already created and listeners initialized. boolean hasSequencingCallbackFactory = isSequencingCallbackRequired(); if (!hasSequencingCallbackFactory) { createSequencingCallbackFactory(); if(getOwnerSession().hasExternalTransactionController()) { getOwnerSession().getExternalTransactionController().initializeSequencingListeners(); } } // In AddDescriptors case sequencing is already set. if (descriptors == null) { if (getOwnerSession().isServerSession()) { setSequencingServer(this); } setSequencing(this); } logDebugSequencingConnected(nAlreadyConnectedSequences); } @Override public void onDisconnect() { if (!isConnected()) { return; } setSequencing(null); setSequencingServer(null); setSequencingCallbackFactory(null); if(getOwnerSession().hasExternalTransactionController() && !getOwnerSession().hasBroker()) { getOwnerSession().getExternalTransactionController().clearSequencingListeners(); } setLocks(null); clearStates(); if (getConnectionHandler() != null) { getConnectionHandler().onDisconnect(); setConnectionHandler(null); } if (getPreallocationHandler(null) != null) { getPreallocationHandler(null).onDisconnect(); clearPreallocationHandler(); } onDisconnectSequences(0); getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_disconnected"); } protected PreallocationHandler getPreallocationHandler(String context) { if (preallocationHandler != null) { if (context == null) { return preallocationHandler.get("default"); } else { PreallocationHandler handler = preallocationHandler.get(context); if (handler == null && !"default".equals(context)) { handler = new PreallocationHandler(); preallocationHandler.put(context, handler); handler.onConnect(); } return handler; } } return null; } protected void createPreallocationHandler(String context) { if (preallocationHandler == null) { preallocationHandler = new ConcurrentHashMap<>(5); } preallocationHandler.put(context, new PreallocationHandler()); } protected void clearPreallocationHandler() { preallocationHandler = null; } protected void clearPreallocationHandler(AbstractSession session) { preallocationHandler.remove(getContext(session)); } /* * If passed collection is null then connect all sequences used by owner session's descriptors. * Otherwise connect sequences used by passed descriptors. */ protected void onConnectSequences(Collection descriptors) { boolean isConnected = isConnected(); int nAlreadyConnectedSequences = 0; if (connectedSequences == null) { connectedSequences = new Vector<>(); } else { nAlreadyConnectedSequences = connectedSequences.size(); } boolean shouldUseTransaction = false; boolean shouldUsePreallocation = false; boolean shouldAcquireValueAfterInsert = false; if (descriptors == null) { descriptors = getOwnerSession().getDescriptors().values(); } Iterator itDescriptors = descriptors.iterator(); while (itDescriptors.hasNext()) { ClassDescriptor descriptor = itDescriptors.next(); // Find root sequence, because inheritance needs to be resolved here. // TODO: The way we initialize sequencing needs to be in line with descriptor init. ClassDescriptor parentDescriptor = descriptor; while (!parentDescriptor.usesSequenceNumbers() && parentDescriptor.isChildDescriptor()) { ClassDescriptor newDescriptor = getOwnerSession().getDescriptor(parentDescriptor.getInheritancePolicy().getParentClass()); // Avoid issue with error cases of self parent, or null parent. if ((newDescriptor == null) || (newDescriptor == parentDescriptor)) { break; } parentDescriptor = newDescriptor; } if (!parentDescriptor.usesSequenceNumbers()) { continue; } String seqName = parentDescriptor.getSequenceNumberName(); Sequence sequence = getSequence(seqName); if (sequence == null) { sequence = new DefaultSequence(seqName); getOwnerSession().getDatasourcePlatform().addSequence(sequence, isConnected); } // PERF: Initialize the sequence, this avoid having to look it up every time. descriptor.setSequence(sequence); if (connectedSequences.contains(sequence)) { continue; } try { if (sequence instanceof DefaultSequence && !connectedSequences.contains(getDefaultSequence())) { getDefaultSequence().onConnect(getOwnerSession().getDatasourcePlatform()); connectedSequences.add(nAlreadyConnectedSequences, getDefaultSequence()); shouldUseTransaction |= getDefaultSequence().shouldUseTransaction(); shouldUsePreallocation |= getDefaultSequence().shouldUsePreallocation(); shouldAcquireValueAfterInsert |= getDefaultSequence().shouldAcquireValueAfterInsert(); } sequence.onConnect(getOwnerSession().getDatasourcePlatform()); connectedSequences.add(sequence); shouldUseTransaction |= sequence.shouldUseTransaction(); shouldUsePreallocation |= sequence.shouldUsePreallocation(); shouldAcquireValueAfterInsert |= sequence.shouldAcquireValueAfterInsert(); } catch (RuntimeException ex) { // defaultSequence has to disconnect the last for (int i = connectedSequences.size() - 1; i >= nAlreadyConnectedSequences; i--) { try { Sequence sequenceToDisconnect = connectedSequences.get(i); sequenceToDisconnect.onDisconnect(getOwnerSession().getDatasourcePlatform()); } catch (RuntimeException ex2) { //ignore } } if (nAlreadyConnectedSequences == 0) { connectedSequences = null; } throw ex; } } if (nAlreadyConnectedSequences == 0) { if (shouldAcquireValueAfterInsert && !shouldUsePreallocation) { whenShouldAcquireValueForAll = AFTER_INSERT; } else if (!shouldAcquireValueAfterInsert && shouldUsePreallocation) { whenShouldAcquireValueForAll = BEFORE_INSERT; } } else { if (whenShouldAcquireValueForAll == AFTER_INSERT) { if (!shouldAcquireValueAfterInsert || shouldUsePreallocation) { whenShouldAcquireValueForAll = UNDEFINED; } } else if (whenShouldAcquireValueForAll == BEFORE_INSERT) { if (shouldAcquireValueAfterInsert || !shouldUsePreallocation) { whenShouldAcquireValueForAll = UNDEFINED; } } } atLeastOneSequenceShouldUseTransaction |= shouldUseTransaction; atLeastOneSequenceShouldUsePreallocation |= shouldUsePreallocation; } /* * Keeps the first nAlreadyConnectedSequences sequences connected, * disconnects the rest. */ protected void onDisconnectSequences(int nAlreadyConnectedSequences) { RuntimeException exception = null; // defaultSequence has to disconnect the last for (int i = connectedSequences.size() - 1; i >= nAlreadyConnectedSequences; i--) { try { Sequence sequenceToDisconnect = connectedSequences.get(i); sequenceToDisconnect.onDisconnect(getOwnerSession().getDatasourcePlatform()); } catch (RuntimeException ex) { if (exception == null) { exception = ex; } } } if (nAlreadyConnectedSequences == 0) { connectedSequences = null; whenShouldAcquireValueForAll = UNDEFINED; atLeastOneSequenceShouldUseTransaction = false; atLeastOneSequenceShouldUsePreallocation = false; } if (exception != null) { throw exception; } } protected void initializeStates(int nAlreadyConnectedSequences) { if (states == null) { states = new State[NUMBER_OF_STATES]; } int nSize = connectedSequences.size(); for (int i = nAlreadyConnectedSequences; i < nSize; i++) { Sequence sequence = connectedSequences.get(i); State state = getState(sequence.shouldUsePreallocation(), sequence.shouldUseTransaction()); if (state == null) { createState(sequence.shouldUsePreallocation(), sequence.shouldUseTransaction()); } } } protected void clearStates() { states = null; } protected int getStateId(boolean shouldUsePreallocation, boolean shouldUseTransaction) { if (!shouldUsePreallocation) { // Non-Oracle native sequencing uses this state return NOPREALLOCATION; } else if (!shouldUseTransaction) { // Oracle native sequencing uses this state return PREALLOCATION_NOTRANSACTION; } else if (getConnectionHandler() == null) { // TableSequence and UnaryTableSequence in case there is no separate connection(s) available use this state return PREALLOCATION_TRANSACTION_NOACCESSOR; } else/*if(getConnectionHandler()!=null)*/ { // TableSequence and UnaryTableSequence in case there is separate connection(s) available use this state return PREALLOCATION_TRANSACTION_ACCESSOR; } } protected State getState(boolean shouldUsePreallocation, boolean shouldUseTransaction) { return states[getStateId(shouldUsePreallocation, shouldUseTransaction)]; } protected void createState(boolean shouldUsePreallocation, boolean shouldUseTransaction) { if (!shouldUsePreallocation) { // Non-Oracle native sequencing uses this state states[NOPREALLOCATION] = new NoPreallocation_State(); } else if (!shouldUseTransaction) { // Oracle native sequencing uses this state states[PREALLOCATION_NOTRANSACTION] = new Preallocation_NoTransaction_State(); } else if (getConnectionHandler() == null) { // TableSequence and UnaryTableSequence in case there is no separate connection(s) available use this state states[PREALLOCATION_TRANSACTION_NOACCESSOR] = new Preallocation_Transaction_NoAccessor_State(); } else/*if(getConnectionHandler()!=null)*/ { // TableSequence and UnaryTableSequence in case there is separate connection(s) available use this state states[PREALLOCATION_TRANSACTION_ACCESSOR] = new Preallocation_Transaction_Accessor_State(); } } protected void createSequencingCallbackFactory() { if (states[PREALLOCATION_TRANSACTION_NOACCESSOR] != null) { setSequencingCallbackFactory(states[PREALLOCATION_TRANSACTION_NOACCESSOR].getSequencingCallbackFactory()); } else { setSequencingCallbackFactory(null); } } @Override public Object getNextValue(AbstractSession writeSession, Class cls) { Sequence sequence = getSequence(cls); State state = getState(sequence.shouldUsePreallocation(), sequence.shouldUseTransaction()); return state.getNextValue(sequence, writeSession); } @SuppressWarnings({"unchecked"}) protected void logDebugSequencingConnected(int nAlreadyConnectedSequences) { Vector[] sequenceVectors = (Vector[]) new Vector[NUMBER_OF_STATES]; int size = connectedSequences.size(); for (int i = nAlreadyConnectedSequences; i < size; i++) { Sequence sequence = connectedSequences.get(i); int stateId = getStateId(sequence.shouldUsePreallocation(), sequence.shouldUseTransaction()); Vector v = sequenceVectors[stateId]; if (v == null) { v = new Vector<>(); sequenceVectors[stateId] = v; } v.add(sequence); } for (int i = 0; i < NUMBER_OF_STATES; i++) { Vector v = sequenceVectors[i]; if (v != null) { getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequencing_connected", states[i]); for (int j = 0; j < v.size(); j++) { Sequence sequence = v.get(j); Object[] args = { sequence.getName(), Integer.toString(sequence.getPreallocationSize()), Integer.toString(sequence.getInitialValue())}; getOwnerSession().log(SessionLog.FINEST, SessionLog.SEQUENCING, "sequence_without_state", args); } } } } public int getPreallocationSize() { return getDefaultSequence().getPreallocationSize(); } public int getInitialValue() { return getDefaultSequence().getInitialValue(); } @Override public int whenShouldAcquireValueForAll() { return whenShouldAcquireValueForAll; } protected Sequence getDefaultSequence() { return getOwnerSession().getDatasourcePlatform().getDefaultSequence(); } protected Sequence getSequence(String seqName) { return getOwnerSession().getDatasourcePlatform().getSequence(seqName); } @Override public void setConnectionPool(ConnectionPool connectionPool) { this.connectionPool = connectionPool; } private String getContext(AbstractSession writeSession) { String context = "default"; if (writeSession != null) { MultitenantPolicy policy = writeSession.getProject().getMultitenantPolicy(); if (policy != null && policy.isSchemaPerMultitenantPolicy()) { SchemaPerMultitenantPolicy tableMtPolicy = (SchemaPerMultitenantPolicy) policy; if (tableMtPolicy.isSchemaPerTable()) { String tenantContext = (String) writeSession.getProperty(tableMtPolicy.getContextProperty()); if (tenantContext != null) { context = tenantContext; } } } } return context; } }