| /* |
| * 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.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; |
| |
| protected Sequence() { |
| super(); |
| setName("SEQUENCE"); |
| } |
| |
| /** |
| * Create a new sequence with the name. |
| */ |
| protected Sequence(String name) { |
| this(); |
| setName(name); |
| } |
| |
| /** |
| * Create a new sequence with the name and sequence pre-allocation size. |
| */ |
| protected Sequence(String name, int size) { |
| this(); |
| setName(name); |
| setPreallocationSize(size); |
| } |
| |
| protected 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() + ")"; |
| } |
| } |