diff --git a/jpalite-core/pom.xml b/jpalite-core/pom.xml
index 461d58f..c4bfb69 100644
--- a/jpalite-core/pom.xml
+++ b/jpalite-core/pom.xml
@@ -46,6 +46,14 @@
com.github.jsqlparserjsqlparser
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ io.quarkusquarkus-opentelemetry
diff --git a/jpalite-core/src/main/java/io/jpalite/CachingException.java b/jpalite-core/src/main/java/io/jpalite/CachingException.java
new file mode 100644
index 0000000..62ac045
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/CachingException.java
@@ -0,0 +1,26 @@
+package io.jpalite;
+
+import jakarta.persistence.PersistenceException;
+
+public class CachingException extends PersistenceException
+{
+ public CachingException()
+ {
+ super();
+ }
+
+ public CachingException(String message)
+ {
+ super(message);
+ }
+
+ public CachingException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+
+ public CachingException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/ConverterClass.java b/jpalite-core/src/main/java/io/jpalite/ConverterClass.java
index 0abc352..b3d62cd 100644
--- a/jpalite-core/src/main/java/io/jpalite/ConverterClass.java
+++ b/jpalite-core/src/main/java/io/jpalite/ConverterClass.java
@@ -26,13 +26,6 @@ public interface ConverterClass
*/
String getName();
- /**
- * The class of the converter
- *
- * @return The class
- */
- Class> getConvertClass();
-
/**
* A check to see if the converter should be auto applied
*
@@ -47,7 +40,7 @@ public interface ConverterClass
*/
//We need to use the raw type here as the converter is not generic
@SuppressWarnings("java:S1452")
- TradeSwitchConvert, ?> getConverter();
+ FieldConvertType, ?> getConverter();
/**
* The attribute type
@@ -55,11 +48,4 @@ public interface ConverterClass
* @return The attribute type
*/
Class> getAttributeType();
-
- /**
- * The database type
- *
- * @return The database type
- */
- Class> getDbType();
}
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityCache.java b/jpalite-core/src/main/java/io/jpalite/EntityCache.java
index b99fec7..4945ac4 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityCache.java
+++ b/jpalite-core/src/main/java/io/jpalite/EntityCache.java
@@ -19,60 +19,38 @@
import jakarta.annotation.Nonnull;
import jakarta.persistence.Cache;
-import jakarta.transaction.*;
+import jakarta.transaction.SystemException;
-import java.util.List;
+import java.time.Instant;
public interface EntityCache extends Cache
{
/**
* Create a new transaction and associate it with the current thread.
*
- * @throws jakarta.transaction.NotSupportedException Thrown if the thread is already
- * associated with a transaction and the Transaction Manager
- * implementation does not support nested transactions.
- * @throws jakarta.transaction.SystemException Thrown if the transaction manager
- * encounters an unexpected error condition.
+ * @throws jakarta.transaction.SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
*/
- public void begin() throws NotSupportedException, SystemException;
+ public void begin() throws SystemException;
/**
* Complete the transaction associated with the current thread. When this
* method completes, the thread is no longer associated with a transaction.
*
- * @throws jakarta.transaction.RollbackException Thrown to indicate that
- * the transaction has been rolled back rather than committed.
- * @throws jakarta.transaction.HeuristicMixedException Thrown to indicate that a heuristic
- * decision was made and that some relevant updates have been committed
- * while others have been rolled back.
- * @throws jakarta.transaction.HeuristicRollbackException Thrown to indicate that a
- * heuristic decision was made and that all relevant updates have been
- * rolled back.
- * @throws SecurityException Thrown to indicate that the thread is
- * not allowed to commit the transaction.
- * @throws IllegalStateException Thrown if the current thread is
- * not associated with a transaction.
- * @throws SystemException Thrown if the transaction manager
- * encounters an unexpected error condition.
+ * @throws SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
*/
- void commit() throws RollbackException,
- HeuristicMixedException, HeuristicRollbackException, SecurityException,
- IllegalStateException, SystemException;
+ void commit() throws SystemException;
/**
* Roll back the transaction associated with the current thread. When this
* method completes, the thread is no longer associated with a
* transaction.
*
- * @throws SecurityException Thrown to indicate that the thread is
- * not allowed to roll back the transaction.
- * @throws IllegalStateException Thrown if the current thread is
- * not associated with a transaction.
- * @throws SystemException Thrown if the transaction manager
- * encounters an unexpected error condition.
+ * @throws SystemException Thrown if the transaction manager
+ * encounters an unexpected error condition.
*/
- void rollback() throws IllegalStateException, SecurityException,
- SystemException;
+ void rollback() throws SystemException;
/**
* Search the cache for an entity using the primary key.
@@ -83,17 +61,6 @@ void rollback() throws IllegalStateException, SecurityException,
*/
T find(Class entityType, Object primaryKey);
- /**
- * Search for the entity in the cache using the where clause
- *
- * @param entityType
- * @param query
- * @param
- * @return
- */
- @Nonnull
- List search(Class entityType, String query);
-
/**
* Add an entity to the cache.
*
@@ -106,21 +73,28 @@ void rollback() throws IllegalStateException, SecurityException,
*
* @param entity The entity to update or add
*/
- void update(JPAEntity entity);
+ void replace(JPAEntity entity);
+
/**
- * Detach an entity from the cache and mark the entity as DETACHED
+ * Return the time for when an entity-type was last updated
*
- * @param entity the entity to detach
+ * @param entityType The entity type
+ * @return The instance when the entity was last updated
*/
- void remove(JPAEntity entity);
+ @Nonnull
+ Instant getLastModified(Class entityType);
/**
* Return the time for when an entity-type was last updated
*
* @param entityType The entity type
- * @param
* @return The time since epoch the entity was updated
+ * @deprecated Replaced by {{@link #getLastModified(Class)}}
*/
- long lastModified(Class entityType);
+ @Deprecated(forRemoval = true, since = "3.0.0")
+ default long lastModified(Class entityType)
+ {
+ return getLastModified(entityType).toEpochMilli();
+ }
}
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityField.java b/jpalite-core/src/main/java/io/jpalite/EntityField.java
index e94bcc8..9639784 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityField.java
+++ b/jpalite-core/src/main/java/io/jpalite/EntityField.java
@@ -179,7 +179,7 @@ public interface EntityField
*
* @return The attribute converter class. If no converter is set, null is returned
*/
- @SuppressWarnings("java:S3740")
+ @SuppressWarnings({"java:S3740", "rawtypes"})
// Suppress warning for generic types
- TradeSwitchConvert getConverterClass();
+ FieldConvertType getConverter();
}
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java b/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java
index f3adba6..b098901 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java
+++ b/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java
@@ -128,20 +128,20 @@ public interface EntityMetaData
@Nullable
@SuppressWarnings("java:S1452")
//generic wildcard is required
- EntityMetaData> getIPrimaryKeyMetaData();
+ EntityMetaData> getPrimaryKeyMetaData();
/**
* Return a list of all the defined id fields
*
- * @return Set of id fields
+ * @return List of id fields
*/
@Nonnull
List getIdFields();
/**
- * True if the entity have more than one ID field
+ * True if the entity has more than one ID field
*
- * @return true if there are more than one if fields in the entity
+ * @return true if there are more than one if field in the entity
*/
boolean hasMultipleIdFields();
@@ -160,14 +160,24 @@ public interface EntityMetaData
*/
boolean isCacheable();
+ /**
+ * Retrieves the idle time of the entity. The units are dependent the value in {@link #getCacheTimeUnit}
+ *
+ * @return The idle time in.
+ */
long getIdleTime();
+ /**
+ * Retrieves the time unit used for caching.
+ *
+ * @return The {@link TimeUnit} used for caching.
+ */
TimeUnit getCacheTimeUnit();
/**
- * True if the entity have a version field
+ * Checks if the entity has a version field.
*
- * @return
+ * @return True if the entity has a version field, false otherwise.
*/
boolean hasVersionField();
@@ -179,14 +189,12 @@ public interface EntityMetaData
EntityField getVersionField();
/**
- * Return a comma delimited string with columns
- */
- String getColumns();
-
- /**
- * The protobuf protocal file for the entity
+ * Deprecated since version 3.0.0 and marked for removal.
+ * Returns the columns associated with the entity.
*
- * @return The proto file as a string
+ * @return The columns as a comma-delimited string.
+ * @deprecated This method is deprecated and will be removed in a future version.
*/
- String getProtoFile();
+ @Deprecated(since = "3.0.0", forRemoval = true)
+ String getColumns();
}//EntityMetaData
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java b/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java
index 3edfcba..dd7b475 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java
+++ b/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java
@@ -24,14 +24,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
+import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
@@ -49,33 +48,27 @@ public class EntityMetaDataManager
loadEntities();
}
- private static int loadEntities()
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private static void loadEntities()
{
lock.lock();
try {
if (!registryLoaded) {
try {
+ REGISTRY_CONVERTERS.clear();
+
ClassLoader loader = Thread.currentThread().getContextClassLoader();
long start = System.currentTimeMillis();
- Enumeration urls = loader.getResources("META-INF/io.jpalite.converters");
- while (urls.hasMoreElements()) {
- URL location = urls.nextElement();
- try (InputStream inputStream = location.openStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
-
- String line = reader.readLine();
- while (line != null) {
- ConverterClass convertClass = new ConverterClassImpl(loader.loadClass(line));
- REGISTRY_CONVERTERS.put(convertClass.getAttributeType(), convertClass);
- line = reader.readLine();
- }//while
- }//try
- }//while
+ ServiceLoader converterLoader = ServiceLoader.load(FieldConvertType.class);
+ for (FieldConvertType, ?> converter : converterLoader) {
+ registerConverter(converter);
+ }
+
LOG.info("Loaded {} converters in {}ms", REGISTRY_CONVERTERS.size(), System.currentTimeMillis() - start);
start = System.currentTimeMillis();
- urls = loader.getResources("META-INF/persistenceUnits.properties");
+ Enumeration urls = loader.getResources("META-INF/persistenceUnits.properties");
while (urls.hasMoreElements()) {
URL location = urls.nextElement();
try (InputStream inputStream = location.openStream()) {
@@ -98,11 +91,8 @@ private static int loadEntities()
}//while
LOG.info("Loaded {} entities in {}ms", REGISTRY_ENTITY_CLASSES.size(), System.currentTimeMillis() - start);
}//try
- catch (ClassNotFoundException ex) {
- throw new PersistenceException("Error loading converter class", ex);
- }//catch
catch (IOException ex) {
- throw new PersistenceException("Error reading persistenceUnits.properties or org.tradeswitch.converters", ex);
+ throw new PersistenceException("Error reading persistenceUnits.properties", ex);
}//catch
registryLoaded = true;
@@ -111,8 +101,6 @@ private static int loadEntities()
finally {
lock.unlock();
}
-
- return REGISTRY_ENTITY_NAMES.size();
}//loadEntities
public static int getEntityCount()
@@ -131,6 +119,14 @@ public static EntityMetaData getMetaData(Class> entityName)
return metaData;
}//getMetaData
+ public static void registerConverter(@Nonnull FieldConvertType, ?> converter)
+ {
+ ConverterClass convertClass = new ConverterClassImpl(converter);
+ if (convertClass.isAutoApply()) {
+ REGISTRY_CONVERTERS.put(convertClass.getAttributeType(), convertClass);
+ }
+ }
+
public static void register(@Nonnull EntityMetaData> metaData)
{
if (REGISTRY_ENTITY_NAMES.containsKey(metaData.getName())) {
diff --git a/jpalite-core/src/main/java/io/jpalite/TradeSwitchConvert.java b/jpalite-core/src/main/java/io/jpalite/FieldConvertType.java
similarity index 56%
rename from jpalite-core/src/main/java/io/jpalite/TradeSwitchConvert.java
rename to jpalite-core/src/main/java/io/jpalite/FieldConvertType.java
index 585889a..7727ea3 100644
--- a/jpalite-core/src/main/java/io/jpalite/TradeSwitchConvert.java
+++ b/jpalite-core/src/main/java/io/jpalite/FieldConvertType.java
@@ -17,22 +17,32 @@
package io.jpalite;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.AttributeConverter;
-import org.infinispan.protostream.GeneratedSchema;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
-public interface TradeSwitchConvert extends AttributeConverter
+public interface FieldConvertType extends AttributeConverter
{
- String getFieldType();
+ Class> getAttributeType();
- String prototypeLib();
+ void toJson(JsonGenerator jsonGenerator, X value) throws IOException;
- GeneratedSchema getSchema();
+ X fromJson(JsonNode json);
- default X convertToEntityAttribute(ResultSet pResultSet, int pColumn) throws SQLException
+ void writeField(Object value, DataOutputStream out) throws IOException;
+
+ X readField(DataInputStream in) throws IOException;
+
+
+ @SuppressWarnings("unchecked")
+ default X convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
{
- return convertToEntityAttribute((Y) pResultSet.getObject(pColumn));
+ return (resultSet.wasNull() ? null : convertToEntityAttribute((Y) resultSet.getObject(column)));
}
}
diff --git a/jpalite-core/src/main/java/io/jpalite/JPACache.java b/jpalite-core/src/main/java/io/jpalite/JPACache.java
new file mode 100644
index 0000000..a083680
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/JPACache.java
@@ -0,0 +1,23 @@
+package io.jpalite;
+
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+public interface JPACache
+{
+ T find(String cacheRegion, String key);
+
+ boolean containsKey(String cacheRegion, String key);
+
+ void add(String cacheRegion, String key, T value, long expireTime, TimeUnit expireTimeUnit);
+
+ void replace(String cacheRegion, String key, T value, long expireTime, TimeUnit expireTimeUnit);
+
+ void evict(String cacheRegion, String key);
+
+ void evictAll(String cacheRegion);
+
+ void evictAllRegions();
+
+ Instant getLastModified(String cacheRegion);
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/JPAEntity.java b/jpalite-core/src/main/java/io/jpalite/JPAEntity.java
index 17e99f6..85c9643 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPAEntity.java
+++ b/jpalite-core/src/main/java/io/jpalite/JPAEntity.java
@@ -156,7 +156,7 @@ public interface JPAEntity extends Serializable
void _setPendingAction(PersistenceAction pendingAction);
/**
- * Get the current value of a specific field.
+ * Get the value of a specific field converted to database format
*
* @param fieldName The field name
* @return The value assigned to the value
@@ -302,6 +302,19 @@ public interface JPAEntity extends Serializable
*/
byte[] _serialize();
+ /**
+ * Retrieve content of the JPAEntity as a JSON formatted string*
+ */
+ String _toJson();
+
+ /**
+ * Load the entity from a JSON string
+ *
+ * @param jsonStr The json string
+ */
+ void _fromJson(String jsonStr);
+
+
/**
* Compare the primary keys of the to entities
*
diff --git a/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java b/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java
index f0edb7b..b8efcd8 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java
+++ b/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java
@@ -28,8 +28,6 @@
/**
* The JPALite implementation
- *
- * @see TradeSwitch Persistence Manager in Confluence
*/
public interface JPALiteEntityManager extends EntityManager, Closeable
{
@@ -43,7 +41,7 @@ public interface JPALiteEntityManager extends EntityManager, Closeable
String PERSISTENCE_QUERY_LOG_SLOWTIME = "jpalite.persistence.log.slowQueries";
/**
* The javax.persistence.query.timeout hint defines, in seconds, how long a query is allowed to run before it gets
- * cancelled. TradeSwitch doesn’t handle this timeout itself but provides it to the JDBC driver via the JDBC
+ * cancelled. The JPA stack does’t handle this timeout itself but provides it to the JDBC driver via the JDBC
* Statement.setTimeout method.
*/
String PERSISTENCE_QUERY_TIMEOUT = "jakarta.persistence.query.timeout";
@@ -52,45 +50,40 @@ public interface JPALiteEntityManager extends EntityManager, Closeable
*/
String PERSISTENCE_LOCK_TIMEOUT = "jakarta.persistence.lock.timeout";
/**
- * Valid values are USE or BYPASS. If setting is not recognized it defaults to USE.
+ * Valid values are USE or BYPASS. If the setting is not recognised, it defaults to USE.
*
- * The retrieveMode hint supports the values USE and BYPASS and tells TradeSwitch if it shall USE the second-level
+ * The retrieveMode hint supports the values USE and BYPASS and tell Query implementation if it shall USE the second-level
* cache to retrieve an entity or if it shall BYPASS it and get it directly from the database.
*/
String PERSISTENCE_CACHE_RETRIEVEMODE = "jakarta.persistence.cache.retrieveMode";
/**
* If set to true entities retrieved in {@link Query#getResultList()} is also cached
*/
- String TRADESWITCH_CACHE_RESULTLIST = "org.tradeswitch.cache.resultList";
- /**
- * Used to hint persistence layer that the provided name should be used as the connection name, in the case of a
- * JDBC type connection it will be used as the cursor name.
- */
- String TRADESWITCH_CONNECTION_NAME = "org.tradeswitch.connectionName";
+ String PERSISTENCE_CACHE_RESULTLIST = "jpalite.cache.resultList";
/**
* Hint the JQPL parser to ignore fetchtype setting on basic fields effectively setting all basic fields to be
* EAGERly fetched.
*/
- String TRADESWITCH_OVERRIDE_BASIC_FETCHTYPE = "org.tradeswitch.override.basicFetchType";
+ String PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE = "jpalite.override.basicFetchType";
/**
* Valid values are EAGER or LAZY. If the setting is not recognised it is ignored.
*
* Hint the JQPL parser to ignore fetchtype settings on all fields and effectively setting all fields to be EAGERly
* or LAZYly fetched.
*/
- String TRADESWITCH_OVERRIDE_FETCHTYPE = "org.tradeswitch.override.FetchType";
+ String PERSISTENCE_OVERRIDE_FETCHTYPE = "jpalite.override.FetchType";
/**
* Valid values are TRUE or FALSE. If the setting is not recognized it is ignored. A hint that can be passed to the
* Entity Manager or any Query to log the actual query that is executed.
*/
- String JPALITE_SHOW_SQL = "jpalite.showSql";
+ String PERSISTENCE_SHOW_SQL = "jpalite.showSql";
/**
* Valid values are EAGER or LAZY. If the setting is not recognised it is ignored.
*
- * A hint that can be passed to a Native Query that selection is done on the primary key. This will allow the query
+ * A hint that can be passed to a Native Query that query is done using only the primary key. This will allow the query
* executor to use L2 caching.
*/
- String TRADESWITCH_ONLY_PRIMARYKEY_USED = "org.tradeswitch.primarykey.used";
+ String PERSISTENCE_PRIMARYKEY_USED = "jpalite.primarykey.used";
/**
* Synchronize the entity to the underlying database.
diff --git a/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java b/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java
index 31b329a..5581396 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java
+++ b/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java
@@ -17,18 +17,55 @@
package io.jpalite;
+import io.jpalite.impl.CacheFormat;
import jakarta.persistence.spi.PersistenceUnitInfo;
-import org.infinispan.commons.configuration.BasicConfiguration;
public interface JPALitePersistenceUnit extends PersistenceUnitInfo
{
+ /**
+ * The name of the datasource to associate with persistence unit
+ *
+ * @return The datasource name
+ */
String getDataSourceName();
- String getCacheName();
+ /**
+ * The cache region prefix. If null or blank no prefix will be applied
+ *
+ * @return The prefix
+ */
+ String getCacheRegionPrefix();
+ /**
+ * Retrieves the cache provider for the persistence unit.
+ *
+ * @return The cache provider as a string
+ */
String getCacheProvider();
- BasicConfiguration getCacheConfig();
+ /**
+ * Gets the cache configuration for the persistence unit.
+ *
+ * @return The cache configuration as a string
+ */
+ String getCacheConfig();
+
+
+ /**
+ * Gets the cache client for the persistence unit.
+ *
+ * @return The cache client as a string
+ */
+ String getCacheClient();
+
+ /**
+ * Retrieves the cache format for the persistence unit.
+ *
+ * @return The cache format for the persistence unit. The possible values are:
+ * - {@link CacheFormat#BINARY}: Indicates that the cache format is binary.
+ * - {@link CacheFormat#JSON}: Indicates that the cache format is JSON.
+ */
+ CacheFormat getCacheFormat();
Boolean getMultiTenantMode();
}
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java b/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java
index 74a4ff6..9d86c51 100644
--- a/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java
+++ b/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java
@@ -30,9 +30,9 @@
public interface PersistenceContext extends EntityTransaction, AutoCloseable
{
/**
- * The tradeswitch.persistence.jta hint is used to indicated that the transaction management is done via JTA.
+ * The jpalite.persistence.jta hint is used to signal the transaction management under JTA control.
*/
- String PERSISTENCE_JTA_MANAGED = "tradeswitch.persistence.jta";
+ String PERSISTENCE_JTA_MANAGED = "jpalite.persistence.jta";
/**
* The method is used to retrieve the persistence unit used to create the context
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java b/jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java
new file mode 100644
index 0000000..3f95ae4
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java
@@ -0,0 +1,26 @@
+package io.jpalite;
+
+import jakarta.persistence.PersistenceException;
+
+public class PersistenceUnitNotFoundException extends PersistenceException
+{
+ public PersistenceUnitNotFoundException()
+ {
+ super();
+ }
+
+ public PersistenceUnitNotFoundException(String message)
+ {
+ super(message);
+ }
+
+ public PersistenceUnitNotFoundException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+
+ public PersistenceUnitNotFoundException(Throwable cause)
+ {
+ super(cause);
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java b/jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java
new file mode 100644
index 0000000..63b8430
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java
@@ -0,0 +1,7 @@
+package io.jpalite.impl;
+
+public enum CacheFormat
+{
+ BINARY,
+ JSON
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java
index d81fab2..f0b2fcb 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java
@@ -18,65 +18,38 @@
package io.jpalite.impl;
import io.jpalite.ConverterClass;
-import io.jpalite.TradeSwitchConvert;
+import io.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
-import jakarta.persistence.PersistenceException;
-import lombok.Data;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@Data
+@Slf4j
+@Getter
public class ConverterClassImpl implements ConverterClass
{
- private static final Logger LOG = LoggerFactory.getLogger(ConverterClassImpl.class);
- private Class> convertClass;
- private boolean autoApply;
- private TradeSwitchConvert, ?> converter;
- private Class> attributeType;
- private Class> dbType;
+ private final boolean autoApply;
+ private final FieldConvertType, ?> converter;
+ private final Class> attributeType;
+
- public ConverterClassImpl(Class> convertClass)
+ public ConverterClassImpl(FieldConvertType, ?> converter)
{
- this.convertClass = convertClass;
- Converter converter = this.convertClass.getAnnotation(Converter.class);
- if (converter == null) {
- LOG.warn("Missing @Converter annotation on {}", this.convertClass.getSimpleName());
+ this.converter = converter;
+
+ Converter converterAnnotation = this.converter.getClass().getAnnotation(Converter.class);
+ if (converterAnnotation == null) {
autoApply = false;
}//if
else {
- autoApply = converter.autoApply();
+ autoApply = converterAnnotation.autoApply();
}//else
- for (Method method : this.convertClass.getDeclaredMethods()) {
- //Not a SYNTHETIC (generated method)
- if (method.getName().equals("convertToDatabaseColumn") &&
- ((method.getModifiers() & 0x00001000) != 0x00001000) &&
- method.getParameterTypes().length == 1) {
- attributeType = method.getParameterTypes()[0];
- dbType = method.getReturnType();
- break;
- }//if
- }//for
- if (attributeType == null) {
- LOG.warn("Error detecting the attribute type in {}", this.convertClass.getSimpleName());
- attributeType = Object.class;
- dbType = Object.class;
- }//if
-
- try {
- this.converter = (TradeSwitchConvert, ?>) this.convertClass.getConstructor().newInstance();
- }//try
- catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException ex) {
- throw new PersistenceException(this.convertClass.getSimpleName() + " failed to instantiate", ex);
- }//catch
+ attributeType = converter.getAttributeType();
}//ConverterClass
@Override
public String getName()
{
- return convertClass.getCanonicalName();
+ return converter.getClass().getCanonicalName();
}
}//ConverterClass
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java b/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java
index 6d8dfea..bbcd093 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java
@@ -24,7 +24,6 @@
import jakarta.persistence.spi.ClassTransformer;
import jakarta.persistence.spi.PersistenceUnitTransactionType;
import lombok.Setter;
-import org.infinispan.commons.configuration.BasicConfiguration;
import javax.sql.DataSource;
import java.net.URL;
@@ -40,9 +39,11 @@ public class CustomPersistenceUnit implements JPALitePersistenceUnit
private Boolean multiTenantMode = false;
private String dataSourceName;
- private String cacheName;
+ private String cacheRegionPrefix;
+ private String cacheClient;
private String cacheProvider;
- private BasicConfiguration cacheConfig;
+ private String cacheConfig;
+ private CacheFormat cacheFormat;
private String providerClass = JPALitePersistenceProviderImpl.class.getName();
private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
private ValidationMode validationMode = ValidationMode.NONE;
@@ -61,9 +62,9 @@ public String getDataSourceName()
}
@Override
- public String getCacheName()
+ public String getCacheRegionPrefix()
{
- return cacheName;
+ return cacheRegionPrefix;
}
@Override
@@ -73,11 +74,23 @@ public String getCacheProvider()
}
@Override
- public BasicConfiguration getCacheConfig()
+ public String getCacheClient()
+ {
+ return cacheClient;
+ }
+
+ @Override
+ public String getCacheConfig()
{
return cacheConfig;
}
+ @Override
+ public CacheFormat getCacheFormat()
+ {
+ return cacheFormat;
+ }
+
@Override
public Boolean getMultiTenantMode()
{
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java
index e89c451..864cd16 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java
@@ -18,6 +18,9 @@
package io.jpalite.impl;
import io.jpalite.*;
+import io.jpalite.impl.fieldtypes.EnumFieldType;
+import io.jpalite.impl.fieldtypes.ObjectFieldType;
+import io.jpalite.impl.fieldtypes.OrdinalEnumFieldType;
import jakarta.persistence.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -45,87 +48,58 @@ public class EntityFieldImpl implements EntityField
private static final boolean NATIVE_IMAGE = ImageInfo.inImageCode();
/**
* The entity class
- *
- * @return the entity class
*/
- private Class> enityClass;
+ private final Class> enityClass;
/**
* Identifier of the entity
- *
- * @param name changes the name of the entity
- * @return name of the client
*/
private final String name;
/**
* A unique field number assigned to the field.
- *
- * @return the field number
*/
private final int fieldNr;
/**
* The java class of the field
- *
- * @return the java class
*/
private Class> type;
/**
* The type of field as per {@link FieldType}
- *
- * @return The field type
*/
private FieldType fieldType;
/**
* The SQL column linked to the field
- *
- * @return The SQL Column name
*/
private String column;
/**
* The mapping type specified by the field. See {@link MappingType}.
- *
- * @return The {@link MappingType}
*/
private MappingType mappingType;
/**
* True if the field is to be unique in the table
- *
- * @return the unique setting
*/
private boolean unique;
/**
* True of the field can be null
- *
- * @return the nullable setting
*/
private boolean nullable;
/**
* True if the field is insertable
- *
- * @return the insertable setting
*/
private boolean insertable;
/**
* True if the field is updatable.
- *
- * @return the updatable setting
*/
private boolean updatable;
/**
* True if the field is an ID Field
- *
- * @return the idField setting
*/
private boolean idField;
/**
* True if the field is a Version Field
- *
- * @return the version field setting
*/
private boolean versionField;
/**
* The getter for the field
- *
- * @return the setter method for the field
*/
private MethodHandle getter;
/**
@@ -142,39 +116,29 @@ public class EntityFieldImpl implements EntityField
private Method setterMethod;
/**
* The {@link CascadeType} assigned to the field.
- *
- * @return the {@link CascadeType} setting
*/
private Set cascade;
/**
* The {@link FetchType} assigned to the field.
- *
- * @return the {@link FetchType} setting
*/
private FetchType fetchType;
/**
* Only applicable to non-Basic fields and indicates that the field is linked the field specified in mappedBy in the
* entity represented by the field.
- *
- * @return the mappedBy setting
*/
private String mappedBy;
/**
* The columnDefinition value defined in the JoinColumn annotation linked to the field
- *
- * @return the columnDefinition setting
*/
private String columnDefinition;
/**
* The table value defined in the JoinColumn annotation linked to the field
- *
- * @return the table setting
*/
private String table;
/**
* The converter class used to convert the field to a SQL type
*/
- private TradeSwitchConvert converterClass;
+ private FieldConvertType, ?> converter;
/**
* Create a new entity field definition
@@ -206,8 +170,11 @@ public EntityFieldImpl(Class> enitityClass, Field field, int fieldNr)
idField = false;
versionField = false;
- checkForConvert(field);
+ //The order below is important
processMappingType(field);
+
+ findConverter(field);
+
findGetterSetter(field);
}//EntityField
@@ -311,7 +278,7 @@ private void prosesBasicField(Field field)
}//if
insertable = false;
updatable = false;
- }//ifated
+ }//if
nullable = false;
}//if
@@ -398,7 +365,7 @@ private boolean checkManyToManyField(Field field)
return false;
}//checkManyToManyField
- private void checkForConvert(Field field)
+ private void findConverter(Field field)
{
Convert customType = field.getAnnotation(Convert.class);
if (customType != null) {
@@ -406,7 +373,7 @@ private void checkForConvert(Field field)
//Check if the converter class was explicitly overridden
if (customType.converter() != null) {
fieldType = FieldType.TYPE_CUSTOMTYPE;
- converterClass = (TradeSwitchConvert) customType.converter().getConstructor().newInstance();
+ converter = (FieldConvertType, ?>) customType.converter().getConstructor().newInstance();
return;
}//if
}//try
@@ -424,8 +391,31 @@ private void checkForConvert(Field field)
ConverterClass converterClass = EntityMetaDataManager.getConvertClass(type);
if (converterClass != null) {
fieldType = FieldType.TYPE_CUSTOMTYPE;
- this.converterClass = converterClass.getConverter();
+ converter = converterClass.getConverter();
}//if
+ else {
+ switch (fieldType) {
+ case TYPE_ENUM -> {
+ converter = new EnumFieldType((Class>) type);
+ fieldType = FieldType.TYPE_CUSTOMTYPE;
+ }
+ case TYPE_ORDINAL_ENUM -> {
+ converter = new OrdinalEnumFieldType((Class>) type);
+ fieldType = FieldType.TYPE_CUSTOMTYPE;
+ }
+ case TYPE_OBJECT -> {
+ converter = new ObjectFieldType();
+ fieldType = FieldType.TYPE_CUSTOMTYPE;
+ }
+ case TYPE_ENTITY -> {
+ //ignore
+ }
+ default -> LOG.warn("No converter class found for field {} type {}", name, fieldType);
+
+ }
+ }//else
+
+
}//checkForConvert
@Override
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java
index 87f7959..87aa73a 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java
@@ -96,10 +96,10 @@ public EntityMetaDataImpl(Class entityClass)
IdClass idClass = entityClass.getAnnotation(IdClass.class);
if (idClass != null) {
-
if (!EntityMetaDataManager.isRegistered(idClass.value())) {
+ //TODO: Added support for @EmbeddedId and fix implementation of @IdClass
primaryKey = new EntityMetaDataImpl(idClass.value());
- ((EntityMetaDataImpl) primaryKey).entityType = EntityType.ENTITY_IDCLASS;
+ ((EntityMetaDataImpl>) primaryKey).entityType = EntityType.ENTITY_IDCLASS;
EntityMetaDataManager.register(primaryKey);
}//if
@@ -109,7 +109,7 @@ public EntityMetaDataImpl(Class entityClass)
}//if
versionField = null;
- StringBuilder stringBuilder = new StringBuilder("");
+ StringBuilder stringBuilder = new StringBuilder();
for (Field vField : entityClass.getDeclaredFields()) {
if (!Modifier.isStatic(vField.getModifiers()) &&
!Modifier.isFinal(vField.getModifiers()) &&
@@ -196,64 +196,6 @@ public String toString()
return "[" + entityName + "] Metadata -> Type:" + entityType + ", Entity Class:" + entityClass.getName() + ", Primary Key Class:" + primKeyClass;
}//toString
- @Override
- public String getProtoFile()
- {
- StringBuilder protoFile = new StringBuilder("// File name: ")
- .append(getName()).append(".proto\n")
- .append("// Generated from : ")
- .append(getClass().getName())
- .append("\n")
- .append("syntax = \"proto2\";\n")
- .append("package org.tradeswitch;\n");
-
- Set protoLibs = new HashSet<>();
- entityFields.values().stream()
- .filter(f -> f.getFieldType() == FieldType.TYPE_CUSTOMTYPE &&
- f.getConverterClass().prototypeLib() != null &&
- !f.getConverterClass().prototypeLib().isBlank())
- .forEach(f -> protoLibs.add(f.getConverterClass().prototypeLib()));
-
- protoLibs.forEach(lib -> protoFile.append("import \"").append(lib).append("\";\n"));
-
- protoFile.append("message ")
- .append(entityClass.getSimpleName())
- .append("{\n");
-
- for (EntityField field : entityFields.values()) {
- protoFile.append("\t")
- .append(field.isNullable() ? "optional " : "required ");
-
- switch (field.getFieldType()) {
- case TYPE_ENTITY -> {
- EntityMetaData> vMetaData = EntityMetaDataManager.getMetaData(field.getType());
- if (vMetaData.getEntityType() == EntityType.ENTITY_EMBEDDABLE) {
- if (!vMetaData.isCacheable()) {
- throw new EntityMapException("Entity " + getName() + " is marked as cacheable but embeddable " + vMetaData.getName() + " to not marked as cacheable");
- }//if
- protoFile.append(field.getType().getSimpleName());
- }//if
- else {
- protoFile.append(vMetaData.getIdField().getFieldType().getProtoType());
- }//else
- }//case
- case TYPE_ENUM -> protoFile.append("string");
- case TYPE_ORDINAL_ENUM -> protoFile.append("uint32");
- case TYPE_CUSTOMTYPE -> protoFile.append(field.getConverterClass().getFieldType());
- default -> protoFile.append(field.getFieldType().getProtoType());
- }//switch
-
- protoFile.append(" ")
- .append(field.getName())
- .append(" = ")
- .append(field.getFieldNr())
- .append(";\n");
- }//for
- protoFile.append("}\n");
-
- return protoFile.toString();
- }//getProtoFile
-
@Override
public EntityType getEntityType()
{
@@ -411,7 +353,7 @@ public EntityField getVersionField()
@Override
@Nullable
- public EntityMetaData> getIPrimaryKeyMetaData()
+ public EntityMetaData> getPrimaryKeyMetaData()
{
return primaryKey;
}//getIPrimaryKeyMetaData
@@ -430,6 +372,7 @@ public List getIdFields()
}//getIdFields
@Override
+ @Deprecated
public String getColumns()
{
return columns;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java
index 80491d2..32d9a93 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java
@@ -17,18 +17,20 @@
package io.jpalite.impl;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.jpalite.PersistenceContext;
import io.jpalite.*;
import io.jpalite.impl.queries.JPALiteQueryImpl;
import io.jpalite.impl.queries.QueryImpl;
-import io.jpalite.impl.serializers.JPAEntityMarshaller;
import io.jpalite.queries.QueryLanguage;
import jakarta.annotation.Nonnull;
import jakarta.persistence.*;
import jakarta.persistence.spi.LoadState;
-import org.infinispan.protostream.FileDescriptorSource;
-import org.infinispan.protostream.GeneratedSchema;
-import org.infinispan.protostream.SerializationContext;
import org.slf4j.LoggerFactory;
import java.io.*;
@@ -43,7 +45,7 @@
import static jakarta.persistence.LockModeType.*;
/**
- * This class will be made the super class of all entity classes defined and managed by the TradeSwitch Entity Manager.
+ * This class will be made the super class of all entity classes defined and managed by the Entity Manager.
*
* The JPA Maven plugin class will modify the bytecode of all entity classes change the super class to piont to
* this class.
@@ -54,7 +56,7 @@
* using here ;-) )
*/
@SuppressWarnings({"java:S100", "java:S116"})
-public class JPAEntityImpl implements JPAEntity, GeneratedSchema
+public class JPAEntityImpl implements JPAEntity
{
public static final String SELECT_CLAUSE = "select ";
public static final String FROM_CLAUSE = " from ";
@@ -428,7 +430,6 @@ public void _makeReference(Object primaryKey)
_setPrimaryKey(primaryKey);
_markLazyLoaded();
_clearModified();
- _setPendingAction(PersistenceAction.NONE);
}//_makeReference
@Override
@@ -632,7 +633,7 @@ public X _getField(@Nonnull String fieldName)
}//if
return (X) switch (entityField.getFieldType()) {
- case TYPE_CUSTOMTYPE -> entityField.getConverterClass().convertToDatabaseColumn(value);
+ case TYPE_CUSTOMTYPE -> entityField.getConverter().convertToDatabaseColumn(value);
case TYPE_ENUM -> ((Enum>) value).name();
case TYPE_ORDINAL_ENUM -> ((Enum>) value).ordinal();
@@ -693,7 +694,7 @@ public Object _getPrimaryKey()
}//if
if ($$metadata.getIdFields().size() > 1) {
- EntityMetaData> primaryKey = $$metadata.getIPrimaryKeyMetaData();
+ EntityMetaData> primaryKey = $$metadata.getPrimaryKeyMetaData();
Object primKey = null;
if (primaryKey != null) {
primKey = primaryKey.getNewEntity();
@@ -722,7 +723,7 @@ public void _setPrimaryKey(Object primaryKey)
if ($$metadata.getIdFields().size() > 1) {
- EntityMetaData> primaryKeyMetaData = $$metadata.getIPrimaryKeyMetaData();
+ EntityMetaData> primaryKeyMetaData = $$metadata.getPrimaryKeyMetaData();
if (primaryKeyMetaData == null) {
throw new IllegalStateException("Missing IDClass for Entity [" + $$metadata.getName() + "]");
}//if
@@ -776,7 +777,7 @@ protected LocalDateTime _JPAReadLocalDateTime(ResultSet resultSet, int column) t
protected Object _JPAReadCustomType(ResultSet resultSet, EntityField field, int column) throws SQLException
{
- return field.getConverterClass().convertToEntityAttribute(resultSet, column);
+ return field.getConverter().convertToEntityAttribute(resultSet, column);
}//_JPAReadCustomType
private Object _JPAReadENUM(EntityField field, ResultSet row, int column) throws SQLException
@@ -799,33 +800,37 @@ private Object _JPAReadOrdinalENUM(EntityField field, ResultSet row, int column)
public JPAEntity _JPAReadEntity(EntityField field, ResultSet resultSet, String colPrefix, int col) throws SQLException
{
- EntityMetaData> fieldMetaData = EntityMetaDataManager.getMetaData(field.getType());
-
- //Read the primary key of the field and then check if the entity is not already managed
- JPAEntity entity = (JPAEntity) fieldMetaData.getNewEntity();
- entity._setPersistenceContext(_getPersistenceContext());
- ((JPAEntityImpl) entity)._JPAReadField(resultSet, fieldMetaData.getIdField(), colPrefix, col);
-
JPAEntity managedEntity = null;
- if (entity._getPrimaryKey() != null) {
- if (_getPersistenceContext() != null) {
- managedEntity = (JPAEntity) _getPersistenceContext().l1Cache().find(fieldMetaData.getEntityClass(), entity._getPrimaryKey(), true);
- }//if
- if (managedEntity == null) {
- if (field.getFetchType() == FetchType.LAZY && (colPrefix == null || colPrefix.equals(resultSet.getMetaData().getColumnName(col)))) {
- entity._markLazyLoaded();
- }//if
- else {
- entity._mapResultSet(colPrefix, resultSet);
- }//else
+ //Read the field so that wasNull() can be used
+ resultSet.getObject(col);
+ if (!field.isNullable() || !resultSet.wasNull()) {
+ EntityMetaData> fieldMetaData = EntityMetaDataManager.getMetaData(field.getType());
+ //Read the primary key of the field and then check if the entity is not already managed
+ JPAEntity entity = (JPAEntity) fieldMetaData.getNewEntity();
+ entity._setPersistenceContext(_getPersistenceContext());
+ ((JPAEntityImpl) entity)._JPAReadField(resultSet, fieldMetaData.getIdField(), colPrefix, col);
+ if (entity._getPrimaryKey() != null) {
if (_getPersistenceContext() != null) {
- _getPersistenceContext().l1Cache().manage(entity);
+ managedEntity = (JPAEntity) _getPersistenceContext().l1Cache().find(fieldMetaData.getEntityClass(), entity._getPrimaryKey(), true);
+ }//if
+
+ if (managedEntity == null) {
+ if (field.getFetchType() == FetchType.LAZY && (colPrefix == null || colPrefix.equals(resultSet.getMetaData().getColumnName(col)))) {
+ entity._markLazyLoaded();
+ }//if
+ else {
+ entity._mapResultSet(colPrefix, resultSet);
+ }//else
+
+ if (_getPersistenceContext() != null) {
+ _getPersistenceContext().l1Cache().manage(entity);
+ }//if
+ return entity;
}//if
- return entity;
}//if
- }//if
+ }
return managedEntity;
}//_JPAReadEntity
@@ -840,18 +845,20 @@ public void _JPAReadField(ResultSet row, EntityField field, String colPrefix, in
case TYPE_INTEGER -> field.invokeSetter(this, _JPAReadInteger(row, columnNr));
case TYPE_LONGLONG -> field.invokeSetter(this, _JPAReadLong(row, columnNr));
case TYPE_DOUBLEDOUBLE -> field.invokeSetter(this, _JPAReadDouble(row, columnNr));
+
case TYPE_BOOL -> field.invokeSetter(this, row.getBoolean(columnNr));
case TYPE_INT -> field.invokeSetter(this, row.getInt(columnNr));
case TYPE_LONG -> field.invokeSetter(this, row.getLong(columnNr));
case TYPE_DOUBLE -> field.invokeSetter(this, row.getDouble(columnNr));
+
case TYPE_STRING -> field.invokeSetter(this, _JPAReadString(row, columnNr));
case TYPE_TIMESTAMP -> field.invokeSetter(this, row.getTimestamp(columnNr));
case TYPE_LOCALTIME -> field.invokeSetter(this, _JPAReadLocalDateTime(row, columnNr));
- case TYPE_CUSTOMTYPE -> field.invokeSetter(this, _JPAReadCustomType(row, field, columnNr));
case TYPE_ENUM -> field.invokeSetter(this, _JPAReadENUM(field, row, columnNr));
case TYPE_ORDINAL_ENUM -> field.invokeSetter(this, _JPAReadOrdinalENUM(field, row, columnNr));
case TYPE_BYTES -> field.invokeSetter(this, row.getBytes(columnNr));
case TYPE_OBJECT -> field.invokeSetter(this, row.getObject(columnNr));
+ case TYPE_CUSTOMTYPE -> field.invokeSetter(this, _JPAReadCustomType(row, field, columnNr));
case TYPE_ENTITY -> {
if (field.getMappingType() == MappingType.ONE_TO_ONE || field.getMappingType() == MappingType.MANY_TO_ONE || field.getMappingType() == MappingType.EMBEDDED) {
field.invokeSetter(this, _JPAReadEntity(field, row, colPrefix, columnNr));
@@ -905,27 +912,193 @@ public void _mapResultSet(String colPrefix, ResultSet resultSet)
catch (Exception ex) {
throw new EntityMapException("Error extracting the ResultSet Metadata", ex);
}//catch
- }//_maresultSet
+ }//_mapResultSet
- private void writeObjects(ObjectOutput outStream) throws IOException
+ private void writeFields(DataOutputStream out) throws IOException
{
Collection fieldList = $$metadata.getEntityFields();
- outStream.writeShort(fieldList.size());
for (EntityField field : fieldList) {
Object value = field.invokeGetter(this);
- outStream.writeUTF(field.getName());
- outStream.writeObject(value);
+ if (value != null) {
+ out.writeShort(field.getFieldNr());
+ if (field.getFieldType() == FieldType.TYPE_ENTITY) {
+ EntityMetaData> metaData = ((JPAEntity) value)._getMetaData();
+ if (metaData.getEntityType() == EntityType.ENTITY_EMBEDDABLE) {
+ ((JPAEntityImpl) value).writeFields(out);
+ }//if
+ else {
+ Object primaryKey = ((JPAEntity) value)._getPrimaryKey();
+ //If the entity has multiple keys, then that primary key will be stored in an embedded object
+ if (primaryKey instanceof JPAEntity primaryKeyEntity) {
+ ((JPAEntityImpl) primaryKeyEntity).writeFields(out);
+ }//if
+ else {
+ EntityField keyField = metaData.getIdField();
+ out.writeShort(keyField.getFieldNr());
+ keyField.getConverter().writeField(primaryKey, out);
+ out.writeShort(0); //End of entity
+ }//else
+ }//else
+ }
+ else {
+ field.getConverter().writeField(value, out);
+ }//else
+ }//if
+ }//for
+ out.writeShort(0); //End of stream indicator
+ }//writeFields
+
+
+ private void readFields(DataInputStream in) throws IOException
+ {
+ int fieldNr = in.readShort();
+ while (fieldNr > 0) {
+ EntityField field = $$metadata.getEntityFieldByNr(fieldNr);
+
+ if (field.getFieldType() == FieldType.TYPE_ENTITY) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
+
+ JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
+ entity.readFields(in);
+ if (metaData.getEntityType() == EntityType.ENTITY_DATABASE) {
+ entity._markLazyLoaded();
+ }
+ field.invokeSetter(this, entity);
+ }
+ else {
+ field.invokeSetter(this, field.getConverter().readField(in));
+ }
+
+ fieldNr = in.readShort();
+ }//while
+
+ _clearModified();
+ }//readFields
+
+
+ @SuppressWarnings("unchecked")
+ private void generateJson(JsonGenerator jsonGenerator) throws IOException
+ {
+ jsonGenerator.writeStartObject();
+
+ Collection fieldList = $$metadata.getEntityFields();
+ for (EntityField field : fieldList) {
+ Object value = field.invokeGetter(this);
+ if (!field.isNullable() || value != null) {
+ if (field.getFieldType() == FieldType.TYPE_ENTITY) {
+ if (field.getMappingType() == MappingType.ONE_TO_ONE || field.getMappingType() == MappingType.MANY_TO_ONE || field.getMappingType() == MappingType.EMBEDDED) {
+ jsonGenerator.writeFieldName(field.getName());
+ if (value == null) {
+ jsonGenerator.writeNull();
+ }
+ else {
+
+ EntityMetaData> metaData = ((JPAEntity) value)._getMetaData();
+ if (metaData.getEntityType() == EntityType.ENTITY_EMBEDDABLE) {
+ ((JPAEntityImpl) value).generateJson(jsonGenerator);
+ }//if
+ else {
+ Object primaryKey = ((JPAEntity) value)._getPrimaryKey();
+ //If the entity has multiple keys, then that primary key will be stored in an embedded object
+ if (primaryKey instanceof JPAEntity primaryKeyEntity) {
+ ((JPAEntityImpl) primaryKeyEntity).generateJson(jsonGenerator);
+ }//if
+ else {
+ EntityField keyField = metaData.getIdField();
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeFieldName(keyField.getName());
+ keyField.getConverter().toJson(jsonGenerator, primaryKey);
+ jsonGenerator.writeEndObject();
+ }//else
+ }//else
+ }//else
+ }//if
+ }//if
+ else {
+ jsonGenerator.writeFieldName(field.getName());
+ if (value == null) {
+ jsonGenerator.writeNull();
+ }
+ else {
+ field.getConverter().toJson(jsonGenerator, value);
+ }
+ }//else
+ }//if
}//for
- }//writeObjects
+ jsonGenerator.writeEndObject();
+ }//_toJson
+
+ @Override
+ public String _toJson()
+ {
+ try {
+ ObjectMapper mapper = new ObjectMapper(JsonFactory.builder().build());
+ mapper.registerModule(new JavaTimeModule());
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ JsonGenerator jsonGenerator = mapper
+ .writerWithDefaultPrettyPrinter()
+ .createGenerator(outputStream);
+
+ generateJson(jsonGenerator);
+ jsonGenerator.close();
+ return outputStream.toString();
+ }
+ catch (IOException ex) {
+ throw new CachingException("Error generating json structure for entity [" + this._getMetaData().getName() + "]", ex);
+ }
+ }
+
+ private void _fromJson(JsonNode jsonNode)
+ {
+ Iterator> iter = jsonNode.fields();
+
+ while (iter.hasNext()) {
+ Map.Entry node = iter.next();
+
+ EntityField field = $$metadata.getEntityField(node.getKey());
+ if (node.getValue().isNull()) {
+ field.invokeSetter(this, null);
+ }
+ else {
+ if (field.getFieldType() == FieldType.TYPE_ENTITY) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
+
+ JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
+ entity._fromJson(node.getValue());
+
+ if (metaData.getEntityType() == EntityType.ENTITY_DATABASE) {
+ entity._markLazyLoaded();
+ }
+ field.invokeSetter(this, entity);
+ }
+ else {
+ field.invokeSetter(this, field.getConverter().fromJson(node.getValue()));
+ }
+ }//else
+ }
+ _clearModified();
+ }
+
+ public void _fromJson(String jsonStr)
+ {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode nodes = mapper.readTree(jsonStr);
+ _fromJson(nodes);
+ }
+ catch (JsonProcessingException ex) {
+ throw new PersistenceException("Error parsing json text string", ex);
+ }
+ }
@Override
public byte[] _serialize()
{
try {
ByteArrayOutputStream recvOut = new ByteArrayOutputStream();
- ObjectOutputStream stream = new ObjectOutputStream(recvOut);
- writeObjects(stream);
- stream.flush();
+ DataOutputStream out = new DataOutputStream(recvOut);
+ writeFields(out);
+ out.flush();
return recvOut.toByteArray();
}//try
@@ -934,29 +1107,15 @@ public byte[] _serialize()
}//catch
}//_serialise
- @SuppressWarnings("java:S112") // Throwable is correct
- private void readObjects(ObjectInput inputStream) throws Throwable
- {
- int nrItems = inputStream.readShort();
- while (nrItems > 0) {
- nrItems--;
- String fieldName = inputStream.readUTF();
- EntityField field = $$metadata.getEntityField(fieldName);
- field.invokeSetter(this, inputStream.readObject());
- }//while
- }//readObjects
-
@Override
public void _deserialize(byte[] bytes)
{
try {
ByteArrayInputStream recvOut = new ByteArrayInputStream(bytes);
- ObjectInputStream stream = new ObjectInputStream(recvOut);
- readObjects(stream);
-
- _clearModified();
+ DataInputStream in = new DataInputStream(recvOut);
+ readFields(in);
}//try
- catch (Throwable ex) {
+ catch (IOException ex) {
throw new PersistenceException("Error de-serialising the entity", ex);
}//catch
}//_deserialize
@@ -967,41 +1126,4 @@ public boolean _entityEquals(JPAEntity entity)
return (entity._getMetaData().getEntityClass().equals(_getMetaData().getEntityClass()) &&
entity._getPrimaryKey().equals(_getPrimaryKey()));
}
-
- @Override
- public String getProtoFileName()
- {
- return getClass().getSimpleName() + ".proto";
- }
-
- @Override
- public String getProtoFile() throws UncheckedIOException
- {
- return _getMetaData().getProtoFile();
- }
-
- @Override
- public void registerSchema(SerializationContext serCtx)
- {
- /*
- * Register the marshals and schemas for converter classes if not already registered
- */
- _getMetaData().getEntityFields().stream()
- .filter(f -> f.getFieldType() == FieldType.TYPE_CUSTOMTYPE &&
- f.getConverterClass().prototypeLib() != null &&
- !f.getConverterClass().prototypeLib().isBlank() &&
- !serCtx.canMarshall(f.getConverterClass().prototypeLib()))
- .forEach(f -> {
- f.getConverterClass().getSchema().registerSchema(serCtx);
- f.getConverterClass().getSchema().registerMarshallers(serCtx);
- });
-
- serCtx.registerProtoFiles(FileDescriptorSource.fromString(getProtoFileName(), getProtoFile()));
- }
-
- @Override
- public void registerMarshallers(SerializationContext serCtx)
- {
- serCtx.registerMarshaller(new JPAEntityMarshaller<>(getClass()));
- }
}//JPAEntityImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/JPALiteEntityManagerImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/JPALiteEntityManagerImpl.java
index 4ba49ef..fa28958 100755
--- a/jpalite-core/src/main/java/io/jpalite/impl/JPALiteEntityManagerImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/JPALiteEntityManagerImpl.java
@@ -79,7 +79,7 @@ public JPALiteEntityManagerImpl(PersistenceContext persistenceContext, EntityMan
else {
opened = null;
}//else
- }//TradeSwitchEntityManagerImpl
+ }//JPALiteEntityManagerImpl
//
@Override
@@ -793,6 +793,6 @@ public T unwrap(Class cls)
throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
}
-}//TradeSwitchEntityManagerImpl
+}//JPALiteEntityManagerImpl
//--------------------------------------------------------------------[ End ]---
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityCacheImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityCacheImpl.java
new file mode 100644
index 0000000..96ec234
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityCacheImpl.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.jpalite.impl.caching;
+
+import io.jpalite.*;
+import io.jpalite.impl.CacheFormat;
+import io.jpalite.impl.JPAConfig;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import jakarta.annotation.Nonnull;
+import jakarta.persistence.SharedCacheMode;
+import jakarta.transaction.SystemException;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.InvocationTargetException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("java:S3740")//Have to work without generics
+@Slf4j
+public class EntityCacheImpl implements EntityCache
+{
+ private static final int ACTION_REPLACE = 1;
+ private static final int ACTION_REMOVE = 2;
+
+ private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(EntityCacheImpl.class.getName());
+ public static final String NO_TRANSACTION_ACTIVE = "No Transaction active";
+ public static final String ENTITY_ATTR = "entity";
+ public static final String ENTITY_KEY = "key";
+ private static final boolean CACHING_ENABLED = JPAConfig.getValue("jpalite.persistence.l2cache", true);
+
+ private final CacheFormat cacheFormat;
+ private final List batchQueue = new ArrayList<>();
+ private boolean inTransaction;
+ private JPACache jpaCache = null;
+
+ private record CacheEntry(int action, JPAEntity entity)
+ {
+ }
+
+ @SuppressWarnings("unchecked")
+ public EntityCacheImpl(JPALitePersistenceUnit persistenceUnit)
+ {
+ cacheFormat = persistenceUnit.getCacheFormat();
+ inTransaction = false;
+ if (CACHING_ENABLED && !persistenceUnit.getSharedCacheMode().equals(SharedCacheMode.NONE)) {
+ try {
+ Class jpaCacheClass = (Class) Thread.currentThread().getContextClassLoader().loadClass(persistenceUnit.getCacheProvider());
+ jpaCache = jpaCacheClass.getConstructor(String.class, String.class, String.class).newInstance(persistenceUnit.getCacheClient(), persistenceUnit.getCacheConfig(), persistenceUnit.getCacheRegionPrefix());
+ }
+ catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
+ IllegalAccessException | NoSuchMethodException ex) {
+ throw new CachingException("Error loading cache provider class [" + persistenceUnit.getCacheProvider() + "]", ex);
+ }
+ }//if
+ }//EntityCacheImpl
+
+ public T find(Class entityType, Object primaryKey)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::find").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ long start = System.currentTimeMillis();
+ if (jpaCache != null) {
+ EntityMetaData metaData = EntityMetaDataManager.getMetaData(entityType);
+ if (metaData.isCacheable()) {
+ String key = primaryKey.toString();
+ span.setAttribute(ENTITY_KEY, key);
+ span.setAttribute(ENTITY_ATTR, entityType.getName());
+ if (cacheFormat == CacheFormat.BINARY) {
+ byte[] bytes = jpaCache.find(metaData.getName(), key);
+ if (bytes != null) {
+ LOG.debug("Searching L2 cache (Binary) for key [{}] - Hit in {}ms", key, System.currentTimeMillis() - start);
+ T entity = metaData.getNewEntity();
+ ((JPAEntity) entity)._deserialize(bytes);
+ return entity;
+ }//if
+ }//if
+ else {
+ String jsonStr = jpaCache.find(metaData.getName(), key);
+ if (jsonStr != null) {
+ LOG.debug("Searching L2 cache (JSON) for key [{}] - Hit in {}ms", key, System.currentTimeMillis() - start);
+ T entity = metaData.getNewEntity();
+ ((JPAEntity) entity)._fromJson(jsonStr);
+ return entity;
+ }//if
+ }
+ LOG.debug("Searching L2 cache for key [{}] - Missed in {}ms", key, System.currentTimeMillis() - start);
+ }//if
+ else {
+ LOG.debug("Entity {} is not cacheable", metaData.getName());
+ }//else
+ }//if
+ }//try
+ finally {
+ span.end();
+ }//finally
+
+ return null;
+ }//find
+
+ @Override
+ public void replace(JPAEntity entity)
+ {
+ if (jpaCache != null && entity._getMetaData().isCacheable() && inTransaction) {
+ batchQueue.add(new CacheEntry(ACTION_REPLACE, entity));
+ }//if
+ }//replace
+
+ @Override
+ public void add(JPAEntity entity)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::add").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (jpaCache != null && entity._getMetaData().isCacheable()) {
+ long start = System.currentTimeMillis();
+ String key = entity._getPrimaryKey().toString();
+ span.setAttribute(ENTITY_KEY, key);
+ span.setAttribute(ENTITY_ATTR, entity._getMetaData().getName());
+
+ jpaCache.add(entity._getMetaData().getName(), key, (cacheFormat.equals(CacheFormat.BINARY) ? entity._serialize() : entity._toJson()), entity._getMetaData().getIdleTime(), entity._getMetaData().getCacheTimeUnit());
+ LOG.debug("Adding/Replacing Entity with key [{}] in L2 cache in {}ms", key, System.currentTimeMillis() - start);
+ }//if
+ }//try
+ finally {
+ span.end();
+ }
+ }//add
+
+ @Override
+ @Nonnull
+ public Instant getLastModified(Class entityType)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::getLastModified").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ EntityMetaData metaData = EntityMetaDataManager.getMetaData(entityType);
+ if (jpaCache != null && metaData.isCacheable()) {
+ return jpaCache.getLastModified(metaData.getName());
+ }//if
+
+ return Instant.now();
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//getLastModified
+
+ @Override
+ public boolean contains(Class entityType, Object primaryKey)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::contains").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityType);
+ if (jpaCache != null && metaData.isCacheable()) {
+ return jpaCache.containsKey(metaData.getName(), primaryKey.toString());
+ }//if
+ return false;
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//contains
+
+ @Override
+ public void evict(Class entityType, Object primaryKey)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::evict using Primary key").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityType);
+ if (jpaCache != null && metaData.isCacheable()) {
+ jpaCache.evict(metaData.getName(), primaryKey.toString());
+ }//if
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//evict
+
+ @Override
+ public void evict(Class entityType)
+ {
+ Span span = TRACER.spanBuilder("EntityCache::evict by type").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityType);
+ if (jpaCache != null && metaData.isCacheable()) {
+ jpaCache.evictAll(metaData.getName());
+ }//if
+ }//try
+ finally {
+ span.end();
+ }
+ }//evict
+
+ @Override
+ public void evictAll()
+ {
+ Span span = TRACER.spanBuilder("EntityCache::evictAll").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (jpaCache != null) {
+ jpaCache.evictAllRegions();
+ }//if
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//evictAll
+
+ @Override
+ public void begin() throws SystemException
+ {
+ Span span = TRACER.spanBuilder("EntityCache::begin").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (CACHING_ENABLED) {
+ if (inTransaction) {
+ throw new SystemException("Transaction already in progress");
+ }//if
+ inTransaction = true;
+ }//if
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//begin
+
+ @Override
+ public void commit() throws SystemException
+ {
+ Span span = TRACER.spanBuilder("EntityCache::commit").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (jpaCache != null) {
+ if (!inTransaction) {
+ throw new SystemException(NO_TRANSACTION_ACTIVE);
+ }//if
+
+ inTransaction = false;
+ batchQueue.forEach(e -> {
+ if (e.action == ACTION_REMOVE) {
+ jpaCache.evict(e.entity()._getMetaData().getName(),
+ e.entity._getPrimaryKey().toString());
+ }//if
+ else {
+ jpaCache.replace(e.entity()._getMetaData().getName(),
+ e.entity._getPrimaryKey().toString(),
+ (cacheFormat.equals(CacheFormat.BINARY) ? e.entity()._serialize() : e.entity()._toJson()),
+ e.entity()._getMetaData().getIdleTime(),
+ e.entity()._getMetaData().getCacheTimeUnit());
+ }//if
+ });
+ batchQueue.clear();
+ }//if
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//commit
+
+
+ @Override
+ public void rollback() throws SystemException
+ {
+ if (CACHING_ENABLED) {
+ if (!inTransaction) {
+ throw new SystemException(NO_TRANSACTION_ACTIVE);
+ }//if
+
+ inTransaction = false;
+ batchQueue.clear();
+ }//if
+ }//rollback
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T unwrap(Class cls)
+ {
+ if (cls.isAssignableFrom(this.getClass())) {
+ return (T) this;
+ }
+
+ if (cls.isAssignableFrom(EntityCache.class)) {
+ return (T) this;
+ }
+
+ throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityL2CacheImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityL2CacheImpl.java
similarity index 75%
rename from jpalite-core/src/main/java/io/jpalite/impl/EntityL2CacheImpl.java
rename to jpalite-core/src/main/java/io/jpalite/impl/caching/EntityL2CacheImpl.java
index 0020492..328c0cf 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/EntityL2CacheImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityL2CacheImpl.java
@@ -15,9 +15,10 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package io.jpalite.impl.caching;
import io.jpalite.*;
+import io.jpalite.impl.JPAConfig;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
@@ -29,16 +30,16 @@
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.persistence.SharedCacheMode;
-import jakarta.transaction.*;
+import jakarta.transaction.SystemException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
-import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.commons.api.query.Query;
+import org.infinispan.commons.configuration.StringConfiguration;
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -51,12 +52,12 @@ public class EntityL2CacheImpl implements EntityCache
public static final String ENTITY_ATTR = "entity";
private RemoteCacheManager remoteCacheManager;
private final JPALitePersistenceUnit persistenceUnit;
- private static final boolean CACHING_ENABLED = JPAConfig.getValue("tradeswitch.persistence.l2cache", true);
+ private static final boolean CACHING_ENABLED = JPAConfig.getValue("jpalite.persistence.l2cache", true);
private boolean inTransaction;
private final List batchQueue = new ArrayList<>();
private static final int ACTION_ADD = 0;
- private static final int ACTION_UPDATE = 1;
+ private static final int ACTION_REPLACE = 1;
private static final int ACTION_REMOVE = 2;
@Getter
@@ -106,9 +107,9 @@ private RemoteCache getCache()
}//if
}//if
- RemoteCache cache = remoteCacheManager.getCache(persistenceUnit.getCacheName());
+ RemoteCache cache = remoteCacheManager.getCache(persistenceUnit.getCacheRegionPrefix());
if (cache == null) {
- cache = remoteCacheManager.administration().getOrCreateCache(persistenceUnit.getCacheName(), persistenceUnit.getCacheConfig());
+ cache = remoteCacheManager.administration().getOrCreateCache(persistenceUnit.getCacheRegionPrefix(), new StringConfiguration(persistenceUnit.getCacheConfig()));
}//if
return cache;
}
@@ -165,39 +166,9 @@ public T find(Class entityType, Object primaryKey)
return null;
}//find
- @Override
- @Nonnull
- public List search(Class entityType, String query)
- {
- Span span = TRACER.spanBuilder("EntityL2CacheImpl::search").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- checkEntityType(entityType);
- RemoteCache cache = getCache();
- if (cache != null) {
- String queryText = "from org.tradeswitch." + entityType.getSimpleName() + " " + query;
- try {
- span.setAttribute("query", queryText);
- span.setAttribute(ENTITY_ATTR, entityType.getName());
-
- LOG.debug("Querying L2 cache : {}", queryText);
- Query q = cache.query(queryText);
- List result = q.execute().list();
- LOG.debug("Querying L2 cache - Found {} records", result.size());
- return result;
- }//try
- catch (HotRodClientException ex) {
- LOG.debug("Search error:{}", ex.getMessage(), ex);
- }//catch
- }//if
- return Collections.emptyList();
- }//try
- finally {
- span.end();
- }//finally
- }//search
@Override
- public void update(JPAEntity entity)
+ public void replace(JPAEntity entity)
{
checkEntityInstance(entity);
@@ -205,11 +176,11 @@ public void update(JPAEntity entity)
RemoteCache cache = getCache();
if (cache != null) {
String key = makeCacheKey(entity.getClass(), entity._getPrimaryKey());
- batchQueue.add(new CacheEntry(ACTION_UPDATE, key, entity, -1, TimeUnit.SECONDS, entity._getMetaData().getIdleTime(), entity._getMetaData().getCacheTimeUnit()));
+ batchQueue.add(new CacheEntry(ACTION_REPLACE, key, entity, -1, TimeUnit.SECONDS, entity._getMetaData().getIdleTime(), entity._getMetaData().getCacheTimeUnit()));
batchQueue.add(new CacheEntry(ACTION_ADD, entity.getClass().getName(), System.currentTimeMillis(), -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS));
}//if
}//if
- }//update
+ }//replace
@Override
public void add(JPAEntity entity)
@@ -237,61 +208,33 @@ public void add(JPAEntity entity)
}//add
@Override
- public void remove(JPAEntity entity)
- {
- Span span = TRACER.spanBuilder("EntityL2CacheImpl::remove").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- checkEntityInstance(entity);
-
- if (CACHING_ENABLED) {
- long start = System.currentTimeMillis();
- RemoteCache cache = getCache();
- if (cache != null) {
- String key = makeCacheKey(entity.getClass(), entity._getPrimaryKey());
- span.setAttribute("key", key);
- span.setAttribute(ENTITY_ATTR, entity._getMetaData().getName());
- if (inTransaction) {
- batchQueue.add(new CacheEntry(ACTION_REMOVE, key, entity, -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS));
- batchQueue.add(new CacheEntry(ACTION_ADD, entity.getClass().getName(), System.currentTimeMillis(), -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS));
- }//if
- else {
- cache.remove(key);
- //Write a timestamp for the update
- cache.put(entity.getClass().getName(), System.currentTimeMillis(), -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS);
- LOG.debug("Removed Entity with key [{}] from L2 cache in {}m", key, System.currentTimeMillis() - start);
- }//else
- }//if
- }//if
- }//try
- finally {
- span.end();
- }//finally
- }//remove
-
- public long lastModified(Class entityType)
+ @Nonnull
+ public Instant getLastModified(Class entityType)
{
- Long time = -1L;
- Span span = TRACER.spanBuilder("EntityL2CacheImpl::lastModified").setSpanKind(SpanKind.SERVER).startSpan();
+ Span span = TRACER.spanBuilder("EntityL2CacheImpl::getLastModified").setSpanKind(SpanKind.SERVER).startSpan();
try (Scope ignored = span.makeCurrent()) {
checkEntityType(entityType);
+
+ Long time;
RemoteCache cache = getCache();
if (cache != null) {
span.setAttribute(ENTITY_ATTR, entityType.getName());
time = cache.get(entityType.getName());
- if (time != null) {
- return time;
- }//if
-
- time = System.currentTimeMillis();
- cache.put(entityType.getName(), time, -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS);
+ if (time == null) {
+ time = System.currentTimeMillis();
+ cache.put(entityType.getName(), time, -1, TimeUnit.SECONDS, -1, TimeUnit.SECONDS);
+ }
}//if
+ else {
+ time = System.currentTimeMillis();
+ }
+
+ return Instant.ofEpochMilli(time);
}//try
finally {
span.end();
}//finally
-
- return time;
- }//lastModified
+ }//getLastModified
@Override
public boolean contains(Class entityType, Object primaryKey)
@@ -362,13 +305,13 @@ public void evictAll()
}//evictAll
@Override
- public void begin() throws NotSupportedException, SystemException
+ public void begin() throws SystemException
{
Span span = TRACER.spanBuilder("EntityL2CacheImpl::begin").setSpanKind(SpanKind.SERVER).startSpan();
try (Scope ignored = span.makeCurrent()) {
if (CACHING_ENABLED) {
if (inTransaction) {
- throw new NotSupportedException("Transaction already in progress");
+ throw new SystemException("Transaction already in progress");
}//if
inTransaction = true;
}//if
@@ -379,7 +322,7 @@ public void begin() throws NotSupportedException, SystemException
}//begin
@Override
- public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException
+ public void commit() throws SystemException
{
Span span = TRACER.spanBuilder("EntityL2CacheImpl::commit").setSpanKind(SpanKind.SERVER).startSpan();
try (Scope ignored = span.makeCurrent()) {
@@ -415,7 +358,7 @@ public void commit() throws RollbackException, HeuristicMixedException, Heuristi
@Override
- public void rollback() throws IllegalStateException, SecurityException, SystemException
+ public void rollback() throws SystemException
{
if (CACHING_ENABLED) {
if (!inTransaction) {
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/caching/JPALiteInfinispanCache.java b/jpalite-core/src/main/java/io/jpalite/impl/caching/JPALiteInfinispanCache.java
new file mode 100644
index 0000000..33e61c3
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/caching/JPALiteInfinispanCache.java
@@ -0,0 +1,120 @@
+package io.jpalite.impl.caching;
+
+import io.jpalite.CachingException;
+import io.jpalite.JPACache;
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.infinispan.client.runtime.InfinispanClientProducer;
+import org.infinispan.client.hotrod.RemoteCache;
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.commons.configuration.StringConfiguration;
+
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+
+public class JPALiteInfinispanCache implements JPACache
+{
+ private static final String REGION_TIMESTAMP_NAME = "io.jpalite.region.$timestamps$";
+
+ private final String cacheClientName;
+ private final String configuration;
+ private final String regionPrefix;
+
+ private RemoteCacheManager remoteCacheManager;
+
+ public JPALiteInfinispanCache(String cacheClientName, String configuration, String regionPrefix)
+ {
+ this.regionPrefix = (regionPrefix == null || "".equals(regionPrefix)) ? "" : regionPrefix + " - ";
+ this.cacheClientName = cacheClientName;
+ this.configuration = configuration;
+ }
+
+ private RemoteCache getCache(String cacheRegion)
+ {
+ if (remoteCacheManager == null) {
+ InstanceHandle infinispanClientProducer = Arc.container().instance(InfinispanClientProducer.class);
+ if (infinispanClientProducer.isAvailable()) {
+ remoteCacheManager = infinispanClientProducer.get().getNamedRemoteCacheManager(cacheClientName);
+ }//if
+ if (remoteCacheManager == null || !remoteCacheManager.isStarted()) {
+ remoteCacheManager = null;
+ throw new CachingException("Error loading cache provider");
+ }//if
+ }//if
+
+
+ RemoteCache cache = remoteCacheManager.getCache(regionPrefix + cacheRegion);
+ if (cache == null) {
+ cache = remoteCacheManager.administration().getOrCreateCache(regionPrefix + cacheRegion, new StringConfiguration(configuration));
+ }//if
+
+ return cache;
+ }
+
+ @Override
+ public T find(String cacheRegion, String key)
+ {
+ RemoteCache cache = getCache(cacheRegion);
+ return cache.get(key);
+ }
+
+ @Override
+ public boolean containsKey(String cacheRegion, String key)
+ {
+ RemoteCache cache = getCache(cacheRegion);
+ return cache.containsKey(key);
+ }
+
+ @Override
+ public void add(String cacheRegion, String key, T value, long expireTime, TimeUnit expireTimeUnit)
+ {
+ getCache(cacheRegion).put(key, value, -1, TimeUnit.SECONDS, expireTime, expireTimeUnit);
+ getCache(REGION_TIMESTAMP_NAME).put(cacheRegion, DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.MILLIS)));
+ }
+
+ @Override
+ public void replace(String cacheRegion, String key, T value, long expireTime, TimeUnit expireTimeUnit)
+ {
+ getCache(cacheRegion).replace(key, value, -1, TimeUnit.SECONDS, expireTime, expireTimeUnit);
+ getCache(REGION_TIMESTAMP_NAME).put(cacheRegion, DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.MILLIS)));
+ }
+
+ @Override
+ public void evict(String cacheRegion, String key)
+ {
+ getCache(cacheRegion).remove(key);
+ getCache(REGION_TIMESTAMP_NAME).put(cacheRegion, DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.MILLIS)));
+ }
+
+ @Override
+ public void evictAll(String cacheRegion)
+ {
+ getCache(cacheRegion).clear();
+ getCache(REGION_TIMESTAMP_NAME).put(cacheRegion, DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.MILLIS)));
+ }
+
+ @Override
+ public void evictAllRegions()
+ {
+ RemoteCache cache = getCache(REGION_TIMESTAMP_NAME);
+ cache.keySet().forEach(region -> getCache(region).clear());
+ cache.clear();
+ }
+
+ @Override
+ public Instant getLastModified(String cacheRegion)
+ {
+ RemoteCache cache = getCache(REGION_TIMESTAMP_NAME);
+ String lastModified = cache.get(cacheRegion);
+ if (lastModified == null) {
+ Instant time = Instant.now();
+ cache.put(cacheRegion, DateTimeFormatter.ISO_INSTANT.format(time.truncatedTo(ChronoUnit.MILLIS)));
+ return time;
+ }
+
+ return Instant.parse(lastModified);
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java b/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java
index 09c14e6..d3d9927 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java
@@ -29,8 +29,6 @@
/**
* The DatabasePoolFactory class is part of the JPALite implementation
- *
- * @see TradeSwitch Persistence Manager in Confluence
*/
public class DatabasePoolFactory
{
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java
index f983629..474272c 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java
@@ -35,9 +35,7 @@
import static io.jpalite.PersistenceContext.PERSISTENCE_JTA_MANAGED;
/**
- * The DatabasePoolImpl class is part of the TradeSwitch JPA implementation
- *
- * @see TradeSwitch Persistence Manager in Confluence
+ * The DatabasePoolImpl class is part of the JPA implementation
*/
public class DatabasePoolImpl implements DatabasePool
{
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java
index babb2ff..30395fe 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java
+++ b/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java
@@ -20,7 +20,7 @@
import io.jpalite.PersistenceContext;
import io.jpalite.*;
import io.jpalite.impl.EntityL1LocalCacheImpl;
-import io.jpalite.impl.EntityL2CacheImpl;
+import io.jpalite.impl.caching.EntityCacheImpl;
import io.jpalite.impl.queries.EntityDeleteQueryImpl;
import io.jpalite.impl.queries.EntityInsertQueryImpl;
import io.jpalite.impl.queries.EntityUpdateQueryImpl;
@@ -34,10 +34,10 @@
import io.quarkus.runtime.Application;
import jakarta.annotation.Nonnull;
import jakarta.enterprise.inject.spi.CDI;
-import jakarta.persistence.RollbackException;
-import jakarta.persistence.TransactionRequiredException;
import jakarta.persistence.*;
-import jakarta.transaction.*;
+import jakarta.transaction.Status;
+import jakarta.transaction.SystemException;
+import jakarta.transaction.TransactionManager;
import org.eclipse.microprofile.config.ConfigProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,8 +48,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import static io.jpalite.JPALiteEntityManager.JPALITE_SHOW_SQL;
import static io.jpalite.JPALiteEntityManager.PERSISTENCE_QUERY_LOG_SLOWTIME;
+import static io.jpalite.JPALiteEntityManager.PERSISTENCE_SHOW_SQL;
import static io.jpalite.PersistenceAction.*;
/**
@@ -197,7 +197,8 @@ public PersistenceContextImpl(DatabasePool pool, JPALitePersistenceUnit persiste
entityL1Cache = new EntityL1LocalCacheImpl(this);
- entityL2Cache = new EntityL2CacheImpl(this.persistenceUnit);
+ //entityL2Cache = new EntityL2CacheImpl(this.persistenceUnit);
+ entityL2Cache = new EntityCacheImpl(this.persistenceUnit);
LOG.debug("Created {}", this);
@@ -229,7 +230,7 @@ public void setProperty(String name, Object value)
slowQueryTime = slowQuery;
}//if
}
- case JPALITE_SHOW_SQL -> {
+ case PERSISTENCE_SHOW_SQL -> {
if (value instanceof String strValue) {
value = Boolean.parseBoolean(strValue);
}//if
@@ -772,7 +773,7 @@ private void flushEntityInternal(@Nonnull JPAEntity entity)
if (action == PersistenceAction.DELETE) {
entity._setEntityState(EntityState.REMOVED);
if (entity._getMetaData().isCacheable()) {
- l2Cache().remove(entity);
+ l2Cache().evict(entity.get$$EntityClass(), entity._getPrimaryKey());
}//if
cascadeRemove(Set.of(MappingType.MANY_TO_ONE), entity);
@@ -787,7 +788,7 @@ private void flushEntityInternal(@Nonnull JPAEntity entity)
}//try
}//if
else if (entity._getMetaData().isCacheable()) {
- l2Cache().update(entity);
+ l2Cache().replace(entity);
}//else if
cascadePersist(Set.of(MappingType.ONE_TO_MANY, MappingType.ONE_TO_ONE), entity);
@@ -974,7 +975,7 @@ public void begin()
catch (SQLException ex) {
throw new PersistenceException("Error beginning a transaction", ex);
}//catch
- catch (SystemException | NotSupportedException ex) {
+ catch (SystemException ex) {
throw new PersistenceException("Error beginning a transaction in TransactionManager", ex);
}//catch
finally {
@@ -1019,8 +1020,7 @@ public void commit()
setRollbackOnly();
throw new PersistenceException("Error committing transaction", ex);
}//catch
- catch (SystemException | jakarta.transaction.RollbackException |
- HeuristicMixedException | HeuristicRollbackException ex) {
+ catch (SystemException ex) {
setRollbackOnly();
throw new PersistenceException("Error committing transaction in TransactionManager", ex);
}//catch
@@ -1091,6 +1091,11 @@ public boolean isActive()
}
//
+ public boolean isReadonly()
+ {
+ return readOnly;
+ }
+
//
public long getSlowQueryTime()
{
@@ -1113,11 +1118,6 @@ public void setEnableLogging(boolean pEnableLogging)
connection.setEnableLogging(pEnableLogging && showSql);
}//setEnableLogging
- public boolean isReadonly()
- {
- return readOnly;
- }
-
public void setReadonly(boolean pReadonly)
{
readOnly = pReadonly;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java
new file mode 100644
index 0000000..365418e
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java
@@ -0,0 +1,78 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class BigDecimalFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return BigDecimal.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, BigDecimal value) throws IOException
+ {
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeFieldName("scale");
+ jsonGenerator.writeNumber(value.scale());
+ jsonGenerator.writeFieldName("value");
+ jsonGenerator.writeString(value.toPlainString());
+ jsonGenerator.writeEndObject();
+ }
+
+ @Override
+ public BigDecimal fromJson(JsonNode json)
+ {
+ int scale = json.get("scale").asInt();
+ return new BigDecimal(json.get("value").textValue()).setScale(scale, RoundingMode.HALF_DOWN);
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ value = BigDecimal.ZERO;
+ }
+ out.writeInt(((BigDecimal) value).scale());
+ out.writeUTF(((BigDecimal) value).toPlainString());
+ }
+
+ @Override
+ public BigDecimal readField(DataInputStream in) throws IOException
+ {
+ int scale = in.readInt();
+ return new BigDecimal(in.readUTF()).setScale(scale, RoundingMode.HALF_DOWN);
+ }
+
+ @Override
+ public BigDecimal convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ BigDecimal val = resultSet.getBigDecimal(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public BigDecimal convertToDatabaseColumn(BigDecimal attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public BigDecimal convertToEntityAttribute(BigDecimal dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java
new file mode 100644
index 0000000..934d6b7
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java
@@ -0,0 +1,67 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class BoolFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return boolean.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Boolean value) throws IOException
+ {
+ jsonGenerator.writeBoolean(value);
+ }
+
+ @Override
+ public Boolean fromJson(JsonNode json)
+ {
+ return json.booleanValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ value = Boolean.FALSE;
+ }
+ out.writeBoolean((Boolean) value);
+ }
+
+ @Override
+ public Boolean readField(DataInputStream in) throws IOException
+ {
+ return in.readBoolean();
+ }
+
+ @Override
+ public Boolean convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ return convertToEntityAttribute(resultSet.getBoolean(column));
+ }
+
+ @Override
+ public Boolean convertToDatabaseColumn(Boolean attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Boolean convertToEntityAttribute(Boolean dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java
new file mode 100644
index 0000000..4babca2
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java
@@ -0,0 +1,66 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class BooleanFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return Boolean.class;
+ }
+
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Boolean value) throws IOException
+ {
+ jsonGenerator.writeBoolean(value);
+ }
+
+ @Override
+ public Boolean fromJson(JsonNode json)
+ {
+ return json.booleanValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ out.writeBoolean((Boolean) value);
+ }
+
+ @Override
+ public Boolean readField(DataInputStream in) throws IOException
+ {
+ return in.readBoolean();
+ }
+
+ @Override
+ public Boolean convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ boolean val = resultSet.getBoolean(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public Boolean convertToDatabaseColumn(Boolean attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Boolean convertToEntityAttribute(Boolean dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java
new file mode 100644
index 0000000..4f23057
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java
@@ -0,0 +1,82 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.CachingException;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class BytesFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return byte[].class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, byte[] value) throws IOException
+ {
+ jsonGenerator.writeBinary(value);
+ }
+
+ @Override
+ public byte[] fromJson(JsonNode json)
+ {
+ try {
+ return json.binaryValue();
+ }
+ catch (IOException ex) {
+ throw new CachingException("Error reading byte[]", ex);
+ }
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ byte[] bytes = (byte[]) value;
+ out.writeInt(bytes.length);
+ if (bytes.length > 0) {
+ out.write(bytes);
+ }
+ }
+
+ @Override
+ public byte[] readField(DataInputStream in) throws IOException
+ {
+ byte[] bytes = null;
+
+ int size = in.readInt();
+ if (size > 0) {
+ bytes = in.readNBytes(size);
+ }//if
+
+ return bytes;
+ }
+
+ @Override
+ public byte[] convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ byte[] val = resultSet.getBytes(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public byte[] convertToDatabaseColumn(byte[] attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public byte[] convertToEntityAttribute(byte[] dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
new file mode 100644
index 0000000..805499a
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
@@ -0,0 +1,65 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class DoubleDoubleFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return Double.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Double value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Double fromJson(JsonNode json)
+ {
+ return json.doubleValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ out.writeDouble((Double) value);
+ }
+
+ @Override
+ public Double readField(DataInputStream in) throws IOException
+ {
+ return in.readDouble();
+ }
+
+ @Override
+ public Double convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ double val = resultSet.getDouble(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public Double convertToDatabaseColumn(Double attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Double convertToEntityAttribute(Double dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java
new file mode 100644
index 0000000..51d7dac
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java
@@ -0,0 +1,67 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class DoubleFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return double.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Double value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Double fromJson(JsonNode json)
+ {
+ return json.doubleValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ value = 0.0;
+ }
+ out.writeDouble((Double) value);
+ }
+
+ @Override
+ public Double readField(DataInputStream in) throws IOException
+ {
+ return in.readDouble();
+ }
+
+ @Override
+ public Double convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ return convertToEntityAttribute(resultSet.getDouble(column));
+ }
+
+ @Override
+ public Double convertToDatabaseColumn(Double attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Double convertToEntityAttribute(Double dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java
new file mode 100644
index 0000000..c1c9107
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java
@@ -0,0 +1,83 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import io.jpalite.FieldConvertType;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Special converter type of enums. It is used internally by {@link io.jpalite.impl.EntityFieldImpl}
+ * Note that the type must not have a @Converter annotation
+ */
+public class EnumFieldType implements FieldConvertType, String>
+{
+ private final Class> enumType;
+
+ public EnumFieldType(Class> enumType)
+ {
+ this.enumType = enumType;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Class getAttributeType()
+ {
+ return Enum.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Enum> value) throws IOException
+ {
+ jsonGenerator.writeString(value.name());
+ }
+
+ @Override
+ public Enum> fromJson(JsonNode enumName)
+ {
+ for (Enum> enumValue : enumType.getEnumConstants()) {
+ if (enumValue.name().equals(enumName.textValue())) {
+ return enumValue;
+ }//if
+ }//for
+
+ return null;
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ out.writeShort(((Enum>) value).ordinal());
+ }
+
+ @Override
+ public Enum> readField(DataInputStream in) throws IOException
+ {
+ int ordinal = in.readShort();
+ return enumType.getEnumConstants()[ordinal];
+ }
+
+ @Override
+ public Enum> convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ String val = resultSet.getString(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public String convertToDatabaseColumn(Enum> attribute)
+ {
+ return (attribute).name();
+ }
+
+ @Override
+ public Enum> convertToEntityAttribute(String dbData)
+ {
+ return fromJson(TextNode.valueOf(dbData));
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java
new file mode 100644
index 0000000..9fc268f
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java
@@ -0,0 +1,67 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class IntFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return int.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Integer value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Integer fromJson(JsonNode json)
+ {
+ return json.intValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ value = 0;
+ }
+ out.writeInt((Integer) value);
+ }
+
+ @Override
+ public Integer readField(DataInputStream in) throws IOException
+ {
+ return in.readInt();
+ }
+
+ @Override
+ public Integer convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ return convertToEntityAttribute(resultSet.getInt(column));
+ }
+
+ @Override
+ public Integer convertToDatabaseColumn(Integer attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Integer convertToEntityAttribute(Integer dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java
new file mode 100644
index 0000000..4abc2c5
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java
@@ -0,0 +1,65 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class IntegerFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return Integer.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Integer value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Integer fromJson(JsonNode json)
+ {
+ return json.intValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ out.writeInt((Integer) value);
+ }
+
+ @Override
+ public Integer readField(DataInputStream in) throws IOException
+ {
+ return in.readInt();
+ }
+
+ @Override
+ public Integer convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ int val = resultSet.getInt(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public Integer convertToDatabaseColumn(Integer attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Integer convertToEntityAttribute(Integer dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
new file mode 100644
index 0000000..4a3b71d
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
@@ -0,0 +1,77 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+@Converter(autoApply = true)
+public class LocalDateTimeFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return LocalDateTime.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, LocalDateTime value) throws IOException
+ {
+ jsonGenerator.writeString(DateTimeFormatter.ISO_INSTANT.format(value.truncatedTo(ChronoUnit.NANOS)));
+ }
+
+ @Override
+ public LocalDateTime fromJson(JsonNode json)
+ {
+ return LocalDateTime.from(Instant.parse(json.textValue()));
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ out.writeLong(0L);
+ }
+ else {
+ out.writeLong(((LocalDateTime) value).toInstant(ZoneOffset.UTC).getEpochSecond());
+ }
+ }
+
+ @Override
+ public LocalDateTime readField(DataInputStream in) throws IOException
+ {
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(in.readLong()), ZoneId.of("UTC"));
+ }
+
+ @Override
+ public LocalDateTime convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ Timestamp val = resultSet.getTimestamp(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public Timestamp convertToDatabaseColumn(LocalDateTime attribute)
+ {
+ return Timestamp.from(attribute.toInstant(ZoneOffset.UTC));
+ }
+
+ @Override
+ public LocalDateTime convertToEntityAttribute(Timestamp dbData)
+ {
+ return dbData.toLocalDateTime();
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java
new file mode 100644
index 0000000..7c6327d
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java
@@ -0,0 +1,67 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class LongFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return long.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Long value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Long fromJson(JsonNode json)
+ {
+ return json.longValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ if (value == null) {
+ value = 0L;
+ }
+ out.writeLong((Long) value);
+ }
+
+ @Override
+ public Long readField(DataInputStream in) throws IOException
+ {
+ return in.readLong();
+ }
+
+ @Override
+ public Long convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ return convertToEntityAttribute(resultSet.getLong(column));
+ }
+
+ @Override
+ public Long convertToDatabaseColumn(Long attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Long convertToEntityAttribute(Long dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java
new file mode 100644
index 0000000..9a1c320
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java
@@ -0,0 +1,65 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.FieldConvertType;
+import jakarta.persistence.Converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Converter(autoApply = true)
+public class LongLongFieldType implements FieldConvertType
+{
+ @Override
+ public Class getAttributeType()
+ {
+ return Long.class;
+ }
+
+ @Override
+ public void toJson(JsonGenerator jsonGenerator, Long value) throws IOException
+ {
+ jsonGenerator.writeNumber(value);
+ }
+
+ @Override
+ public Long fromJson(JsonNode json)
+ {
+ return json.longValue();
+ }
+
+ @Override
+ public void writeField(Object value, DataOutputStream out) throws IOException
+ {
+ out.writeLong((Long) value);
+ }
+
+ @Override
+ public Long readField(DataInputStream in) throws IOException
+ {
+ return in.readLong();
+ }
+
+ @Override
+ public Long convertToEntityAttribute(ResultSet resultSet, int column) throws SQLException
+ {
+ long val = resultSet.getLong(column);
+ return (resultSet.wasNull() ? null : convertToEntityAttribute(val));
+ }
+
+ @Override
+ public Long convertToDatabaseColumn(Long attribute)
+ {
+ return attribute;
+ }
+
+ @Override
+ public Long convertToEntityAttribute(Long dbData)
+ {
+ return dbData;
+ }
+}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java
new file mode 100644
index 0000000..13689cc
--- /dev/null
+++ b/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java
@@ -0,0 +1,92 @@
+package io.jpalite.impl.fieldtypes;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.jpalite.CachingException;
+import io.jpalite.FieldConvertType;
+
+import java.io.*;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Special converter type of Object type. It is used internally by {@link io.jpalite.impl.EntityFieldImpl}
+ * Note that the type must not have a @Converter annotation
+ */
+public class ObjectFieldType implements FieldConvertType