From 0acb197bf1267f071c3f5f88d0045cae95509716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Tue, 4 Feb 2025 09:24:23 +0100 Subject: [PATCH] Issue #2343 - EclipseLink disallows @Version attribute of type java.time.LocalDateTime - code refactoring (#2349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../descriptors/InstantLockingPolicy.java | 71 ++--- ...Policy.java => JavaTimeLockingPolicy.java} | 169 +++-------- .../LocalDateTimeLockingPolicy.java | 71 ++--- .../descriptors/TimestampLockingPolicy.java | 277 +++++++++++++----- .../accessors/mappings/VersionAccessor.java | 24 +- 5 files changed, 334 insertions(+), 278 deletions(-) rename foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/{AbstractTsLockingPolicy.java => JavaTimeLockingPolicy.java} (55%) diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java index 896a414696..df7c6d17f1 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java @@ -11,10 +11,10 @@ */ package org.eclipse.persistence.descriptors; -import java.sql.Timestamp; import java.time.Instant; import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractRecord; @@ -24,7 +24,7 @@ /** * Version policy used for optimistic locking with {@link Instant} field. */ -public class InstantLockingPolicy extends AbstractTsLockingPolicy { +public class InstantLockingPolicy extends JavaTimeLockingPolicy { /** * Create a new instance of version policy used for optimistic locking @@ -35,17 +35,6 @@ public InstantLockingPolicy() { super(); } - /** - * Create a new instance of version policy used for optimistic locking - * with {@link Instant} field. - * Defaults to using the time retrieved from the server. - * - * @param fieldName the field where the write lock value will be stored - */ - public InstantLockingPolicy(String fieldName) { - super(fieldName); - } - /** * Create a new instance of version policy used for optimistic locking * with {@link Instant} field. @@ -53,52 +42,52 @@ public InstantLockingPolicy(String fieldName) { * * @param field the field where the write lock value will be stored */ - InstantLockingPolicy(DatabaseField field) { + public InstantLockingPolicy(DatabaseField field) { super(field); } @Override - int compareTsLockValues(Instant value1, Instant value2) { + int compareJavaTimeLockValues(Instant value1, Instant value2) { return value1.compareTo(value2); } @Override - Class getDefaultTsLockFieldType() { + Class getDefaultJavaTimeLockFieldType() { return ClassConstants.TIME_INSTANT; } @Override - Instant getBaseTsValue() { + Instant getBaseJavaTimeValue() { // LocalDateTime is immutable so constant is safe return Instant.MIN; } @Override - Instant getInitialTsWriteValue(AbstractSession session) { - if (usesLocalTime()) { - return Instant.now(); - } - if (usesServerTime()) { - AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); - while (readSession.isUnitOfWork()) { - readSession = readSession.getParent() - .getSessionForClass(getDescriptor().getJavaClass()); - } - Timestamp ts = readSession.getDatasourceLogin() - .getDatasourcePlatform() - .getTimestampFromServer(session, readSession.getName()); - return ts.toInstant(); + Instant getInitialJavaTimeWriteValue(AbstractSession session) { + switch (getTimeSource()) { + case Local: + return Instant.now(); + case Server: + AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); + Platform platform = session.getDatasourcePlatform(); + while (readSession.isUnitOfWork()) { + readSession = readSession.getParent() + .getSessionForClass(getDescriptor().getJavaClass()); + } + return platform.convertObject( + session.executeQuery(platform.getTimestampQuery()), ClassConstants.TIME_INSTANT); + default: + return null; } - return null; } @Override - Instant getNewTsLockValue(ModifyQuery query) { - return getInitialTsWriteValue(query.getSession()); + Instant getNewJavaTimeLockValue(ModifyQuery query) { + return getInitialJavaTimeWriteValue(query.getSession()); } @Override - Instant getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { + Instant getJavaTimeValueToPutInCache(AbstractRecord row, AbstractSession session) { if (isStoredInCache()) { return session.getDatasourcePlatform() .convertObject(row.get(getWriteLockField()), ClassConstants.TIME_INSTANT); @@ -108,7 +97,7 @@ Instant getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { } @Override - Instant getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + Instant getWriteJavaTimeLockValue(Object domainObject, Object primaryKey, AbstractSession session) { Instant writeLockFieldValue = null; if (isStoredInCache()) { writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance() @@ -128,7 +117,7 @@ Instant getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSess } @Override - boolean isNewerTsVersion(Instant current, Object domainObject, Object primaryKey, AbstractSession session) { + boolean isNewerJavaTimeVersion(Instant current, Object domainObject, Object primaryKey, AbstractSession session) { Instant writeLockFieldValue; if (isStoredInCache()) { writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance() @@ -137,12 +126,12 @@ boolean isNewerTsVersion(Instant current, Object domainObject, Object primaryKey writeLockFieldValue = (Instant)lockValueFromObject(domainObject); } - return isNewerTsVersion(current, writeLockFieldValue); + return isNewerJavaTimeVersion(current, writeLockFieldValue); } @Override - boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + boolean isNewerJavaTimeVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { Instant writeLockFieldValue; Instant newWriteLockFieldValue = session.getDatasourcePlatform() .convertObject(row.get(getWriteLockField()), ClassConstants.TIME_INSTANT); @@ -152,11 +141,11 @@ boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primary } else { writeLockFieldValue = (Instant) lockValueFromObject(domainObject); } - return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); + return isNewerJavaTimeVersion(newWriteLockFieldValue, writeLockFieldValue); } @Override - boolean isNewerTsVersion(Instant first, Instant second) { + boolean isNewerJavaTimeVersion(Instant first, Instant second) { // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. if (first == null) { return false; diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/JavaTimeLockingPolicy.java similarity index 55% rename from foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java rename to foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/JavaTimeLockingPolicy.java index e26c0a64a3..c8ef913f08 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/JavaTimeLockingPolicy.java @@ -11,10 +11,8 @@ */ package org.eclipse.persistence.descriptors; -import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; -import java.util.Map; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.expressions.ExpressionBuilder; @@ -24,56 +22,20 @@ import org.eclipse.persistence.queries.ModifyQuery; /** - * Common timestamp version policy used for optimistic locking. + * Common {@link VersionLockingPolicy} based on {@code java.time} classes + * used for optimistic locking. */ -public abstract class AbstractTsLockingPolicy extends VersionLockingPolicy { +public abstract class JavaTimeLockingPolicy extends VersionLockingPolicy { - /** Time from the server. */ - public static final int SERVER_TIME = 1; - /** Local time. */ - public static final int LOCAL_TIME = 2; - - // Mapping of timestamp class name to corresponding AbstractTsLockingPolicy factory method - private static final Map FACTORY = Map.of( - Timestamp.class.getName(), AbstractTsLockingPolicy::createTimestampLockingPolicy, - LocalDateTime.class.getName(), AbstractTsLockingPolicy::createLocalDateTimeLockingPolicy, - Instant.class.getName(), AbstractTsLockingPolicy::createInstantLockingPolicy - ); - - /** - * AbstractTsLockingPolicy factory method. - * - * @param typeName raw {@code MetadataClass} name - * @param field version field - */ - public static AbstractTsLockingPolicy create(String typeName, DatabaseField field) { - LockingPolicySupplier factory = FACTORY.get(typeName); - if (factory == null) { - throw new UnsupportedOperationException(String.format("Cannot create AbstractTsLockingPolicy for %s", typeName)); - } - return factory.create(field); - } - - private int retrieveTimeFrom; + private TimeSource timeSource; /** * Creates an instance of timestamp version policy used for optimistic locking. * Defaults to using the time retrieved from the server. */ - public AbstractTsLockingPolicy() { + public JavaTimeLockingPolicy() { super(); - this.useServerTime(); - } - - /** - * Creates an instance of timestamp version policy used for optimistic locking. - * Defaults to using the time retrieved from the server. - * - * @param fieldName the field where the write lock value will be stored - */ - public AbstractTsLockingPolicy(String fieldName) { - super(fieldName); - this.useServerTime(); + this.timeSource = TimeSource.Server; } /** @@ -82,19 +44,20 @@ public AbstractTsLockingPolicy(String fieldName) { * * @param field the field where the write lock value will be stored */ - public AbstractTsLockingPolicy(DatabaseField field) { + public JavaTimeLockingPolicy(DatabaseField field) { super(field); - this.useServerTime(); + this.timeSource = TimeSource.Server; } /* * Following methods mapping removes unsafe casts in child classes. + * Abstract methods are pkg only visible to avoid them in the API. */ /** * This method compares two writeLockValues. * The writeLockValues should be non-null and of {@link LocalDateTime}, - * {@link Instant} or {@link Timestamp} type. + * or {@link Instant} type. * * @param value1 the 1st value to compare * @param value2 the 2nd value to compare @@ -104,12 +67,12 @@ public AbstractTsLockingPolicy(DatabaseField field) { * @throws NullPointerException if the passed value is null * @throws ClassCastException if the passed value is of a wrong type. */ - abstract int compareTsLockValues(T value1, T value2); + abstract int compareJavaTimeLockValues(T value1, T value2); @Override @SuppressWarnings("unchecked") public int compareWriteLockValues(Object value1, Object value2) { - return compareTsLockValues((T) value1, (T) value2); + return compareJavaTimeLockValues((T) value1, (T) value2); } /** @@ -117,12 +80,12 @@ public int compareWriteLockValues(Object value1, Object value2) { * * @return the default timestamp locking filed java type */ - abstract Class getDefaultTsLockFieldType(); + abstract Class getDefaultJavaTimeLockFieldType(); @Override @SuppressWarnings("unchecked") protected Class getDefaultLockingFieldType() { - return (Class) getDefaultTsLockFieldType(); + return (Class) getDefaultJavaTimeLockFieldType(); } /** @@ -131,12 +94,12 @@ protected Class getDefaultLockingFieldType() { * * @return timestamp base value */ - abstract T getBaseTsValue(); + abstract T getBaseJavaTimeValue(); @Override @SuppressWarnings("unchecked") public C getBaseValue() { - return (C) getBaseTsValue(); + return (C) getBaseJavaTimeValue(); } /** @@ -145,12 +108,12 @@ public C getBaseValue() { * @param session the database session * @return the initial locking value */ - abstract T getInitialTsWriteValue(AbstractSession session); + abstract T getInitialJavaTimeWriteValue(AbstractSession session); @Override @SuppressWarnings("unchecked") protected C getInitialWriteValue(AbstractSession session) { - return (C) getInitialTsWriteValue(session); + return (C) getInitialJavaTimeWriteValue(session); } /** @@ -159,12 +122,12 @@ protected C getInitialWriteValue(AbstractSession session) { * @param query modify query * @return the new timestamp value */ - abstract T getNewTsLockValue(ModifyQuery query); + abstract T getNewJavaTimeLockValue(ModifyQuery query); @Override @SuppressWarnings("unchecked") public C getNewLockValue(ModifyQuery query) { - return (C) getNewTsLockValue(query); + return (C) getNewJavaTimeLockValue(query); } /** @@ -175,12 +138,12 @@ public C getNewLockValue(ModifyQuery query) { * @param session the database session * @return the value that should be stored in the identity map */ - abstract T getTsValueToPutInCache(AbstractRecord row, AbstractSession session); + abstract T getJavaTimeValueToPutInCache(AbstractRecord row, AbstractSession session); @Override @SuppressWarnings("unchecked") public C getValueToPutInCache(AbstractRecord row, AbstractSession session) { - return (C) getTsValueToPutInCache(row, session); + return (C) getJavaTimeValueToPutInCache(row, session); } /** @@ -191,12 +154,12 @@ public C getValueToPutInCache(AbstractRecord row, AbstractSession session) { * @param session the database session * @return the optimistic lock value for the object */ - abstract T getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session); + abstract T getWriteJavaTimeLockValue(Object domainObject, Object primaryKey, AbstractSession session); @Override @SuppressWarnings("unchecked") public C getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { - return (C) getWriteTsLockValue(domainObject, primaryKey, session); + return (C) getWriteJavaTimeLockValue(domainObject, primaryKey, session); } /** @@ -209,17 +172,16 @@ public C getWriteLockValue(Object domainObject, Object primaryKey, AbstractS * @return value of {@code true} if the {@code first} is newer than the {@code second} * or {@code false} otherwise */ - abstract boolean isNewerTsVersion(T current, Object domainObject, Object primaryKey, AbstractSession session); + abstract boolean isNewerJavaTimeVersion(T current, Object domainObject, Object primaryKey, AbstractSession session); /** - * INTERNAL: * Compares the value with the value from the object (or cache). * Will return true if the currentValue is newer than the domainObject. */ @Override @SuppressWarnings("unchecked") public boolean isNewerVersion(Object current, Object domainObject, Object primaryKey, AbstractSession session) { - return isNewerTsVersion((T) current, domainObject, primaryKey, session); + return isNewerJavaTimeVersion((T) current, domainObject, primaryKey, session); } /** @@ -232,11 +194,11 @@ public boolean isNewerVersion(Object current, Object domainObject, Object primar * @return value of {@code true} if the {@code first} is newer than the {@code second} * or {@code false} otherwise */ - abstract boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session); + abstract boolean isNewerJavaTimeVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session); @Override public boolean isNewerVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { - return isNewerTsVersion(row, domainObject, primaryKey, session); + return isNewerJavaTimeVersion(row, domainObject, primaryKey, session); } /** @@ -247,12 +209,12 @@ public boolean isNewerVersion(AbstractRecord row, Object domainObject, Object pr * @return value of {@code true} if the {@code first} is newer than the {@code second} * or {@code false} otherwise */ - abstract boolean isNewerTsVersion(T first, T second) ; + abstract boolean isNewerJavaTimeVersion(T first, T second) ; @Override @SuppressWarnings("unchecked") public boolean isNewerVersion(Object first, Object second) { - return isNewerTsVersion((T) first, (T) second); + return isNewerJavaTimeVersion((T) first, (T) second); } /** @@ -267,7 +229,7 @@ public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, Abstra } /** - * Timestamp versioning should not be able to do this. + * Time-stamp versioning should not be able to do this. * Override the superclass behavior. * * @param value the source value @@ -278,70 +240,33 @@ protected Number incrementWriteLockValue(Number value) { } /** - * Set time source policy. + * Set time-stamp source policy. * - * @param useServer set this policy to get the time from the server when {@code true} - * or from the local machine when {@code false} + * @param timeSource set this policy to retrieve the time-stamp from the server + * when set to {@link TimeSource#Server} or from the local machine + * when set to {@link TimeSource#Local} */ - public void setUsesServerTime(boolean useServer) { - if (useServer) { - useServerTime(); - } else { - useLocalTime(); - } + public void setTimeSource(TimeSource timeSource) { + this.timeSource = timeSource; } /** - * Set time source policy to get the time from the local machine. - */ - public void useLocalTime() { - retrieveTimeFrom = LOCAL_TIME; - } - - /** - * Set time source policy to get the time from the server. - */ - public void useServerTime() { - retrieveTimeFrom = SERVER_TIME; - } - - /** - * Return whether time source policy uses local time. + * Get time-stamp source policy. * - * @return value of {@code true} when policy uses local time or {@code false} otherwise. + * @return the time-stamp source policy */ - public boolean usesLocalTime() { - return retrieveTimeFrom == LOCAL_TIME; + public TimeSource getTimeSource() { + return timeSource; } /** - * Return whether time source policy uses server time. - * - * @return value of {@code true} when policy uses server time or {@code false} otherwise. + * Time-stamp source policy. */ - public boolean usesServerTime() { - return retrieveTimeFrom == SERVER_TIME; - } - - // TimestampLockingPolicy factory method - private static TimestampLockingPolicy createTimestampLockingPolicy(DatabaseField field) { - return new TimestampLockingPolicy(field); - } - - // LocalDateTimeLockingPolicy factory method - private static LocalDateTimeLockingPolicy createLocalDateTimeLockingPolicy(DatabaseField field) { - return new LocalDateTimeLockingPolicy(field); - } - - // InstantLockingPolicy factory method - private static InstantLockingPolicy createInstantLockingPolicy(DatabaseField field) { - return new InstantLockingPolicy(field); - } - - // AbstractTsLockingPolicy factory interface - @FunctionalInterface - private interface LockingPolicySupplier { - AbstractTsLockingPolicy create(DatabaseField field); + public enum TimeSource { + /** Retrieve from the server. */ + Server, + /** Retrieve from the local machine. */ + Local } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java index f4277d5cf0..4ebb181513 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java @@ -11,10 +11,10 @@ */ package org.eclipse.persistence.descriptors; -import java.sql.Timestamp; import java.time.LocalDateTime; import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractRecord; @@ -23,7 +23,7 @@ /** * Version policy used for optimistic locking with {@link LocalDateTime} field. */ -public class LocalDateTimeLockingPolicy extends AbstractTsLockingPolicy { +public class LocalDateTimeLockingPolicy extends JavaTimeLockingPolicy { /** * Create a new instance of version policy used for optimistic locking @@ -34,17 +34,6 @@ public LocalDateTimeLockingPolicy() { super(); } - /** - * Create a new instance of version policy used for optimistic locking - * with {@link LocalDateTime} field. - * Defaults to using the time retrieved from the server. - * - * @param fieldName the field where the write lock value will be stored - */ - public LocalDateTimeLockingPolicy(String fieldName) { - super(fieldName); - } - /** * Create a new instance of version policy used for optimistic locking * with {@link LocalDateTime} field. @@ -52,52 +41,52 @@ public LocalDateTimeLockingPolicy(String fieldName) { * * @param field the field where the write lock value will be stored */ - LocalDateTimeLockingPolicy(DatabaseField field) { + public LocalDateTimeLockingPolicy(DatabaseField field) { super(field); } @Override - int compareTsLockValues(LocalDateTime value1, LocalDateTime value2) { + int compareJavaTimeLockValues(LocalDateTime value1, LocalDateTime value2) { return value1.compareTo(value2); } @Override - Class getDefaultTsLockFieldType() { + Class getDefaultJavaTimeLockFieldType() { return ClassConstants.LOCAL_DATETIME; } @Override - LocalDateTime getBaseTsValue() { + LocalDateTime getBaseJavaTimeValue() { // LocalDateTime is immutable so constant is safe return LocalDateTime.MIN; } @Override - LocalDateTime getInitialTsWriteValue(AbstractSession session) { - if (usesLocalTime()) { - return LocalDateTime.now(); - } - if (usesServerTime()) { - AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); - while (readSession.isUnitOfWork()) { - readSession = readSession.getParent() - .getSessionForClass(getDescriptor().getJavaClass()); - } - Timestamp ts = readSession.getDatasourceLogin() - .getDatasourcePlatform() - .getTimestampFromServer(session, readSession.getName()); - return ts.toLocalDateTime(); + LocalDateTime getInitialJavaTimeWriteValue(AbstractSession session) { + switch (getTimeSource()) { + case Local: + return LocalDateTime.now(); + case Server: + AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); + Platform platform = session.getDatasourcePlatform(); + while (readSession.isUnitOfWork()) { + readSession = readSession.getParent() + .getSessionForClass(getDescriptor().getJavaClass()); + } + return platform.convertObject( + session.executeQuery(platform.getTimestampQuery()), ClassConstants.LOCAL_DATETIME); + default: + return null; } - return null; } @Override - LocalDateTime getNewTsLockValue(ModifyQuery query) { - return getInitialTsWriteValue(query.getSession()); + LocalDateTime getNewJavaTimeLockValue(ModifyQuery query) { + return getInitialJavaTimeWriteValue(query.getSession()); } @Override - LocalDateTime getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { + LocalDateTime getJavaTimeValueToPutInCache(AbstractRecord row, AbstractSession session) { if (isStoredInCache()) { return session.getDatasourcePlatform() .convertObject(row.get(getWriteLockField()), ClassConstants.LOCAL_DATETIME); @@ -107,7 +96,7 @@ LocalDateTime getTsValueToPutInCache(AbstractRecord row, AbstractSession session } @Override - LocalDateTime getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + LocalDateTime getWriteJavaTimeLockValue(Object domainObject, Object primaryKey, AbstractSession session) { LocalDateTime writeLockFieldValue = null; if (isStoredInCache()) { writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance() @@ -127,7 +116,7 @@ LocalDateTime getWriteTsLockValue(Object domainObject, Object primaryKey, Abstra } @Override - boolean isNewerTsVersion(LocalDateTime current, Object domainObject, Object primaryKey, AbstractSession session) { + boolean isNewerJavaTimeVersion(LocalDateTime current, Object domainObject, Object primaryKey, AbstractSession session) { LocalDateTime writeLockFieldValue; if (isStoredInCache()) { writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance() @@ -136,12 +125,12 @@ boolean isNewerTsVersion(LocalDateTime current, Object domainObject, Object prim writeLockFieldValue = (LocalDateTime)lockValueFromObject(domainObject); } - return isNewerTsVersion(current, writeLockFieldValue); + return isNewerJavaTimeVersion(current, writeLockFieldValue); } @Override - boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + boolean isNewerJavaTimeVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { LocalDateTime writeLockFieldValue; LocalDateTime newWriteLockFieldValue = session.getDatasourcePlatform() .convertObject(row.get(getWriteLockField()), ClassConstants.LOCAL_DATETIME); @@ -151,11 +140,11 @@ boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primary } else { writeLockFieldValue = (LocalDateTime) lockValueFromObject(domainObject); } - return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); + return isNewerJavaTimeVersion(newWriteLockFieldValue, writeLockFieldValue); } @Override - boolean isNewerTsVersion(LocalDateTime first, LocalDateTime second) { + boolean isNewerJavaTimeVersion(LocalDateTime first, LocalDateTime second) { // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. if (first == null) { return false; diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java index 98d74fb284..cf8626eb24 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -15,183 +15,316 @@ // Oracle - initial API and implementation from Oracle TopLink package org.eclipse.persistence.descriptors; -import java.sql.Timestamp; - import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.eclipse.persistence.expressions.Expression; +import org.eclipse.persistence.expressions.ExpressionBuilder; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.queries.ModifyQuery; +import java.sql.Timestamp; + /** *

