blob: 04643b8360075c37d49eba1261b1cca21dc7ffec [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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.sequencing;
import java.util.Vector;
import java.io.Serializable;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.exceptions.ValidationException;
/**
* <p>
* <b>Purpose</b>: Abstract class to define sequencing.
* <p>
* <b>Description</b>
* A sequence defines how generated ids are obtained.
* The main sequence types are TableSequence and NativeSequence.
* Descriptors using sequencing will use the sequence object defined in their session's
* DatabaseLogin with the name matching their sequence name. If a specific sequence is
* not defined for the name the DatabaseLogin's default sequence will be used.
* @see TableSequence
* @see NativeSequence
*/
public abstract class Sequence implements Serializable, Cloneable {
// name
protected String name = "";
// preallocation size
protected int size = 50;
// owner platform
protected Platform platform;
protected int initialValue = 1;
// number of times onConnect was called - number of times onDisconnect was called
protected int depth;
protected String qualifier = "";
// true indicates that qualifier was set through setQualifier method,
// false - copied from platform (or not set at all).
protected boolean isCustomQualifier;
// indicates whether the existing pk value should always be overridden by the sequence.
// note that even if set to false sequence always overrides if shouldAcquireValueAfterInsert returns true.
protected boolean shouldAlwaysOverrideExistingValue;
public Sequence() {
super();
setName("SEQUENCE");
}
/**
* Create a new sequence with the name.
*/
public Sequence(String name) {
this();
setName(name);
}
/**
* Create a new sequence with the name and sequence pre-allocation size.
*/
public Sequence(String name, int size) {
this();
setName(name);
setPreallocationSize(size);
}
public Sequence(String name, int size, int initialValue) {
this();
setName(name);
setPreallocationSize(size);
setInitialValue(initialValue);
}
public boolean isNative() {
return false;
}
public boolean isTable() {
return false;
}
public boolean isUnaryTable() {
return false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPreallocationSize() {
return size;
}
public void setPreallocationSize(int size) {
this.size = size;
}
public int getInitialValue() {
return initialValue;
}
public void setInitialValue(int initialValue) {
this.initialValue = initialValue;
}
@Override
public Object clone() {
try {
Sequence clone = (Sequence)super.clone();
if (isConnected()) {
clone.depth = 1;
clone.onDisconnect();
clone.setDatasourcePlatform(null);
}
return clone;
} catch (Exception exception) {
throw new InternalError("Clone failed");
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Sequence) {
return equalNameAndSize(this, (Sequence)obj);
} else {
return false;
}
}
/**
* INTERNAL:
* Used in equals.
*/
public static boolean equalNameAndSize(Sequence seq1, Sequence seq2) {
if (seq1 == seq2) {
return true;
}
return seq1.getName().equals(seq2.getName()) && (seq1.getPreallocationSize() == seq2.getPreallocationSize());
}
@Override
public int hashCode() {
String name = getName();
int result = name != null ? name.hashCode() : 0;
result = 31 * result + getPreallocationSize();
return result;
}
protected void setDatasourcePlatform(Platform platform) {
this.platform = platform;
}
public Platform getDatasourcePlatform() {
return platform;
}
/**
* INTERNAL:
* Indicates whether sequencing value should be acquired after INSERT.
* Note that preallocation could be used only in case sequencing values
* should be acquired before insert (this method returns false).
* In default implementation, it is true for table sequencing and native
* sequencing on Oracle platform, false for native sequencing on other platforms.
*/
public abstract boolean shouldAcquireValueAfterInsert();
/**
* INTERNAL:
* Indicates whether several sequencing values should be acquired at a time
* and be kept by TopLink. This in only possible in case sequencing numbers should
* be acquired before insert (shouldAcquireValueAfterInsert()==false).
* In default implementation, it is true for table sequencing and native
* sequencing on Oracle platform, false for native sequencing on other platforms.
*/
public boolean shouldUsePreallocation() {
return !shouldAcquireValueAfterInsert();
}
/**
* INTERNAL:
* Indicates whether TopLink should internally call beginTransaction() before
* getGeneratedValue/Vector, and commitTransaction after.
* In default implementation, it is true for table sequencing and
* false for native sequencing.
*/
public abstract boolean shouldUseTransaction();
/**
* INTERNAL:
* Return the newly-generated sequencing value.
* Used only in case preallocation is not used (shouldUsePreallocation()==false).
* Accessor may be non-null only in case shouldUseSeparateConnection()==true.
* Even in this case accessor could be null - if SequencingControl().shouldUseSeparateConnection()==false;
* Therefore in case shouldUseSeparateConnection()==true, implementation should handle
* both cases: use a separate connection if provided (accessor != null), or get by
* without it (accessor == null).
* @param accessor Accessor is a separate sequencing accessor (may be null);
* @param writeSession Session is a Session used for writing (either ClientSession or DatabaseSession);
* @param seqName String is sequencing number field name
*/
public abstract Object getGeneratedValue(Accessor accessor, AbstractSession writeSession, String seqName);
/**
* INTERNAL:
* Return the newly-generated sequencing value.
* Used only in case preallocation is not used (shouldUsePreallocation()==false).
* Accessor may be non-null only in case shouldUseSeparateConnection()==true.
* Even in this case accessor could be null - if SequencingControl().shouldUseSeparateConnection()==false;
* Therefore in case shouldUseSeparateConnection()==true, implementation should handle
* both cases: use a separate connection if provided (accessor != null), or get by
* without it (accessor == null).
* @param accessor Accessor is a separate sequencing accessor (may be null);
* @param writeSession Session is a Session used for writing (either ClientSession or DatabaseSession);
*/
public Object getGeneratedValue(Accessor accessor, AbstractSession writeSession) {
return getGeneratedValue(accessor, writeSession, getName());
}
/**
* INTERNAL:
* Return a Vector of newly-generated sequencing values.
* Used only in case preallocation is used (shouldUsePreallocation()==true).
* Accessor may be non-null only in case shouldUseSeparateConnection()==true.
* Even in this case accessor could be null - if SequencingControl().shouldUseSeparateConnection()==false;
* Therefore in case shouldUseSeparateConnection()==true, implementation should handle
* both cases: use a separate connection if provided (accessor != null), or get by
* without it (accessor == null).
* @param accessor Accessor is a separate sequencing accessor (may be null);
* @param writeSession Session is a Session used for writing (either ClientSession or DatabaseSession);
* @param seqName String is sequencing number field name
* @param size int number of values to preallocate (output Vector size).
*/
public abstract Vector getGeneratedVector(Accessor accessor, AbstractSession writeSession, String seqName, int size);
/**
* INTERNAL:
* Return a Vector of newly-generated sequencing values.
* Used only in case preallocation is used (shouldUsePreallocation()==true).
* Accessor may be non-null only in case shouldUseSeparateConnection()==true.
* Even in this case accessor could be null - if SequencingControl().shouldUseSeparateConnection()==false;
* Therefore in case shouldUseSeparateConnection()==true, implementation should handle
* both cases: use a separate connection if provided (accessor != null), or get by
* without it (accessor == null).
* @param accessor Accessor is a separate sequencing accessor (may be null);
* @param writeSession Session is a Session used for writing (either ClientSession or DatabaseSession);
*/
public Vector getGeneratedVector(Accessor accessor, AbstractSession writeSession) {
return getGeneratedVector(accessor, writeSession, getName(), getPreallocationSize());
}
/**
* INTERNAL:
* This method is called when Sequencing object is created.
* Don't override this method.
*/
public void onConnect(Platform platform) {
setDatasourcePlatform(platform);
if(!isCustomQualifier) {
qualifier = getDatasourcePlatform().getTableQualifier();
}
onConnect();
depth++;
}
/**
* INTERNAL:
* This method is called when Sequencing object is created.
* If it requires initialization, subclass should override this method.
*/
public abstract void onConnect();
/**
* INTERNAL:
* This method is called when Sequencing object is destroyed.
* Don't override this method.
*/
public void onDisconnect(Platform platform) {
if (isConnected()) {
depth--;
if(depth==0 && !isCustomQualifier) {
qualifier = "";
}
// Can no longer disconnect sequences, as they are part of descriptor shared meta-data.
}
}
/**
* INTERNAL:
* This method is called when Sequencing object is destroyed.
* If it requires deinitialization, subclass should override this method.
*/
public abstract void onDisconnect();
/**
* PUBLIC:
* Indicates that Sequence is connected.
*/
public boolean isConnected() {
return platform != null;
}
/**
* INTERNAL:
* Make sure that the sequence is not used by more than one platform.
*/
protected void verifyPlatform(Platform otherPlatform) {
if (getDatasourcePlatform() != otherPlatform) {
String hashCode1 = Integer.toString(System.identityHashCode(getDatasourcePlatform()));
String name1 = getDatasourcePlatform().toString() + '(' + hashCode1 + ')';
String hashCode2 = Integer.toString(System.identityHashCode(otherPlatform));
String name2 = otherPlatform.toString() + '(' + hashCode2 + ')';
throw ValidationException.sequenceCannotBeConnectedToTwoPlatforms(getName(), name1, name2);
}
}
/**
* INTERNAL:
*/
public void setQualifier(String qualifier) {
if(qualifier == null) {
qualifier = "";
}
this.isCustomQualifier = qualifier.length() > 0;
this.qualifier = qualifier;
}
/**
* INTERNAL:
*/
public boolean isCustomQualifier() {
return isCustomQualifier;
}
/**
* INTERNAL:
*/
public String getQualifier() {
return qualifier;
}
/**
* INTERNAL:
*/
public String getQualified(String str) {
if (qualifier.equals("")) {
return str;
} else {
return qualifier + "." + str;
}
}
/**
* ADVANCED:
* Set that to true if the sequence should always override the existing pk value.
*/
public void setShouldAlwaysOverrideExistingValue(boolean shouldAlwaysOverrideExistingValue) {
this.shouldAlwaysOverrideExistingValue = shouldAlwaysOverrideExistingValue;
}
/**
* INTERNAL:
* Indicates whether the existing pk value should always be overridden by the sequence.
* As always the version of the method taking seqName is provided for the benefit
* of DefaultSequence.
*/
public boolean shouldAlwaysOverrideExistingValue() {
return shouldAlwaysOverrideExistingValue(getName());
}
/**
* INTERNAL:
* Indicates whether the existing pk value should always be overridden by the sequence.
*/
public boolean shouldAlwaysOverrideExistingValue(String seqName) {
return this.shouldAlwaysOverrideExistingValue || shouldAcquireValueAfterInsert();
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + getName() + ")";
}
}