Purpose: Used to allow a single version timestamp to be used for optimistic locking. * * @since TOPLink/Java 2.0 */ -public class TimestampLockingPolicy extends AbstractTsLockingPolicy { +public class TimestampLockingPolicy extends VersionLockingPolicy { + protected int retrieveTimeFrom; + public final static int SERVER_TIME = 1; + public final static int LOCAL_TIME = 2; /** + * PUBLIC: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. */ public TimestampLockingPolicy() { super(); + this.useServerTime(); } /** + * PUBLIC: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. - * - * @param fieldName the field where the write lock value will be stored + * @param fieldName the field where the write lock value will be stored. */ public TimestampLockingPolicy(String fieldName) { super(fieldName); + this.useServerTime(); } /** + * INTERNAL: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. - * - * @param field the field where the write lock value will be stored + * @param field the field where the write lock value will be stored. */ - TimestampLockingPolicy(DatabaseField field) { + public TimestampLockingPolicy(DatabaseField field) { super(field); + this.useServerTime(); } + /** + * INTERNAL: + * This method compares two writeLockValues. + * The writeLockValues should be non-null and of type java.sql.Timestamp. + * Returns: + * -1 if value1 is less (older) than value2; + * 0 if value1 equals value2; + * 1 if value1 is greater (newer) than value2. + * Throws: + * NullPointerException if the passed value is null; + * ClassCastException if the passed value is of a wrong type. + */ @Override - int compareTsLockValues(Timestamp value1, Timestamp value2) { - return value1.compareTo(value2); + public int compareWriteLockValues(Object value1, Object value2) { + java.sql.Timestamp timestampValue1 = (java.sql.Timestamp)value1; + java.sql.Timestamp timestampValue2 = (java.sql.Timestamp)value2; + return timestampValue1.compareTo(timestampValue2); } + /** + * INTERNAL: + * Return the default timestamp locking filed java type, default is Timestamp. + */ @Override - Class getDefaultTsLockFieldType() { - return ClassConstants.TIMESTAMP; + @SuppressWarnings({"unchecked"}) + protected Class getDefaultLockingFieldType() { + return (Class) ClassConstants.TIMESTAMP; } + /** + * INTERNAL: + * This is the base value that is older than all other values, it is used in the place of + * null in some situations. + */ @Override - Timestamp getBaseTsValue(){ - return new Timestamp(0); + @SuppressWarnings({"unchecked"}) + public T getBaseValue(){ + return (T) new Timestamp(0); } + /** + * INTERNAL: + * returns the initial locking value + */ @Override - Timestamp getInitialTsWriteValue(AbstractSession session) { + @SuppressWarnings({"unchecked"}) + protected T getInitialWriteValue(AbstractSession session) { if (usesLocalTime()) { - return new Timestamp(System.currentTimeMillis()); + return (T) new Timestamp(System.currentTimeMillis()); } if (usesServerTime()) { AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); while (readSession.isUnitOfWork()) { - readSession = readSession.getParent() - .getSessionForClass(getDescriptor().getJavaClass()); + readSession = readSession.getParent().getSessionForClass(getDescriptor().getJavaClass()); } - return readSession.getDatasourceLogin() - .getDatasourcePlatform() - .getTimestampFromServer(session, readSession.getName()); + return (T) readSession.getDatasourceLogin().getDatasourcePlatform().getTimestampFromServer(session, readSession.getName()); } return null; + } + /** + * INTERNAL: + * Returns the new Timestamp value. + */ @Override - Timestamp getNewTsLockValue(ModifyQuery query) { - return getInitialTsWriteValue(query.getSession()); + public T getNewLockValue(ModifyQuery query) { + return getInitialWriteValue(query.getSession()); } + /** + * INTERNAL: + * Return the value that should be stored in the identity map. If the value + * is stored in the object, then return a null. + */ @Override - Timestamp getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { + @SuppressWarnings({"unchecked"}) + public T getValueToPutInCache(AbstractRecord row, AbstractSession session) { if (isStoredInCache()) { - return session.getDatasourcePlatform() - .convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); + return (T) session.getDatasourcePlatform().convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); } else { return null; } } + /** + * INTERNAL: + * Return the number of versions different between these objects. + */ + @Override + public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { + java.sql.Timestamp writeLockFieldValue; + java.sql.Timestamp newWriteLockFieldValue = (java.sql.Timestamp)currentValue; + if (newWriteLockFieldValue == null) { + return 0;//merge it as either the object is new or being forced merged. + } + if (isStoredInCache()) { + writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); + } + if ((writeLockFieldValue != null) && (newWriteLockFieldValue.equals(writeLockFieldValue))) { + return 0; + } + if ((writeLockFieldValue != null) && !(newWriteLockFieldValue.after(writeLockFieldValue))) { + return -1; + } + + return 1; + } + + /** + * INTERNAL: + * This method will return the optimistic lock value for the object. + */ @Override - Timestamp getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { - Timestamp writeLockFieldValue = null; + @SuppressWarnings({"unchecked"}) + public T getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + java.sql.Timestamp writeLockFieldValue = null; if (isStoredInCache()) { - writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() - .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { //CR#2281 notStoredInCache prevent ClassCastException Object lockValue = lockValueFromObject(domainObject); if (lockValue != null) { - if (lockValue instanceof Timestamp) { - writeLockFieldValue = (Timestamp) lockValueFromObject(domainObject); + if (lockValue instanceof java.sql.Timestamp) { + writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); } else { throw OptimisticLockException.needToMapJavaSqlTimestampWhenStoredInObject(); } } } - return writeLockFieldValue; + return (T) writeLockFieldValue; + } + + /** + * INTERNAL: + * Return an expression that updates the write lock + */ + @Override + public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) { + return builder.currentTimeStamp(); + } + + /** + * INTERNAL: + * Timestamp versioning should not be able to do this. Override the superclass behavior. + */ + @Override + protected Number incrementWriteLockValue(Number numberValue) { + return null; } + /** + * INTERNAL: + * Compares the value with the value from the object (or cache). + * Will return true if the currentValue is newer than the domainObject. + */ @Override - boolean isNewerTsVersion(Timestamp current, Object domainObject, Object primaryKey, AbstractSession session) { - Timestamp writeLockFieldValue; + public boolean isNewerVersion(Object currentValue, Object domainObject, Object primaryKey, AbstractSession session) { + java.sql.Timestamp writeLockFieldValue; + java.sql.Timestamp newWriteLockFieldValue = (java.sql.Timestamp)currentValue; if (isStoredInCache()) { - writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() - .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { - writeLockFieldValue = (Timestamp)lockValueFromObject(domainObject); + writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); } - return isNewerTsVersion(current, writeLockFieldValue); - + return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); } + /** + * INTERNAL: + * Compares the value from the row and from the object (or cache). + * Will return true if the row is newer than the object. + */ @Override - boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { - Timestamp writeLockFieldValue; - Timestamp newWriteLockFieldValue = session.getDatasourcePlatform() - .convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); + public boolean isNewerVersion(AbstractRecord databaseRow, Object domainObject, Object primaryKey, AbstractSession session) { + java.sql.Timestamp writeLockFieldValue; + java.sql.Timestamp newWriteLockFieldValue = session.getDatasourcePlatform().convertObject(databaseRow.get(getWriteLockField()), ClassConstants.TIMESTAMP); if (isStoredInCache()) { - writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() - .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { - writeLockFieldValue = (Timestamp) lockValueFromObject(domainObject); + writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); } - return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); + + return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); } + /** + * INTERNAL: + * Compares two values. + * Will return true if the firstLockFieldValue is newer than the secondWriteLockFieldValue. + */ @Override - boolean isNewerTsVersion(Timestamp first, Timestamp second) { + public boolean isNewerVersion(Object firstLockFieldValue, Object secondWriteLockFieldValue) { + java.sql.Timestamp firstValue = (java.sql.Timestamp)firstLockFieldValue; + java.sql.Timestamp secondValue = (java.sql.Timestamp)secondWriteLockFieldValue; + // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. - if (first == null) { + if (firstValue == null) { return false; } + // bug 6342382: first is not null, second is null, so we know first>second. - if (second == null) { + if(secondValue == null) { return true; } - return first.after(second); + + if (firstValue.after(secondValue)){ + return true; + } + return false; } /** - * INTERNAL: - * Return the number of versions different between these objects. + * PUBLIC: + * Set if policy uses server time. */ - @Override - public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { - Timestamp writeLockFieldValue; - Timestamp newWriteLockFieldValue = (Timestamp)currentValue; - if (newWriteLockFieldValue == null) { - return 0;//merge it as either the object is new or being forced merged. - } - if (isStoredInCache()) { - writeLockFieldValue = (Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); + public void setUsesServerTime(boolean usesServerTime) { + if (usesServerTime) { + useServerTime(); } else { - writeLockFieldValue = (Timestamp)lockValueFromObject(domainObject); - } - if ((writeLockFieldValue != null) && (newWriteLockFieldValue.equals(writeLockFieldValue))) { - return 0; - } - if ((writeLockFieldValue != null) && !(newWriteLockFieldValue.after(writeLockFieldValue))) { - return -1; + useLocalTime(); } - return 1; } + /** + * PUBLIC: + * set this policy to get the time from the local machine. + */ + public void useLocalTime() { + retrieveTimeFrom = LOCAL_TIME; + } + + /** + * PUBLIC: + * set this policy to get the time from the server. + */ + public void useServerTime() { + retrieveTimeFrom = SERVER_TIME; + } + + /** + * PUBLIC: + * Return true if policy uses local time. + */ + public boolean usesLocalTime() { + return (retrieveTimeFrom == LOCAL_TIME); + } + + /** + * PUBLIC: + * Return true if policy uses server time. + */ + public boolean usesServerTime() { + return (retrieveTimeFrom == SERVER_TIME); + } } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/accessors/mappings/VersionAccessor.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/accessors/mappings/VersionAccessor.java index 8d89999c55..3a936955ba 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/accessors/mappings/VersionAccessor.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/accessors/mappings/VersionAccessor.java @@ -25,9 +25,12 @@ import java.time.Instant; import java.time.LocalDateTime; -import org.eclipse.persistence.descriptors.AbstractTsLockingPolicy; +import org.eclipse.persistence.descriptors.InstantLockingPolicy; +import org.eclipse.persistence.descriptors.LocalDateTimeLockingPolicy; +import org.eclipse.persistence.descriptors.TimestampLockingPolicy; import org.eclipse.persistence.descriptors.VersionLockingPolicy; import org.eclipse.persistence.exceptions.ValidationException; +import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor; import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; @@ -124,7 +127,7 @@ public void process() { for (MetadataDescriptor owningDescriptor : getOwningDescriptors()) { VersionLockingPolicy policy = isValidVersionLockingType(lockType) ? new VersionLockingPolicy(getDatabaseField()) - : AbstractTsLockingPolicy.create(lockType.getName(), getDatabaseField()); + : createDateTimeVersionLockingPolicy(lockType.getName(), getDatabaseField()); policy.storeInObject(); policy.setIsCascaded(getDescriptor().usesCascadedOptimisticLocking()); owningDescriptor.setOptimisticLockingPolicy(policy); @@ -134,4 +137,21 @@ public void process() { } } } + + // Create DateTime based VersionLockingPolicy corresponding to field type name + private static VersionLockingPolicy createDateTimeVersionLockingPolicy(String typeName, DatabaseField field) { + switch (typeName) { + case "java.sql.Timestamp": + return new TimestampLockingPolicy(field); + case "java.time.LocalDateTime": + return new LocalDateTimeLockingPolicy(field); + case "java.time.Instant": + return new InstantLockingPolicy(field); + // This is not accessible as long as isValidTimestampVersionLockingType(MetadataClass) check + // is not broken so this exception always means bug in the code. + default: + throw new UnsupportedOperationException("Cannot create VersionLockingPolicy for " + typeName); + } + } + }