getIdFields()
- {
- return idFields;
- }//getIdFields
-
- @Override
- @Deprecated
- public boolean isLegacyEntity()
- {
- return legacyEntity;
- }
-
- @Override
- @Deprecated
- public String getColumns()
- {
- return columns;
- }//getColumns
-}//EntityMetaDataImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java
deleted file mode 100644
index 0089d1e..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/JPAEntityImpl.java
+++ /dev/null
@@ -1,1059 +0,0 @@
-/*
- * 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;
-
-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.queries.QueryLanguage;
-import jakarta.annotation.Nonnull;
-import jakarta.persistence.*;
-import jakarta.persistence.spi.LoadState;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.util.*;
-import java.util.function.Consumer;
-
-import static jakarta.persistence.LockModeType.*;
-
-/**
- * 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.
- *
- * To prevent any mishaps with duplicate method names hiding access to the class all methods here will be prefixed with
- * '_' and attributes with '$$' knowing that it is considered a bad naming convention and be flagged as such by the IDE
- * and SonarQube (hoping that, you, the developer, do not pick the same method and variable names as what I have been
- * using here ;-) )
- */
-@SuppressWarnings({"java:S100", "java:S116"})
-public class JPAEntityImpl implements JPAEntity
-{
- public static final String SELECT_CLAUSE = "select ";
- public static final String FROM_CLAUSE = " from ";
- public static final String WHERE_CLAUSE = " where ";
- /**
- * A set of fields that was modified
- */
- private final transient Set $$modifiedList = new HashSet<>();
- /**
- * A set of fields that must be loaded on first access
- */
- private final transient Set $$fetchLazy = new HashSet<>();
- /**
- * The current entity state
- */
- private transient EntityState $$state = EntityState.TRANSIENT;
- /**
- * The action to perform on this entity when it is flushed by the persistence context
- */
- private transient PersistenceAction $$pendingAction = PersistenceAction.NONE;
- /**
- * The lock mode for the entity
- */
- private transient LockModeType $$lockMode = LockModeType.NONE;
- /**
- * The persistence context this entity belongs too.
- */
- private transient PersistenceContext $$persistenceContext = null;
- /**
- * The metadata for the entity
- */
- private final transient EntityMetaData> $$metadata;
- /**
- * Set to true if the entity is being mapped
- */
- private transient boolean $$mapping = false;
- /**
- * Set to true if the entity is lazy loaded.
- */
- private transient boolean $$lazyLoaded = false;
- /**
- * Indicator that an entity was created but no fields has been set yet.
- */
- private transient boolean $$blankEntity = true;
-
- /**
- * Control value to prevent recursive iteration by toString
- */
- private transient boolean inToString = false;
-
- protected JPAEntityImpl()
- {
- if (EntityMetaDataManager.isRegistered(getClass())) {
- $$metadata = EntityMetaDataManager.getMetaData(getClass());
-
- //Find all BASIC and ONE_TO_MANY fields that are flagged as being lazily fetched and add them to our $$fetchLazy list
- $$metadata.getEntityFields()
- .stream()
- .filter(f -> f.getFetchType() == FetchType.LAZY && (f.getMappingType() == MappingType.BASIC || f.getMappingType() == MappingType.ONE_TO_MANY))
- .forEach(f -> $$fetchLazy.add(f.getName()));
-
- //Force the default lock mode to OPTIMISTIC_FORCE_INCREMENT if the entity has a version field
- if ($$metadata.hasVersionField()) {
- $$lockMode = OPTIMISTIC_FORCE_INCREMENT;
- }//if
- }//if
- else {
- $$metadata = null;
- }//else
- }//JPAEntityImpl
-
- @Override
- public Class> _getEntityClass()
- {
- return getClass();
- }
-
- @Override
- public String toString()
- {
- if ($$metadata == null) {
- return super.toString();
- }//if
-
- StringBuilder toString = new StringBuilder(_getEntityInfo())
- .append(" ::")
- .append(_getStateInfo()).append(", ");
-
- toString.append(_getDataInfo());
-
- return toString.toString();
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o instanceof JPAEntityImpl e) {
- return _getPrimaryKey() != null && _getPrimaryKey().equals(e._getPrimaryKey());
- }
- return false;
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hashCode(_getPrimaryKey());
- }
-
- private String _getEntityInfo()
- {
- return "Entity " + $$metadata.getName();
- }//_getEntityInfo
-
- @SuppressWarnings({"java:S3776", "java:S3740"}) //The method cannot be simplified without increasing its complexity
- private String _getDataInfo()
- {
- StringBuilder toString = new StringBuilder();
-
- if (inToString) {
- toString.append(" [Circular reference detected]");
- }//if
- else {
- try {
- inToString = true;
-
- if ($$lazyLoaded) {
- toString.append(" [Lazy on PK=")
- .append(_getPrimaryKey())
- .append("] ");
- }//if
- else {
- toString.append("Data(");
-
- boolean first = true;
- for (EntityField field : _getMetaData().getEntityFields()) {
- if (!first) {
- toString.append(", ");
- }//if
- first = false;
-
- if (field.isIdField()) {
- toString.append("*");
- }//if
- toString.append(field.getName()).append("=");
- if ($$fetchLazy.contains(field.getName())) {
- toString.append("[Lazy]");
- }//if
- else {
- Object val = field.invokeGetter(this);
- if (val instanceof Map, ?> mapVal) {
- val = "[Map " + mapVal.size() + " items]";
- }//if
- else if (val instanceof List> listVal) {
- val = "[List " + listVal.size() + " items]";
- }//else if
- toString.append(val);
- }//else
- }//for
- toString.append(")");
- }//if
- }//try
- finally {
- inToString = false;
- }//finally
- }//else
-
- return toString.toString();
- }//_getDataInfo
-
- private String _getStateInfo()
- {
- return " State:" + $$state + ", " + "Action:" + $$pendingAction;
- }//_getStateInfo
-
- @Override
- public JPAEntity _clone()
- {
- JPAEntityImpl clone = (JPAEntityImpl) $$metadata.getNewEntity();
- clone.$$blankEntity = false;
- _getMetaData().getEntityFields()
- .stream()
- .filter(f -> !f.isIdField() && !f.isVersionField())
- .forEach(f ->
- {
- Object vVal = f.invokeGetter(this);
- f.invokeSetter(clone, vVal);
- });
- clone.$$fetchLazy.addAll($$fetchLazy);
- return clone;
- }//_clone
-
- @Override
- public void _replaceWith(JPAEntity entity)
- {
- if (!_getMetaData().getName().equals(entity._getMetaData().getName())) {
- throw new IllegalArgumentException("Attempting to replace entities of different types");
- }//if
-
- if (_getEntityState() != EntityState.DETACHED && _getEntityState() != EntityState.TRANSIENT) {
- throw new IllegalArgumentException("The content of an entity can only be replaced if it is DETACHED or TRANSIENT");
- }//if
-
- if (entity._getEntityState() != EntityState.MANAGED && entity._getEntityState() != EntityState.DETACHED) {
- throw new IllegalArgumentException("The provided entity must be in an MANAGED or DETACHED state");
- }//if
-
- $$mapping = true;
- try {
- _getMetaData().getEntityFields()
- .stream()
- .filter(f -> !_isLazyLoaded(f.getName()))
- .forEach(f -> f.invokeSetter(this, f.invokeGetter(entity)));
- $$fetchLazy.clear();
- $$fetchLazy.addAll(((JPAEntityImpl) entity).$$fetchLazy);
- $$blankEntity = false;
- _setPendingAction(entity._getPendingAction());
- $$modifiedList.clear();
- $$modifiedList.addAll(((JPAEntityImpl) entity).$$modifiedList);
-
- entity._getPersistenceContext().l1Cache().manage(this);
- entity._getPersistenceContext().l1Cache().detach(entity);
- }//try
- finally {
- $$mapping = false;
- }
- }//_replaceWith
-
- @Override
- public void _refreshEntity(Map properties)
- {
- if ($$blankEntity) {
- throw new IllegalStateException("Entity is not initialised");
- }//if
-
- if (_getEntityState() == EntityState.TRANSIENT || _getEntityState() == EntityState.REMOVED || _getPersistenceContext() == null) {
- throw new IllegalStateException("Entity is not managed or detached");
- }//if
-
- if (_getPersistenceContext().isReleased()) {
- throw new LazyInitializationException("Entity is not attached to an active persistence context");
- }//if
-
- try {
- _clearModified();
-
- //Detach the entity from L1 cache
- PersistenceContext persistenceContext = _getPersistenceContext();
- persistenceContext.l1Cache().detach(this);
-
- String queryStr = SELECT_CLAUSE + $$metadata.getName() + FROM_CLAUSE + $$metadata.getName() + WHERE_CLAUSE + $$metadata.getIdField().getName() + "=:p";
- JPALiteQueryImpl> query = new JPALiteQueryImpl<>(queryStr,
- QueryLanguage.JPQL,
- persistenceContext,
- $$metadata.getEntityClass(),
- properties,
- $$lockMode);
- query.setParameter("p", _getPrimaryKey());
- JPAEntity replaceEntity = (JPAEntity) query.getSingleResult();
- _replaceWith(replaceEntity);
- $$lazyLoaded = false;
-
- for (EntityField field : _getMetaData().getEntityFields()) {
- if ((field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.REFRESH))) {
- if (field.getMappingType() == MappingType.ONE_TO_ONE || field.getMappingType() == MappingType.MANY_TO_ONE) {
- JPAEntity entity = (JPAEntity) field.invokeGetter(this);
- if (entity != null) {
- entity._refreshEntity(properties);
- }
- }
- else {
- if (field.getMappingType() == MappingType.ONE_TO_MANY || field.getMappingType() == MappingType.MANY_TO_MANY) {
- @SuppressWarnings("unchecked")
- List entities = (List) field.invokeGetter(this);
- for (JPAEntity entity : entities) {
- entity._refreshEntity(properties);
- }
- }
- }
- }
- }
- }//try
- catch (NoResultException ex) {
- throw new EntityNotFoundException(String.format("Lazy load of entity '%s' for key '%s' failed", $$metadata.getName(), _getPrimaryKey()));
- }
- catch (PersistenceException ex) {
- throw new LazyInitializationException("Error lazy fetching entity " + $$metadata.getName(), ex);
- }//catch
- }//_refreshEntity
-
- private void _queryOneToMany(EntityField entityField)
- {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityField.getType());
- EntityField mappingField = metaData.getEntityField(entityField.getMappedBy());
-
- JPALiteQueryImpl> query = new JPALiteQueryImpl<>(SELECT_CLAUSE + metaData.getName() + FROM_CLAUSE + metaData.getName() + WHERE_CLAUSE + mappingField.getName() + "=:p",
- QueryLanguage.JPQL,
- _getPersistenceContext(),
- metaData.getEntityClass(),
- Collections.emptyMap());
- query.setParameter("p", _getPrimaryKey());
- entityField.invokeSetter(this, query.getResultList());
- }//_fetchOneToMany
-
- private void _queryBasicField(EntityField entityField)
- {
- String queryStr = SELECT_CLAUSE + " E." + entityField.getName() + FROM_CLAUSE + $$metadata.getName() + " E " + WHERE_CLAUSE + " E." + $$metadata.getIdField().getName() + "=:p";
- Query query = new QueryImpl(queryStr,
- _getPersistenceContext(),
- entityField.getType(),
- new HashMap<>());
- query.setParameter("p", _getPrimaryKey());
-
- //Will call _markField which will remove the field from the list
- entityField.invokeSetter(this, query.getSingleResult());
- }//_queryBasicField
-
- @Override
- public void _lazyFetchAll(boolean forceEagerLoad)
- {
- Set lazyFields = new HashSet<>($$fetchLazy);
- lazyFields.forEach(this::_lazyFetch);
- _getMetaData().getEntityFields()
- .stream()
- .filter(f -> f.getMappingType().equals(MappingType.MANY_TO_ONE) && (forceEagerLoad || f.getFetchType() == FetchType.EAGER))
- .forEach(f -> {
- JPAEntity manyToOneField = (JPAEntity) f.invokeGetter(this);
- if (manyToOneField != null) {
- _getPersistenceContext().l1Cache().manage(manyToOneField);
- manyToOneField._refreshEntity(Collections.emptyMap());
- }//if
- });
- }//_lazyFetchAll
-
- @Override
- public void _lazyFetch(String fieldName)
- {
- //Lazy fetching is only applicable for MANAGED and DETACHED entities
- if (_getEntityState() == EntityState.TRANSIENT || _getEntityState() == EntityState.REMOVED) {
- return;
- }//if
-
- if (_isLazyLoaded()) {
- //Refresh the entity. Refreshing will also clear the lazy loaded flag
- _refreshEntity(Collections.emptyMap());
- }//if
-
- if ($$fetchLazy.contains(fieldName)) {
- if (_getPersistenceContext().isReleased()) {
- throw new LazyInitializationException("Entity is not attached to an active persistence context");
- }//if
-
- EntityField entityField = $$metadata.getEntityField(fieldName);
- if (entityField.getMappingType() == MappingType.BASIC) {
- _queryBasicField(entityField);
- }//if
- else {
- _queryOneToMany(entityField);
- }//else
- }//if
- }//_lazyFetch
-
- @Override
- public boolean _isLazyLoaded()
- {
- return $$lazyLoaded;
- }//_isLazyLoaded
-
- @Override
- public boolean _isLazyLoaded(String fieldName)
- {
- return $$fetchLazy.contains(fieldName);
- }
-
- @Override
- public void _markLazyLoaded()
- {
- $$lazyLoaded = true;
- }//_markLazyLoaded
-
- @Override
- public void _makeReference(Object primaryKey)
- {
- if (!$$blankEntity) {
- throw new IllegalArgumentException("Entity must be blank to be made into a reference");
- }//if
-
- _setPrimaryKey(primaryKey);
- _markLazyLoaded();
- _clearModified();
- }//_makeReference
-
- @Override
- public EntityMetaData> _getMetaData()
- {
- if ($$metadata == null) {
- throw new IllegalArgumentException(getClass() + " is not a known entity or not yet registered");
- }//if
-
- return $$metadata;
- }
-
- @Override
- public Set _getModifiedFields()
- {
- return $$modifiedList;
- }
-
- @Override
- public void _clearModified()
- {
- $$modifiedList.clear();
- if ($$pendingAction == PersistenceAction.UPDATE) {
- $$pendingAction = PersistenceAction.NONE;
- }//if
- }
-
- @Override
- public LoadState _loadState()
- {
- return (_isLazyLoaded() || $$blankEntity) ? LoadState.NOT_LOADED : LoadState.LOADED;
- }
-
- @Override
- public boolean _isFieldModified(String fieldName)
- {
- return $$modifiedList.contains(fieldName);
- }
-
- @Override
- public void _clearField(String fieldName)
- {
- $$modifiedList.remove(fieldName);
- if ($$modifiedList.isEmpty() && $$pendingAction == PersistenceAction.UPDATE) {
- $$pendingAction = PersistenceAction.NONE;
- }//if
- }
-
- @Override
- public void _markField(String fieldName)
- {
- if ($$metadata.isEntityField(fieldName)) {
- EntityField vEntityField = $$metadata.getEntityField(fieldName);
-
- if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && vEntityField.isIdField()) {
- if (!$$metadata.isLegacyEntity()) {
- throw new PersistenceException("The ID field cannot be modified");
- }//if
- LoggerFactory.getLogger(JPAEntityImpl.class).warn("Legacy Mode :: Allowing modifying of ID Field {} in Entity {}", vEntityField.getName(), $$metadata.getName());
- }//if
-
- if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && vEntityField.isVersionField()) {
- throw new PersistenceException("A VERSION field cannot be modified");
- }//if
-
- if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && !vEntityField.isUpdatable()) {
- if (!$$metadata.isLegacyEntity()) {
- throw new PersistenceException("Attempting to updated a field that is marked as NOT updatable");
- }//if
- LoggerFactory.getLogger(JPAEntityImpl.class).warn("Legacy Mode :: Allowing modifying of NOT updatable field {} in Entity {}", vEntityField.getName(), $$metadata.getName());
- }//if
-
- /*
- * _markField is call whenever a field is updated
- * When this happens we can clear the $$blankEntity flag (as it is not true anymore! :-) )
- * We are also clearing the fetch lazy status for this field, if any
- * Lastly we are marking this fields as modified
- */
- $$blankEntity = false;
- $$fetchLazy.remove(fieldName);
-
- /*
- * ONE_TO_MANY fields is not really part of the current entity and any change to a ONE_TO_MANY field
- * do not trigger an update to the current entity.
- */
- if (!$$mapping && vEntityField.getMappingType() != MappingType.ONE_TO_MANY) {
- $$modifiedList.add(fieldName);
- if ($$pendingAction == PersistenceAction.NONE) {
- _setPendingAction(PersistenceAction.UPDATE);
- }//if
- }//if
- }//if
- }
-
- @Override
- public boolean _isEntityModified()
- {
- return !$$modifiedList.isEmpty();
- }
-
- @Override
- public LockModeType _getLockMode()
- {
- return $$lockMode;
- }
-
- @Override
- public void _setLockMode(LockModeType lockMode)
- {
- if (lockMode == OPTIMISTIC || lockMode == OPTIMISTIC_FORCE_INCREMENT || lockMode == WRITE || lockMode == READ) {
- if (!_getMetaData().hasVersionField()) {
- throw new PersistenceException("Entity has not version field");
- }//if
-
- /*
- If the entity is not new and is not dirty but is locked optimistically, we need to update the version
- The JPA Specification states that for versioned objects, it is permissible for an implementation to use
- LockMode- Type.OPTIMISTIC_FORCE_INCREMENT where LockModeType.OPTIMISTIC/READ was requested, but not vice versa.
- We choose to handle Type.OPTIMISTIC/READ) as Type.OPTIMISTIC_FORCE_INCREMENT
- */
- lockMode = OPTIMISTIC_FORCE_INCREMENT;
- }//if
- if (lockMode == NONE && _getMetaData().hasVersionField()) {
- throw new PersistenceException("Entity has version field and cannot be locked with LockModeType.NONE");
- }//if
-
- $$lockMode = lockMode;
- }
-
- @Override
- public EntityState _getEntityState()
- {
- return $$state;
- }
-
- @Override
- public void _setEntityState(EntityState newState)
- {
- if ($$state != newState && newState != EntityState.REMOVED) {
- $$metadata.getEntityFields().stream()
- .filter(f -> f.isEntityField() && f.getMappingType() != MappingType.ONE_TO_MANY)
- .forEach(f -> {
- JPAEntity vEntity = (JPAEntity) f.invokeGetter(this);
- if (vEntity != null) {
- vEntity._setEntityState(newState);
- }//if
- });
- }//if
- $$state = newState;
- }
-
- @Override
- public PersistenceContext _getPersistenceContext()
- {
- return $$persistenceContext;
- }
-
- @Override
- public void _setPersistenceContext(PersistenceContext persistenceContext)
- {
- if ($$persistenceContext != persistenceContext) {
- $$persistenceContext = persistenceContext;
- $$metadata.getEntityFields().stream()
- .filter(f -> f.isEntityField() && f.getMappingType() != MappingType.ONE_TO_MANY)
- .forEach(f -> {
- JPAEntity vEntity = (JPAEntity) f.invokeGetter(this);
- if (vEntity != null) {
- vEntity._setPersistenceContext(persistenceContext);
- }//if
- });
- }//if
- }
-
- @Override
- public PersistenceAction _getPendingAction()
- {
- return $$pendingAction;
- }
-
- @Override
- public void _setPendingAction(PersistenceAction pendingAction)
- {
- $$pendingAction = pendingAction;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public X _getDBValue(@Nonnull String fieldName)
- {
- EntityField entityField = _getMetaData().getEntityField(fieldName);
-
- Object value = entityField.invokeGetter(this);
- if (value == null) {
- return null;
- }//if
-
- if (entityField.isEntityField()) {
- return (X) value;
- }
-
- return (X) entityField.getConverter().convertToDatabaseColumn(value);
- }//getField
-
- @Override
- public void _updateRestrictedField(Consumer method)
- {
- boolean mappingStatus = $$mapping;
- try {
- $$mapping = true;
- method.accept(this);
- }
- finally {
- $$mapping = mappingStatus;
- }
- }
-
- @Override
- public void _merge(JPAEntity entity)
- {
- if (!_getMetaData().getName().equals(entity._getMetaData().getName())) {
- throw new IllegalArgumentException("Attempting to merge entities of different types");
- }//if
-
- if (!entity._getPrimaryKey().equals(_getPrimaryKey())) {
- throw new EntityMapException("Error merging entities, primary key mismatch. Expected " + _getPrimaryKey() + ", but got " + entity._getPrimaryKey());
- }//if
-
- /*
- * If the entity has a version field, we need to check that the version of the entity
- * being merged matches the current version, except if the entity was created by reference.
- */
- if (!$$lazyLoaded && $$metadata.hasVersionField()) {
- EntityField field = $$metadata.getVersionField();
- Object val = field.invokeGetter(entity);
- if (val != null && !val.equals(field.invokeGetter(this))) {
- throw new OptimisticLockException("Error merging entities, version mismatch. Expected " + field.invokeGetter(this) + ", but got " + val);
- }//if
- }//if
-
- for (String fieldName : entity._getModifiedFields()) {
- EntityField field = $$metadata.getEntityField(fieldName);
- if (!field.isIdField()) {
- field.invokeSetter(this, field.invokeGetter(entity));
- }//if
- }//for
- $$lazyLoaded = false;
- }//merge
-
- @Override
- public Object _getPrimaryKey()
- {
- if ($$metadata == null || $$metadata.getIdFields().isEmpty()) {
- return null;
- }//if
-
- if ($$metadata.getIdFields().size() > 1) {
- EntityMetaData> primaryKey = $$metadata.getPrimaryKeyMetaData();
- Object primKey = null;
- if (primaryKey != null) {
- primKey = primaryKey.getNewEntity();
- for (EntityField entityField : $$metadata.getIdFields()) {
- EntityField keyField = primaryKey.getEntityField(entityField.getName());
- keyField.invokeSetter(primKey, entityField.invokeGetter(this));
- }//for
- }//if
- return primKey;
- }//if
- else {
- return $$metadata.getIdFields().getFirst().invokeGetter(this);
- }//else
- }//_getPrimaryKey
-
- @Override
- public void _setPrimaryKey(Object primaryKey)
- {
- if (_getEntityState() != EntityState.TRANSIENT) {
- throw new IllegalStateException("The primary key can only be set for an entity with a TRANSIENT state");
- }//if
-
- if ($$metadata.getIdFields().isEmpty()) {
- throw new IllegalStateException("Entity [" + $$metadata.getName() + "] do not have any ID fields");
- }//if
-
-
- if ($$metadata.getIdFields().size() > 1) {
- EntityMetaData> primaryKeyMetaData = $$metadata.getPrimaryKeyMetaData();
- if (primaryKeyMetaData == null) {
- throw new IllegalStateException("Missing IDClass for Entity [" + $$metadata.getName() + "]");
- }//if
-
- for (EntityField entityField : $$metadata.getIdFields()) {
- EntityField keyField = primaryKeyMetaData.getEntityField(entityField.getName());
- entityField.invokeSetter(this, keyField.invokeGetter(primaryKey));
- }//for
- }//if
- else {
- $$metadata.getIdFields().getFirst().invokeSetter(this, primaryKey);
- }//else
- }//_setPrimaryKey
-
- public JPAEntity _JPAReadEntity(EntityField field, ResultSet resultSet, String colPrefix, int col) throws SQLException
- {
- JPAEntity managedEntity = null;
-
- //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) {
- 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
- }//if
- }
-
- return managedEntity;
- }//_JPAReadEntity
-
- @SuppressWarnings("java:S6205") // False error
- public void _JPAReadField(ResultSet row, EntityField field, String colPrefix, int columnNr)
- {
- try {
- $$mapping = true;
- if (field.isEntityField()) {
- 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));
- }//if
- }
- else {
- field.invokeSetter(this, field.getConverter().convertToEntityAttribute(row, columnNr));
- }
- }//try
- catch (SQLException ex) {
- throw new EntityMapException("Error setting field '" + field.getName() + "'", ex);
- }//catch
- finally {
- $$mapping = false;
- }//finally
- }//setField
-
- public void _mapResultSet(String colPrefix, ResultSet resultSet)
- {
- try {
- ResultSetMetaData resultMetaData = resultSet.getMetaData();
- int columns = resultMetaData.getColumnCount();
-
- Set columnsProcessed = new HashSet<>();
- for (int i = 1; i <= columns; i++) {
- String column = resultMetaData.getColumnName(i);
-
- EntityField field = null;
- String nextColPrefix = null;
- if (colPrefix == null) {
- field = $$metadata.getEntityFieldByColumn(column);
- }//if
- else {
- if (column.length() <= colPrefix.length() || !column.startsWith(colPrefix)) {
- continue;
- }//if
-
- String fieldName = column.substring(colPrefix.length() + 1).split("-")[0];
- if (!fieldName.isEmpty() && !columnsProcessed.contains(fieldName)) {
- columnsProcessed.add(fieldName);
- field = $$metadata.getEntityFieldByNr(Integer.parseInt(fieldName));
- nextColPrefix = colPrefix + "-" + fieldName;
- }//if
- }//else
-
- if (field != null) {
- _JPAReadField(resultSet, field, nextColPrefix, i);
- _clearField(field.getName());
- }//if
- }//for
- $$lazyLoaded = false;
- }//try
- catch (Exception ex) {
- throw new EntityMapException("Error extracting the ResultSet Metadata", ex);
- }//catch
- }//_mapResultSet
-
- private void writeFields(DataOutputStream out) throws IOException
- {
- Collection fieldList = $$metadata.getEntityFields();
- for (EntityField field : fieldList) {
- Object value = field.invokeGetter(this);
- if (value != null) {
- out.writeShort(field.getFieldNr());
- if (field.isEntityField()) {
- EntityMetaData> metaData = ((JPAEntity) value)._getMetaData();
- if (metaData.getEntityType() == EntityType.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.isEntityField()) {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
-
- JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
- entity.readFields(in);
- if (metaData.getEntityType() == EntityType.ENTITY) {
- 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.isEntityField()) {
- 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.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
- 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.isEntityField()) {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
-
- JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
- entity._fromJson(node.getValue());
-
- if (metaData.getEntityType() == EntityType.ENTITY) {
- 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();
- DataOutputStream out = new DataOutputStream(recvOut);
- writeFields(out);
- out.flush();
-
- return recvOut.toByteArray();
- }//try
- catch (IOException ex) {
- throw new PersistenceException("Error serialising entity", ex);
- }//catch
- }//_serialise
-
- @Override
- public void _deserialize(byte[] bytes)
- {
- try {
- ByteArrayInputStream recvOut = new ByteArrayInputStream(bytes);
- DataInputStream in = new DataInputStream(recvOut);
- readFields(in);
- }//try
- catch (IOException ex) {
- throw new PersistenceException("Error de-serialising the entity", ex);
- }//catch
- }//_deserialize
-
- @Override
- public boolean _entityEquals(JPAEntity entity)
- {
- return (entity._getMetaData().getEntityClass().equals(_getMetaData().getEntityClass()) &&
- entity._getPrimaryKey().equals(_getPrimaryKey()));
- }
-}//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
deleted file mode 100755
index a63c8d3..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/JPALiteEntityManagerImpl.java
+++ /dev/null
@@ -1,808 +0,0 @@
-/*
- * 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;
-
-import io.jpalite.PersistenceContext;
-import io.jpalite.*;
-import io.jpalite.impl.queries.*;
-import io.jpalite.queries.EntityQuery;
-import io.jpalite.queries.QueryLanguage;
-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 io.quarkus.runtime.BlockingOperationControl;
-import io.quarkus.runtime.BlockingOperationNotAllowedException;
-import jakarta.annotation.Nonnull;
-import jakarta.persistence.*;
-import jakarta.persistence.criteria.CriteriaBuilder;
-import jakarta.persistence.criteria.CriteriaDelete;
-import jakarta.persistence.criteria.CriteriaQuery;
-import jakarta.persistence.criteria.CriteriaUpdate;
-import jakarta.persistence.metamodel.Metamodel;
-import lombok.ToString;
-import lombok.extern.slf4j.Slf4j;
-
-import java.sql.ResultSet;
-import java.util.*;
-
-import static jakarta.persistence.LockModeType.*;
-
-/**
- * The entity manager implementation
- */
-@Slf4j
-@ToString(of = {"persistenceContext", "entityManagerFactory", "threadId"})
-public class JPALiteEntityManagerImpl implements JPALiteEntityManager
-{
- private static final String CRITERIA_QUERY_NOT_SUPPORTED = "CriteriaQuery is not supported";
- private static final String ENTITY_GRAPH_NOT_SUPPORTED = "EntityGraph is not supported";
- private static final String STORED_PROCEDURE_QUERY_NOT_SUPPORTED = "StoredProcedureQuery is not supported";
- private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(JPALiteEntityManagerImpl.class.getName());
- private final EntityManagerFactory entityManagerFactory;
- private final PersistenceContext persistenceContext;
- private final long threadId;
- private final Throwable opened;
- private final Map properties;
-
- private boolean entityManagerOpen;
- private FlushModeType flushMode;
-
- public JPALiteEntityManagerImpl(PersistenceContext persistenceContext, EntityManagerFactory factory)
- {
- this.persistenceContext = persistenceContext;
- this.entityManagerFactory = factory;
-
- entityManagerOpen = true;
- flushMode = FlushModeType.AUTO;
- properties = new HashMap<>(persistenceContext.getProperties());
- threadId = Thread.currentThread().threadId();
-
- if (LOG.isTraceEnabled()) {
- opened = new Throwable();
- }//if
- else {
- opened = null;
- }//else
- }//JPALiteEntityManagerImpl
-
- //
- @Override
- @SuppressWarnings("java:S6205") // false error
- public void setProperty(String name, Object value)
- {
- checkOpen();
-
- persistenceContext.setProperty(name, value);
- properties.put(name, value);
- }//setProperty
-
- @Override
- public Map getProperties()
- {
- checkOpen();
- return properties;
- }//getProperties
-
- private void checkOpen()
- {
- if (!isOpen()) {
- throw new IllegalStateException("EntityManager is closed");
- }//if
-
- if (threadId != Thread.currentThread().threadId()) {
- throw new IllegalStateException("Entity Managers are NOT threadsafe. Opened at ", opened);
- }//if
-
- if (!BlockingOperationControl.isBlockingAllowed()) {
- throw new BlockingOperationNotAllowedException("You have attempted to perform a blocking operation on a IO thread. This is not allowed, as blocking the IO thread will cause major performance issues with your application. If you want to perform blocking EntityManager operations make sure you are doing it from a worker thread.");
- }//if
- }//checkOpen
-
- private void checkEntity(Object entity)
- {
- if (entity == null) {
- throw new IllegalArgumentException("Entity cannot be null");
- }
-
- if (!(entity instanceof JPAEntity)) {
- throw new IllegalArgumentException("Entity is not an instance of JPAEntity");
- }
- }
-
- private void checkEntityClass(Class> entityClass)
- {
- if (!(JPAEntity.class.isAssignableFrom(entityClass))) {
- throw new IllegalArgumentException("Entity " + entityClass.getName() + " is not created using EntityManager");
- }//if
- }//checkEntityClass
-
- private void checkEntityAttached(JPAEntity entity)
- {
- if (entity._getEntityState() != EntityState.MANAGED) {
- throw new IllegalArgumentException("Entity is not current attached to a persistence context");
- }//if
-
- if (entity._getPersistenceContext() != persistenceContext) {
- throw new IllegalArgumentException("Entity is not being managed by this Persistence Context");
- }//if
- }//checkEntityObject
-
- private void checkTransactionRequired()
- {
- if (!persistenceContext.isActive()) {
- throw new TransactionRequiredException();
- }//if
- }//checkTransactionRequired
- //
-
- @Override
- public EntityTransaction getTransaction()
- {
- checkOpen();
- return persistenceContext.getTransaction();
- }
-
- @Override
- public EntityManagerFactory getEntityManagerFactory()
- {
- checkOpen();
-
- return entityManagerFactory;
- }
-
- @Override
- public void close()
- {
- checkOpen();
- entityManagerOpen = false;
- }
-
- @Override
- public boolean isOpen()
- {
- return entityManagerOpen;
- }
-
- @Override
- public X mapResultSet(@Nonnull X entity, ResultSet resultSet)
- {
- checkEntity(entity);
- return persistenceContext.mapResultSet(entity, resultSet);
- }
-
- @Override
- public void setFlushMode(FlushModeType flushMode)
- {
- checkOpen();
- this.flushMode = flushMode;
- }//setFlushMode
-
- @Override
- public FlushModeType getFlushMode()
- {
- checkOpen();
- return flushMode;
- }//getFlushMode
-
- @Override
- public void clear()
- {
- checkOpen();
- persistenceContext.l1Cache().clear();
- }//clear
-
- @Override
- public void detach(Object entity)
- {
- checkOpen();
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- checkEntityAttached((JPAEntity) entity);
- persistenceContext.l1Cache().detach((JPAEntity) entity);
- }//detach
-
- @Override
- public boolean contains(Object entity)
- {
- checkOpen();
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- return persistenceContext.l1Cache().contains((JPAEntity) entity);
- }//contains
-
- //
- @Override
- public EntityGraph createEntityGraph(Class rootType)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
- }
-
- @Override
- public EntityGraph> createEntityGraph(String graphName)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
- }
-
- @Override
- @SuppressWarnings("java:S4144")//Not an error
- public EntityGraph> getEntityGraph(String graphName)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
- }
-
- @Override
- @SuppressWarnings("java:S4144")//Not an error
- public List> getEntityGraphs(Class entityClass)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
- }
- //
-
- //
- @Override
- public void flush()
- {
- checkOpen();
- checkTransactionRequired();
-
- persistenceContext.flush();
- }//flush
-
- @Override
- public void flushOnType(Class> entityClass)
- {
- persistenceContext.flushOnType(entityClass);
- }//flushEntities
-
- @Override
- public void flushEntity(@Nonnull T entity)
- {
- checkOpen();
- checkEntity(entity);
- checkTransactionRequired();
- checkEntityAttached((JPAEntity) entity);
-
- persistenceContext.flushEntity((JPAEntity) entity);
- }//flushEntity
-
- @Override
- public void persist(@Nonnull Object entity)
- {
- checkOpen();
- checkEntity(entity);
- checkTransactionRequired();
- checkEntityClass(entity.getClass());
-
- if (((JPAEntity) entity)._getEntityState() == EntityState.MANAGED) {
- //An existing managed entity is ignored
- return;
- }//if
-
- if (((JPAEntity) entity)._getEntityState() == EntityState.REMOVED) {
- throw new PersistenceException("Attempting to persist an entity that was removed from the database");
- }//if
-
- ((JPAEntity) entity)._setPendingAction(PersistenceAction.INSERT);
- persistenceContext.l1Cache().manage((JPAEntity) entity);
-
- if (flushMode == FlushModeType.AUTO) {
- flushEntity((JPAEntity) entity);
- }//if
- }//persist
-
- /**
- * Many-to-one fields (entities) might be indirectly attached but contain One-to-Many fields
- */
- private void cascadeMerge(JPAEntity entity)
- {
- entity._getMetaData()
- .getEntityFields()
- .stream()
- .filter(f -> f.isEntityField() && !entity._isLazyLoaded(f.getName()))
- .filter(f -> f.getCascade().contains(CascadeType.ALL) || f.getCascade().contains(CascadeType.MERGE))
- .forEach(f ->
- {
- try {
- if (f.getMappingType() == MappingType.ONE_TO_MANY || f.getMappingType() == MappingType.MANY_TO_MANY) {
- @SuppressWarnings("unchecked")
- List entityList = (List) f.invokeGetter(entity);
-
- List newEntityList = new ArrayList<>();
- for (JPAEntity ref : entityList) {
- newEntityList.add(merge(ref));
- }//for
- f.invokeSetter(entity, newEntityList);
- }//if
- else {
- if (f.getMappingType() == MappingType.MANY_TO_ONE || f.getMappingType() == MappingType.ONE_TO_ONE) {
- JPAEntity ref = (JPAEntity) f.invokeGetter(entity);
- f.invokeSetter(entity, merge(ref));
- }
- }
- }//try
- catch (PersistenceException ex) {
- LOG.error("Error processing cascading fields");
- throw ex;
- }//catch
- catch (RuntimeException ex) {
- LOG.error("Error merging ManyToOne field", ex);
- throw new PersistenceException("Error merging ManyToOne field");
- }//catch
- });
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public X merge(X entity)
- {
- Span span = TRACER.spanBuilder("EntityManager::merge").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- checkOpen();
- checkEntity(entity);
- checkTransactionRequired();
- checkEntityClass(entity.getClass());
-
- JPAEntity jpaEntity = (JPAEntity) entity;
- return switch (jpaEntity._getEntityState()) {
- case MANAGED -> {
- checkEntityAttached(jpaEntity);
- yield entity;
- }
- case DETACHED -> {
- X latestEntity = (X) find(jpaEntity.getClass(), jpaEntity._getPrimaryKey(), jpaEntity._getLockMode());
- if (latestEntity == null) {
- throw new IllegalArgumentException("Original entity not found");
- }//if
- ((JPAEntity) latestEntity)._merge(jpaEntity);
- cascadeMerge(jpaEntity);
- yield latestEntity;
- }
-
- case REMOVED ->
- throw new PersistenceException("Attempting to merge an entity that was removed from the database");
-
- case TRANSIENT -> {
- Object primaryKey = jpaEntity._getPrimaryKey();
- if (primaryKey != null) {
- X persistedEntity = find((Class) entity.getClass(), primaryKey);
- if (persistedEntity != null) {
- ((JPAEntity) persistedEntity)._merge(jpaEntity);
- yield persistedEntity;
- }//if
- }//if
-
- persist(entity);
- yield entity;
- }
- };
- }//try
- finally {
- span.end();
- }
- }//merge
-
- @Override
- @SuppressWarnings("unchecked")
- public T clone(@Nonnull T entity)
- {
- checkOpen();
- checkEntity(entity);
- checkEntityClass(entity.getClass());
-
- return (T) ((JPAEntity) entity)._clone();
- }//clone
-
- @Override
- public void remove(Object entity)
- {
- checkOpen();
- checkEntity(entity);
- checkTransactionRequired();
- checkEntityClass(entity.getClass());
-
- ((JPAEntity) entity)._setPendingAction(PersistenceAction.DELETE);
-
- if (flushMode == FlushModeType.AUTO) {
- flushEntity((JPAEntity) entity);
- }//if
- }//remove
- //
-
- //
- @Override
- public void refresh(Object entity)
- {
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- refresh(entity, ((JPAEntity) entity)._getLockMode(), Collections.emptyMap());
- }//refresh
-
- @Override
- public void refresh(Object entity, Map properties)
- {
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- refresh(entity, ((JPAEntity) entity)._getLockMode(), properties);
- }//refresh
-
- @Override
- public void refresh(Object entity, LockModeType lockMode)
- {
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- refresh(entity, lockMode, Collections.emptyMap());
- }//refresh
-
- @Override
- public void refresh(Object entity, LockModeType lockMode, Map properties)
- {
- checkOpen();
- checkEntity(entity);
- checkTransactionRequired();
- checkEntityAttached((JPAEntity) entity);
-
- ((JPAEntity) entity)._setLockMode(lockMode);
- ((JPAEntity) entity)._refreshEntity(properties);
- }//refresh
- //
-
- //
- @Override
- public T find(Class entityClass, Object primaryKey)
- {
- return find(entityClass, primaryKey, LockModeType.NONE, null);
- }
-
- @Override
- public T find(Class entityClass, Object primaryKey, Map properties)
- {
- return find(entityClass, primaryKey, LockModeType.NONE, properties);
- }
-
- @Override
- public T find(Class entityClass, Object primaryKey, LockModeType lockMode)
- {
- return find(entityClass, primaryKey, lockMode, null);
- }
-
- @Override
- public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties)
- {
- Span span = TRACER.spanBuilder("EntityManager::find").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- checkOpen();
- checkEntityClass(entityClass);
-
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityClass);
- span.setAttribute("entity", metaData.getName());
-
- Map hints = new HashMap<>(this.properties);
- if (properties != null) {
- hints.putAll(properties);
- }//if
-
- EntityQuery entityQuery = new EntitySelectQueryImpl(primaryKey, metaData);
- JPALiteQueryImpl query = new JPALiteQueryImpl<>(entityQuery.getQuery(),
- entityQuery.getLanguage(),
- persistenceContext,
- entityClass,
- hints,
- lockMode);
- query.setParameter(1, primaryKey);
- try {
- return query.getSingleResult();
- }//try
- catch (NoResultException ex) {
- return null;
- }//catch
- }//try
- finally {
- span.end();
- }
- }//find
-
- @Override
- @SuppressWarnings("unchecked")
- public T getReference(Class entityClass, Object primaryKey)
- {
- checkEntityClass(entityClass);
-
- EntityMetaData metaData = EntityMetaDataManager.getMetaData(entityClass);
- JPAEntity newEntity = (JPAEntity) metaData.getNewEntity();
- newEntity._makeReference(primaryKey);
- persistenceContext.l1Cache().manage(newEntity);
-
- return (T) newEntity;
- }
- //
-
- //
- @Override
- public LockModeType getLockMode(Object entity)
- {
- checkOpen();
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- checkEntityAttached((JPAEntity) entity);
-
- return ((JPAEntity) entity)._getLockMode();
- }
-
- @Override
- public void lock(Object entity, LockModeType lockMode)
- {
- lock(entity, lockMode, null);
- }//lock
-
- @Override
- public void lock(Object entity, LockModeType lockMode, Map properties)
- {
- checkOpen();
- checkEntity(entity);
- checkEntityClass(entity.getClass());
- checkTransactionRequired();
-
- if (entity instanceof JPAEntity jpaEntity) {
- jpaEntity._setLockMode(lockMode);
-
- //For pessimistic locking a select for update query is to be executed
- if (lockMode == PESSIMISTIC_READ || lockMode == PESSIMISTIC_FORCE_INCREMENT || lockMode == PESSIMISTIC_WRITE) {
- Map hints = new HashMap<>(this.properties);
- if (properties != null) {
- hints.putAll(properties);
- }//if
-
- String sqlQuery = "select " +
- jpaEntity._getMetaData().getIdField().getColumn() +
- " from " +
- jpaEntity._getMetaData().getTable() +
- " where " +
- jpaEntity._getMetaData().getIdField().getColumn() +
- "=?";
-
- JPALiteQueryImpl> query = new JPALiteQueryImpl<>(sqlQuery,
- QueryLanguage.NATIVE,
- persistenceContext,
- jpaEntity._getMetaData().getEntityClass(),
- hints,
- lockMode);
- query.setParameter(1, jpaEntity._getPrimaryKey());
-
- try {
- //Lock to row and continue
- query.getSingleResult();
- }//try
- catch (NoResultException ex) {
- getTransaction().setRollbackOnly();
- throw new EntityNotFoundException(jpaEntity._getMetaData().getName() + " with key " + jpaEntity._getPrimaryKey() + " not found");
- }//catch
- catch (PersistenceException ex) {
- getTransaction().setRollbackOnly();
- }//if
- }//if
- else {
- //For optimistic locking we need to flush the entity
- flush();
- }//else
- }//if
- }//lock
- //
-
- //
- @Override
- public Query createQuery(String query)
- {
- checkOpen();
- return new QueryImpl(query, persistenceContext, Object[].class, properties);
- }//createQuery
-
- @Override
- public TypedQuery createQuery(String query, Class resultClass)
- {
- checkOpen();
- return new TypedQueryImpl<>(query, QueryLanguage.JPQL, persistenceContext, resultClass, properties);
- }//createQuery
-
- @Override
- public TypedQuery createNamedQuery(String name, Class resultClass)
- {
- checkOpen();
-
- NamedQueries namedQueries = resultClass.getAnnotation(NamedQueries.class);
- if (namedQueries != null) {
- for (NamedQuery namedQuery : namedQueries.value()) {
- if (namedQuery.name().equals(name)) {
- return new NamedQueryImpl<>(namedQuery, persistenceContext, resultClass, properties);
- }//if
- }//for
- }//if
-
- NamedQuery namedQuery = resultClass.getAnnotation(NamedQuery.class);
- if (namedQuery != null && namedQuery.name().equals(name)) {
- return new NamedQueryImpl<>(namedQuery, persistenceContext, resultClass, properties);
- }//if
-
- NamedNativeQueries namedNativeQueries = resultClass.getAnnotation(NamedNativeQueries.class);
- if (namedNativeQueries != null) {
- for (NamedNativeQuery nativeQuery : namedNativeQueries.value()) {
- if (nativeQuery.name().equals(name)) {
- return new NamedNativeQueryImpl<>(nativeQuery, persistenceContext, resultClass, properties);
- }//if
- }//for
- }//if
-
- NamedNativeQuery namedNativeQuery = resultClass.getAnnotation(NamedNativeQuery.class);
- if (namedNativeQuery != null && namedNativeQuery.name().equals(name)) {
- return new NamedNativeQueryImpl<>(namedNativeQuery, persistenceContext, resultClass, properties);
- }//if
-
- throw new IllegalArgumentException("Named query '" + name + "' not found");
- }//createNamedQuery
-
- @Override
- @SuppressWarnings({"unchecked", "rawtypes"})
- public Query createNativeQuery(String sqlString, Class resultClass)
- {
- checkOpen();
- return new NativeQueryImpl<>(sqlString, persistenceContext, resultClass, properties);
- }//createNativeQuery
-
- @Override
- public Query createNativeQuery(String sqlString)
- {
- checkOpen();
- return new NativeQueryImpl<>(sqlString, persistenceContext, Object.class, properties);
- }
-
- @Override
- public TypedQuery createQuery(CriteriaQuery criteriaQuery)
- {
- checkOpen();
- throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public Query createQuery(CriteriaUpdate updateQuery)
- {
- checkOpen();
- throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public Query createQuery(CriteriaDelete deleteQuery)
- {
- checkOpen();
- throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public Query createNamedQuery(String name)
- {
- checkOpen();
-
- throw new UnsupportedOperationException("Global Named Queries are not supported");
- }
-
- @Override
- public Query createNativeQuery(String sqlString, String resultSetMapping)
- {
- checkOpen();
-
- throw new UnsupportedOperationException("ResultSetMapping is not supported");
- }//createNativeQuery
-
- @Override
- public StoredProcedureQuery createNamedStoredProcedureQuery(String name)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public StoredProcedureQuery createStoredProcedureQuery(String procedureName)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings)
- {
- checkOpen();
-
- throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
- }
-
- @Override
- @SuppressWarnings("java:S4144")//Not an error
- public CriteriaBuilder getCriteriaBuilder()
- {
- checkOpen();
-
- throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
- }
- //
-
- @Override
- public void joinTransaction()
- {
- checkOpen();
-
- persistenceContext.joinTransaction();
- }
-
- @Override
- public boolean isJoinedToTransaction()
- {
- checkOpen();
-
- return persistenceContext.isJoinedToTransaction();
- }
-
- @Override
- public Metamodel getMetamodel()
- {
- checkOpen();
- return entityManagerFactory.getMetamodel();
- }
-
- @Override
- public Object getDelegate()
- {
- checkOpen();
- return this;
- }
-
- @Override
- @SuppressWarnings({"unchecked"})
- public T unwrap(Class cls)
- {
- checkOpen();
-
- if (cls.isAssignableFrom(this.getClass())) {
- return (T) this;
- }
-
- if (cls.isAssignableFrom(PersistenceContext.class)) {
- return (T) persistenceContext;
- }
-
- throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
- }
-}//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
deleted file mode 100644
index 96ec234..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/caching/EntityCacheImpl.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * 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/caching/JPALiteInfinispanCache.java b/jpalite-core/src/main/java/io/jpalite/impl/caching/JPALiteInfinispanCache.java
deleted file mode 100644
index 33e61c3..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/caching/JPALiteInfinispanCache.java
+++ /dev/null
@@ -1,120 +0,0 @@
-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/PersistenceContextImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java
deleted file mode 100644
index 36000e2..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/PersistenceContextImpl.java
+++ /dev/null
@@ -1,1159 +0,0 @@
-/*
- * 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.db;
-
-import io.jpalite.PersistenceContext;
-import io.jpalite.*;
-import io.jpalite.impl.EntityL1LocalCacheImpl;
-import io.jpalite.impl.caching.EntityCacheImpl;
-import io.jpalite.impl.queries.EntityDeleteQueryImpl;
-import io.jpalite.impl.queries.EntityInsertQueryImpl;
-import io.jpalite.impl.queries.EntityUpdateQueryImpl;
-import io.jpalite.queries.EntityQuery;
-import io.opentelemetry.api.GlobalOpenTelemetry;
-import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.api.trace.SpanKind;
-import io.opentelemetry.api.trace.StatusCode;
-import io.opentelemetry.api.trace.Tracer;
-import io.opentelemetry.context.Scope;
-import io.quarkus.runtime.Application;
-import jakarta.annotation.Nonnull;
-import jakarta.enterprise.inject.spi.CDI;
-import jakarta.persistence.*;
-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;
-
-import java.io.PrintWriter;
-import java.sql.*;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static io.jpalite.JPALiteEntityManager.*;
-import static io.jpalite.PersistenceAction.*;
-
-/**
- * The persistence context is responsible for managing the connection, persisting entities to the database and keeps
- * tract of transaction blocks started and needs to do the cleanup on close.
- */
-public class PersistenceContextImpl implements PersistenceContext
-{
- private static final Logger LOG = LoggerFactory.getLogger(PersistenceContextImpl.class);
- private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(PersistenceContextImpl.class.getName());
- /**
- * The database pool we belong to
- */
- private final DatabasePool pool;
- /**
- * Control counter to manage transaction depth. Every call to {@link #begin()} will increment it and calls to
- * {@link #commit()} and {@link #rollback()} will decrement it.
- */
- private final AtomicInteger transactionDepth;
- /**
- * Control variable to record the current {@link #transactionDepth}.
- */
- private final Deque openStack;
- /**
- * The connection name used to open a new connection
- */
- private final Deque connectionNames;
- /**
- * Stack for all save points created by beginTrans()
- */
- private final Deque savepoints;
- /**
- * The level 1 cache
- */
- private final EntityLocalCache entityL1Cache;
- /**
- * The level 2 cache
- */
- private final EntityCache entityL2Cache;
- /**
- * List of all callback listeners
- */
- private final List listeners;
- /**
- * List of callback listeners to add. This list is populated if a new listener is removed form with in a callback.
- */
- private final List pendingAdd;
- /**
- * List of callback listeners to delete. This list is populated if a listener is removed form with in a callback.
- */
- private final List pendingRemoval;
- /**
- * The connection assigned to the manager
- */
- private ConnectionWrapper connection;
- /**
- * The last query executed in by the connection
- */
- private String lastQuery;
- /**
- * The current connection name assigned to the connection
- */
- private String connectionName;
- /**
- * The execution time after which queries are considered run too slowly
- */
- private long slowQueryTime;
- /**
- * If true create a connection that shows the SQL
- */
- private boolean showSql;
- /**
- * The cache store mode in effect
- */
- private CacheStoreMode cacheStoreMode;
- /**
- * Control variable to indicate that we have forced rollback
- */
- private boolean rollbackOnly = false;
- /**
- * Read only indicator
- */
- private boolean readOnly;
- /**
- * Control variable to make sure that a transaction callback does not call begin, commit or rollback
- */
- private boolean inCallbackHandler;
- /**
- * The JTA transaction manager
- */
- private TransactionManager transactionManager;
- /**
- * True if join to a JTA transaction
- */
- private boolean joinedToTransaction;
- /**
- * True if the context should automatically detect and join a JTA managed transaction.
- */
- private boolean autoJoinTransaction;
- /**
- * The persistence context properties
- */
- private final Map properties;
- /**
- * The persistence unit used to create the context
- */
- private final JPALitePersistenceUnit persistenceUnit;
- private final long threadId;
- private final long instanceNr;
- private static final AtomicLong instanceCount = new AtomicLong(0);
- private boolean released;
- private final String hostname;
-
- private enum CallbackMethod
- {
- PRE_BEGIN,
- POST_BEGIN,
- PRE_COMMIT,
- POST_COMMIT,
- PRE_ROLLBACK,
- POST_ROLLBACK
- }
-
- public PersistenceContextImpl(DatabasePool pool, JPALitePersistenceUnit persistenceUnit)
- {
- this.pool = pool;
- readOnly = false;
- this.persistenceUnit = persistenceUnit;
- properties = new HashMap<>();
- listeners = new ArrayList<>();
- pendingAdd = new ArrayList<>();
- pendingRemoval = new ArrayList<>();
- transactionDepth = new AtomicInteger(0);
- instanceNr = instanceCount.incrementAndGet();
- openStack = new ArrayDeque<>();
- connectionNames = new ArrayDeque<>();
- savepoints = new ArrayDeque<>();
- connectionName = Thread.currentThread().getName();
- cacheStoreMode = CacheStoreMode.USE;
- slowQueryTime = 500L;
- joinedToTransaction = false;
- autoJoinTransaction = false;
- transactionManager = null;
- showSql = false;
- released = false;
-
- hostname = ConfigProvider.getConfig().getOptionalValue("HOSTNAME", String.class).orElse("localhost");
- threadId = Thread.currentThread().threadId();
- persistenceUnit.getProperties().forEach((k, v) -> setProperty(k.toString(), v));
-
- entityL1Cache = new EntityL1LocalCacheImpl(this);
-
- entityL2Cache = new EntityCacheImpl(this.persistenceUnit);
-
- LOG.debug("Created {}", this);
- }//PersistenceContextImpl
-
- @Override
- public JPALitePersistenceUnit getPersistenceUnit()
- {
- return persistenceUnit;
- }//getPersistenceUnit
-
- @Override
- public void setProperty(String name, Object value)
- {
- switch (name) {
- case PERSISTENCE_CACHE_STOREMODE -> {
- if (value instanceof String strValue) {
- value = CacheStoreMode.valueOf(strValue);
- }//if
- if (value instanceof CacheStoreMode cacheMode) {
- cacheStoreMode = cacheMode;
- }//if
- }
- case PERSISTENCE_JTA_MANAGED -> {
- if (value instanceof String strValue) {
- value = Boolean.parseBoolean(strValue);
- }//if
- if (value instanceof Boolean jtaManaged) {
- autoJoinTransaction = jtaManaged;
- }//if
- }
- case PERSISTENCE_QUERY_LOG_SLOWTIME -> {
- if (value instanceof String strValue) {
- value = Long.parseLong(strValue);
- }//if
- if (value instanceof Long slowQuery) {
- slowQueryTime = slowQuery;
- }//if
- }
- case PERSISTENCE_SHOW_SQL -> {
- if (value instanceof String strValue) {
- value = Boolean.parseBoolean(strValue);
- }//if
- if (value instanceof Boolean showQuerySql) {
- this.showSql = showQuerySql;
- if (connection != null) {
- connection.setEnableLogging(this.showSql);
- }//if
- }//if
- }
- default -> {
- //ignore the rest
- }
- }//switch
-
- properties.put(name, value);
- }
-
- @Override
- public Map getProperties()
- {
- return properties;
- }
-
- @Override
- public EntityLocalCache l1Cache()
- {
- return entityL1Cache;
- }//l1Cache
-
- @Override
- public EntityCache l2Cache()
- {
- return entityL2Cache;
- }//l2Cache
-
- private void checkEntityAttached(JPAEntity entity)
- {
- if (entity._getEntityState() != EntityState.MANAGED) {
- throw new IllegalArgumentException("Entity is not current attached to a Persistence Context");
- }//if
-
- if (entity._getPersistenceContext() != this) {
- throw new IllegalArgumentException("Entity is not being managed by this Persistence Context");
- }//if
- }//checkEntityAttached
-
- private void checkRecursiveCallback()
- {
- if (inCallbackHandler) {
- throw new PersistenceException("The EntityTransaction methods begin, commit and rollback cannot be called from within a EntityListener callback");
- }//if
- }//checkRecursiveCallback
-
- private void checkThread()
- {
- if (threadId != Thread.currentThread().threadId()) {
- throw new IllegalStateException("Persistence Context is assigned different thread. Expected " + threadId + ", calling thread is " + Thread.currentThread().threadId());
- }//if
- }//checkThread
-
- private void checkReleaseState()
- {
- if (released) {
- throw new PersistenceException("Persistence Context has detached from the database pool cannot be used");
- }//if
- }//checkReleaseState
-
- private void checkOpen()
- {
- checkReleaseState();
-
- if (connection == null) {
- throw new IllegalStateException("Persistence Context is closed.");
- }//if
- }//checkOpen
-
- @Override
- public String toString()
- {
- return "Persistence Context " + instanceNr + " [Stack " + openStack.size() + ", " + pool + "]";
- }//toString
-
- @Override
- public void addTransactionListener(EntityTransactionListener listener)
- {
- if (inCallbackHandler) {
- pendingAdd.add(listener);
- }//if
- else {
- listeners.add(listener);
- }//else
- }//addTransactionListener
-
- @Override
- public void removeTransactionListener(EntityTransactionListener listener)
- {
- if (inCallbackHandler) {
- pendingRemoval.add(listener);
- }//if
- else {
- listeners.remove(listener);
- }//else
- }//removeTransactionListener
-
- @Override
- public void setLastQuery(String lastQuery)
- {
- this.lastQuery = lastQuery;
- }
-
- @Override
- public String getLastQuery()
- {
- return lastQuery;
- }
-
- @Override
- public int getTransactionDepth()
- {
- return transactionDepth.get();
- }
-
- @Override
- public int getOpenLevel()
- {
- return openStack.size();
- }
-
- @Override
- public String getConnectionName()
- {
- return connectionName;
- }
-
- @Override
- public void setConnectionName(String connectionName)
- {
- this.connectionName = connectionName;
- }
-
- @SuppressWarnings({"java:S1141", "java:S2077", "tainting"})
- //Having try-resource in a bigger try block is allowed. Dynamically formatted SQL is verified to be safe
- @Override
- @Nonnull
- public Connection getConnection(String connectionName)
- {
- checkReleaseState();
- checkThread();
-
- openStack.push(transactionDepth.get());
- connectionNames.push(this.connectionName);
-
- if (connectionName == null) {
- if (this.connectionName == null) {
- this.connectionName = Thread.currentThread().getName();
- }//if
- }//if
- else {
- this.connectionName = connectionName;
- }//else
- LOG.trace("Opening persistence context. Level: {} with cursor {}", openStack.size(), this.connectionName);
-
- if (connection == null) {
- try {
- connection = new ConnectionWrapper(this, pool.getConnection(), slowQueryTime);
-
- try (Statement writeStmt = connection.createStatement()) {
- String applicationName = Application.currentApplication().getName() + "@" + hostname;
- if (applicationName.length() > 61) {
- applicationName = applicationName.substring(0, 61);
- }//if
- String applicationNameQry = "set application_name to '" + applicationName + "'";
- writeStmt.execute(applicationNameQry);
- }//try
- catch (SQLException ex) {
- LOG.error("Error setting the JDBC application name", ex);
- }//catch
-
- connection.setEnableLogging(showSql);
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("Error configuring database connection", ex);
- }//catch
- }//if
-
- connection.setName(this.connectionName);
-
- if (isAutoJoinTransaction()) {
- joinTransaction();
- }//if
-
- return connection;
- }//getConnection
-
- @Override
- public boolean isReleased()
- {
- return released;
- }//if
-
- @Override
- public void release()
- {
- checkThread();
-
- if (connection != null) {
- LOG.warn("Closing unexpected open transaction on {}", connection, new PersistenceException("Possible unhandled exception"));
- openStack.clear();
- connectionNames.clear();
-
- close();
- }//if
-
- released = true;
- }//release
-
- @Override
- public void close()
- {
- checkOpen();
- checkThread();
-
- LOG.trace("Closing connection level: {}", openStack.size());
- if (!connectionNames.isEmpty()) {
- connectionName = connectionNames.pop();
- }//if
-
- if (!openStack.isEmpty()) {
- int transDepth = openStack.pop();
- if (transDepth < this.transactionDepth.get()) {
- LOG.warn("Closing unexpected open transaction", new PersistenceException("Possible unhandled exception"));
- rollbackToDepth(transDepth);
-
- //Check if the rollback closed the connection, if so we are done
- if (connection == null) {
- return;
- }//if
- }//if
- }//if
-
- if (openStack.isEmpty()) {
- LOG.trace("At level 0, releasing connection {}", connection);
-
- l1Cache().clear();
- openStack.clear();
- connectionNames.clear();
- savepoints.clear();
- transactionDepth.set(0);
- rollbackOnly = false;
- readOnly = false;
- try {
- if (!connection.isClosed() && !connection.getAutoCommit()) {
- connection.rollback();
- connection.setAutoCommit(true);
- }//if
- connection.realClose();
- connection = null;
- }//try
- catch (SQLException ex) {
- LOG.error("Error closing connection", ex);
- }//catch
- }//if
-
- }//close
-
- @Override
- public X mapResultSet(@Nonnull X entity, ResultSet resultSet)
- {
- return mapResultSet(entity, null, resultSet);
- }
-
- @Override
- public X mapResultSet(@Nonnull X entity, String colPrefix, ResultSet resultSet)
- {
- ((JPAEntity) entity)._setPersistenceContext(this);
- ((JPAEntity) entity)._mapResultSet(colPrefix, resultSet);
- l1Cache().manage((JPAEntity) entity);
- return entity;
- }
-
- private boolean doesNeedFlushing(JPAEntity entity)
- {
- if (entity._getPersistenceContext() != this) {
- throw new PersistenceException("Entity belongs to another persistence context and cannot be updated. I am [" + this + "], Entity [" + entity + "]");
- }//if
-
- return entity._getEntityState() == EntityState.MANAGED && (entity._getPendingAction() != PersistenceAction.NONE || entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT);
- }//doesNeedFlushing
-
- @Override
- public void flush()
- {
- checkOpen();
- checkThread();
-
- l1Cache().foreach(e ->
- {
- if (doesNeedFlushing((JPAEntity) e)) {
- flushEntityInternal((JPAEntity) e);
- }//if
- });
- }//flush
-
- @Override
- public void flushOnType(Class> entityClass)
- {
- checkOpen();
- checkThread();
-
- l1Cache().foreachType(entityClass, e ->
- {
- if (doesNeedFlushing((JPAEntity) e)) {
- flushEntityInternal((JPAEntity) e);
- }//if
- });
- }//flushOnType
-
- @Override
- public void flushEntity(@Nonnull JPAEntity entity)
- {
- checkOpen();
- checkThread();
- checkEntityAttached(entity);
-
- flushEntityInternal(entity);
- }
-
- @SuppressWarnings("java:S6205") //Not a redundant block
- private void invokeCallbackHandlers(PersistenceAction action, boolean preAction, Object entity)
- {
- /*
- * Callback are not invoked if the transaction is marked for rollback
- */
- if (!getRollbackOnly()) {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entity.getClass());
- try {
- switch (action) {
- case INSERT -> {
- if (preAction) {
- metaData.getLifecycleListeners().prePersist(entity);
- }
- else {
- metaData.getLifecycleListeners().postPersist(entity);
- }
- }
- case UPDATE -> {
- if (preAction) {
- metaData.getLifecycleListeners().preUpdate(entity);
- }
- else {
- metaData.getLifecycleListeners().postUpdate(entity);
- }
- }
- case DELETE -> {
- if (preAction) {
- metaData.getLifecycleListeners().preRemove(entity);
- }
- else {
- metaData.getLifecycleListeners().postRemove(entity);
- }
- }
- default -> {//do nothing
- }
- }//switch
- }//try
- catch (PersistenceException ex) {
- setRollbackOnly();
- throw ex;
- }//catch
- }//if
- }//invokeCallbackHandlers
-
- private void bindParameters(PreparedStatement statement, Object... params)
- {
- if (params != null) {
- int startAt = 0;
-
- for (Object param : params) {
- try {
- startAt++;
-
- if (param instanceof Boolean) {
- param = param == Boolean.TRUE ? 1 : 0;
- }//if
- if (param instanceof byte[] vBytes) {
- statement.setBytes(startAt, vBytes);
- }//if
- else {
- statement.setObject(startAt, param, Types.OTHER);
- }//else
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("Error setting parameter (" + startAt + "=" + param, ex);
- }//catch
- }//for
- }//if
- }//bindParameters
-
- private boolean isOptimisticLocked(JPAEntity entity)
- {
- return (entity._getLockMode() == LockModeType.OPTIMISTIC || entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT);
- }//isOptimisticLocked
-
- @SuppressWarnings("unchecked")
- private void cascadePersist(Set mappings, @Nonnull JPAEntity entity)
- {
- try {
- for (EntityField field : entity._getMetaData().getEntityFields()) {
- if ((field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.PERSIST))) {
-
- if (field.getMappingType() == MappingType.ONE_TO_MANY && mappings.contains(MappingType.ONE_TO_MANY)) {
- List entityList = (List) field.invokeGetter(entity);
- if (entityList != null) {
- entityList.stream()
- //Check if the entity is new and unattached or was persisted but not flushed
- .filter(e -> (e._getEntityState() == EntityState.TRANSIENT || e._getPendingAction() == PersistenceAction.INSERT))
- .forEach(e ->
- {
- try {
- EntityField entityField = e._getMetaData().getEntityField(field.getMappedBy());
- entityField.invokeSetter(e, entity);
- e._setPendingAction(PersistenceAction.INSERT);
- l1Cache().manage(e);
- flushEntity(e);
- }//try
- catch (RuntimeException ex) {
- setRollbackOnly();
- throw new PersistenceException("Error cascading persist entity", ex);
- }//catch
- });
- }//if
- entity._clearField(field.getName());
- }//if
- else if ((field.getMappingType() == MappingType.MANY_TO_ONE && mappings.contains(MappingType.MANY_TO_ONE) || (field.getMappingType() == MappingType.ONE_TO_ONE && mappings.contains(MappingType.ONE_TO_ONE)))) {
- JPAEntity jpaEntity = (JPAEntity) field.invokeGetter(entity);
- flushEntity(jpaEntity);
- }//else if
-
- }//if
- }//for
- }//try
- catch (RuntimeException ex) {
- setRollbackOnly();
- throw new PersistenceException("Error cascading persist entity", ex);
- }//catch
- }//cascadePersist
-
- @SuppressWarnings("unchecked")
- private void cascadeRemove(Set mappings, @Nonnull JPAEntity entity)
- {
- try {
- for (EntityField field : entity._getMetaData().getEntityFields()) {
-
- if (mappings.contains(field.getMappingType()) && (field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.REMOVE))) {
- if (mappings.contains(MappingType.MANY_TO_ONE) || mappings.contains(MappingType.ONE_TO_ONE)) {
- JPAEntity entityValue = (JPAEntity) field.invokeGetter(entity);
- if (entityValue != null && !entityValue._isLazyLoaded()) {
- entityValue._setPendingAction(DELETE);
- flushEntity(entityValue);
- }//if
- }//if
- else if (mappings.contains(MappingType.ONE_TO_MANY)) {
- List entityList = (List) field.invokeGetter(entity);
- if (entityList != null) {
- entityList.stream()
- .filter(e -> (!e._isLazyLoaded()))
- .forEach(e ->
- {
- e._setPendingAction(DELETE);
- flushEntity(e);
- });
- }//if
- }//else if
- }//if
- }//for
- }//try
- catch (RuntimeException ex) {
- setRollbackOnly();
- throw new PersistenceException("Error cascading remove entity", ex);
- }//catch
- }//cascadeRemove
-
- private EntityQuery getFlushQuery(PersistenceAction action, @Nonnull JPAEntity entity)
- {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entity.getClass());
- return switch (action) {
- case INSERT -> {
- cascadePersist(Set.of(MappingType.MANY_TO_ONE), entity);
- yield new EntityInsertQueryImpl(entity, metaData);
- }
- case UPDATE -> new EntityUpdateQueryImpl(entity, metaData);
- case DELETE -> {
- cascadeRemove(Set.of(MappingType.ONE_TO_MANY, MappingType.ONE_TO_ONE), entity);
- yield new EntityDeleteQueryImpl(entity, metaData);
- }
- default -> throw new IllegalStateException("Unexpected value: " + action);
- };
- }//getFlushQuery
-
- private void flushEntityInternal(@Nonnull JPAEntity entity)
- {
- PersistenceAction action = entity._getPendingAction();
- if (action == NONE) {
- /*
- If the entity is not new and is not dirty but is locked optimistically, we need to update the version
- */
- if (entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT) {
- action = UPDATE;
- }//if
- else {
- return;
- }//else
- }//if
-
- Span span = TRACER.spanBuilder("PersistenceContextImpl::flushEntity").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- span.setAttribute("action", action.name());
- invokeCallbackHandlers(action, true, entity);
- if (!getRollbackOnly()) {
- entity._setPendingAction(NONE);
- EntityQuery flushQuery = getFlushQuery(action, entity);
-
- if (flushQuery.getQuery() != null && !flushQuery.getQuery().isBlank()) {
- String sqlQuery = flushQuery.getQuery();
- span.setAttribute("query", sqlQuery);
-
- //noinspection SqlSourceToSinkFlow
- try (PreparedStatement statement = connection.prepareStatement(sqlQuery, Statement.RETURN_GENERATED_KEYS)) {
- bindParameters(statement, flushQuery.getParameters());
-
- int rows = statement.executeUpdate();
- if (rows > 0) {
- if (action == PersistenceAction.DELETE) {
- entity._setEntityState(EntityState.REMOVED);
- if (entity._getMetaData().isCacheable()) {
- l2Cache().evict(entity._getEntityClass(), entity._getPrimaryKey());
- }//if
-
- cascadeRemove(Set.of(MappingType.MANY_TO_ONE), entity);
- }//if
- else {
- if (action == PersistenceAction.INSERT) {
- try (ResultSet vResultSet = statement.getGeneratedKeys()) {
- if (vResultSet.next()) {
- entity._setPersistenceContext(this);
- entity._mapResultSet(null, vResultSet);
- }//if
- }//try
-
- if (cacheStoreMode == CacheStoreMode.USE) {
- l2Cache().add(entity);
- }//else if
- }//if
- else if (entity._getMetaData().isCacheable() && cacheStoreMode != CacheStoreMode.BYPASS) {
- l2Cache().replace(entity);
- }//else if
-
- cascadePersist(Set.of(MappingType.ONE_TO_MANY, MappingType.ONE_TO_ONE), entity);
- }//else
- }//if
- /*
- * If zero rows were updated or deleted and the entity was optimistic locked, then throw an exception
- */
- else if (action != INSERT && isOptimisticLocked(entity)) {
- setRollbackOnly();
- throw new OptimisticLockException(entity);
- }//else if
- }//try
- catch (SQLException ex) {
- setRollbackOnly();
-
- LOG.error("Failed to flush entity {}, Query: {}", entity._getMetaData().getName(), flushQuery.getQuery(), ex);
- throw new PersistenceException("Error persisting entity in database");
- }//catch
- }//if
- }//if
-
- entity._clearModified();
- invokeCallbackHandlers(action, false, entity);
- }//try
- finally {
- span.end();
- }//finally
- }//flushEntity
-
-
- //
- @Override
- public void setAutoJoinTransaction()
- {
- autoJoinTransaction = true;
- }//setAutoJoinTransaction
-
- @Override
- public boolean isAutoJoinTransaction()
- {
- return autoJoinTransaction;
- }//isAutoJoinTransactions
-
- @Override
- public void joinTransaction()
- {
- if (!joinedToTransaction) {
- try {
- if (transactionManager == null) {
- transactionManager = (TransactionManager) CDI.current().select(getClass().getClassLoader().loadClass(TransactionManager.class.getName())).get();
- if (transactionManager == null) {
- throw new ClassNotFoundException("Transaction Manager not set");
- }//if
- }//if
-
- //If we not in a JTA transaction, escape here
- if (!isInJTATransaction()) {
- return;
- }//if
-
- joinedToTransaction = true;
-
- switch (transactionManager.getStatus()) {
- case Status.STATUS_ACTIVE, Status.STATUS_PREPARED, Status.STATUS_PREPARING -> begin();
- case Status.STATUS_MARKED_ROLLBACK -> {
- begin();
- setRollbackOnly();
- }
- default ->
- throw new TransactionRequiredException("Explicitly joining a JTA transaction requires a JTA transaction be currently active");
- }//switch
- }//try
- catch (ClassNotFoundException ex) {
- throw new PersistenceException("No JTA TransactionManager found, mostly likely this is not an EE application", ex);
- }//catch
- catch (SystemException ex) {
- throw new PersistenceException(ex.getMessage(), ex);
- }//catch
- }//if
- }//joinTransaction
-
- @Override
- public boolean isJoinedToTransaction()
- {
- return joinedToTransaction;
- }
-
- private boolean isInJTATransaction()
- {
- if (transactionManager != null) {
- try {
- int status = transactionManager.getStatus();
- return (status == Status.STATUS_ACTIVE || status == Status.STATUS_COMMITTING || status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_PREPARED || status == Status.STATUS_PREPARING);
- }
- catch (Exception ex) {
- throw new PersistenceException(ex);
- }
- }//if
- return false;
- }//joinTransaction
-
-
- @Override
- public void afterCompletion(int status)
- {
- if (isActive() && status == Status.STATUS_ROLLEDBACK) {
- setRollbackOnly();
- rollback();
- }//if
- }//afterCompletion
-
- private void rollbackToDepth(int depth)
- {
- while (transactionDepth.get() > depth) {
- rollback();
- }//while
- }//rollbackToDepth
-
- @Override
- public EntityTransaction getTransaction()
- {
- if (isAutoJoinTransaction() || isJoinedToTransaction()) {
- throw new IllegalStateException("Transaction is not accessible when using JTA with JPA-compliant transaction access enabled");
- }//if
-
- return this;
- }//getTransaction
-
- private void transactionCallback(CallbackMethod callback)
- {
- checkRecursiveCallback();
-
- inCallbackHandler = true;
- for (EntityTransactionListener listener : listeners) {
- switch (callback) {
- case PRE_BEGIN -> listener.preTransactionBeginEvent();
- case POST_BEGIN -> listener.postTransactionBeginEvent();
- case PRE_COMMIT -> listener.preTransactionCommitEvent();
- case POST_COMMIT -> listener.postTransactionCommitEvent();
- case PRE_ROLLBACK -> listener.preTransactionRollbackEvent();
- case POST_ROLLBACK -> listener.postTransactionRollbackEvent();
- }//switch
- }//for
- inCallbackHandler = false;
-
- if (!pendingRemoval.isEmpty()) {
- pendingRemoval.forEach(listeners::remove);
- }//if
-
- if (!pendingAdd.isEmpty()) {
- listeners.addAll(pendingAdd);
- }//if
- }//transactionCallback
-
- @Override
- public void begin()
- {
- checkReleaseState();
- checkThread();
- Span span = TRACER.spanBuilder("PersistenceContextImpl::begin").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignore = span.makeCurrent()) {
- checkRecursiveCallback();
-
- if (isActive()) {
- if (getRollbackOnly()) {
- throw new IllegalStateException("Transaction is current in a rollback only state");
- }//if
- LOG.trace("Set a savepoint at depth {}", transactionDepth.get());
- savepoints.add(connection.setSavepoint());
- transactionDepth.incrementAndGet();
- LOG.debug("Legacy support - Transaction is already active, using depth counter");
- }//if
- else {
- LOG.trace("Beginning a new transaction on {}", this);
- transactionCallback(CallbackMethod.PRE_BEGIN);
- rollbackOnly = false;
- getConnection(connectionName).setAutoCommit(false);
- transactionDepth.set(1);
- l2Cache().begin();
- transactionCallback(CallbackMethod.POST_BEGIN);
- }//else
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("Error beginning a transaction", ex);
- }//catch
- catch (SystemException ex) {
- throw new PersistenceException("Error beginning a transaction in TransactionManager", ex);
- }//catch
- finally {
- span.end();
- }
- }//begin
-
- @Override
- public void commit()
- {
- checkThread();
-
- if (isActive()) {
- Span span = TRACER.spanBuilder("PersistenceContextImpl::commit").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- if (getRollbackOnly()) {
- span.setStatus(StatusCode.ERROR, "Transaction marked for rollback and cannot be committed");
- throw new RollbackException("Transaction marked for rollback and cannot be committed");
- }//if
-
- if (transactionDepth.decrementAndGet() > 0) {
- if (LOG.isTraceEnabled()) {
- LOG.trace("Commit savepoint at depth {}", transactionDepth.get());
- }//if
- savepoints.removeLast();
- return;
- }//if
-
- transactionCallback(CallbackMethod.PRE_COMMIT);
-
- flush();
- connection.commit();
- connection.setAutoCommit(true);
- l1Cache().clear();
- l2Cache().commit();
-
- transactionCallback(CallbackMethod.POST_COMMIT);
- close();
- LOG.trace("Transaction Committed on {}", this);
- }//try
- catch (SQLException ex) {
- setRollbackOnly();
- throw new PersistenceException("Error committing transaction", ex);
- }//catch
- catch (SystemException ex) {
- setRollbackOnly();
- throw new PersistenceException("Error committing transaction in TransactionManager", ex);
- }//catch
- finally {
- span.end();
- }//finally
- }//if
- transactionDepth.set(0);
- }//commit
-
- @Override
- public void rollback()
- {
- checkThread();
- Span span = TRACER.spanBuilder("PersistenceContextImpl::rollback").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- if (isActive()) {
- if (transactionDepth.decrementAndGet() > 0) {
- connection.rollback(savepoints.pop());
- if (LOG.isTraceEnabled()) {
- LOG.trace("Rolling back to savepoint at depth {}", transactionDepth.get());
- }//if
- rollbackOnly = false;
- return;
- }//if
-
- transactionCallback(CallbackMethod.PRE_ROLLBACK);
-
- rollbackOnly = false;
- connection.rollback();
- l2Cache().rollback();
- connection.setAutoCommit(true);
-
- l1Cache().clear();
- transactionCallback(CallbackMethod.POST_ROLLBACK);
-
- close();
- LOG.trace("Transaction rolled back on {}", this);
- }//if
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("Error rolling transaction back", ex);
- }//catch
- catch (SystemException ex) {
- throw new PersistenceException("Error rolling transaction back in TransactionManager", ex);
- }//catch
- finally {
- span.end();
- }//finally
- }//rollback
-
- @Override
- public void setRollbackOnly()
- {
- rollbackOnly = true;
- }
-
- @Override
- public boolean getRollbackOnly()
- {
- return rollbackOnly;
- }
-
- @Override
- public boolean isActive()
- {
- return transactionDepth.get() > 0;
- }
- //
-
- public boolean isReadonly()
- {
- return readOnly;
- }
-
- //
- public long getSlowQueryTime()
- {
- return slowQueryTime;
- }
-
- public void setSlowQueryTime(long pSlowQueryTime)
- {
- slowQueryTime = pSlowQueryTime;
- }
-
- public boolean isEnableLogging()
- {
- return connection.isEnableLogging();
- }
-
- public void setEnableLogging(boolean pEnableLogging)
- {
- checkOpen();
- connection.setEnableLogging(pEnableLogging && showSql);
- }//setEnableLogging
-
- public void setReadonly(boolean pReadonly)
- {
- readOnly = pReadonly;
- }
-
- public void setAuditWriter(PrintWriter pAuditWriter)
- {
- connection.setAuditWriter(pAuditWriter);
- }
-
- public PrintWriter getAuditWriter()
- {
- return connection.getAuditWriter();
- }
- //
-
- @SuppressWarnings("unchecked")
- @Override
- public T unwrap(Class cls)
- {
- if (cls.isAssignableFrom(this.getClass())) {
- return (T) this;
- }
-
- if (cls.isAssignableFrom(pool.getClass())) {
- return (T) pool;
- }//if
-
- throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
- }//unwrap
-}//PersistenceContextImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/parsers/JPQLParser.java b/jpalite-core/src/main/java/io/jpalite/impl/parsers/JPQLParser.java
deleted file mode 100644
index edbfdbe..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/JPQLParser.java
+++ /dev/null
@@ -1,1070 +0,0 @@
-/*
- * 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.parsers;
-
-import io.jpalite.*;
-import io.jpalite.impl.queries.QueryParameterImpl;
-import io.jpalite.parsers.QueryParser;
-import io.jpalite.parsers.QueryStatement;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.PersistenceException;
-import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.expression.*;
-import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
-import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
-import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import net.sf.jsqlparser.schema.Column;
-import net.sf.jsqlparser.schema.Table;
-import net.sf.jsqlparser.statement.Statement;
-import net.sf.jsqlparser.statement.delete.Delete;
-import net.sf.jsqlparser.statement.insert.Insert;
-import net.sf.jsqlparser.statement.select.*;
-import net.sf.jsqlparser.statement.update.Update;
-import net.sf.jsqlparser.statement.update.UpdateSet;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-@SuppressWarnings("java:S1452") //generic wildcard is required
-public class JPQLParser extends JsqlVistorBase implements QueryParser
-{
-
- private enum Phase
- {
- FROM,
- JOIN,
- SELECT,
- WHERE,
- GROUP_BY,
- HAVING,
- ORDERBY
- }
-
- /**
- * The parsed query
- */
- private final String query;
- private QueryStatement queryStatement = QueryStatement.OTHER;
-
- /**
- * Starting number to generate unique tables aliases
- */
- private int tableNr = 1;
- /**
- * List of return types
- */
- private final Map> returnTypes;
- /**
- * List of tables used
- */
- private final List entityInfoList;
- /**
- * We may use either positional or named parameters, but we cannot mix them within the same query.
- */
- private boolean usingNamedParameters;
- /**
- * Map of parameters used in the query
- */
- private final List> queryParameters;
- /**
- * Instance of the defined joins in the query
- */
- private List joins;
- /**
- * State variable used to indicate that in section we are processing
- */
- private Phase currentPhase = Phase.FROM;
- /**
- * The "from" table in the select statement
- */
- private Table fromTable = null;
- /**
- * If not null the fetchtype settings on the basic fields are ignored and this value is used
- */
- private FetchType overrideBasicFetchType = null;
- /**
- * If not null the fetchtype settings on the ALL fields are ignored and this value is used
- */
- private FetchType overrideAllFetchType = null;
- private boolean selectUsingPrimaryKey = false;
- private boolean usingSubSelect = false;
- private String tableAlias = null;
-
- public class EntityInfo
- {
- private final List aliases;
- private final EntityMetaData> metadata;
- private final String tableAlias;
-
- public EntityInfo(String alias, EntityMetaData> metaData)
- {
- aliases = new ArrayList<>();
- aliases.add(alias);
- metadata = metaData;
- tableAlias = "t" + tableNr;
- tableNr++;
- }
-
- public EntityInfo(String alias, EntityMetaData> metaData, String tableAlias)
- {
- aliases = new ArrayList<>();
- aliases.add(alias);
- metadata = metaData;
- this.tableAlias = tableAlias;
- }
-
- @Override
- public String toString()
- {
- return aliases.getFirst() + "->" + metadata + ", " + metadata.getTable() + " " + tableAlias;
- }
-
- public String getColumnAlias()
- {
- return aliases.getFirst();
- }//getColumnAlias
-
- public void addColAlias(String alias)
- {
- aliases.add(alias);
- }
-
- public boolean containsEntityAlias(String alias)
- {
- return aliases.contains(alias);
- }
-
- public String getTableAlias()
- {
- return tableAlias;
- }
-
- public EntityMetaData> getMetadata()
- {
- return metadata;
- }
- }//EntityInfo
-
- /**
- * Constructor for the class. The method takes as input a JQPL Statement and convert it to a Native Statement. Note
- * that the original pStatement is modified
- *
- * @param rawQuery The JQPL query
- * @param queryHints The query hints
- */
- public JPQLParser(String rawQuery, Map queryHints)
- {
- returnTypes = new LinkedHashMap<>();
- entityInfoList = new ArrayList<>();
- usingNamedParameters = false;
- queryParameters = new ArrayList<>();
-
- if (queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE) != null) {
- overrideAllFetchType = (FetchType) queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE);
- }//if
-
- if (queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE) != null) {
- overrideBasicFetchType = (FetchType) queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE);
- }//if
-
- try {
- Statement vStatement = CCJSqlParserUtil.parse(rawQuery);
- vStatement.accept(this);
- query = vStatement.toString().replace(":?", "?");
- }//try
- catch (JSQLParserException ex) {
- throw new PersistenceException("Error parsing query", ex);
- }//catch
- }//JpqlToNative
-
- @Override
- public boolean isSelectUsingPrimaryKey()
- {
- return selectUsingPrimaryKey;
- }//isSelectUsingPrimaryKey
-
- EntityInfo findEntityInfoWithTableAlias(String tableAlias)
- {
- for (EntityInfo vInfo : entityInfoList) {
- if (vInfo.getTableAlias().equals(tableAlias)) {
- return vInfo;
- }//if
- }//for
- return null;
- }//findEntityInfoWithTableAlias
-
- EntityInfo findEntityInfoWithColAlias(String colAlias)
- {
- for (EntityInfo vInfo : entityInfoList) {
- if (vInfo.containsEntityAlias(colAlias)) {
- return vInfo;
- }//if
- }//for
- return null;
- }//findEntityInfoWithColAlias
-
- EntityInfo findEntityInfoWithEntity(Class> entityClass)
- {
- for (EntityInfo info : entityInfoList) {
- if (info.getMetadata().getEntityClass().equals(entityClass)) {
- return info;
- }//if
- }//for
- return null;
- }//findEntityInfoWithEntity
-
- @Override
- public QueryStatement getStatement()
- {
- return queryStatement;
- }
-
- /**
- * Return the Native query
- *
- * @return the SQL query
- */
- @Override
- public String getQuery()
- {
- return query;
- }//getNativeStatement
-
- /**
- * Return the type of parameter that is used.
- *
- * @return True if using named parameters
- */
- @Override
- public boolean isUsingNamedParameters()
- {
- return usingNamedParameters;
- }//isUsingNamedParameters
-
- @Override
- public int getNumberOfParameters()
- {
- return queryParameters.size();
- }//getNumberOfParameters
-
- /**
- * Return a map of the query parameters defined.
- *
- * @return The query parameters
- */
- @Override
- public List> getQueryParameters()
- {
- return queryParameters;
- }
-
- /**
- * Return a list of all the return type in the select
- *
- * @return the list
- */
- @Override
- public List> getReturnTypes()
- {
- return new ArrayList<>(returnTypes.values());
- }//getReturnTypes
-
- /**
- * Check if the given return type is provided by the JQPL guery. If not an IllegalArgumentException exception is
- * generated
- *
- * @param typeToCheck The class to check
- * @throws IllegalArgumentException if the type is not provided
- */
- @Override
- public void checkType(Class> typeToCheck)
- {
- if (queryStatement == QueryStatement.SELECT) {
- if (!typeToCheck.isArray()) {
- if (returnTypes.size() > 1) {
- throw new IllegalArgumentException("Type specified for Query [" + typeToCheck.getName() + "] does not support multiple result set.");
- }//if
- if (!returnTypes.get("c1").isAssignableFrom(typeToCheck)) {
- throw new IllegalArgumentException("Type specified for Query [" + typeToCheck.getName() + "] is incompatible with query return type [" + returnTypes.get("c1").getName() + "]");
- }//if
- }//if
- else {
- if (typeToCheck != byte[].class && typeToCheck != Object[].class) {
- throw new IllegalArgumentException("Cannot create TypedQuery for query with more than one return using requested result type " + typeToCheck.arrayType().getName() + "[]");
- }//if
- }//else
- }//if
- }//checkType
-
- private void joinAccept(Join join)
- {
- if (!join.getOnExpressions().isEmpty()) {
- throw new IllegalArgumentException("JOIN ON is not supported in JQPL - " + join);
- }//if
-
- //JOIN . eg e.department d
- Table joinTable = (Table) join.getRightItem();
- String joinField = joinTable.getName(); //=department
- String fromEntity = joinTable.getSchemaName(); //=e
-
- String joinAlias;
- if (joinTable.getAlias() != null) {
- joinAlias = joinTable.getAlias().getName(); // =d
- }//if
- else {
- //No Alias was set. Make it the same as the schema.table value
- joinAlias = joinTable.getFullyQualifiedName(); //.=e.department
- joinTable.setAlias(new Alias(joinAlias, false));
- }//else
-
- joinTable.accept(this);
- EntityInfo joinEntityInfo = findEntityInfoWithColAlias(joinAlias); // =d or .=e.department
-
- /*
- * If the schema name is not present we are busy with a new Cartesian style join
- * eg select d from Employee e, Department d where ...
- * This case we just process the table
- */
- if (fromEntity != null) {
- EntityInfo fromEntityInfo = findEntityInfoWithColAlias(fromEntity);
- EntityField fromEntityField = fromEntityInfo.getMetadata().getEntityField(joinField); //=department
- EntityField joinEntityField;
- if (fromEntityField.getMappedBy() != null) {
- joinEntityField = joinEntityInfo.getMetadata().getEntityField(fromEntityField.getMappedBy());
- if (fromEntityInfo.getMetadata().hasMultipleIdFields()) {
- throw new IllegalArgumentException("Cannot JOIN on multiple id fields");
- }//if
- fromEntityField = fromEntityInfo.getMetadata().getIdField();
- }//if
- else {
- if (joinEntityInfo.getMetadata().hasMultipleIdFields()) {
- throw new IllegalArgumentException("Cannot JOIN on multiple id fields");
- }//if
- joinEntityField = joinEntityInfo.getMetadata().getIdField();
- }//else
-
- BinaryExpression expression = new EqualsTo();
- expression.setLeftExpression(new Column(new Table(fromEntityInfo.getTableAlias()), fromEntityField.getColumn()));
- expression.setRightExpression(new Column(new Table(joinEntityInfo.getTableAlias()), joinEntityField.getColumn()));
- join.getOnExpressions().add(expression);
-
- if (fromEntityField.getMappingType() == MappingType.MANY_TO_ONE || fromEntityField.getMappingType() == MappingType.ONE_TO_ONE) {
- join.withInner(!fromEntityField.isNullable())
- .withLeft(fromEntityField.isNullable())
- .withRight(false)
- .withOuter(false)
- .withCross(false)
- .withFull(false)
- .withStraight(false)
- .withNatural(false);
- }//if
- }//if
- }//joinAccept
-
- private void addJoin(Table table)
- {
- Join join = new Join();
- join.setInner(true);
- join.setRightItem(table);
-
- joins.add(join);
- joinAccept(join);
- }//addJoin
-
- private EntityInfo findMappedBy(String fieldName)
- {
- for (EntityInfo info : entityInfoList) {
- for (EntityField vField : info.getMetadata().getEntityFields()) {
- if (fieldName.equals(vField.getMappedBy())) {
- //Yes, we have winner :-)
- return info;
- }//if
- }//for
- }//for
- return null;
- }//findMappedBy
-
- private void expandEntity(boolean root, EntityMetaData> entity, String selectNr, String colAlias, EntityField entityField, String tableAlias, List newList)
- {
- String newTableAlias = tableAlias + "." + entityField.getName();
- if (!root) {
- colAlias += "-" + entityField.getFieldNr();
- }//if
-
- //only XXXX_TO_ONE type mappings can be expanded
- if (entityField.getMappingType() == MappingType.ONE_TO_ONE || entityField.getMappingType() == MappingType.MANY_TO_ONE) {
- //Check if we already have a JOIN for the entity
- EntityInfo entityInfo = findEntityInfoWithEntity(entityField.getType());
- //We will expand if FetchType is EAGER or if we have an existing JOIN on the Entity
- if (entityInfo != null || (overrideAllFetchType != null && overrideAllFetchType == FetchType.EAGER) || (overrideAllFetchType == null && entityField.getFetchType() == FetchType.EAGER)) {
- if (entityInfo == null) {
- //if where have many to one mapping on the field, check if one of the other tables ( FROM and JOIN) have an ONE_TO_MANY link
- //back to this entity
- if (entityField.getMappingType() == MappingType.MANY_TO_ONE) {
- EntityInfo info = findMappedBy(entityField.getName());
- if (info != null) {
- getAllColumns(selectNr, colAlias, info.getMetadata(), info.getColumnAlias(), newList);
- return;
- }//if
- }//if
-
- Table table = new Table(tableAlias, entityField.getName());
- table.setAlias(new Alias(tableAlias + "." + entityField.getName(), false));
- addJoin(table);
- entityInfo = findEntityInfoWithEntity(entityField.getType());
- }//if
- else {
- if (!entityInfo.containsEntityAlias(newTableAlias)) {
- entityInfo.addColAlias(newTableAlias);
- }//if
- }//else
-
- getAllColumns(selectNr, colAlias, entityInfo.getMetadata(), newTableAlias, newList);
- }//if
- else {
- newList.add(createSelectColumn(entityField.getName(), selectNr + colAlias, tableAlias));
- }//else
- }//if
- else if (entityField.getMappingType() == MappingType.EMBEDDED) {
- EntityInfo entityInfo = findEntityInfoWithTableAlias(newTableAlias);
- if (entityInfo == null) {
- EntityInfo parentEntityInfo = findEntityInfoWithEntity(entity.getEntityClass());
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityField.getType());
- entityInfo = new EntityInfo(tableAlias + "." + entityField.getName(), metaData, parentEntityInfo.getTableAlias());
- entityInfoList.add(entityInfo);
- }//if
-
- getAllColumns(selectNr, colAlias, entityInfo.getMetadata(), newTableAlias, newList);
- }//else
- }//expandEntity
-
- private SelectItem createSelectColumn(String field, String colField, String tableAlias)
- {
- Column newColumn = createColumn(field, tableAlias);
- SelectExpressionItem newItem = new SelectExpressionItem(newColumn);
- if (colField != null) {
- newItem.setAlias(new Alias("\"" + colField + "\"", false));
- }//if
- return newItem;
- }//createSelectColumn
-
- private Column createColumn(String field, String tableAlias)
- {
- Table table = new Table();
- String[] parts = tableAlias.split("\\.");
- if (parts.length > 1) {
- table.setSchemaName(parts[0]);
- table.setName(tableAlias.substring(parts[1].length() + 2));
- }//if
- else {
- table.setName(parts[0]);
- }//else
-
- table.setAlias(new Alias(tableAlias, false));
- return new Column(table, field);
- }//createColumn
-
- private void getAllColumns(String selectNr, String colAlias, EntityMetaData> entity, String tableAlias, List newList)
- {
- for (EntityField field : entity.getEntityFields()) {
- if (field.getMappingType() == MappingType.BASIC) {
- if (field.isIdField() || (overrideBasicFetchType != null && overrideBasicFetchType == FetchType.EAGER) || (overrideBasicFetchType == null && field.getFetchType() == FetchType.EAGER)) {
- newList.add(createSelectColumn(field.getName(), selectNr + colAlias + "-" + field.getFieldNr(), tableAlias));
- }//if
- }//if
- else {
- expandEntity(false, entity, selectNr, colAlias, field, tableAlias, newList);
- }//else
- }//for
- }//getAllColumns
-
- private EntityInfo findEntity(String selectPath)
- {
- EntityInfo entityInfo = findEntityInfoWithColAlias(selectPath);
- if (entityInfo != null) {
- return entityInfo;
- }//if
-
- int vDot = selectPath.lastIndexOf(".");
- if (vDot == -1) {
- throw new IllegalStateException("Invalid fields specified");
- }//if
-
- String path = selectPath.substring(0, vDot);
- String field = selectPath.substring(vDot + 1);
- entityInfo = findEntityInfoWithColAlias(path);
- if (entityInfo == null) {
- entityInfo = findEntity(path);
- }//if
-
- EntityField entityField = entityInfo.getMetadata().getEntityField(field);
- if (entityField.getMappingType() == MappingType.EMBEDDED) {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityField.getType());
- entityInfo = new EntityInfo(path + "." + entityField.getName(), metaData, entityInfo.getTableAlias());
- entityInfoList.add(entityInfo);
- }//if
- else {
- Table table = new Table(path, field);
- table.setAlias(new Alias(path + "." + entityField.getName(), false));
- addJoin(table);
- }//else
-
- return findEntityInfoWithColAlias(selectPath);
- }//findEntity
-
- private void processSelectItem(String colLabel, SelectItem item, List newList)
- {
- /*
- * case 1. select e from Employee e
- * Only one select item, selecting specifically the entity
- *
- * case 2. select e.name, e.department from Employee e
- * Only one or more items from the entity
- * The fields can either be entity, embedded class or basic field.
- *
- * processSelectItem() will be called for each item found
- */
- SelectExpressionItem selectItem = (SelectExpressionItem) item;
- selectItem.setAlias(new Alias(colLabel, false));
- if (selectItem.getExpression() instanceof Column column) {
- if ("NEW".equalsIgnoreCase(column.getColumnName())) {
- throw new IllegalArgumentException("JPQL Constructor Expressions are not supported - " + column);
- }//if
-
- //Check if we are working with a full entity or a field in an entity
- if (column.getTable() == null) {
- /*
- * We will get here for any field being specified. eg select e.name | select e.department | select e.department.name
- */
-
- EntityInfo entityInfo = findEntityInfoWithColAlias(column.getColumnName());
- if (entityInfo == null) {
- throw new IllegalArgumentException("Unknown column - " + column);
- }//if
-
-
- addResultType(colLabel, entityInfo.getMetadata().getEntityClass());
- getAllColumns(colLabel, "", entityInfo.getMetadata(), column.getColumnName(), newList);
- }//if
- else {
- /*
- * We will get here for selectItem where a field was specified. Eg select e.department from Employee e
- */
- String fieldName = column.getColumnName();
- String fullPath = column.getTable().getFullyQualifiedName();
-
- //Find the Entity from the path
- EntityInfo entityInfo = findEntity(fullPath);
- EntityField entityField = entityInfo.getMetadata().getEntityField(fieldName);
- addResultType(colLabel, entityField.getType());
-
- if (entityField.getMappingType() == MappingType.BASIC) {
- newList.add(createSelectColumn(entityField.getName(), colLabel, fullPath));
- }//if
- else {
- expandEntity(true, entityInfo.getMetadata(), colLabel, "", entityField, fullPath, newList);
- }//else
- }//else
- }//if
- else {
- selectItem.setAlias(new Alias("\"" + colLabel + "\"", false));
- newList.add(item);
- }//else
- }//processSelectItem
-
- private void processSelectItems(List selectItems, List newList)
- {
- for (int nr = 0; nr < selectItems.size(); nr++) {
- String colLabel = "c" + (nr + 1);
- SelectItem item = selectItems.get(nr);
- if (item instanceof SelectExpressionItem) {
- processSelectItem(colLabel, item, newList);
- }//if
- else {
- newList.add(item);
- }//else
- }//for
- }//processSelectItems
-
- private void processUpdateSet(List updateSets)
- {
- for (UpdateSet item : updateSets) {
- ArrayList newColList = new ArrayList<>();
- for (Column column : item.getColumns()) {
- if (column.getTable() == null) {
- column.setTable(new Table("X"));
- }//if
- String fieldName = column.getColumnName();
- String fullPath = column.getTable().getFullyQualifiedName();
- EntityInfo entityInfo = findEntity(fullPath);
- EntityField entityField = entityInfo.getMetadata().getEntityField(fieldName);
- if (entityField.getMappingType() == MappingType.EMBEDDED) {
- throw new PersistenceException("Embedded field are not supported in update sets");
- }//if
- else {
- Column newCol = createColumn(fieldName, fullPath);
- newColList.add(newCol);
- }//if
- }//for
- item.setColumns(newColList);
- }//for
- }//processUpdateSet
-
- @Override
- public void visit(Update update)
- {
- queryStatement = QueryStatement.UPDATE;
- if (update.getTable().getAlias() == null) {
- update.getTable().setAlias(new Alias("X", false));
- fromTable = new Table(update.getTable().getName());
- fromTable.setAlias(new Alias("X", false));
- }//if
- update.getTable().accept(this);
- currentPhase = Phase.SELECT;
- processUpdateSet(update.getUpdateSets());
- for (UpdateSet updateSet : update.getUpdateSets()) {
- for (Column column : updateSet.getColumns()) {
- column.accept(this);
- }//for
- for (Expression expression : updateSet.getExpressions()) {
- expression.accept(this);
- }//for
- }//for
-
- currentPhase = Phase.WHERE;
- if (update.getWhere() != null) {
- update.getWhere().accept(this);
- }//if
- }//visit
-
- @Override
- public void visit(Delete delete)
- {
- queryStatement = QueryStatement.DELETE;
- if (delete.getTable().getAlias() == null) {
- delete.getTable().setAlias(new Alias(delete.getTable().getName(), false));
- fromTable = new Table(delete.getTable().getName());
- fromTable.setAlias(new Alias(delete.getTable().getName(), false));
- }//if
- delete.getTable().accept(this);
-
- currentPhase = Phase.WHERE;
- if (delete.getWhere() != null) {
- delete.getWhere().accept(this);
- }//if
- }
-
- @Override
- public void visit(Insert insert)
- {
- queryStatement = QueryStatement.INSERT;
- throw new PersistenceException("INSERT queries are not valid in JPQL");
- }
-
- private void addResultType(String column, Class> classType)
- {
- if (!usingSubSelect) {
- returnTypes.put(column, classType);
- }//if
- }
-
- @Override
- public void visit(SubSelect subSelect)
- {
- usingSubSelect = true;
-
- if (subSelect.getSelectBody() != null) {
- subSelect.getSelectBody().accept(this);
- }//if
-
- if (subSelect.getWithItemsList() != null) {
- for (WithItem item : subSelect.getWithItemsList()) {
- item.accept(this);
- }//for
- }//if
-
- usingSubSelect = false;
- }
-
- @Override
- public void visit(PlainSelect plainSelect)
- {
- queryStatement = QueryStatement.SELECT;
- currentPhase = Phase.FROM;
- if (plainSelect.getFromItem() instanceof Table table) {
- if (table.getAlias() == null) {
- tableAlias = table.getName();
- table.setAlias(new Alias(table.getName(), false));
- }//if
- fromTable = new Table(table.getName());
- fromTable.setAlias(new Alias(table.getAlias().getName(), false));
-
- plainSelect.getFromItem().accept(this);
- }
-
- currentPhase = Phase.JOIN;
- if (plainSelect.getJoins() == null) {
- joins = new ArrayList<>();
- plainSelect.setJoins(joins);
- }//if
- else {
- joins = plainSelect.getJoins();
- }//else
-
- for (Join join : joins) {
- joinAccept(join);
- }//for
-
- currentPhase = Phase.SELECT;
- if (plainSelect.getSelectItems() != null) {
-
- List selectItemList = plainSelect.getSelectItems();
- List newList = new ArrayList<>();
- plainSelect.setSelectItems(newList);
- processSelectItems(selectItemList, newList);
- for (SelectItem item : plainSelect.getSelectItems()) {
- item.accept(this);
- }//for
- }
-
- currentPhase = Phase.WHERE;
- selectUsingPrimaryKey = false; //Catch the case where there are no WHERE clause
- if (plainSelect.getWhere() != null) {
- //Set to true, if a tableColumn referencing a non-ID field is found it will be changed to false
- selectUsingPrimaryKey = true;
- plainSelect.getWhere().accept(this);
- }
-
- currentPhase = Phase.HAVING;
- if (plainSelect.getHaving() != null) {
- plainSelect.getHaving().accept(this);
- }
-
- currentPhase = Phase.GROUP_BY;
- if (plainSelect.getGroupBy() != null) {
- plainSelect.getGroupBy().accept(this);
- }//if
-
- currentPhase = Phase.ORDERBY;
- if (plainSelect.getOrderByElements() != null) {
- for (OrderByElement vElement : plainSelect.getOrderByElements()) {
- vElement.accept(this);
- }
- }//if
- }//visitPlainSelect
-
- private void addQueryParameter(Expression expression, Class parameterType)
- {
- if (expression instanceof JdbcParameter parameter) {
- if (queryParameters.isEmpty()) {
- usingNamedParameters = false;
- }//if
- else if (usingNamedParameters) {
- throw new IllegalArgumentException("Mixing positional and named parameters are not allowed");
- }//else if
-
- queryParameters.add(new QueryParameterImpl<>(parameter.getIndex(), parameterType));
- }//if
- else if (expression instanceof JdbcNamedParameter parameter) {
- if (queryParameters.isEmpty()) {
- usingNamedParameters = true;
- }//if
- else if (!usingNamedParameters) {
- throw new IllegalArgumentException("Mixing positional and named parameters are not allowed");
- }//else if
-
- queryParameters.add(new QueryParameterImpl<>(parameter.getName(), queryParameters.size() + 1, parameterType));
- }//else
- }//addQueryParameter
-
- private boolean processWhereColumn(BinaryExpression expression, Expression parameter, Column tableColumn)
- {
- EntityInfo entityInfo = findEntityInfoWithColAlias(tableColumn.getFullyQualifiedName());
-
- if (entityInfo == null && tableColumn.getTable() == null) {
- tableColumn.setTable(fromTable);
- entityInfo = findEntityInfoWithColAlias(tableColumn.getFullyQualifiedName());
- }//if
-
- if (entityInfo == null) {
- entityInfo = findEntityInfoWithColAlias(tableColumn.getTable().getName());
- if (entityInfo != null) {
- EntityField field = entityInfo.getMetadata().getEntityField(tableColumn.getColumnName());
- tableColumn.setColumnName(field.getName());
- entityInfo = findEntityInfoWithColAlias(tableColumn.getFullyQualifiedName());
- }//if
- else {
- entityInfo = findEntityInfoWithColAlias(fromTable.getAlias().getName());
- if (entityInfo != null) {
- if (tableColumn.getTable().getAlias() == null && !tableColumn.getFullyQualifiedName().startsWith(entityInfo.getColumnAlias())) {
- String schema = entityInfo.getColumnAlias();
- if (tableColumn.getTable().getSchemaName() != null) {
- schema += "." + tableColumn.getTable().getSchemaName();
- }//if
- tableColumn.getTable().setSchemaName(schema);
- }//if
- String path = tableColumn.getFullyQualifiedName();
- int dot = path.lastIndexOf('.');
- String field = path.substring(dot + 1);
- path = path.substring(0, dot);
-
- EntityInfo foundInfo = entityInfo;
- entityInfo = findJoins(path, entityInfo);
- EntityField entityField = entityInfo.getMetadata().getEntityField(field);
- if (entityField.isEntityField()) {
- entityInfo = findJoins(tableColumn.getFullyQualifiedName(), foundInfo);
- tableColumn.setColumnName(entityInfo.getMetadata().getIdField().getName());
- }
- tableColumn.getTable().setAlias(new Alias(entityInfo.getColumnAlias(), false));
- }//if
- }//else
- }//if
-
- if (entityInfo != null && (entityInfo.getMetadata().getEntityType() == EntityType.EMBEDDABLE || entityInfo.getMetadata().getEntityType() == EntityType.ID_CLASS)) {
- addQueryParameter(parameter, entityInfo.getMetadata().getEntityClass());
-
- List colList = new ArrayList<>();
- List paramList = new ArrayList<>();
- for (EntityField entityField : entityInfo.getMetadata().getEntityFields()) {
- Table table = new Table();
- table.setName(tableColumn.getTable().getFullyQualifiedName() + "." + tableColumn.getColumnName());
- colList.add(new Column(table, entityField.getName()));
- paramList.add(new JdbcParameter());
- }//for
- ValueListExpression leftList = new ValueListExpression();
- leftList.setExpressionList(new ExpressionList(colList));
- expression.setLeftExpression(leftList);
-
- ValueListExpression rightList = new ValueListExpression();
- rightList.setExpressionList(new ExpressionList(paramList));
- expression.setRightExpression(rightList);
-
- //Only visit the left tableColumn expression, we have already processed the parameters
- expression.getLeftExpression().accept(this);
-
- return false;
- }//if
-
- return true;
- }
-
- @SuppressWarnings("java:S6201") //instanceof check variable cannot be used here
- private void visitEntity(BinaryExpression expression)
- {
- Column tableColumn = null;
- Expression parameter = null;
-
- if (expression.getLeftExpression() instanceof Column && (expression.getRightExpression() instanceof JdbcParameter || expression.getRightExpression() instanceof JdbcNamedParameter)) {
- tableColumn = (Column) expression.getLeftExpression();
- parameter = expression.getRightExpression();
- }//if
- else if (expression.getRightExpression() instanceof Column && (expression.getLeftExpression() instanceof JdbcParameter || expression.getLeftExpression() instanceof JdbcNamedParameter)) {
- tableColumn = (Column) expression.getRightExpression();
- parameter = expression.getLeftExpression();
- }//else
-
- if (tableColumn != null && !processWhereColumn(expression, parameter, tableColumn)) {
- return;
- }//if
-
- expression.getLeftExpression().accept(this);
- if (expression.getRightExpression() instanceof Column vCol) {
- String s = vCol.getColumnName().toLowerCase();
- if (s.equals("true") || s.equals("false")) {
- expression.setRightExpression(new BooleanValue(vCol.getColumnName()));
- }//if
- }//if
- expression.getRightExpression().accept(this);
- }
-
- @Override
- public void visit(EqualsTo pExpression)
- {
- visitEntity(pExpression);
- }
-
- @Override
- public void visit(NotEqualsTo pExpression)
- {
- visitEntity(pExpression);
- }
-
- @Override
- public void visit(Table tableName)
- {
- if (tableName.getAlias() == null) {
- throw new IllegalArgumentException("Missing alias for " + tableName.getName());
- }//if
-
- EntityInfo entityInfo;
- EntityMetaData> metaData;
- if (tableName.getSchemaName() != null) {
- entityInfo = findEntityInfoWithColAlias(tableName.getSchemaName());
- if (entityInfo == null) {
- throw new IllegalArgumentException("Invalid schema - " + tableName);
- }//if
-
- EntityField field = entityInfo.getMetadata().getEntityField(tableName.getName());
- metaData = EntityMetaDataManager.getMetaData(field.getType());
- }//if
- else {
- metaData = EntityMetaDataManager.getMetaData(tableName.getName());
- tableName.setName(tableName.getAlias().getName());
- }//else
-
- entityInfo = new EntityInfo(tableName.getAlias().getName(), metaData);
- entityInfoList.add(entityInfo);
-
- tableName.setAlias(new Alias(entityInfo.getTableAlias(), false));
- tableName.setName(metaData.getTable());
- tableName.setSchemaName(null);
- }//visitTable
-
- @SuppressWarnings("java:S1643") //StringBuilder cannot be used here
- private EntityInfo findJoins(String path, EntityInfo entityInfo)
- {
- String[] pathElements = path.split("\\.");
- String pathElement = pathElements[0];
- for (int i = 1; i < pathElements.length; i++) {
- EntityField field = entityInfo.getMetadata().getEntityField(pathElements[i]);
- if (!field.isEntityField()) {
- break;
- }//if
-
- pathElement = pathElement + "." + field.getName();
- entityInfo = findEntity(pathElement);
- }//for
-
- return entityInfo;
- }//findJoins
-
- @Override
- public void visit(Column tableColumn)
- {
- /*
- * A Column can either be point to an entity in which case we need to use the primary key field or to the actual field
- */
- EntityInfo entityInfo = findEntityInfoWithColAlias(tableColumn.getFullyQualifiedName());
- if (entityInfo == null) {
- if (tableColumn.getTable() == null) {
- tableColumn.setTable(fromTable);
- }//if
- String colPath = tableColumn.getName(true);
- int dot = colPath.lastIndexOf('.');
- if (dot == -1) {
- throw new IllegalArgumentException("Missing alias on column '" + tableColumn + "'");
- }//if
- colPath = colPath.substring(0, dot);
-
- entityInfo = findEntityInfoWithColAlias(colPath);
- if (entityInfo == null) {
- if (this.tableAlias != null) {
- String[] fullName = tableColumn.getFullyQualifiedName().split("\\.");
- List parts = new ArrayList<>();
- parts.add(tableAlias);
- parts.addAll(List.of(fullName));
- tableColumn.setTable(new Table(parts.subList(0, parts.size() - 1)));
- tableColumn.setColumnName(parts.getLast());
- colPath = tableColumn.getName(true);
- dot = colPath.lastIndexOf('.');
- colPath = colPath.substring(0, dot);
-
- entityInfo = findEntity(colPath);
- }//if
-
- if (entityInfo == null) {
- throw new IllegalArgumentException("Missing entity alias prefix on column '" + tableColumn + "'");
- }//if
- }//if
-
- EntityField field = entityInfo.getMetadata().getEntityField(tableColumn.getColumnName());
- tableColumn.setColumnName(field.getColumn());
- tableColumn.setTable(new Table(entityInfo.getTableAlias()));
- if (currentPhase == Phase.WHERE && (!entityInfo.getTableAlias().equals("t1") || !field.isIdField())) {
- selectUsingPrimaryKey = false;
- }
- }//if
- else {
- if (entityInfo.getMetadata().hasMultipleIdFields()) {
- throw new IllegalArgumentException("WHERE on Entity columns with multiple ID fields are not supported - " + tableColumn);
- }//if
-
- tableColumn.setTable(new Table(entityInfo.getTableAlias()));
- tableColumn.setColumnName(entityInfo.getMetadata().getIdField().getColumn());
- if (currentPhase == Phase.WHERE && !entityInfo.getTableAlias().equals("t1")) {
- selectUsingPrimaryKey = false;
- }//if
- }//else
- }//visitColumn
-
- @Override
- public void visit(SelectExpressionItem selectExpressionItem)
- {
- selectExpressionItem.getExpression().accept(this);
- }//visitSelectExpressionItem
-
- @Override
- public void visit(Function function)
- {
- if (function.getParameters() != null) {
- for (Expression item : function.getParameters().getExpressions()) {
- /*
- * Only add a return type if the function was used in the select
- */
- if (currentPhase == Phase.SELECT) {
- addResultType("c" + (returnTypes.size() + 1), Object.class);
- }//if
-
- item.accept(this);
- }//for
- }//if
- }
-
- @Override
- public void visit(JdbcParameter jdbcParameter)
- {
- addQueryParameter(jdbcParameter, Object.class);
-
- jdbcParameter.setUseFixedIndex(false);
- jdbcParameter.setIndex(queryParameters.size());
-
- /*
- * Only add a return type if the parameter was used in the select
- */
- if (currentPhase == Phase.SELECT) {
- addResultType("c" + (returnTypes.size() + 1), Object.class);
- }//if
- }//visitJdbcParameter
-
- @Override
- public void visit(JdbcNamedParameter jdbcNamedParameter)
- {
- addQueryParameter(jdbcNamedParameter, Object.class);
- jdbcNamedParameter.setName("?");
-
- /*
- * Only add a return type if the parameter was used in the select
- */
- if (currentPhase == Phase.SELECT) {
- addResultType("c" + (returnTypes.size() + 1), Object.class);
- }//if
- }//visitJdbcNamedParameter
-}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java
deleted file mode 100644
index a288c51..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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.providers;
-
-import io.jpalite.PersistenceContext;
-import io.jpalite.*;
-import io.jpalite.impl.JPAConfig;
-import io.jpalite.impl.JPALiteEntityManagerImpl;
-import io.jpalite.impl.caching.EntityCacheImpl;
-import io.jpalite.impl.db.DatabasePoolFactory;
-import jakarta.persistence.*;
-import jakarta.persistence.criteria.CriteriaBuilder;
-import jakarta.persistence.metamodel.Metamodel;
-import lombok.extern.slf4j.Slf4j;
-
-import java.sql.SQLException;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Properties;
-import java.util.ServiceLoader;
-
-import static io.jpalite.JPALiteEntityManager.PERSISTENCE_QUERY_LOG_SLOWTIME;
-import static io.jpalite.JPALiteEntityManager.PERSISTENCE_SHOW_SQL;
-import static io.jpalite.PersistenceContext.PERSISTENCE_JTA_MANAGED;
-
-@SuppressWarnings("unchecked")
-@Slf4j
-public class JPALiteEntityManagerFactoryImpl implements EntityManagerFactory
-{
- private static final String NOT_SUPPORTED = "Not supported by current implementation";
- private final long defaultSlowQueryTime = JPAConfig.getValue("jpalite.slowQueryTime", 500L);
- private final boolean defaultShowQueries = JPAConfig.getValue("jpalite.showQueries", false);
- private final String persistenceUnitName;
- private boolean openFactory;
-
- public JPALiteEntityManagerFactoryImpl(String persistenceUnitName)
- {
- this.persistenceUnitName = persistenceUnitName;
- openFactory = true;
-
- LOG.info("Building the Entity Manager Factory for EntityManager named {}", persistenceUnitName);
- }
-
- @Override
- public EntityManager createEntityManager()
- {
- return entityManagerBuilder(SynchronizationType.UNSYNCHRONIZED, Collections.emptyMap());
- }
-
- @Override
- public EntityManager createEntityManager(Map map)
- {
- return entityManagerBuilder(SynchronizationType.UNSYNCHRONIZED, map);
- }
-
- @Override
- public EntityManager createEntityManager(SynchronizationType synchronizationType)
- {
- return entityManagerBuilder(synchronizationType, Collections.emptyMap());
- }
-
- @Override
- public EntityManager createEntityManager(SynchronizationType pSynchronizationType, Map map)
- {
- return entityManagerBuilder(pSynchronizationType, map);
- }
-
- private JPALitePersistenceUnit getPersistenceUnit()
- {
- ServiceLoader loader = ServiceLoader.load(PersistenceUnitProvider.class);
- for (PersistenceUnitProvider persistenceUnitProvider : loader) {
- JPALitePersistenceUnit persistenceUnit = persistenceUnitProvider.getPersistenceUnit(persistenceUnitName);
- if (persistenceUnit != null) {
- if (persistenceUnit.getMultiTenantMode().equals(Boolean.TRUE)) {
- ServiceLoader multiTenantLoader = ServiceLoader.load(MultiTenantProvider.class);
- for (MultiTenantProvider multiTenantProvider : multiTenantLoader) {
- JPALitePersistenceUnit legacyPersistenceUnit = multiTenantProvider.getPersistenceUnit(persistenceUnit);
- if (legacyPersistenceUnit != null) {
- return legacyPersistenceUnit;
- }//if
- }//for
- }//if
-
- return persistenceUnit;
- }//if
- }//for
-
- throw new PersistenceUnitNotFoundException(String.format("No PersistenceUnit was found for '%s'. %d SPI services found implementing PersistenceUnitProvider.class.",
- persistenceUnitName, loader.stream().count()));
- }//getPersistenceUnit
-
- private PersistenceContext getPersistenceContext(SynchronizationType synchronizationType, Map properties) throws SQLException
- {
- JPALitePersistenceUnit persistenceUnit = getPersistenceUnit();
-
- DatabasePool databasePool = DatabasePoolFactory.getDatabasePool(persistenceUnit.getDataSourceName());
-
- Properties localProperties = persistenceUnit.getProperties();
- localProperties.putAll(properties);
- localProperties.put(PERSISTENCE_JTA_MANAGED, synchronizationType == SynchronizationType.SYNCHRONIZED);
- localProperties.putIfAbsent(PERSISTENCE_QUERY_LOG_SLOWTIME, defaultSlowQueryTime);
- localProperties.putIfAbsent(PERSISTENCE_SHOW_SQL, defaultShowQueries);
-
- return databasePool.getPersistenceContext(persistenceUnit);
- }//getPersistenceContext
-
- private EntityManager entityManagerBuilder(SynchronizationType synchronizationType, Map entityProperties)
- {
- try {
- PersistenceContext persistenceContext = getPersistenceContext(synchronizationType, entityProperties);
- return new JPALiteEntityManagerImpl(persistenceContext, this);
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("Error connecting to the database", ex);
- }//catch
- }//entityBuilder
-
- @Override
- public CriteriaBuilder getCriteriaBuilder()
- {
- //Criteria Queries are not supported
- throw new UnsupportedOperationException(NOT_SUPPORTED);
- }
-
- @Override
- public Metamodel getMetamodel()
- {
- //Criteria Queries are not supported
- throw new UnsupportedOperationException(NOT_SUPPORTED);
- }
-
- @Override
- public boolean isOpen()
- {
- return openFactory;
- }
-
- @Override
- public void close()
- {
- openFactory = false;
- }
-
- @Override
- public Map getProperties()
- {
- return Collections.emptyMap();
- }
-
- @Override
- public Cache getCache()
- {
- return new EntityCacheImpl(getPersistenceUnit());
- }//getCache
-
- @Override
- public PersistenceUnitUtil getPersistenceUnitUtil()
- {
- return new PersistenceUnitUtil()
- {
- private JPAEntity checkEntity(Object entity)
- {
- if (entity instanceof JPAEntity jpaEntity) {
- return jpaEntity;
- }//if
-
- throw new IllegalStateException(entity.getClass().getName() + " is not a JPA Entity");
- }//checkEntity
-
- @Override
- public boolean isLoaded(Object entity, String field)
- {
- return !checkEntity(entity)._isLazyLoaded(field);
- }
-
- @Override
- public boolean isLoaded(Object entity)
- {
- return !checkEntity(entity)._isLazyLoaded();
- }
-
- @Override
- public Object getIdentifier(Object entity)
- {
- JPAEntity jpaEntity = checkEntity(entity);
- return jpaEntity._getEntityState() == EntityState.TRANSIENT ? null : jpaEntity._getPrimaryKey();
- }
- };
- }
-
- @Override
- public void addNamedQuery(String name, Query query)
- {
- throw new UnsupportedOperationException("Global Named Queries are not supported");
- }
-
- @Override
- public T unwrap(Class cls)
- {
- if (cls.isAssignableFrom(this.getClass())) {
- return (T) this;
- }
-
- throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
- }
-
- @Override
- public void addNamedEntityGraph(String graphName, EntityGraph entityGraph)
- {
- throw new UnsupportedOperationException(NOT_SUPPORTED);
- }
-}
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/JPALiteQueryImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/queries/JPALiteQueryImpl.java
deleted file mode 100644
index f3a496b..0000000
--- a/jpalite-core/src/main/java/io/jpalite/impl/queries/JPALiteQueryImpl.java
+++ /dev/null
@@ -1,1089 +0,0 @@
-/*
- * 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.queries;
-
-import io.jpalite.PersistenceContext;
-import io.jpalite.*;
-import io.jpalite.impl.db.ConnectionWrapper;
-import io.jpalite.impl.parsers.QueryParserFactory;
-import io.jpalite.parsers.QueryParser;
-import io.jpalite.parsers.QueryStatement;
-import io.jpalite.queries.QueryLanguage;
-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.*;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-
-import java.lang.reflect.InvocationTargetException;
-import java.sql.*;
-import java.util.Date;
-import java.util.*;
-
-import static io.jpalite.JPALiteEntityManager.*;
-import static jakarta.persistence.LockModeType.*;
-
-@SuppressWarnings("DuplicatedCode")
-@Slf4j
-public class JPALiteQueryImpl implements Query
-{
- private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(JPALiteQueryImpl.class.getName());
- public static final String SQL_QUERY = "query";
- public static final String MIXING_POSITIONAL_AND_NAMED_PARAMETERS_ARE_NOT_ALLOWED = "Mixing positional and named parameters are not allowed";
- /**
- * The Persistence context link to the query
- */
- private final PersistenceContext persistenceContext;
- /**
- * The query (native) that will be executed
- */
- private String query;
- /**
- * The raw query that will be executed
- */
- private final String rawQuery;
- /**
- * The query language
- */
- private final QueryLanguage queryLanguage;
- /**
- * We may use either positional or named parameters but we cannot mix them within the same query.
- */
- private boolean usingNamedParameters = false;
- /**
- * True if selecting using primary key
- */
- private boolean selectUsingPrimaryKey = false;
- private QueryStatement queryStatement = QueryStatement.OTHER;
- /**
- * The parameters that have been set
- */
- private List> params;
- /**
- * The query hints defined
- */
- private final Map hints;
- /**
- * The maximum number of rows to return for {@link #getResultList()}
- */
- private int maxResults = Integer.MAX_VALUE;
- /**
- * The number of rows in the cursor that should be skipped before returning a row.
- */
- private int firstResult = 0;
- /**
- * The lock mode of the returned item
- */
- private LockModeType lockMode;
- /**
- * The expected return type
- */
- private final Class> resultClass;
-
- @Getter
- private String connectionName;
- private int queryTimeout;
- private int lockTimeout;
- private CacheRetrieveMode cacheRetrieveMode;
- private CacheStoreMode cacheStoreMode;
- private boolean cacheResultList;
- private boolean showSql;
- private Class>[] queryResultTypes;
- private FieldType returnType;
-
- /**
- * This method supports both Native and JPQL based queries.
- *
- * resultClass defined the class the result will be mapped into and can be either and Entity Class or a base class
- * or an array of base class types.
- *
- * The query language parameter defined the type of query. The following types are supported:
- *
- * JPQL queries
- * JPQL queries can either be a single or a multi select query.
- *
- * A Single Select query is a query that only have one entity (eg select e from Employee e) or a specific field in
- * an entity (eg select e.name from Employee E). In the case of a single select query resultClass MUST match the
- * type of select expression.
- *
- * A Multi select query is a query that have more than one entity or entity fields eg (select e, d from Employee e
- * JOIN e.department d) or (select e.name, e.department from Employee e). In the case of a multi select query
- * resultClass must be an Object array (Object[].class).
- *
- * An exception to the above is if the selection return different unique types of only entities ( eg select e,
- * e.department from Employee e) in which case resultClass could be the specific Entity in the multi select result
- * set. This only applies to Entities and not entity fields!
- *
- *
- *
- * Native Queries
- * Native Queries are normal SQL queries and can also have a single or multi select query. resultClass can either
- * be a specific Entity class, a specific base class or a base type array. If an entity class is specified as the
- * result class, the result set mapping process will try and use the column names found in the result set to map the
- * result to the entity class.
- *
- * NOTE: Only the @Basic fields in the entity will (or can) be mapped.
- *
- * @param queryText The query to execute
- * @param queryLanguage The query language
- * @param persistenceContext The persistence context to use for the query
- * @param resultClass The expected result class
- */
- public JPALiteQueryImpl(String queryText, QueryLanguage queryLanguage, PersistenceContext persistenceContext, Class resultClass, @Nonnull Map hints, LockModeType lockMode)
- {
- Span span = TRACER.spanBuilder("JPAQuery::Init").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- if (queryText == null || queryText.isEmpty()) {
- throw new IllegalArgumentException("No query was specified");
- }//if
-
- Boolean globalShowSQL = (Boolean) persistenceContext.getProperties().get(PERSISTENCE_SHOW_SQL);
- showSql = globalShowSQL != null && globalShowSQL;
- this.lockMode = lockMode;
- rawQuery = queryText;
- this.queryLanguage = queryLanguage;
- this.persistenceContext = persistenceContext;
- this.resultClass = resultClass;
- connectionName = persistenceContext.getConnectionName();
- cacheRetrieveMode = CacheRetrieveMode.USE;
- cacheStoreMode = CacheStoreMode.USE;
- queryTimeout = 0;
- lockTimeout = 0;
- params = new ArrayList<>();
- queryResultTypes = null;
- query = null;
- cacheResultList = false;
-
- //Check that a valid return class was specified
- checkResultClass(resultClass);
-
- this.hints = new HashMap<>();
- hints.forEach(this::setHint);
-
- span.setAttribute("queryLang", this.queryLanguage.name());
- span.setAttribute(SQL_QUERY, queryText);
- }//try
- finally {
- span.end();
- }
- }//JpaLiteQueryImpl
-
- public JPALiteQueryImpl(String queryText, QueryLanguage queryLanguage, PersistenceContext persistenceContext, Class resultClass, @Nonnull Map hints)
- {
- this(queryText, queryLanguage, persistenceContext, resultClass, hints, NONE);
- }//JpaLiteQueryImpl
-
- private void checkResultClass(Class> returnClass)
- {
- Class> checkedClass = returnClass;
- if (checkedClass.isArray()) {
- checkedClass = checkedClass.arrayType();
- }//if
-
- returnType = FieldType.fieldType(checkedClass);
- }//checkResultClass
-
- private void checkUsingPositionalParameters()
- {
- if (params.isEmpty()) {
- usingNamedParameters = false;
- }
- else if (usingNamedParameters) {
- throw new IllegalArgumentException(MIXING_POSITIONAL_AND_NAMED_PARAMETERS_ARE_NOT_ALLOWED);
- }//if
- }
-
- private void checkUsingNamedParameters()
- {
- if (params.isEmpty()) {
- usingNamedParameters = true;
- }
- else if (!usingNamedParameters) {
- throw new IllegalArgumentException(MIXING_POSITIONAL_AND_NAMED_PARAMETERS_ARE_NOT_ALLOWED);
- }//if
- }
-
- private Object getColumnValue(Object entity, ResultSet resultSet, int columnNr)
- {
- try {
- return switch (returnType) {
- case TYPE_BOOLEAN -> resultSet.getBoolean(columnNr);
- case TYPE_INTEGER -> resultSet.getInt(columnNr);
- case TYPE_LONGLONG -> resultSet.getLong(columnNr);
- case TYPE_DOUBLEDOUBLE -> resultSet.getDouble(columnNr);
- case TYPE_STRING -> resultSet.getString(columnNr);
- case TYPE_TIMESTAMP -> resultSet.getTimestamp(columnNr);
- case TYPE_ENTITY -> persistenceContext.mapResultSet(entity, "c" + columnNr + "_", resultSet);
- default -> resultSet.getObject(columnNr);
- };
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("SQL Error reading column from result set", ex);
- }//catch
- }//getColumnValue
-
- @Nonnull
- private Object[] buildArray(@Nonnull ResultSet resultSet)
- {
- List resultList = new ArrayList<>();
- try {
- if (queryResultTypes.length == 0) {
- if (resultClass.isArray()) {
- ResultSetMetaData metaData = resultSet.getMetaData();
- for (int i = 1; i <= metaData.getColumnCount(); i++) {
- resultList.add(getColumnValue(null, resultSet, i));
- }//for
- }//if
- }//if
- else {
- for (int i = 1; i <= queryResultTypes.length; i++) {
- resultList.add(getColumnValue(getNewObject(queryResultTypes[i - 1]), resultSet, i));
- }//for
- }//else
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("SQL Error mapping result to entity", ex);
- }//catch
-
- return resultList.toArray();
- }//buildArray
-
- private Object getNewObject(Class> returnClass)
- {
- if (returnType == FieldType.TYPE_ENTITY) {
- try {
- return returnClass.getConstructor().newInstance();
- }//try
- catch (InstantiationException | IllegalAccessException | InvocationTargetException |
- NoSuchMethodException pE) {
- throw new PersistenceException("Error creating a new entity from class type " + returnClass.getName());
- }//catch
- }//if
- return new Object();
- }//getNewObject
-
- protected Object mapResultSet(ResultSet resultSet)
- {
- if (resultClass.isArray() && !resultClass.isAssignableFrom(byte[].class)) {
- return buildArray(resultSet);
- }//if
- else {
- if (returnType == FieldType.TYPE_ENTITY) {
-
- JPAEntity entity = (JPAEntity) getNewObject(resultClass);
- if (queryResultTypes.length == 0) {
- entity._mapResultSet(null, resultSet);
- }//if
- else {
- entity._mapResultSet("c1", resultSet);
- }//else
-
- //Check if the entity is not already in L1 Cache
- JPAEntity l1Entity = (JPAEntity) persistenceContext.l1Cache().find(entity._getEntityClass(), entity._getPrimaryKey());
- if (l1Entity == null) {
- persistenceContext.l1Cache().manage(entity);
- }//if
- else {
- entity = l1Entity;
- }
-
- return entity;
- }//if
- else {
- return getColumnValue(null, resultSet, 1);
- }
- }//else
- }//mapResultSet
-
- private PreparedStatement bindParameters(PreparedStatement statement) throws SQLException
- {
- for (QueryParameterImpl> parameter : params) {
- if (parameter.getValue() != null) {
- if (parameter.getValue().getClass().isAssignableFrom(Boolean.class)) {
- statement.setObject(parameter.getPosition(), Boolean.TRUE.equals(parameter.getValue()) ? 1 : 0, Types.OTHER);
- }
- else {
- if (parameter.getParameterType().equals(Object.class)) {
- statement.setObject(parameter.getPosition(), parameter.getValue(), Types.OTHER);
- }//if
- else {
- EntityMetaData> metaData = EntityMetaDataManager.getMetaData(parameter.getParameterType());
- for (EntityField entityField : metaData.getEntityFields()) {
- Object value = entityField.invokeGetter(parameter.getValue());
- statement.setObject(parameter.getPosition(), value, Types.OTHER);
- }//for
- }//else
- }//else
- }//if
- else {
- statement.setNull(parameter.getPosition(), Types.OTHER);
- }//else
- }//for
-
- return statement;
- }//bindParameters
-
- private String getQuery()
- {
- if (query == null) {
- processQuery();
- }//if
-
- return query;
- }//getQuery
-
- private String getQueryWithLimits(int firstResult, int maxResults)
- {
- String queryStr = getQuery();
- if (queryStatement == QueryStatement.SELECT && (firstResult > 0 || maxResults < Integer.MAX_VALUE)) {
- queryStr = "select * from (" + queryStr + ") __Q";
- if (firstResult > 0) {
- queryStr += " offset " + firstResult;
- }//if
-
- if (maxResults < Integer.MAX_VALUE) {
- queryStr += " limit " + maxResults;
- }//else
- }//if
-
- return queryStr;
- }//applyLimits
-
- private boolean isPessimisticLocking(LockModeType lockMode)
- {
- return (lockMode == PESSIMISTIC_READ || lockMode == PESSIMISTIC_FORCE_INCREMENT || lockMode == PESSIMISTIC_WRITE);
- }//isPessimisticLocking
-
- private String applyLocking(String sqlQuery)
- {
- if (queryStatement == QueryStatement.SELECT && isPessimisticLocking(lockMode)) {
- return sqlQuery + switch (lockMode) {
- case PESSIMISTIC_READ -> " FOR SHARE ";
- case PESSIMISTIC_FORCE_INCREMENT, PESSIMISTIC_WRITE -> " FOR UPDATE ";
- default -> "";
- };
- }//if
- return sqlQuery;
- }//applyLocking
-
- @SuppressWarnings("java:S2077") // Dynamic formatted SQL is verified to be safe
- private void applyLockTimeout(Statement statement)
- {
- if (queryStatement == QueryStatement.SELECT && lockTimeout > 0 && isPessimisticLocking(lockMode)) {
- try {
- statement.execute("SET LOCAL lock_timeout = '" + lockTimeout + "s'");
- }//try
- catch (SQLException ex) {
- LOG.warn("Error setting lock timeout.", ex);
- }//catch
- }//if
- }//applyLockTimeout
-
- private Object executeQuery(String sqlQuery, SQLFunction function)
- {
- Span span = TRACER.spanBuilder("JPAQuery::executeQuery").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent();
- Connection connection = persistenceContext.getConnection(getConnectionName());
- PreparedStatement vStatement = bindParameters(connection.prepareStatement(sqlQuery))) {
-
- span.setAttribute(SQL_QUERY, sqlQuery);
-
- if (JPAEntity.class.isAssignableFrom(resultClass)) {
- persistenceContext.flushOnType(resultClass);
- }//if
-
- applyLockTimeout(vStatement);
- vStatement.setQueryTimeout(queryTimeout);
-
- boolean currentState = connection.unwrap(ConnectionWrapper.class).setEnableLogging(showSql);
- try (ResultSet vResultSet = vStatement.executeQuery()) {
- return function.apply(vResultSet);
- }//try
- finally {
- connection.unwrap(ConnectionWrapper.class).setEnableLogging(currentState);
- }//finally
-
- }//try
- catch (SQLTimeoutException ex) {
- throw new QueryTimeoutException("Query timeout after " + queryTimeout + " seconds");
- }//catch
- catch (SQLException ex) {
- if ("57014".equals(ex.getSQLState())) { //Postgresql state for query that timed out
- throw new QueryTimeoutException("Query timeout after " + queryTimeout + " seconds");
- }//if
- else {
- throw new PersistenceException("SQL Error executing the query: " + query, ex);
- }//else
- }//catch
- finally {
- span.end();
- }//finally
- }//executeQuery
-
- @Override
- @SuppressWarnings("unchecked")
- public List getResultList()
- {
- Span span = TRACER.spanBuilder("JPAQuery::getResultList").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- span.setAttribute("resultType", resultClass.getSimpleName());
-
- if (lockMode != LockModeType.NONE && !persistenceContext.getTransaction().isActive()) {
- throw new TransactionRequiredException("No transaction is in progress");
- }//if
-
- if (maxResults < 0) {
- return Collections.emptyList();
- }//if
-
- String queryStr = applyLocking(getQueryWithLimits(firstResult, maxResults));
- return (List) executeQuery(queryStr, r ->
- {
- List resultList = new ArrayList<>();
- while (r.next()) {
- T entity = (T) mapResultSet(r);
-
- if (isPessimisticLocking(lockMode)) {
- ((JPAEntity) entity)._setLockMode(lockMode);
- }//if
-
- if (cacheResultList &&
- entity instanceof JPAEntity jpaEntity &&
- jpaEntity._getMetaData().isCacheable() &&
- cacheStoreMode != CacheStoreMode.BYPASS
- ) {
- if (cacheStoreMode == CacheStoreMode.USE) {
- persistenceContext.l2Cache().add(jpaEntity);
- }
- else {
- persistenceContext.l2Cache().replace(jpaEntity);
- }
- }//if
- resultList.add(entity);
- }//while
- return resultList;
- });
- }//try
- finally {
- span.end();
- }
- }//getResultList
-
- @SuppressWarnings("unchecked")
- private T checkCache()
- {
- T result = null;
-
- if (selectUsingPrimaryKey) {
- QueryParameterImpl> firstParam = params.stream().findFirst().orElse(null);
-
- //Only check L1 cache if the primaryKey is set
- if (firstParam != null) {
- Object primaryKey = firstParam.getValue();
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking L1 cache for Entity [{}] using key [{}]", resultClass.getSimpleName(), primaryKey);
- }//if
-
- result = (T) persistenceContext.l1Cache().find(resultClass, primaryKey);
- if (result == null) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Not found in L1 cache");
- }//if
- result = checkL2Cache(primaryKey);
- }//if
- }//if
- }//if
-
- return result;
- }//checkCache
-
- @SuppressWarnings("unchecked")
- private T checkL2Cache(Object primaryKey)
- {
- T result = null;
-
- if (selectUsingPrimaryKey && cacheRetrieveMode == CacheRetrieveMode.USE) {
- EntityMetaData metaData = EntityMetaDataManager.getMetaData(resultClass);
- if (metaData.isCacheable()) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking L2 cache for Entity [{}] using key [{}]", resultClass.getSimpleName(), primaryKey);
- }//if
-
- result = (T) persistenceContext.l2Cache().find(resultClass, primaryKey);
- if (result instanceof JPAEntity entity) {
- persistenceContext.l1Cache().manage(entity);
-
- FetchType hintValue = (FetchType) hints.get(PERSISTENCE_OVERRIDE_FETCHTYPE);
- if (hintValue == null || hintValue.equals(FetchType.EAGER)) {
- entity._lazyFetchAll(hintValue != null);
- }//if
- }//if
- else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Not found in L2 cache");
- }//if
- }
- }//if
- }//if
-
- return result;
- }//checkCache
-
- @Override
- @SuppressWarnings("unchecked")
- public T getSingleResult()
- {
- Span span = TRACER.spanBuilder("JPAQuery::getSingleResult").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- span.setAttribute("resultType", resultClass.getSimpleName());
-
- //Must parse the query before check the cache
- String queryStr = applyLocking(getQueryWithLimits(firstResult, maxResults));
-
- if (returnType == FieldType.TYPE_ENTITY) {
- T result = checkCache();
- if (result != null) {
- return result;
- }//if
- }//if
-
-
- return (T) executeQuery(queryStr, r ->
- {
- if (r.next()) {
- T result = (T) mapResultSet(r);
-
- if (r.next()) {
- throw new NonUniqueResultException("Query did not return a unique result");
- }//if
-
- if (result instanceof JPAEntity jpaEntity) {
- if (jpaEntity._getMetaData().isCacheable() && cacheStoreMode != CacheStoreMode.BYPASS) {
- if (cacheStoreMode == CacheStoreMode.USE) {
- persistenceContext.l2Cache().add(jpaEntity);
- }
- else {
- persistenceContext.l2Cache().replace(jpaEntity);
- }
- }//if
-
- if (isPessimisticLocking(lockMode)) {
- jpaEntity._setLockMode(lockMode);
- }//if
- }//if
-
- span.setAttribute("result", "Result found");
- return result;
- }//if
- else {
- span.setAttribute("result", "No Result found");
- throw new NoResultException("No Result found");
- }//else
- });
- }//try
- finally {
- span.end();
- }
- }//getSingleResult
-
- @Override
- public int executeUpdate()
- {
- Span span = TRACER.spanBuilder("JPAQuery::executeUpdate").setSpanKind(SpanKind.SERVER).startSpan();
- try (Scope ignored = span.makeCurrent()) {
- span.setAttribute(SQL_QUERY, getQuery());
-
- if (queryStatement == QueryStatement.SELECT || queryStatement == QueryStatement.INSERT) {
- throw new IllegalStateException("SELECT and INSERT is not allowed in executeUpdate");
- }//if
-
- try (Connection connection = persistenceContext.getConnection(getConnectionName());
- PreparedStatement statement = bindParameters(connection.prepareStatement(getQuery()))) {
- statement.setEscapeProcessing(false);
-
- boolean currentState = connection.unwrap(ConnectionWrapper.class).setEnableLogging(showSql);
- try {
- return statement.executeUpdate();
- }//try
- finally {
- connection.unwrap(ConnectionWrapper.class).setEnableLogging(currentState);
- }//finally
- }//try
- catch (SQLException ex) {
- throw new PersistenceException("SQL Error executing the update: " + query, ex);
- }//catch
- }//try
- finally {
- span.end();
- }
- }//executeUpdate
-
- @Override
- public Query setMaxResults(int maxResults)
- {
- if (maxResults < 0) {
- throw new IllegalArgumentException("The max results value cannot be negative");
- }//if
-
- this.maxResults = maxResults;
- return this;
- }//setMaxResults
-
- @Override
- public int getMaxResults()
- {
- return maxResults;
- }//getMaxResults
-
- @Override
- public Query setFirstResult(int startPosition)
- {
- if (startPosition < 0) {
- throw new IllegalArgumentException("The first results value cannot be negative");
- }//if
-
- firstResult = startPosition;
- return this;
- }//setFirstResult
-
- @Override
- public int getFirstResult()
- {
- return firstResult;
- }
-
- @Override
- @SuppressWarnings({"java:S6205"}) // This improves the readability of the assignment
- public Query setHint(String hintName, Object value)
- {
- hints.put(hintName, value);
- switch (hintName) {
- case PERSISTENCE_QUERY_TIMEOUT -> {
- if (value instanceof Long aLong) {
- queryTimeout = aLong.intValue();
- }
- else if (value instanceof Integer anInteger) {
- queryTimeout = anInteger;
- }
- else if (value instanceof String aString) {
- queryTimeout = Integer.parseInt(aString);
- }
- }
- case PERSISTENCE_LOCK_TIMEOUT -> {
- if (value instanceof Long aLong) {
- lockTimeout = aLong.intValue();
- }
- else if (value instanceof Integer anInteger) {
- lockTimeout = anInteger;
- }
- else if (value instanceof String aString) {
- lockTimeout = Integer.parseInt(aString);
- }
- }
- case PERSISTENCE_CACHE_RETRIEVEMODE -> {
- if (value instanceof String aString) {
- cacheRetrieveMode = CacheRetrieveMode.valueOf(aString);
- }
- if (value instanceof CacheRetrieveMode mode) {
- cacheRetrieveMode = mode;
- }
- }
- case PERSISTENCE_CACHE_STOREMODE -> {
- if (value instanceof String aString) {
- cacheStoreMode = CacheStoreMode.valueOf(aString);
- }
- if (value instanceof CacheStoreMode mode) {
- cacheStoreMode = mode;
- }
- }
- case PERSISTENCE_SHOW_SQL -> {
- if (value instanceof Boolean showSqlHint) {
- this.showSql = showSqlHint;
- }//if
- else {
- showSql = Boolean.parseBoolean(value.toString());
- }
- }
- case PERSISTENCE_CACHE_RESULTLIST -> {
- EntityMetaData vMetaData = EntityMetaDataManager.getMetaData(resultClass);
- if (vMetaData.isCacheable()) {
- cacheResultList = Boolean.parseBoolean(value.toString());
- }//if
- else {
- cacheResultList = false;
- }//else
- }
-
- case PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE, PERSISTENCE_OVERRIDE_FETCHTYPE -> {
- if (value instanceof FetchType fetchType) {
- hints.put(hintName, fetchType);
- }//if
- else {
- hints.put(hintName, FetchType.valueOf(value.toString()));
- }
- }
- default -> LOG.trace("Unknown Query Hint[{}] - Ignored", hintName);
- }//switch
-
- return this;
- }
-
- @Override
- public Map getHints()
- {
- return hints;
- }
-
- @SuppressWarnings("java:S6126") // IDE adds tabs and spaces in a text block
- private void processQuery()
- {
- if (isPessimisticLocking(lockMode)) {
- /*
- It is illegal to do a "SELECT FOR UPDATE" query that contains joins.
- We are forcing the parser to generate a query that do not have any joins.
- */
- hints.put(PERSISTENCE_OVERRIDE_FETCHTYPE, FetchType.LAZY);
- }//if
-
- try {
- QueryParser parser = QueryParserFactory.getParser(queryLanguage, rawQuery, hints);
- parser.checkType(resultClass);
- queryResultTypes = parser.getReturnTypes().toArray(new Class>[0]);
- query = parser.getQuery();
-
- if (usingNamedParameters != parser.isUsingNamedParameters()) {
- throw new IllegalArgumentException(MIXING_POSITIONAL_AND_NAMED_PARAMETERS_ARE_NOT_ALLOWED);
- }//if
-
- /*
- Check that the correct parameters are have value.
- Create a new list of parameters such that for every parameter used in the query
- an entry exists.
- The problem here is that for named parameters the same name could
- be used more than once in the query (which is okay)
- */
- List> parameters = new ArrayList<>();
- parser.getQueryParameters().forEach(templateParam -> {
- QueryParameterImpl> providedParameter = params.stream()
- .filter(p -> p.getName().equals(templateParam.getName()))
- .findFirst()
- .orElse(null);
-
- if (providedParameter == null) {
- throw new IllegalArgumentException(String.format("Parameter '%s' is not set", templateParam.getName()));
- }//if
-
- parameters.add(templateParam.copyAndSet(providedParameter.getValue()));
- });
- params = parameters;
-
- selectUsingPrimaryKey = parser.isSelectUsingPrimaryKey();
- queryStatement = parser.getStatement();
-
- if (showSql) {
- LOG.info("\n------------ Query Parser -------------\n" +
- "Query language: {}\n" +
- "----------- Raw ----------\n" +
- "{}\n" +
- "---------- Parsed --------\n" +
- "{}\n" +
- "--------------------------------------",
- queryLanguage, rawQuery, query);
- }//if
- }//try
- catch (PersistenceException ex) {
- LOG.error("Error parsing query. Language: {}, query: {}", queryLanguage, rawQuery);
- throw new QueryParsingException("Error parsing query", ex);
- }//catch
- }//processQuery
-
- @Override
- public Query setParameter(Parameter param, X value)
- {
- if (param.getName() != null) {
- return setParameter(param.getName(), value);
- }//if
-
- return setParameter(param.getPosition(), value);
- }//setParameter
-
- @Override
- public Query setParameter(Parameter param, Calendar value, TemporalType temporalType)
- {
- if (param.getName() != null) {
- return setParameter(param.getName(), value, temporalType);
- }//if
-
- return setParameter(param.getPosition(), value, temporalType);
- }//setParameter
-
- @Override
- public Query setParameter(Parameter param, Date value, TemporalType temporalType)
- {
- if (param.getName() != null) {
- return setParameter(param.getName(), value, temporalType);
- }//if
-
- return setParameter(param.getPosition(), value, temporalType);
- }//setParameter
-
- @SuppressWarnings("unchecked")
- private QueryParameterImpl findOrCreateParameter(String name)
- {
- checkUsingNamedParameters();
-
- QueryParameterImpl> param = params.stream()
- .filter(p -> p.getName().equals(name))
- .findFirst()
- .orElse(null);
- if (param == null) {
- param = new QueryParameterImpl<>(name, params.size() + 1, Object.class);
- params.add(param);
- }
-
- return (QueryParameterImpl) param;
- }
-
- @SuppressWarnings("unchecked")
- private QueryParameterImpl findOrCreateParameter(int position)
- {
- checkUsingPositionalParameters();
- QueryParameterImpl> param = params.stream()
- .filter(p -> p.getPosition() == position)
- .findFirst()
- .orElse(null);
- if (param == null) {
- param = new QueryParameterImpl<>(position, Object.class);
- params.add(param);
- }
-
- return (QueryParameterImpl) param;
- }
-
- @Override
- public Query setParameter(String pName, Object value)
- {
- QueryParameterImpl parameter = findOrCreateParameter(pName);
- parameter.setValue(value);
-
- return this;
- }//setParameter
-
- @Override
- public Query setParameter(String name, Calendar value, TemporalType temporalType)
- {
- QueryParameterImpl parameter = findOrCreateParameter(name);
-
- switch (temporalType) {
- case DATE -> parameter.setValue(new java.sql.Date(value.getTimeInMillis()));
- case TIME -> parameter.setValue(new java.sql.Time(value.getTimeInMillis()));
- case TIMESTAMP -> parameter.setValue(new Timestamp(value.getTimeInMillis()));
- }//switch
-
- return this;
- }//setParameter
-
- @Override
- public Query setParameter(String name, Date value, TemporalType temporalType)
- {
- QueryParameterImpl parameter = findOrCreateParameter(name);
-
- switch (temporalType) {
- case DATE -> parameter.setValue(new java.sql.Date(value.getTime()));
- case TIME -> parameter.setValue(new java.sql.Time(value.getTime()));
- case TIMESTAMP -> parameter.setValue(new Timestamp(value.getTime()));
- }//switch
-
- return this;
- }//setParameter
-
- @Override
- public Query setParameter(int position, Object value)
- {
- QueryParameterImpl parameter = findOrCreateParameter(position);
- parameter.setValue(value);
-
- return this;
- }//setParameter
-
- @Override
- public Query setParameter(int position, Calendar value, TemporalType temporalType)
- {
- QueryParameterImpl parameter = findOrCreateParameter(position);
-
- switch (temporalType) {
- case DATE -> parameter.setValue(new java.sql.Date(value.getTimeInMillis()));
- case TIME -> parameter.setValue(new java.sql.Time(value.getTimeInMillis()));
- case TIMESTAMP -> parameter.setValue(new Timestamp(value.getTimeInMillis()));
- }//switch
-
- return this;
- }//setParameter
-
- @Override
- public Query setParameter(int position, Date value, TemporalType temporalType)
- {
- QueryParameterImpl parameter = findOrCreateParameter(position);
-
- switch (temporalType) {
- case DATE -> parameter.setValue(new java.sql.Date(value.getTime()));
- case TIME -> parameter.setValue(new java.sql.Time(value.getTime()));
- case TIMESTAMP -> parameter.setValue(new Timestamp(value.getTime()));
- }//switch
-
- return this;
- }//setParameter
-
- @Override
- public Set> getParameters()
- {
- return new HashSet<>(params);
- }//getParameters
-
- @Override
- @Nonnull
- public Parameter> getParameter(String name)
- {
- checkUsingNamedParameters();
-
- Parameter> param = params.stream()
- .filter(p -> p.getName().equals(name))
- .findFirst()
- .orElse(null);
- if (param == null) {
- throw new IllegalArgumentException("Named parameter [" + name + "] does not exist");
- }//if
-
- return param;
- }//getParameters
-
- @Override
- @Nonnull
- @SuppressWarnings("unchecked")
- public Parameter getParameter(String name, Class type)
- {
- Parameter> parameter = getParameter(name);
-
- if (!type.isAssignableFrom(parameter.getParameterType())) {
- throw new IllegalArgumentException("Parameter [" + parameter.getParameterType().getName() + "] is not assignable to type " + type.getName());
- }//if
-
- return (Parameter) parameter;
- }//getParameters
-
- @Override
- @Nonnull
- public Parameter> getParameter(int position)
- {
- checkUsingPositionalParameters();
- Parameter> param = params.stream()
- .filter(p -> p.getPosition() == position)
- .findFirst().orElse(null);
- if (param == null) {
- throw new IllegalArgumentException("Positional parameter [" + position + "] does not exist");
- }//if
-
- return param;
- }//getParameters
-
- @Override
- @Nonnull
- @SuppressWarnings("unchecked")
- public Parameter getParameter(int position, Class type)
- {
- Parameter parameter = (Parameter) getParameter(position);
-
- if (!type.isAssignableFrom(parameter.getParameterType())) {
- throw new IllegalArgumentException("Parameter [" + parameter.getParameterType().getName() + "] is not assignable to type " + type.getName());
- }//if
-
- return parameter;
- }//getParameters
-
- @Override
- public boolean isBound(Parameter> param)
- {
- if (param.getName() != null) {
- return ((QueryParameterImpl>) getParameter(param.getName())).isBounded();
- }//if
-
- return ((QueryParameterImpl>) getParameter(param.getPosition())).isBounded();
- }//isBound
-
- @Override
- @SuppressWarnings("unchecked")
- public X getParameterValue(Parameter param)
- {
- if (param.getName() != null) {
- return (X) getParameterValue(param.getName());
- }//if
-
- return (X) getParameterValue(param.getPosition());
- }//getParameterValue
-
- @Override
- public Object getParameterValue(String name)
- {
- QueryParameterImpl> vParameter = (QueryParameterImpl>) getParameter(name);
-
- return vParameter.getValue();
- }//getParameterValue
-
- @Override
- public Object getParameterValue(int position)
- {
- QueryParameterImpl> vParameter = (QueryParameterImpl>) getParameter(position);
-
- return vParameter.getValue();
- }//getParameterValue
-
- @Override
- public Query setFlushMode(FlushModeType flushMode)
- {
- throw new UnsupportedOperationException("FlushMode is not supported");
- }
-
- @Override
- public FlushModeType getFlushMode()
- {
- return FlushModeType.AUTO;
- }
-
- @Override
- public Query setLockMode(LockModeType lockMode)
- {
- this.lockMode = lockMode;
- return this;
- }
-
- @Override
- public LockModeType getLockMode()
- {
- return lockMode;
- }
-
- @Override
- public X unwrap(Class cls)
- {
- throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
- }
-}
diff --git a/jpalite-core/src/main/java/io/jpalite/Caching.java b/jpalite-core/src/main/java/org/jpalite/Caching.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/Caching.java
rename to jpalite-core/src/main/java/org/jpalite/Caching.java
index 9255439..71fd44e 100644
--- a/jpalite-core/src/main/java/io/jpalite/Caching.java
+++ b/jpalite-core/src/main/java/org/jpalite/Caching.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
diff --git a/jpalite-core/src/main/java/io/jpalite/CachingException.java b/jpalite-core/src/main/java/org/jpalite/CachingException.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/CachingException.java
rename to jpalite-core/src/main/java/org/jpalite/CachingException.java
index 62ac045..3f42e76 100644
--- a/jpalite-core/src/main/java/io/jpalite/CachingException.java
+++ b/jpalite-core/src/main/java/org/jpalite/CachingException.java
@@ -1,4 +1,4 @@
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/ConverterClass.java b/jpalite-core/src/main/java/org/jpalite/ConverterClass.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/ConverterClass.java
rename to jpalite-core/src/main/java/org/jpalite/ConverterClass.java
index b3d62cd..2bd6316 100644
--- a/jpalite-core/src/main/java/io/jpalite/ConverterClass.java
+++ b/jpalite-core/src/main/java/org/jpalite/ConverterClass.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public interface ConverterClass
{
diff --git a/jpalite-core/src/main/java/io/jpalite/DataSourceProvider.java b/jpalite-core/src/main/java/org/jpalite/DataSourceProvider.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/DataSourceProvider.java
rename to jpalite-core/src/main/java/org/jpalite/DataSourceProvider.java
index 9422e43..21e3587 100644
--- a/jpalite-core/src/main/java/io/jpalite/DataSourceProvider.java
+++ b/jpalite-core/src/main/java/org/jpalite/DataSourceProvider.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import javax.sql.DataSource;
diff --git a/jpalite-core/src/main/java/io/jpalite/DatabasePool.java b/jpalite-core/src/main/java/org/jpalite/DatabasePool.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/DatabasePool.java
rename to jpalite-core/src/main/java/org/jpalite/DatabasePool.java
index 3419d16..34f55a2 100644
--- a/jpalite-core/src/main/java/io/jpalite/DatabasePool.java
+++ b/jpalite-core/src/main/java/org/jpalite/DatabasePool.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityCache.java b/jpalite-core/src/main/java/org/jpalite/EntityCache.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/EntityCache.java
rename to jpalite-core/src/main/java/org/jpalite/EntityCache.java
index 4945ac4..6769ccb 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityCache.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityCache.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
import jakarta.persistence.Cache;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityField.java b/jpalite-core/src/main/java/org/jpalite/EntityField.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/EntityField.java
rename to jpalite-core/src/main/java/org/jpalite/EntityField.java
index ee12d38..f37b84b 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityField.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityField.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.CascadeType;
import jakarta.persistence.FetchType;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityLifecycle.java b/jpalite-core/src/main/java/org/jpalite/EntityLifecycle.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/EntityLifecycle.java
rename to jpalite-core/src/main/java/org/jpalite/EntityLifecycle.java
index a094aba..5e4143a 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityLifecycle.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityLifecycle.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public interface EntityLifecycle
{
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityLocalCache.java b/jpalite-core/src/main/java/org/jpalite/EntityLocalCache.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/EntityLocalCache.java
rename to jpalite-core/src/main/java/org/jpalite/EntityLocalCache.java
index dab998f..c632cc1 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityLocalCache.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityLocalCache.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import java.util.function.Consumer;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityMapException.java b/jpalite-core/src/main/java/org/jpalite/EntityMapException.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/EntityMapException.java
rename to jpalite-core/src/main/java/org/jpalite/EntityMapException.java
index cc63014..f4a541a 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityMapException.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityMapException.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java b/jpalite-core/src/main/java/org/jpalite/EntityMetaData.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/EntityMetaData.java
rename to jpalite-core/src/main/java/org/jpalite/EntityMetaData.java
index 67f4219..481b3af 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityMetaData.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityMetaData.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java b/jpalite-core/src/main/java/org/jpalite/EntityMetaDataManager.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java
rename to jpalite-core/src/main/java/org/jpalite/EntityMetaDataManager.java
index dd7b475..23356d3 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityMetaDataManager.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityMetaDataManager.java
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
-import io.jpalite.impl.ConverterClassImpl;
-import io.jpalite.impl.EntityMetaDataImpl;
+import org.jpalite.impl.ConverterClassImpl;
+import org.jpalite.impl.EntityMetaDataImpl;
import jakarta.annotation.Nonnull;
import jakarta.persistence.PersistenceException;
import org.slf4j.Logger;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityState.java b/jpalite-core/src/main/java/org/jpalite/EntityState.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/EntityState.java
rename to jpalite-core/src/main/java/org/jpalite/EntityState.java
index fc9a117..08cc74b 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityState.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityState.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityTransactionListener.java b/jpalite-core/src/main/java/org/jpalite/EntityTransactionListener.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/EntityTransactionListener.java
rename to jpalite-core/src/main/java/org/jpalite/EntityTransactionListener.java
index b32ae75..d88343f 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityTransactionListener.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityTransactionListener.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public interface EntityTransactionListener
{
diff --git a/jpalite-core/src/main/java/io/jpalite/EntityType.java b/jpalite-core/src/main/java/org/jpalite/EntityType.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/EntityType.java
rename to jpalite-core/src/main/java/org/jpalite/EntityType.java
index 1f0dcb7..9ce706f 100644
--- a/jpalite-core/src/main/java/io/jpalite/EntityType.java
+++ b/jpalite-core/src/main/java/org/jpalite/EntityType.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public enum EntityType
{
diff --git a/jpalite-core/src/main/java/io/jpalite/FieldConvertType.java b/jpalite-core/src/main/java/org/jpalite/FieldConvertType.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/FieldConvertType.java
rename to jpalite-core/src/main/java/org/jpalite/FieldConvertType.java
index 7727ea3..8ab1a91 100644
--- a/jpalite-core/src/main/java/io/jpalite/FieldConvertType.java
+++ b/jpalite-core/src/main/java/org/jpalite/FieldConvertType.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/jpalite-core/src/main/java/io/jpalite/FieldType.java b/jpalite-core/src/main/java/org/jpalite/FieldType.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/FieldType.java
rename to jpalite-core/src/main/java/org/jpalite/FieldType.java
index 00334d7..394d60c 100644
--- a/jpalite-core/src/main/java/io/jpalite/FieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/FieldType.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public enum FieldType
{
diff --git a/jpalite-core/src/main/java/io/jpalite/JPACache.java b/jpalite-core/src/main/java/org/jpalite/JPACache.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/JPACache.java
rename to jpalite-core/src/main/java/org/jpalite/JPACache.java
index a083680..7574de9 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPACache.java
+++ b/jpalite-core/src/main/java/org/jpalite/JPACache.java
@@ -1,4 +1,4 @@
-package io.jpalite;
+package org.jpalite;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
diff --git a/jpalite-core/src/main/java/io/jpalite/JPAEntity.java b/jpalite-core/src/main/java/org/jpalite/JPAEntity.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/JPAEntity.java
rename to jpalite-core/src/main/java/org/jpalite/JPAEntity.java
index 7af5d60..6c3eac4 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPAEntity.java
+++ b/jpalite-core/src/main/java/org/jpalite/JPAEntity.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
import jakarta.persistence.LockModeType;
@@ -52,7 +52,7 @@ public interface JPAEntity extends Serializable
* @return The set
*/
Set _getModifiedFields();
-
+
/**
* Clear both the update and snapshot modification flags.
*/
diff --git a/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java b/jpalite-core/src/main/java/org/jpalite/JPALiteEntityManager.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java
rename to jpalite-core/src/main/java/org/jpalite/JPALiteEntityManager.java
index 81552e4..48fad66 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPALiteEntityManager.java
+++ b/jpalite-core/src/main/java/org/jpalite/JPALiteEntityManager.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
import jakarta.persistence.EntityManager;
diff --git a/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java b/jpalite-core/src/main/java/org/jpalite/JPALitePersistenceUnit.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java
rename to jpalite-core/src/main/java/org/jpalite/JPALitePersistenceUnit.java
index 5581396..45585fa 100644
--- a/jpalite-core/src/main/java/io/jpalite/JPALitePersistenceUnit.java
+++ b/jpalite-core/src/main/java/org/jpalite/JPALitePersistenceUnit.java
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
-import io.jpalite.impl.CacheFormat;
+import org.jpalite.impl.CacheFormat;
import jakarta.persistence.spi.PersistenceUnitInfo;
public interface JPALitePersistenceUnit extends PersistenceUnitInfo
diff --git a/jpalite-core/src/main/java/io/jpalite/LazyInitializationException.java b/jpalite-core/src/main/java/org/jpalite/LazyInitializationException.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/LazyInitializationException.java
rename to jpalite-core/src/main/java/org/jpalite/LazyInitializationException.java
index bbedef9..ed04151 100644
--- a/jpalite-core/src/main/java/io/jpalite/LazyInitializationException.java
+++ b/jpalite-core/src/main/java/org/jpalite/LazyInitializationException.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/MappingType.java b/jpalite-core/src/main/java/org/jpalite/MappingType.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/MappingType.java
rename to jpalite-core/src/main/java/org/jpalite/MappingType.java
index 10296ae..d8b429d 100644
--- a/jpalite-core/src/main/java/io/jpalite/MappingType.java
+++ b/jpalite-core/src/main/java/org/jpalite/MappingType.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public enum MappingType
{
diff --git a/jpalite-core/src/main/java/io/jpalite/MultiTenantProvider.java b/jpalite-core/src/main/java/org/jpalite/MultiTenantProvider.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/MultiTenantProvider.java
rename to jpalite-core/src/main/java/org/jpalite/MultiTenantProvider.java
index 1462baa..1eeb819 100644
--- a/jpalite-core/src/main/java/io/jpalite/MultiTenantProvider.java
+++ b/jpalite-core/src/main/java/org/jpalite/MultiTenantProvider.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public interface MultiTenantProvider
{
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceAction.java b/jpalite-core/src/main/java/org/jpalite/PersistenceAction.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/PersistenceAction.java
rename to jpalite-core/src/main/java/org/jpalite/PersistenceAction.java
index da8c23b..dea47b9 100644
--- a/jpalite-core/src/main/java/io/jpalite/PersistenceAction.java
+++ b/jpalite-core/src/main/java/org/jpalite/PersistenceAction.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public enum PersistenceAction
{
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java b/jpalite-core/src/main/java/org/jpalite/PersistenceContext.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/PersistenceContext.java
rename to jpalite-core/src/main/java/org/jpalite/PersistenceContext.java
index 189562c..3bb18a6 100644
--- a/jpalite-core/src/main/java/io/jpalite/PersistenceContext.java
+++ b/jpalite-core/src/main/java/org/jpalite/PersistenceContext.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.annotation.Nonnull;
import jakarta.persistence.EntityTransaction;
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java b/jpalite-core/src/main/java/org/jpalite/PersistenceUnitNotFoundException.java
similarity index 95%
rename from jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java
rename to jpalite-core/src/main/java/org/jpalite/PersistenceUnitNotFoundException.java
index 3f95ae4..49af514 100644
--- a/jpalite-core/src/main/java/io/jpalite/PersistenceUnitNotFoundException.java
+++ b/jpalite-core/src/main/java/org/jpalite/PersistenceUnitNotFoundException.java
@@ -1,4 +1,4 @@
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/PersistenceUnitProvider.java b/jpalite-core/src/main/java/org/jpalite/PersistenceUnitProvider.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/PersistenceUnitProvider.java
rename to jpalite-core/src/main/java/org/jpalite/PersistenceUnitProvider.java
index bc015c0..436caba 100644
--- a/jpalite-core/src/main/java/io/jpalite/PersistenceUnitProvider.java
+++ b/jpalite-core/src/main/java/org/jpalite/PersistenceUnitProvider.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
public interface PersistenceUnitProvider
diff --git a/jpalite-core/src/main/java/io/jpalite/QueryParsingException.java b/jpalite-core/src/main/java/org/jpalite/QueryParsingException.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/QueryParsingException.java
rename to jpalite-core/src/main/java/org/jpalite/QueryParsingException.java
index d61a7c0..bb6ed72 100644
--- a/jpalite-core/src/main/java/io/jpalite/QueryParsingException.java
+++ b/jpalite-core/src/main/java/org/jpalite/QueryParsingException.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/UnknownFieldException.java b/jpalite-core/src/main/java/org/jpalite/UnknownFieldException.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/UnknownFieldException.java
rename to jpalite-core/src/main/java/org/jpalite/UnknownFieldException.java
index dbdfee5..f5c02cd 100644
--- a/jpalite-core/src/main/java/io/jpalite/UnknownFieldException.java
+++ b/jpalite-core/src/main/java/org/jpalite/UnknownFieldException.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite;
+package org.jpalite;
import jakarta.persistence.PersistenceException;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java b/jpalite-core/src/main/java/org/jpalite/impl/CacheFormat.java
similarity index 62%
rename from jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java
rename to jpalite-core/src/main/java/org/jpalite/impl/CacheFormat.java
index 63b8430..5a90ad5 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/CacheFormat.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/CacheFormat.java
@@ -1,4 +1,4 @@
-package io.jpalite.impl;
+package org.jpalite.impl;
public enum CacheFormat
{
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/ConverterClassImpl.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java
rename to jpalite-core/src/main/java/org/jpalite/impl/ConverterClassImpl.java
index f0b2fcb..c3919be 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/ConverterClassImpl.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/ConverterClassImpl.java
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package org.jpalite.impl;
-import io.jpalite.ConverterClass;
-import io.jpalite.FieldConvertType;
+import org.jpalite.ConverterClass;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java b/jpalite-core/src/main/java/org/jpalite/impl/CustomPersistenceUnit.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java
rename to jpalite-core/src/main/java/org/jpalite/impl/CustomPersistenceUnit.java
index bbcd093..a4c1999 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/CustomPersistenceUnit.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/CustomPersistenceUnit.java
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package org.jpalite.impl;
-import io.jpalite.JPALitePersistenceUnit;
-import io.jpalite.impl.providers.JPALitePersistenceProviderImpl;
+import org.jpalite.JPALitePersistenceUnit;
+import org.jpalite.impl.providers.JPALitePersistenceProviderImpl;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.ValidationMode;
import jakarta.persistence.spi.ClassTransformer;
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/EntityFieldImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/EntityFieldImpl.java
new file mode 100644
index 0000000..2289caf
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/EntityFieldImpl.java
@@ -0,0 +1,436 @@
+/*
+ * 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 org.jpalite.impl;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.graalvm.nativeimage.ImageInfo;
+import org.jpalite.*;
+import org.jpalite.impl.fieldtypes.EnumFieldType;
+import org.jpalite.impl.fieldtypes.ObjectFieldType;
+import org.jpalite.impl.fieldtypes.OrdinalEnumFieldType;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static jakarta.persistence.GenerationType.AUTO;
+import static jakarta.persistence.GenerationType.SEQUENCE;
+
+@Data
+@Slf4j
+@SuppressWarnings({"unchecked", "java:S3740"})//Cannot use generics here
+public class EntityFieldImpl implements EntityField
+{
+ private static final boolean NATIVE_IMAGE = ImageInfo.inImageCode();
+ /**
+ * The entity class
+ */
+ private final Class> enityClass;
+ /**
+ * Identifier of the entity
+ */
+ private final String name;
+ /**
+ * A unique field number assigned to the field.
+ */
+ private final int fieldNr;
+ /**
+ * The java class of the field
+ */
+ private Class> type;
+ /**
+ * Set to true if the field points to an entity
+ */
+ private boolean entityField;
+ /**
+ * The SQL column linked to the field
+ */
+ private String column;
+ /**
+ * The mapping type specified by the field. See {@link MappingType}.
+ */
+ private MappingType mappingType;
+ /**
+ * True if the field is to be unique in the table
+ */
+ private boolean unique;
+ /**
+ * True of the field can be null
+ */
+ private boolean nullable;
+ /**
+ * True if the field is insertable
+ */
+ private boolean insertable;
+ /**
+ * True if the field is updatable.
+ */
+ private boolean updatable;
+ /**
+ * True if the field is an ID Field
+ */
+ private boolean idField;
+ /**
+ * True if the field is a Version Field
+ */
+ private boolean versionField;
+ /**
+ * The getter for the field
+ */
+ private MethodHandle getter;
+ /**
+ * The getter reflection method for the field
+ */
+ private Method getterMethod;
+ /**
+ * The setter for the field
+ */
+ private MethodHandle setter;
+ /**
+ * The setter reflection method for the field
+ */
+ private Method setterMethod;
+ /**
+ * The {@link CascadeType} assigned to the field.
+ */
+ private Set cascade;
+ /**
+ * The {@link FetchType} assigned to the field.
+ */
+ 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.
+ */
+ private String mappedBy;
+ /**
+ * The columnDefinition value defined in the JoinColumn annotation linked to the field
+ */
+ private String columnDefinition;
+ /**
+ * The table value defined in the JoinColumn annotation linked to the field
+ */
+ private String table;
+ /**
+ * The converter class used to convert the field to a SQL type
+ */
+ private FieldConvertType, ?> converter;
+
+ /**
+ * Create a new entity field definition
+ *
+ * @param field The field
+ * @param fieldNr The field number
+ */
+ public EntityFieldImpl(Class> enitityClass, Field field, int fieldNr)
+ {
+ type = field.getType();
+ if (!Map.class.isAssignableFrom(type) && field.getGenericType() instanceof ParameterizedType) {
+ type = (Class>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
+ }//if
+
+ enityClass = enitityClass;
+ name = field.getName();
+ this.fieldNr = fieldNr;
+ entityField = (JPAEntity.class.isAssignableFrom(type));
+ mappingType = MappingType.BASIC;
+ unique = false;
+ nullable = true;
+ insertable = true;
+ updatable = true;
+ fetchType = FetchType.EAGER;
+ cascade = new HashSet<>();
+ mappedBy = null;
+ columnDefinition = null;
+ table = null;
+ idField = false;
+ versionField = false;
+
+ //The order below is important
+ processMappingType(field);
+
+ findConverter(field);
+
+ findGetterSetter(field);
+ }//EntityField
+
+ private void findGetterSetter(Field field)
+ {
+ String vMethod = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ String reflectionMethod = null;
+ try {
+ reflectionMethod = "set" + vMethod;
+ setterMethod = enityClass.getMethod(reflectionMethod, field.getType());
+ setter = lookup.unreflect(setterMethod);
+
+ reflectionMethod = ((field.getType() == Boolean.class || field.getType() == boolean.class) ? "is" : "get") + vMethod;
+ getterMethod = enityClass.getMethod(reflectionMethod);
+ getter = lookup.unreflect(getterMethod);
+ }//try
+ catch (IllegalAccessException | NoSuchMethodException | SecurityException ex) {
+ /*
+ * Special case for Boolean that could be either isXXX or
+ * getXXXX
+ */
+ if (field.getType() == Boolean.class || field.getType() == boolean.class) {
+ try {
+ reflectionMethod = "get" + vMethod;
+ getterMethod = enityClass.getMethod(reflectionMethod);
+ getter = lookup.unreflect(getterMethod);
+ }//try
+ catch (IllegalAccessException | NoSuchMethodException | SecurityException ex1) {
+ throw new IllegalCallerException(String.format("Error finding %s::%s", enityClass.getSimpleName(), reflectionMethod), ex);
+ }//catch
+ }//if
+ else {
+ throw new IllegalCallerException(String.format("Error finding %s::%s", enityClass.getSimpleName(), reflectionMethod), ex);
+ }//else
+ }//catch
+ }//findGetterSetter
+
+ private void processMappingType(Field pField)
+ {
+ if (checkEmbeddedField(pField) || checkOneToOneField(pField) ||
+ checkOneToManyField(pField) || checkManyToOneField(pField) ||
+ checkManyToManyField(pField)) {
+ JoinColumn joinColumn = pField.getAnnotation(JoinColumn.class);
+ if (joinColumn != null) {
+ setInsertable(joinColumn.insertable());
+ setNullable(joinColumn.nullable());
+ setUnique(joinColumn.unique());
+ setUpdatable(joinColumn.updatable());
+ setColumn(joinColumn.name());
+ }//if
+ }//if
+ else {
+ prosesBasicField(pField);
+ }//if
+ }//processMappingType
+
+ private void prosesBasicField(Field field)
+ {
+ Basic basic = field.getAnnotation(Basic.class);
+ if (basic != null) {
+ if (isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is referencing an Entity type and cannot be annotated with @Basic.");
+ }//if
+ setFetchType(basic.fetch());
+ setNullable(basic.optional());
+ }//if
+
+ Column col = field.getAnnotation(Column.class);
+ if (col != null) {
+ setColumn(col.name());
+ setInsertable(col.insertable());
+ setNullable(col.nullable());
+ setUnique(col.unique());
+ setUpdatable(col.updatable());
+ setTable(col.table());
+ setColumnDefinition(col.columnDefinition());
+ }//if
+
+ setIdField((field.getAnnotation(Id.class) != null));
+ if (isIdField()) {
+ GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class);
+ if (generatedValue != null) {
+ if (generatedValue.strategy() != AUTO && generatedValue.strategy() != SEQUENCE) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + "@GeneratedValue is not AUTO or SEQUENCE");
+ }//if
+ insertable = false;
+ updatable = false;
+ }//if
+ nullable = false;
+ }//if
+
+ setVersionField(field.getAnnotation(Version.class) != null);
+ }//prosesBasicField
+
+ private boolean checkEmbeddedField(Field field)
+ {
+ Embedded embedded = field.getAnnotation(Embedded.class);
+ if (embedded != null) {
+ if (!isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is NOT referencing an Entity type and cannot NOT be annotated with @Embedded.");
+ }//if
+
+ setMappingType(MappingType.EMBEDDED);
+ return true;
+ }//if
+ return false;
+ }//checkEmbeddedField
+
+ private boolean checkOneToOneField(Field field)
+ {
+ OneToOne oneToOne = field.getAnnotation(OneToOne.class);
+ if (oneToOne != null) {
+ if (!isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is NOT referencing an Entity type and cannot NOT be annotated with @OneToOne.");
+ }//if
+ setMappingType(MappingType.ONE_TO_ONE);
+ setFetchType(oneToOne.fetch());
+ setCascade(new HashSet<>(Arrays.asList(oneToOne.cascade())));
+ setMappedBy(oneToOne.mappedBy());
+ return true;
+ }//if
+ return false;
+ }//checkOneToOneField
+
+ private boolean checkOneToManyField(Field field)
+ {
+ OneToMany oneToMany = field.getAnnotation(OneToMany.class);
+ if (oneToMany != null) {
+ if (!isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is NOT referencing an Entity type and cannot NOT be annotated with @OneToMany.");
+ }//if
+
+ setMappingType(MappingType.ONE_TO_MANY);
+ setFetchType(oneToMany.fetch());
+ setCascade(new HashSet<>(Arrays.asList(oneToMany.cascade())));
+ setMappedBy(oneToMany.mappedBy());
+ return true;
+ }//if
+ return false;
+ }//checkOneToManyField
+
+ private boolean checkManyToOneField(Field field)
+ {
+ ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
+ if (manyToOne != null) {
+ if (!isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is NOT referencing an Entity type and cannot NOT be annotated with @ManyToOne.");
+ }//if
+
+ setMappingType(MappingType.MANY_TO_ONE);
+ setFetchType(manyToOne.fetch());
+ setCascade(new HashSet<>(Arrays.asList(manyToOne.cascade())));
+ return true;
+ }//if
+ return false;
+ }//checkManyToOneField
+
+ private boolean checkManyToManyField(Field field)
+ {
+ ManyToMany manyToMany = field.getAnnotation(ManyToMany.class);
+ if (manyToMany != null) {
+ if (!isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is NOT referencing an Entity type and cannot NOT be annotated with @ManyToMany.");
+ }//if
+
+ setMappingType(MappingType.MANY_TO_MANY);
+ setFetchType(manyToMany.fetch());
+ setCascade(new HashSet<>(Arrays.asList(manyToMany.cascade())));
+ setMappedBy(manyToMany.mappedBy());
+ return true;
+ }//if
+ return false;
+ }//checkManyToManyField
+
+ private void findConverter(Field field)
+ {
+ Convert customType = field.getAnnotation(Convert.class);
+ if (customType != null) {
+ try {
+ //Check if the converter class was explicitly overridden
+ if (customType.converter() != null) {
+ converter = (FieldConvertType, ?>) customType.converter().getConstructor().newInstance();
+ return;
+ }//if
+ }//try
+ catch (InvocationTargetException | InstantiationException | IllegalAccessException |
+ NoSuchMethodException ex) {
+ throw new IllegalArgumentException(getName() + "::" + field.getName() + " failed to instantiate the referenced converter", ex);
+ }//catch
+
+ //If conversion is not required, exit here
+ if (customType.disableConversion()) {
+ return;
+ }//if
+ }//if
+
+ ConverterClass converterClass = EntityMetaDataManager.getConvertClass(type);
+ if (converterClass != null) {
+ converter = converterClass.getConverter();
+ }//if
+ else {
+ if (type.isEnum()) {
+ Enumerated enumField = field.getAnnotation(Enumerated.class);
+ if (enumField == null) {
+ LOG.warn("{}: Field '{}' is not annotated as an enum, assuming it to be one - Developers must fix this", enityClass.getName(), field.getName());
+ converter = new EnumFieldType((Class>) type);
+ }//if
+ else {
+ if (isEntityField()) {
+ throw new PersistenceException(enityClass.getName() + "::" + getName() + " is referencing an Entity type and cannot be annotated with @Enumerated.");
+ }//if
+
+ converter = (enumField.value() == EnumType.ORDINAL ? new OrdinalEnumFieldType((Class>) type) : new EnumFieldType((Class>) type));
+ }//if
+ } else {
+ if (!isEntityField()) {
+ converter = new ObjectFieldType();
+ }
+ }
+ }
+ }//checkForConvert
+
+ @Override
+ public Object invokeGetter(Object entity)
+ {
+ try {
+ if (getter == null) {
+ throw new PersistenceException("No getter method found for " + enityClass.getName() + "::" + getName());
+ }//if
+
+ return NATIVE_IMAGE ? getterMethod.invoke(entity) : getter.invoke(entity);
+ }//try
+ catch (Throwable ex) {
+ throw new PersistenceException("Failed to invoke getter for " + enityClass.getName() + "::" + getName(), ex);
+ }//catch
+ }//invokeGetter
+
+
+ @Override
+ public void invokeSetter(Object entity, Object value)
+ {
+ try {
+ if (setter == null) {
+ throw new PersistenceException("No setter method found for " + enityClass.getName() + "::" + getName());
+ }//if
+
+ if (NATIVE_IMAGE) {
+ setterMethod.invoke(entity, value);
+ }//if
+ else {
+ setter.invoke(entity, value);
+ }//else
+ }//try
+ catch (Throwable ex) {
+ throw new PersistenceException("Failed to invoke setter for " + enityClass.getName() + "::" + getName(), ex);
+ }//catch
+ }//invokeSetter
+}//EntityFieldImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityL1LocalCacheImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/EntityL1LocalCacheImpl.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/EntityL1LocalCacheImpl.java
rename to jpalite-core/src/main/java/org/jpalite/impl/EntityL1LocalCacheImpl.java
index 33385ee..022b05f 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/EntityL1LocalCacheImpl.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/EntityL1LocalCacheImpl.java
@@ -15,12 +15,12 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package org.jpalite.impl;
-import io.jpalite.EntityLocalCache;
-import io.jpalite.EntityState;
-import io.jpalite.JPAEntity;
-import io.jpalite.PersistenceContext;
+import org.jpalite.EntityLocalCache;
+import org.jpalite.EntityState;
+import org.jpalite.JPAEntity;
+import org.jpalite.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityLifecycleImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/EntityLifecycleImpl.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/impl/EntityLifecycleImpl.java
rename to jpalite-core/src/main/java/org/jpalite/impl/EntityLifecycleImpl.java
index e0c5fe3..88084b6 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/EntityLifecycleImpl.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/EntityLifecycleImpl.java
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package org.jpalite.impl;
-import io.jpalite.EntityLifecycle;
-import io.jpalite.EntityMapException;
+import org.jpalite.EntityLifecycle;
+import org.jpalite.EntityMapException;
import jakarta.persistence.*;
import java.lang.reflect.InvocationTargetException;
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/EntityMetaDataImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/EntityMetaDataImpl.java
new file mode 100644
index 0000000..e7df719
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/EntityMetaDataImpl.java
@@ -0,0 +1,382 @@
+/*
+ * 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 org.jpalite.impl;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import jakarta.persistence.*;
+import lombok.extern.slf4j.Slf4j;
+import org.jpalite.*;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+@SuppressWarnings("java:S3740")
+@Slf4j
+public class EntityMetaDataImpl implements EntityMetaData
+{
+ private final String entityName;
+ private final EntityLifecycle lifecycleListeners;
+ private final Class entityClass;
+ private final boolean legacyEntity;
+
+ private final boolean cacheable;
+ private long idleTime = 1;
+ private TimeUnit cacheTimeUnit = TimeUnit.DAYS;
+
+ private final String columns;
+ private String table;
+ private EntityType entityType;
+
+ private EntityMetaData> primaryKey;
+ private final List idFields;
+ private final Map entityFields;
+ private EntityField versionField;
+
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public EntityMetaDataImpl(Class entityClass)
+ {
+ entityType = EntityType.ENTITY;
+ entityFields = new LinkedHashMap<>();
+ idFields = new ArrayList<>();
+
+ this.entityClass = entityClass;
+
+ Entity entity = entityClass.getAnnotation(Entity.class);
+
+ legacyEntity = (entity == null);
+ if (entity != null && !entity.name().isEmpty()) {
+ entityName = entity.name();
+ }//if
+ else {
+ entityName = entityClass.getSimpleName();
+ }//else
+
+ Table tableAnnotation = entityClass.getAnnotation(Table.class);
+ if (tableAnnotation != null) {
+ this.table = tableAnnotation.name();
+ }//if
+
+ Embeddable embeddable = entityClass.getAnnotation(Embeddable.class);
+ if (embeddable != null) {
+ entityType = EntityType.EMBEDDABLE;
+ }//if
+
+ Cacheable cacheableAnnotation = entityClass.getAnnotation(Cacheable.class);
+ if (cacheableAnnotation != null) {
+ this.cacheable = cacheableAnnotation.value();
+ Caching vCaching = entityClass.getAnnotation(Caching.class);
+ if (vCaching != null) {
+ idleTime = vCaching.idleTime();
+ cacheTimeUnit = vCaching.unit();
+ }//if
+ }//if
+ else {
+ this.cacheable = false;
+ }//else
+
+ 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.ID_CLASS;
+ EntityMetaDataManager.register(primaryKey);
+ }//if
+
+ if (primaryKey.getEntityType() != EntityType.ID_CLASS) {
+ throw new IllegalArgumentException("Illegal IdClass specified. [" + idClass.value() + "] is already registered as an entity of type [" + primaryKey.getEntityType() + "]");
+ }//if
+ }//if
+
+ versionField = null;
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Field vField : entityClass.getDeclaredFields()) {
+ if (!Modifier.isStatic(vField.getModifiers()) &&
+ !Modifier.isFinal(vField.getModifiers()) &&
+ !Modifier.isTransient(vField.getModifiers()) &&
+ !vField.isAnnotationPresent(Transient.class)) {
+ processEntityField(vField, stringBuilder);
+ }//if
+ }//for
+
+ if (idFields.isEmpty()) {
+ LOG.warn("Developer Warning - Entity [{}] have no ID Fields defined . This needs to be fixed as not having ID fields is not allowed!", entityName);
+ }//if
+
+ //if
+ if (primaryKey == null && idFields.size() > 1) {
+ throw new IllegalArgumentException("Missing @IdClass definition for Entity. @IdClass definition is required if you have more than one ID field");
+ }//if
+
+ lifecycleListeners = new EntityLifecycleImpl(entityClass);
+
+ if (stringBuilder.length() > 1) {
+ columns = stringBuilder.substring(1);
+ }//if
+ else {
+ columns = "";
+ }//else
+ }//EntityMetaDataImpl
+
+ private void processEntityField(Field field, StringBuilder stringBuilder)
+ {
+ EntityField entityField = new EntityFieldImpl(entityClass, field, entityFields.size() + 1);
+
+ if (entityField.getMappingType() == MappingType.BASIC) {
+ if (entityField.getColumn() == null) {
+ return;
+ }//if
+
+ if (!entityField.getColumnDefinition().isEmpty() && !entityField.getTable().isEmpty()) {
+ stringBuilder.append(",");
+ stringBuilder.append(entityField.getTable()).append(".");
+ stringBuilder.append(entityField.getColumnDefinition()).append(" ").append(entityField.getColumn());
+ }//if
+ else {
+ //Ignore columns that have a '-' in the column definition
+ if (!"-".equals(entityField.getColumnDefinition())) {
+ stringBuilder.append(",");
+ stringBuilder.append(entityField.getColumn());
+ }//if
+ }//else
+
+ if (entityField.isIdField()) {
+ idFields.add(entityField);
+ }//if
+
+ if (entityField.isVersionField()) {
+ versionField = entityField;
+ }//if
+ }//if
+ else {
+ //JoinColumn is not required (or used) if getMappedBy is provided
+ if (entityField.getMappingType() != MappingType.EMBEDDED && entityField.getMappedBy() == null && entityField.getColumn() == null) {
+ return;
+ }//if
+ }//if
+
+ entityFields.put(entityField.getName(), entityField);
+ }//processEntityField
+
+ @Override
+ public String toString()
+ {
+ String primKeyClass;
+ if (primaryKey == null) {
+ if (getIdField() != null) {
+ primKeyClass = getIdField().getType().getName();
+ }//if
+ else {
+ primKeyClass = "N/A";
+ }//else
+ }//if
+ else {
+ primKeyClass = primaryKey.getEntityClass().getName();
+ }//else
+ return "[" + entityName + "] Metadata -> Type:" + entityType + ", Entity Class:" + entityClass.getName() + ", Primary Key Class:" + primKeyClass;
+ }//toString
+
+ @Override
+ public EntityType getEntityType()
+ {
+ return entityType;
+ }//getEntityType
+
+ @Override
+ public String getName()
+ {
+ return entityName;
+ }//getName
+
+ @Override
+ public boolean isCacheable()
+ {
+ return cacheable;
+ }//isCacheable
+
+ /**
+ * The time the entity is to remain in cache before expiring it. Only used if cacheable is true
+ *
+ * @return The idle time setting
+ */
+ public long getIdleTime()
+ {
+ return idleTime;
+ }
+
+ /**
+ * The TimeUnit the idle time is expressed in
+ *
+ * @return The time units
+ */
+ public TimeUnit getCacheTimeUnit()
+ {
+ return cacheTimeUnit;
+ }
+
+ @Nonnull
+ @Override
+ public T getNewEntity()
+ {
+ try {
+ return entityClass.getConstructor().newInstance();
+ }//try
+ catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException ex) {
+ throw new EntityMapException("Error instantiating instance of " + entityClass.getSimpleName());
+ }//catch
+ }//getNewEntity
+
+ @Override
+ public Class getEntityClass()
+ {
+ return entityClass;
+ }//getEntityClass
+
+ @Override
+ public EntityLifecycle getLifecycleListeners()
+ {
+ return lifecycleListeners;
+ }//getLifecycleListeners
+
+ @Override
+ public String getTable()
+ {
+ return table;
+ }//getTable
+
+ @Override
+ @Nonnull
+ public EntityField getEntityField(String fieldName)
+ {
+ EntityField entityField = entityFields.get(fieldName);
+ if (entityField == null) {
+ throw new EntityNotFoundException(fieldName + " is not defined as a field in entity " + this.entityName);
+ }//if
+
+ return entityField;
+ }//getEntityField
+
+ @Override
+ public boolean isEntityField(String fieldName)
+ {
+ return entityFields.containsKey(fieldName);
+ }//isEntityField
+
+ @Nullable
+ public EntityField getEntityFieldByColumn(String column)
+ {
+ for (EntityField field : entityFields.values()) {
+ if (column.equalsIgnoreCase(field.getColumn())) {
+ return field;
+ }//if
+ }//for
+
+ return null;
+ }//getEntityFieldByColumn
+
+ @Override
+ @Nonnull
+ public EntityField getEntityFieldByNr(int fieldNr)
+ {
+ Optional entityField = entityFields.values()
+ .stream()
+ .filter(f -> f.getFieldNr() == fieldNr)
+ .findFirst();
+ if (entityField.isEmpty()) {
+ throw new EntityNotFoundException("There is no entity field with a fields number of " + fieldNr + " in entity " + this.entityName);
+ }//if
+
+ return entityField.get();
+ }//getEntityFieldByNr
+
+ @Override
+ public Collection getEntityFields()
+ {
+ return entityFields.values();
+ }//getEntityFields
+
+ @Override
+ public boolean hasMultipleIdFields()
+ {
+ return false;
+ }//hasMultipleIdFields
+
+ @Override
+ public EntityField getIdField()
+ {
+ if (hasMultipleIdFields()) {
+ throw new IllegalArgumentException("Multiple id fields exists");
+ }//if
+
+ if (idFields.isEmpty()) {
+ return null;
+ }//if
+
+ return idFields.getFirst();
+ }//getIdField
+
+ @Override
+ public boolean hasVersionField()
+ {
+ return versionField != null;
+ }//hasVersionField
+
+ @Override
+ public EntityField getVersionField()
+ {
+ if (versionField == null) {
+ throw new IllegalArgumentException("The entity does not have a version field");
+ }//if
+
+ return versionField;
+ }//getVersionField
+
+ @Override
+ @Nullable
+ public EntityMetaData> getPrimaryKeyMetaData()
+ {
+ return primaryKey;
+ }//getIPrimaryKeyMetaData
+
+
+ @Override
+ @Nonnull
+ public List getIdFields()
+ {
+ return idFields;
+ }//getIdFields
+
+ @Override
+ @Deprecated
+ public boolean isLegacyEntity()
+ {
+ return legacyEntity;
+ }
+
+ @Override
+ @Deprecated
+ public String getColumns()
+ {
+ return columns;
+ }//getColumns
+}//EntityMetaDataImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/JPAConfig.java b/jpalite-core/src/main/java/org/jpalite/impl/JPAConfig.java
similarity index 98%
rename from jpalite-core/src/main/java/io/jpalite/impl/JPAConfig.java
rename to jpalite-core/src/main/java/org/jpalite/impl/JPAConfig.java
index ec5e4d0..2cfc2bc 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/JPAConfig.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/JPAConfig.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite.impl;
+package org.jpalite.impl;
import io.smallrye.config.SmallRyeConfigProviderResolver;
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/JPAEntityImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/JPAEntityImpl.java
new file mode 100644
index 0000000..fc7b440
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/JPAEntityImpl.java
@@ -0,0 +1,1053 @@
+/*
+ * 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 org.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 jakarta.annotation.Nonnull;
+import jakarta.persistence.*;
+import jakarta.persistence.spi.LoadState;
+import org.jpalite.PersistenceContext;
+import org.jpalite.*;
+import org.jpalite.impl.queries.JPALiteQueryImpl;
+import org.jpalite.impl.queries.QueryImpl;
+import org.jpalite.queries.QueryLanguage;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.function.Consumer;
+
+import static jakarta.persistence.LockModeType.*;
+
+/**
+ * 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.
+ *
+ * To prevent any mishaps with duplicate method names hiding access to the class all methods here will be prefixed with
+ * '_' and attributes with '$$' knowing that it is considered a bad naming convention and be flagged as such by the IDE
+ * and SonarQube (hoping that, you, the developer, do not pick the same method and variable names as what I have been
+ * using here ;-) )
+ */
+@SuppressWarnings({"java:S100", "java:S116"})
+public class JPAEntityImpl implements JPAEntity
+{
+ public static final String SELECT_CLAUSE = "select ";
+ public static final String FROM_CLAUSE = " from ";
+ public static final String WHERE_CLAUSE = " where ";
+ /**
+ * A set of fields that was modified
+ */
+ private final transient Set $$modifiedList = new HashSet<>();
+ /**
+ * A set of fields that must be loaded on first access
+ */
+ private final transient Set $$fetchLazy = new HashSet<>();
+ /**
+ * The current entity state
+ */
+ private transient EntityState $$state = EntityState.TRANSIENT;
+ /**
+ * The action to perform on this entity when it is flushed by the persistence context
+ */
+ private transient PersistenceAction $$pendingAction = PersistenceAction.NONE;
+ /**
+ * The lock mode for the entity
+ */
+ private transient LockModeType $$lockMode = LockModeType.NONE;
+ /**
+ * The persistence context this entity belongs too.
+ */
+ private transient PersistenceContext $$persistenceContext = null;
+ /**
+ * The metadata for the entity
+ */
+ private final transient EntityMetaData> $$metadata;
+ /**
+ * Set to true if the entity is being mapped
+ */
+ private transient boolean $$mapping = false;
+ /**
+ * Set to true if the entity is lazy loaded.
+ */
+ private transient boolean $$lazyLoaded = false;
+ /**
+ * Indicator that an entity was created but no fields has been set yet.
+ */
+ private transient boolean $$blankEntity = true;
+
+ /**
+ * Control value to prevent recursive iteration by toString
+ */
+ private transient boolean inToString = false;
+
+ protected JPAEntityImpl()
+ {
+ if (EntityMetaDataManager.isRegistered(getClass())) {
+ $$metadata = EntityMetaDataManager.getMetaData(getClass());
+
+ //Find all BASIC and ONE_TO_MANY fields that are flagged as being lazily fetched and add them to our $$fetchLazy list
+ $$metadata.getEntityFields()
+ .stream()
+ .filter(f -> f.getFetchType() == FetchType.LAZY && (f.getMappingType() == MappingType.BASIC || f.getMappingType() == MappingType.ONE_TO_MANY))
+ .forEach(f -> $$fetchLazy.add(f.getName()));
+
+ //Force the default lock mode to OPTIMISTIC_FORCE_INCREMENT if the entity has a version field
+ if ($$metadata.hasVersionField()) {
+ $$lockMode = OPTIMISTIC_FORCE_INCREMENT;
+ }//if
+ }//if
+ else {
+ $$metadata = null;
+ }//else
+ }//JPAEntityImpl
+
+ @Override
+ public Class> _getEntityClass()
+ {
+ return getClass();
+ }
+
+ @Override
+ public String toString()
+ {
+ if ($$metadata == null) {
+ return super.toString();
+ }//if
+
+ StringBuilder toString = new StringBuilder(_getEntityInfo())
+ .append(" ::")
+ .append(_getStateInfo()).append(", ");
+
+ toString.append(_getDataInfo());
+
+ return toString.toString();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof JPAEntityImpl e) {
+ return _getPrimaryKey() != null && _getPrimaryKey().equals(e._getPrimaryKey());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hashCode(_getPrimaryKey());
+ }
+
+ private String _getEntityInfo()
+ {
+ return "Entity " + $$metadata.getName();
+ }//_getEntityInfo
+
+ @SuppressWarnings({"java:S3776", "java:S3740"}) //The method cannot be simplified without increasing its complexity
+ private String _getDataInfo()
+ {
+ StringBuilder toString = new StringBuilder();
+
+ if (inToString) {
+ toString.append(" [Circular reference detected]");
+ }//if
+ else {
+ try {
+ inToString = true;
+
+ if ($$lazyLoaded) {
+ toString.append(" [Lazy on PK=")
+ .append(_getPrimaryKey())
+ .append("] ");
+ }//if
+ else {
+ toString.append("Data(");
+
+ boolean first = true;
+ for (EntityField field : _getMetaData().getEntityFields()) {
+ if (!first) {
+ toString.append(", ");
+ }//if
+ first = false;
+
+ if (field.isIdField()) {
+ toString.append("*");
+ }//if
+ toString.append(field.getName()).append("=");
+ if ($$fetchLazy.contains(field.getName())) {
+ toString.append("[Lazy]");
+ }//if
+ else {
+ Object val = field.invokeGetter(this);
+ if (val instanceof Map, ?> mapVal) {
+ val = "[Map " + mapVal.size() + " items]";
+ }//if
+ else if (val instanceof List> listVal) {
+ val = "[List " + listVal.size() + " items]";
+ }//else if
+ toString.append(val);
+ }//else
+ }//for
+ toString.append(")");
+ }//if
+ }//try
+ finally {
+ inToString = false;
+ }//finally
+ }//else
+
+ return toString.toString();
+ }//_getDataInfo
+
+ private String _getStateInfo()
+ {
+ return " State:" + $$state + ", " + "Action:" + $$pendingAction;
+ }//_getStateInfo
+
+ @Override
+ public JPAEntity _clone()
+ {
+ JPAEntityImpl clone = (JPAEntityImpl) $$metadata.getNewEntity();
+ clone.$$blankEntity = false;
+ _getMetaData().getEntityFields()
+ .stream()
+ .filter(f -> !f.isIdField() && !f.isVersionField())
+ .forEach(f ->
+ {
+ Object vVal = f.invokeGetter(this);
+ f.invokeSetter(clone, vVal);
+ });
+ clone.$$fetchLazy.addAll($$fetchLazy);
+ return clone;
+ }//_clone
+
+ @Override
+ public void _replaceWith(JPAEntity entity)
+ {
+ if (!_getMetaData().getName().equals(entity._getMetaData().getName())) {
+ throw new IllegalArgumentException("Attempting to replace entities of different types");
+ }//if
+
+ if (_getEntityState() != EntityState.DETACHED && _getEntityState() != EntityState.TRANSIENT) {
+ throw new IllegalArgumentException("The content of an entity can only be replaced if it is DETACHED or TRANSIENT");
+ }//if
+
+ if (entity._getEntityState() != EntityState.MANAGED && entity._getEntityState() != EntityState.DETACHED) {
+ throw new IllegalArgumentException("The provided entity must be in an MANAGED or DETACHED state");
+ }//if
+
+ $$mapping = true;
+ try {
+ _getMetaData().getEntityFields()
+ .stream()
+ .filter(f -> !_isLazyLoaded(f.getName()))
+ .forEach(f -> f.invokeSetter(this, f.invokeGetter(entity)));
+ $$fetchLazy.clear();
+ $$fetchLazy.addAll(((JPAEntityImpl) entity).$$fetchLazy);
+ $$blankEntity = false;
+ _setPendingAction(entity._getPendingAction());
+ $$modifiedList.clear();
+ $$modifiedList.addAll(((JPAEntityImpl) entity).$$modifiedList);
+
+ entity._getPersistenceContext().l1Cache().manage(this);
+ entity._getPersistenceContext().l1Cache().detach(entity);
+ }//try
+ finally {
+ $$mapping = false;
+ }
+ }//_replaceWith
+
+ @Override
+ public void _refreshEntity(Map properties)
+ {
+ if ($$blankEntity) {
+ throw new IllegalStateException("Entity is not initialised");
+ }//if
+
+ if (_getEntityState() == EntityState.TRANSIENT || _getEntityState() == EntityState.REMOVED || _getPersistenceContext() == null) {
+ throw new IllegalStateException("Entity is not managed or detached");
+ }//if
+
+ if (_getPersistenceContext().isReleased()) {
+ throw new LazyInitializationException("Entity is not attached to an active persistence context");
+ }//if
+
+ try {
+ _clearModified();
+
+ //Detach the entity from L1 cache
+ PersistenceContext persistenceContext = _getPersistenceContext();
+ persistenceContext.l1Cache().detach(this);
+
+ String queryStr = SELECT_CLAUSE + $$metadata.getName() + FROM_CLAUSE + $$metadata.getName() + WHERE_CLAUSE + $$metadata.getIdField().getName() + "=:p";
+ JPALiteQueryImpl> query = new JPALiteQueryImpl<>(queryStr,
+ QueryLanguage.JPQL,
+ persistenceContext,
+ $$metadata.getEntityClass(),
+ properties,
+ $$lockMode);
+ query.setParameter("p", _getPrimaryKey());
+ JPAEntity replaceEntity = (JPAEntity) query.getSingleResult();
+ _replaceWith(replaceEntity);
+ $$lazyLoaded = false;
+
+ for (EntityField field : _getMetaData().getEntityFields()) {
+ if ((field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.REFRESH))) {
+ if (field.getMappingType() == MappingType.ONE_TO_ONE || field.getMappingType() == MappingType.MANY_TO_ONE) {
+ JPAEntity entity = (JPAEntity) field.invokeGetter(this);
+ if (entity != null) {
+ entity._refreshEntity(properties);
+ }
+ } else {
+ if (field.getMappingType() == MappingType.ONE_TO_MANY || field.getMappingType() == MappingType.MANY_TO_MANY) {
+ @SuppressWarnings("unchecked")
+ List entities = (List) field.invokeGetter(this);
+ for (JPAEntity entity : entities) {
+ entity._refreshEntity(properties);
+ }
+ }
+ }
+ }
+ }
+ }//try
+ catch (NoResultException ex) {
+ throw new EntityNotFoundException(String.format("Lazy load of entity '%s' for key '%s' failed", $$metadata.getName(), _getPrimaryKey()));
+ }
+ catch (PersistenceException ex) {
+ throw new LazyInitializationException("Error lazy fetching entity " + $$metadata.getName(), ex);
+ }//catch
+ }//_refreshEntity
+
+ private void _queryOneToMany(EntityField entityField)
+ {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityField.getType());
+ EntityField mappingField = metaData.getEntityField(entityField.getMappedBy());
+
+ JPALiteQueryImpl> query = new JPALiteQueryImpl<>(SELECT_CLAUSE + metaData.getName() + FROM_CLAUSE + metaData.getName() + WHERE_CLAUSE + mappingField.getName() + "=:p",
+ QueryLanguage.JPQL,
+ _getPersistenceContext(),
+ metaData.getEntityClass(),
+ Collections.emptyMap());
+ query.setParameter("p", _getPrimaryKey());
+ entityField.invokeSetter(this, query.getResultList());
+ }//_fetchOneToMany
+
+ private void _queryBasicField(EntityField entityField)
+ {
+ String queryStr = SELECT_CLAUSE + " E." + entityField.getName() + FROM_CLAUSE + $$metadata.getName() + " E " + WHERE_CLAUSE + " E." + $$metadata.getIdField().getName() + "=:p";
+ Query query = new QueryImpl(queryStr,
+ _getPersistenceContext(),
+ entityField.getType(),
+ new HashMap<>());
+ query.setParameter("p", _getPrimaryKey());
+
+ //Will call _markField which will remove the field from the list
+ entityField.invokeSetter(this, query.getSingleResult());
+ }//_queryBasicField
+
+ @Override
+ public void _lazyFetchAll(boolean forceEagerLoad)
+ {
+ Set lazyFields = new HashSet<>($$fetchLazy);
+ lazyFields.forEach(this::_lazyFetch);
+ _getMetaData().getEntityFields()
+ .stream()
+ .filter(f -> f.getMappingType().equals(MappingType.MANY_TO_ONE) && (forceEagerLoad || f.getFetchType() == FetchType.EAGER))
+ .forEach(f -> {
+ JPAEntity manyToOneField = (JPAEntity) f.invokeGetter(this);
+ if (manyToOneField != null) {
+ _getPersistenceContext().l1Cache().manage(manyToOneField);
+ manyToOneField._refreshEntity(Collections.emptyMap());
+ }//if
+ });
+ }//_lazyFetchAll
+
+ @Override
+ public void _lazyFetch(String fieldName)
+ {
+ //Lazy fetching is only applicable for MANAGED and DETACHED entities
+ if (_getEntityState() == EntityState.TRANSIENT || _getEntityState() == EntityState.REMOVED) {
+ return;
+ }//if
+
+ if (_isLazyLoaded()) {
+ //Refresh the entity. Refreshing will also clear the lazy loaded flag
+ _refreshEntity(Collections.emptyMap());
+ }//if
+
+ if ($$fetchLazy.contains(fieldName)) {
+ if (_getPersistenceContext().isReleased()) {
+ throw new LazyInitializationException("Entity is not attached to an active persistence context");
+ }//if
+
+ EntityField entityField = $$metadata.getEntityField(fieldName);
+ if (entityField.getMappingType() == MappingType.BASIC) {
+ _queryBasicField(entityField);
+ }//if
+ else {
+ _queryOneToMany(entityField);
+ }//else
+ }//if
+ }//_lazyFetch
+
+ @Override
+ public boolean _isLazyLoaded()
+ {
+ return $$lazyLoaded;
+ }//_isLazyLoaded
+
+ @Override
+ public boolean _isLazyLoaded(String fieldName)
+ {
+ return $$fetchLazy.contains(fieldName);
+ }
+
+ @Override
+ public void _markLazyLoaded()
+ {
+ $$lazyLoaded = true;
+ }//_markLazyLoaded
+
+ @Override
+ public void _makeReference(Object primaryKey)
+ {
+ if (!$$blankEntity) {
+ throw new IllegalArgumentException("Entity must be blank to be made into a reference");
+ }//if
+
+ _setPrimaryKey(primaryKey);
+ _markLazyLoaded();
+ _clearModified();
+ }//_makeReference
+
+ @Override
+ public EntityMetaData> _getMetaData()
+ {
+ if ($$metadata == null) {
+ throw new IllegalArgumentException(getClass() + " is not a known entity or not yet registered");
+ }//if
+
+ return $$metadata;
+ }
+
+ @Override
+ public Set _getModifiedFields()
+ {
+ return $$modifiedList;
+ }
+
+ @Override
+ public void _clearModified()
+ {
+ $$modifiedList.clear();
+ if ($$pendingAction == PersistenceAction.UPDATE) {
+ $$pendingAction = PersistenceAction.NONE;
+ }//if
+ }
+
+ @Override
+ public LoadState _loadState()
+ {
+ return (_isLazyLoaded() || $$blankEntity) ? LoadState.NOT_LOADED : LoadState.LOADED;
+ }
+
+ @Override
+ public boolean _isFieldModified(String fieldName)
+ {
+ return $$modifiedList.contains(fieldName);
+ }
+
+ @Override
+ public void _clearField(String fieldName)
+ {
+ $$modifiedList.remove(fieldName);
+ if ($$modifiedList.isEmpty() && $$pendingAction == PersistenceAction.UPDATE) {
+ $$pendingAction = PersistenceAction.NONE;
+ }//if
+ }
+
+ @Override
+ public void _markField(String fieldName)
+ {
+ if ($$metadata.isEntityField(fieldName)) {
+ EntityField vEntityField = $$metadata.getEntityField(fieldName);
+
+ if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && vEntityField.isIdField()) {
+ if (!$$metadata.isLegacyEntity()) {
+ throw new PersistenceException("The ID field cannot be modified");
+ }//if
+ LoggerFactory.getLogger(JPAEntityImpl.class).warn("Legacy Mode :: Allowing modifying of ID Field {} in Entity {}", vEntityField.getName(), $$metadata.getName());
+ }//if
+
+ if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && vEntityField.isVersionField()) {
+ throw new PersistenceException("A VERSION field cannot be modified");
+ }//if
+
+ if (!$$mapping && !_getEntityState().equals(EntityState.TRANSIENT) && !vEntityField.isUpdatable()) {
+ if (!$$metadata.isLegacyEntity()) {
+ throw new PersistenceException("Attempting to updated a field that is marked as NOT updatable");
+ }//if
+ LoggerFactory.getLogger(JPAEntityImpl.class).warn("Legacy Mode :: Allowing modifying of NOT updatable field {} in Entity {}", vEntityField.getName(), $$metadata.getName());
+ }//if
+
+ /*
+ * _markField is call whenever a field is updated
+ * When this happens we can clear the $$blankEntity flag (as it is not true anymore! :-) )
+ * We are also clearing the fetch lazy status for this field, if any
+ * Lastly we are marking this fields as modified
+ */
+ $$blankEntity = false;
+ $$fetchLazy.remove(fieldName);
+
+ /*
+ * ONE_TO_MANY fields is not really part of the current entity and any change to a ONE_TO_MANY field
+ * do not trigger an update to the current entity.
+ */
+ if (!$$mapping && vEntityField.getMappingType() != MappingType.ONE_TO_MANY) {
+ $$modifiedList.add(fieldName);
+ if ($$pendingAction == PersistenceAction.NONE) {
+ _setPendingAction(PersistenceAction.UPDATE);
+ }//if
+ }//if
+ }//if
+ }
+
+ @Override
+ public boolean _isEntityModified()
+ {
+ return !$$modifiedList.isEmpty();
+ }
+
+ @Override
+ public LockModeType _getLockMode()
+ {
+ return $$lockMode;
+ }
+
+ @Override
+ public void _setLockMode(LockModeType lockMode)
+ {
+ if (lockMode == OPTIMISTIC || lockMode == OPTIMISTIC_FORCE_INCREMENT || lockMode == WRITE || lockMode == READ) {
+ if (!_getMetaData().hasVersionField()) {
+ throw new PersistenceException("Entity has not version field");
+ }//if
+
+ /*
+ If the entity is not new and is not dirty but is locked optimistically, we need to update the version
+ The JPA Specification states that for versioned objects, it is permissible for an implementation to use
+ LockMode- Type.OPTIMISTIC_FORCE_INCREMENT where LockModeType.OPTIMISTIC/READ was requested, but not vice versa.
+ We choose to handle Type.OPTIMISTIC/READ) as Type.OPTIMISTIC_FORCE_INCREMENT
+ */
+ lockMode = OPTIMISTIC_FORCE_INCREMENT;
+ }//if
+ if (lockMode == NONE && _getMetaData().hasVersionField()) {
+ throw new PersistenceException("Entity has version field and cannot be locked with LockModeType.NONE");
+ }//if
+
+ $$lockMode = lockMode;
+ }
+
+ @Override
+ public EntityState _getEntityState()
+ {
+ return $$state;
+ }
+
+ @Override
+ public void _setEntityState(EntityState newState)
+ {
+ if ($$state != newState && newState != EntityState.REMOVED) {
+ $$metadata.getEntityFields().stream()
+ .filter(f -> f.isEntityField() && f.getMappingType() != MappingType.ONE_TO_MANY)
+ .forEach(f -> {
+ JPAEntity vEntity = (JPAEntity) f.invokeGetter(this);
+ if (vEntity != null) {
+ vEntity._setEntityState(newState);
+ }//if
+ });
+ }//if
+ $$state = newState;
+ }
+
+ @Override
+ public PersistenceContext _getPersistenceContext()
+ {
+ return $$persistenceContext;
+ }
+
+ @Override
+ public void _setPersistenceContext(PersistenceContext persistenceContext)
+ {
+ if ($$persistenceContext != persistenceContext) {
+ $$persistenceContext = persistenceContext;
+ $$metadata.getEntityFields().stream()
+ .filter(f -> f.isEntityField() && f.getMappingType() != MappingType.ONE_TO_MANY)
+ .forEach(f -> {
+ JPAEntity vEntity = (JPAEntity) f.invokeGetter(this);
+ if (vEntity != null) {
+ vEntity._setPersistenceContext(persistenceContext);
+ }//if
+ });
+ }//if
+ }
+
+ @Override
+ public PersistenceAction _getPendingAction()
+ {
+ return $$pendingAction;
+ }
+
+ @Override
+ public void _setPendingAction(PersistenceAction pendingAction)
+ {
+ $$pendingAction = pendingAction;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public X _getDBValue(@Nonnull String fieldName)
+ {
+ EntityField entityField = _getMetaData().getEntityField(fieldName);
+
+ Object value = entityField.invokeGetter(this);
+ if (value == null) {
+ return null;
+ }//if
+
+ if (entityField.isEntityField()) {
+ return (X) value;
+ }
+
+ return (X) entityField.getConverter().convertToDatabaseColumn(value);
+ }//getField
+
+ @Override
+ public void _updateRestrictedField(Consumer method)
+ {
+ boolean mappingStatus = $$mapping;
+ try {
+ $$mapping = true;
+ method.accept(this);
+ }
+ finally {
+ $$mapping = mappingStatus;
+ }
+ }
+
+ @Override
+ public void _merge(JPAEntity entity)
+ {
+ if (!_getMetaData().getName().equals(entity._getMetaData().getName())) {
+ throw new IllegalArgumentException("Attempting to merge entities of different types");
+ }//if
+
+ if (!entity._getPrimaryKey().equals(_getPrimaryKey())) {
+ throw new EntityMapException("Error merging entities, primary key mismatch. Expected " + _getPrimaryKey() + ", but got " + entity._getPrimaryKey());
+ }//if
+
+ /*
+ * If the entity has a version field, we need to check that the version of the entity
+ * being merged matches the current version, except if the entity was created by reference.
+ */
+ if (!$$lazyLoaded && $$metadata.hasVersionField()) {
+ EntityField field = $$metadata.getVersionField();
+ Object val = field.invokeGetter(entity);
+ if (val != null && !val.equals(field.invokeGetter(this))) {
+ throw new OptimisticLockException("Error merging entities, version mismatch. Expected " + field.invokeGetter(this) + ", but got " + val);
+ }//if
+ }//if
+
+ for (String fieldName : entity._getModifiedFields()) {
+ EntityField field = $$metadata.getEntityField(fieldName);
+ if (!field.isIdField()) {
+ field.invokeSetter(this, field.invokeGetter(entity));
+ }//if
+ }//for
+ $$lazyLoaded = false;
+ }//merge
+
+ @Override
+ public Object _getPrimaryKey()
+ {
+ if ($$metadata == null || $$metadata.getIdFields().isEmpty()) {
+ return null;
+ }//if
+
+ if ($$metadata.getIdFields().size() > 1) {
+ EntityMetaData> primaryKey = $$metadata.getPrimaryKeyMetaData();
+ Object primKey = null;
+ if (primaryKey != null) {
+ primKey = primaryKey.getNewEntity();
+ for (EntityField entityField : $$metadata.getIdFields()) {
+ EntityField keyField = primaryKey.getEntityField(entityField.getName());
+ keyField.invokeSetter(primKey, entityField.invokeGetter(this));
+ }//for
+ }//if
+ return primKey;
+ }//if
+ else {
+ return $$metadata.getIdFields().getFirst().invokeGetter(this);
+ }//else
+ }//_getPrimaryKey
+
+ @Override
+ public void _setPrimaryKey(Object primaryKey)
+ {
+ if (_getEntityState() != EntityState.TRANSIENT) {
+ throw new IllegalStateException("The primary key can only be set for an entity with a TRANSIENT state");
+ }//if
+
+ if ($$metadata.getIdFields().isEmpty()) {
+ throw new IllegalStateException("Entity [" + $$metadata.getName() + "] do not have any ID fields");
+ }//if
+
+
+ if ($$metadata.getIdFields().size() > 1) {
+ EntityMetaData> primaryKeyMetaData = $$metadata.getPrimaryKeyMetaData();
+ if (primaryKeyMetaData == null) {
+ throw new IllegalStateException("Missing IDClass for Entity [" + $$metadata.getName() + "]");
+ }//if
+
+ for (EntityField entityField : $$metadata.getIdFields()) {
+ EntityField keyField = primaryKeyMetaData.getEntityField(entityField.getName());
+ entityField.invokeSetter(this, keyField.invokeGetter(primaryKey));
+ }//for
+ }//if
+ else {
+ $$metadata.getIdFields().getFirst().invokeSetter(this, primaryKey);
+ }//else
+ }//_setPrimaryKey
+
+ public JPAEntity _JPAReadEntity(EntityField field, ResultSet resultSet, String colPrefix, int col) throws SQLException
+ {
+ JPAEntity managedEntity = null;
+
+ //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) {
+ 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
+ }//if
+ }
+
+ return managedEntity;
+ }//_JPAReadEntity
+
+ @SuppressWarnings("java:S6205") // False error
+ public void _JPAReadField(ResultSet row, EntityField field, String colPrefix, int columnNr)
+ {
+ try {
+ $$mapping = true;
+ if (field.isEntityField()) {
+ 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));
+ }//if
+ } else {
+ field.invokeSetter(this, field.getConverter().convertToEntityAttribute(row, columnNr));
+ }
+ }//try
+ catch (SQLException ex) {
+ throw new EntityMapException("Error setting field '" + field.getName() + "'", ex);
+ }//catch
+ finally {
+ $$mapping = false;
+ }//finally
+ }//setField
+
+ public void _mapResultSet(String colPrefix, ResultSet resultSet)
+ {
+ try {
+ ResultSetMetaData resultMetaData = resultSet.getMetaData();
+ int columns = resultMetaData.getColumnCount();
+
+ Set columnsProcessed = new HashSet<>();
+ for (int i = 1; i <= columns; i++) {
+ String column = resultMetaData.getColumnName(i);
+
+ EntityField field = null;
+ String nextColPrefix = null;
+ if (colPrefix == null) {
+ field = $$metadata.getEntityFieldByColumn(column);
+ }//if
+ else {
+ if (column.length() <= colPrefix.length() || !column.startsWith(colPrefix)) {
+ continue;
+ }//if
+
+ String fieldName = column.substring(colPrefix.length() + 1).split("-")[0];
+ if (!fieldName.isEmpty() && !columnsProcessed.contains(fieldName)) {
+ columnsProcessed.add(fieldName);
+ field = $$metadata.getEntityFieldByNr(Integer.parseInt(fieldName));
+ nextColPrefix = colPrefix + "-" + fieldName;
+ }//if
+ }//else
+
+ if (field != null) {
+ _JPAReadField(resultSet, field, nextColPrefix, i);
+ _clearField(field.getName());
+ }//if
+ }//for
+ $$lazyLoaded = false;
+ }//try
+ catch (Exception ex) {
+ throw new EntityMapException("Error extracting the ResultSet Metadata", ex);
+ }//catch
+ }//_mapResultSet
+
+ private void writeFields(DataOutputStream out) throws IOException
+ {
+ Collection fieldList = $$metadata.getEntityFields();
+ for (EntityField field : fieldList) {
+ Object value = field.invokeGetter(this);
+ if (value != null) {
+ out.writeShort(field.getFieldNr());
+ if (field.isEntityField()) {
+ EntityMetaData> metaData = ((JPAEntity) value)._getMetaData();
+ if (metaData.getEntityType() == EntityType.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.isEntityField()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
+
+ JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
+ entity.readFields(in);
+ if (metaData.getEntityType() == EntityType.ENTITY) {
+ 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.isEntityField()) {
+ 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.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
+ 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.isEntityField()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(field.getType());
+
+ JPAEntityImpl entity = (JPAEntityImpl) metaData.getNewEntity();
+ entity._fromJson(node.getValue());
+
+ if (metaData.getEntityType() == EntityType.ENTITY) {
+ 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();
+ DataOutputStream out = new DataOutputStream(recvOut);
+ writeFields(out);
+ out.flush();
+
+ return recvOut.toByteArray();
+ }//try
+ catch (IOException ex) {
+ throw new PersistenceException("Error serialising entity", ex);
+ }//catch
+ }//_serialise
+
+ @Override
+ public void _deserialize(byte[] bytes)
+ {
+ try {
+ ByteArrayInputStream recvOut = new ByteArrayInputStream(bytes);
+ DataInputStream in = new DataInputStream(recvOut);
+ readFields(in);
+ }//try
+ catch (IOException ex) {
+ throw new PersistenceException("Error de-serialising the entity", ex);
+ }//catch
+ }//_deserialize
+
+ @Override
+ public boolean _entityEquals(JPAEntity entity)
+ {
+ return (entity._getMetaData().getEntityClass().equals(_getMetaData().getEntityClass()) &&
+ entity._getPrimaryKey().equals(_getPrimaryKey()));
+ }
+}//JPAEntityImpl
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/JPALiteEntityManagerImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/JPALiteEntityManagerImpl.java
new file mode 100755
index 0000000..35e102c
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/JPALiteEntityManagerImpl.java
@@ -0,0 +1,808 @@
+/*
+ * 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 org.jpalite.impl;
+
+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 io.quarkus.runtime.BlockingOperationControl;
+import io.quarkus.runtime.BlockingOperationNotAllowedException;
+import jakarta.annotation.Nonnull;
+import jakarta.persistence.*;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaDelete;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.CriteriaUpdate;
+import jakarta.persistence.metamodel.Metamodel;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.jpalite.PersistenceContext;
+import org.jpalite.*;
+import org.jpalite.impl.queries.*;
+import org.jpalite.queries.EntityQuery;
+import org.jpalite.queries.QueryLanguage;
+
+import java.sql.ResultSet;
+import java.util.*;
+
+import static jakarta.persistence.LockModeType.*;
+
+/**
+ * The entity manager implementation
+ */
+@Slf4j
+@ToString(of = {"persistenceContext", "entityManagerFactory", "threadId"})
+public class JPALiteEntityManagerImpl implements JPALiteEntityManager
+{
+ private static final String CRITERIA_QUERY_NOT_SUPPORTED = "CriteriaQuery is not supported";
+ private static final String ENTITY_GRAPH_NOT_SUPPORTED = "EntityGraph is not supported";
+ private static final String STORED_PROCEDURE_QUERY_NOT_SUPPORTED = "StoredProcedureQuery is not supported";
+ private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(JPALiteEntityManagerImpl.class.getName());
+ private final EntityManagerFactory entityManagerFactory;
+ private final PersistenceContext persistenceContext;
+ private final long threadId;
+ private final Throwable opened;
+ private final Map properties;
+
+ private boolean entityManagerOpen;
+ private FlushModeType flushMode;
+
+ public JPALiteEntityManagerImpl(PersistenceContext persistenceContext, EntityManagerFactory factory)
+ {
+ this.persistenceContext = persistenceContext;
+ this.entityManagerFactory = factory;
+
+ entityManagerOpen = true;
+ flushMode = FlushModeType.AUTO;
+ properties = new HashMap<>(persistenceContext.getProperties());
+ threadId = Thread.currentThread().threadId();
+
+ if (LOG.isTraceEnabled()) {
+ opened = new Throwable();
+ }//if
+ else {
+ opened = null;
+ }//else
+ }//JPALiteEntityManagerImpl
+
+ //
+ @Override
+ @SuppressWarnings("java:S6205") // false error
+ public void setProperty(String name, Object value)
+ {
+ checkOpen();
+
+ persistenceContext.setProperty(name, value);
+ properties.put(name, value);
+ }//setProperty
+
+ @Override
+ public Map getProperties()
+ {
+ checkOpen();
+ return properties;
+ }//getProperties
+
+ private void checkOpen()
+ {
+ if (!isOpen()) {
+ throw new IllegalStateException("EntityManager is closed");
+ }//if
+
+ if (threadId != Thread.currentThread().threadId()) {
+ throw new IllegalStateException("Entity Managers are NOT threadsafe. Opened at ", opened);
+ }//if
+
+ if (!BlockingOperationControl.isBlockingAllowed()) {
+ throw new BlockingOperationNotAllowedException("You have attempted to perform a blocking operation on a IO thread. This is not allowed, as blocking the IO thread will cause major performance issues with your application. If you want to perform blocking EntityManager operations make sure you are doing it from a worker thread.");
+ }//if
+ }//checkOpen
+
+ private void checkEntity(Object entity)
+ {
+ if (entity == null) {
+ throw new IllegalArgumentException("Entity cannot be null");
+ }
+
+ if (!(entity instanceof JPAEntity)) {
+ throw new IllegalArgumentException("Entity is not an instance of JPAEntity");
+ }
+ }
+
+ private void checkEntityClass(Class> entityClass)
+ {
+ if (!(JPAEntity.class.isAssignableFrom(entityClass))) {
+ throw new IllegalArgumentException("Entity " + entityClass.getName() + " is not created using EntityManager");
+ }//if
+ }//checkEntityClass
+
+ private void checkEntityAttached(JPAEntity entity)
+ {
+ if (entity._getEntityState() != EntityState.MANAGED) {
+ throw new IllegalArgumentException("Entity is not current attached to a persistence context");
+ }//if
+
+ if (entity._getPersistenceContext() != persistenceContext) {
+ throw new IllegalArgumentException("Entity is not being managed by this Persistence Context");
+ }//if
+ }//checkEntityObject
+
+ private void checkTransactionRequired()
+ {
+ if (!persistenceContext.isActive()) {
+ throw new TransactionRequiredException();
+ }//if
+ }//checkTransactionRequired
+ //
+
+ @Override
+ public EntityTransaction getTransaction()
+ {
+ checkOpen();
+ return persistenceContext.getTransaction();
+ }
+
+ @Override
+ public EntityManagerFactory getEntityManagerFactory()
+ {
+ checkOpen();
+
+ return entityManagerFactory;
+ }
+
+ @Override
+ public void close()
+ {
+ checkOpen();
+ entityManagerOpen = false;
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return entityManagerOpen;
+ }
+
+ @Override
+ public X mapResultSet(@Nonnull X entity, ResultSet resultSet)
+ {
+ checkEntity(entity);
+ return persistenceContext.mapResultSet(entity, resultSet);
+ }
+
+ @Override
+ public void setFlushMode(FlushModeType flushMode)
+ {
+ checkOpen();
+ this.flushMode = flushMode;
+ }//setFlushMode
+
+ @Override
+ public FlushModeType getFlushMode()
+ {
+ checkOpen();
+ return flushMode;
+ }//getFlushMode
+
+ @Override
+ public void clear()
+ {
+ checkOpen();
+ persistenceContext.l1Cache().clear();
+ }//clear
+
+ @Override
+ public void detach(Object entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ checkEntityAttached((JPAEntity) entity);
+ persistenceContext.l1Cache().detach((JPAEntity) entity);
+ }//detach
+
+ @Override
+ public boolean contains(Object entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ return persistenceContext.l1Cache().contains((JPAEntity) entity);
+ }//contains
+
+ //
+ @Override
+ public EntityGraph createEntityGraph(Class rootType)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
+ }
+
+ @Override
+ public EntityGraph> createEntityGraph(String graphName)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
+ }
+
+ @Override
+ @SuppressWarnings("java:S4144")//Not an error
+ public EntityGraph> getEntityGraph(String graphName)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
+ }
+
+ @Override
+ @SuppressWarnings("java:S4144")//Not an error
+ public List> getEntityGraphs(Class entityClass)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(ENTITY_GRAPH_NOT_SUPPORTED);
+ }
+ //
+
+ //
+ @Override
+ public void flush()
+ {
+ checkOpen();
+ checkTransactionRequired();
+
+ persistenceContext.flush();
+ }//flush
+
+ @Override
+ public void flushOnType(Class> entityClass)
+ {
+ persistenceContext.flushOnType(entityClass);
+ }//flushEntities
+
+ @Override
+ public void flushEntity(@Nonnull T entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkTransactionRequired();
+ checkEntityAttached((JPAEntity) entity);
+
+ persistenceContext.flushEntity((JPAEntity) entity);
+ }//flushEntity
+
+ @Override
+ public void persist(@Nonnull Object entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkTransactionRequired();
+ checkEntityClass(entity.getClass());
+
+ if (((JPAEntity) entity)._getEntityState() == EntityState.MANAGED) {
+ //An existing managed entity is ignored
+ return;
+ }//if
+
+ if (((JPAEntity) entity)._getEntityState() == EntityState.REMOVED) {
+ throw new PersistenceException("Attempting to persist an entity that was removed from the database");
+ }//if
+
+ ((JPAEntity) entity)._setPendingAction(PersistenceAction.INSERT);
+ persistenceContext.l1Cache().manage((JPAEntity) entity);
+
+ if (flushMode == FlushModeType.AUTO) {
+ flushEntity((JPAEntity) entity);
+ }//if
+ }//persist
+
+ /**
+ * Many-to-one fields (entities) might be indirectly attached but contain One-to-Many fields
+ */
+ private void cascadeMerge(JPAEntity entity)
+ {
+ entity._getMetaData()
+ .getEntityFields()
+ .stream()
+ .filter(f -> f.isEntityField() && !entity._isLazyLoaded(f.getName()))
+ .filter(f -> f.getCascade().contains(CascadeType.ALL) || f.getCascade().contains(CascadeType.MERGE))
+ .forEach(f ->
+ {
+ try {
+ if (f.getMappingType() == MappingType.ONE_TO_MANY || f.getMappingType() == MappingType.MANY_TO_MANY) {
+ @SuppressWarnings("unchecked")
+ List entityList = (List) f.invokeGetter(entity);
+
+ List newEntityList = new ArrayList<>();
+ for (JPAEntity ref : entityList) {
+ newEntityList.add(merge(ref));
+ }//for
+ f.invokeSetter(entity, newEntityList);
+ }//if
+ else {
+ if (f.getMappingType() == MappingType.MANY_TO_ONE || f.getMappingType() == MappingType.ONE_TO_ONE) {
+ JPAEntity ref = (JPAEntity) f.invokeGetter(entity);
+ f.invokeSetter(entity, merge(ref));
+ }
+ }
+ }//try
+ catch (PersistenceException ex) {
+ LOG.error("Error processing cascading fields");
+ throw ex;
+ }//catch
+ catch (RuntimeException ex) {
+ LOG.error("Error merging ManyToOne field", ex);
+ throw new PersistenceException("Error merging ManyToOne field");
+ }//catch
+ });
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public X merge(X entity)
+ {
+ Span span = TRACER.spanBuilder("EntityManager::merge").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ checkOpen();
+ checkEntity(entity);
+ checkTransactionRequired();
+ checkEntityClass(entity.getClass());
+
+ JPAEntity jpaEntity = (JPAEntity) entity;
+ return switch (jpaEntity._getEntityState()) {
+ case MANAGED -> {
+ checkEntityAttached(jpaEntity);
+ yield entity;
+ }
+ case DETACHED -> {
+ X latestEntity = (X) find(jpaEntity.getClass(), jpaEntity._getPrimaryKey(), jpaEntity._getLockMode());
+ if (latestEntity == null) {
+ throw new IllegalArgumentException("Original entity not found");
+ }//if
+ ((JPAEntity) latestEntity)._merge(jpaEntity);
+ cascadeMerge(jpaEntity);
+ yield latestEntity;
+ }
+
+ case REMOVED ->
+ throw new PersistenceException("Attempting to merge an entity that was removed from the database");
+
+ case TRANSIENT -> {
+ Object primaryKey = jpaEntity._getPrimaryKey();
+ if (primaryKey != null) {
+ X persistedEntity = find((Class) entity.getClass(), primaryKey);
+ if (persistedEntity != null) {
+ ((JPAEntity) persistedEntity)._merge(jpaEntity);
+ yield persistedEntity;
+ }//if
+ }//if
+
+ persist(entity);
+ yield entity;
+ }
+ };
+ }//try
+ finally {
+ span.end();
+ }
+ }//merge
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T clone(@Nonnull T entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+
+ return (T) ((JPAEntity) entity)._clone();
+ }//clone
+
+ @Override
+ public void remove(Object entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkTransactionRequired();
+ checkEntityClass(entity.getClass());
+
+ ((JPAEntity) entity)._setPendingAction(PersistenceAction.DELETE);
+
+ if (flushMode == FlushModeType.AUTO) {
+ flushEntity((JPAEntity) entity);
+ }//if
+ }//remove
+ //
+
+ //
+ @Override
+ public void refresh(Object entity)
+ {
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ refresh(entity, ((JPAEntity) entity)._getLockMode(), Collections.emptyMap());
+ }//refresh
+
+ @Override
+ public void refresh(Object entity, Map properties)
+ {
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ refresh(entity, ((JPAEntity) entity)._getLockMode(), properties);
+ }//refresh
+
+ @Override
+ public void refresh(Object entity, LockModeType lockMode)
+ {
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ refresh(entity, lockMode, Collections.emptyMap());
+ }//refresh
+
+ @Override
+ public void refresh(Object entity, LockModeType lockMode, Map properties)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkTransactionRequired();
+ checkEntityAttached((JPAEntity) entity);
+
+ ((JPAEntity) entity)._setLockMode(lockMode);
+ ((JPAEntity) entity)._refreshEntity(properties);
+ }//refresh
+ //
+
+ //
+ @Override
+ public T find(Class entityClass, Object primaryKey)
+ {
+ return find(entityClass, primaryKey, LockModeType.NONE, null);
+ }
+
+ @Override
+ public T find(Class entityClass, Object primaryKey, Map properties)
+ {
+ return find(entityClass, primaryKey, LockModeType.NONE, properties);
+ }
+
+ @Override
+ public T find(Class entityClass, Object primaryKey, LockModeType lockMode)
+ {
+ return find(entityClass, primaryKey, lockMode, null);
+ }
+
+ @Override
+ public T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties)
+ {
+ Span span = TRACER.spanBuilder("EntityManager::find").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ checkOpen();
+ checkEntityClass(entityClass);
+
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entityClass);
+ span.setAttribute("entity", metaData.getName());
+
+ Map hints = new HashMap<>(this.properties);
+ if (properties != null) {
+ hints.putAll(properties);
+ }//if
+
+ EntityQuery entityQuery = new EntitySelectQueryImpl(primaryKey, metaData);
+ JPALiteQueryImpl query = new JPALiteQueryImpl<>(entityQuery.getQuery(),
+ entityQuery.getLanguage(),
+ persistenceContext,
+ entityClass,
+ hints,
+ lockMode);
+ query.setParameter(1, primaryKey);
+ try {
+ return query.getSingleResult();
+ }//try
+ catch (NoResultException ex) {
+ return null;
+ }//catch
+ }//try
+ finally {
+ span.end();
+ }
+ }//find
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getReference(Class entityClass, Object primaryKey)
+ {
+ checkEntityClass(entityClass);
+
+ EntityMetaData metaData = EntityMetaDataManager.getMetaData(entityClass);
+ JPAEntity newEntity = (JPAEntity) metaData.getNewEntity();
+ newEntity._makeReference(primaryKey);
+ persistenceContext.l1Cache().manage(newEntity);
+
+ return (T) newEntity;
+ }
+ //
+
+ //
+ @Override
+ public LockModeType getLockMode(Object entity)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ checkEntityAttached((JPAEntity) entity);
+
+ return ((JPAEntity) entity)._getLockMode();
+ }
+
+ @Override
+ public void lock(Object entity, LockModeType lockMode)
+ {
+ lock(entity, lockMode, null);
+ }//lock
+
+ @Override
+ public void lock(Object entity, LockModeType lockMode, Map properties)
+ {
+ checkOpen();
+ checkEntity(entity);
+ checkEntityClass(entity.getClass());
+ checkTransactionRequired();
+
+ if (entity instanceof JPAEntity jpaEntity) {
+ jpaEntity._setLockMode(lockMode);
+
+ //For pessimistic locking a select for update query is to be executed
+ if (lockMode == PESSIMISTIC_READ || lockMode == PESSIMISTIC_FORCE_INCREMENT || lockMode == PESSIMISTIC_WRITE) {
+ Map hints = new HashMap<>(this.properties);
+ if (properties != null) {
+ hints.putAll(properties);
+ }//if
+
+ String sqlQuery = "select " +
+ jpaEntity._getMetaData().getIdField().getColumn() +
+ " from " +
+ jpaEntity._getMetaData().getTable() +
+ " where " +
+ jpaEntity._getMetaData().getIdField().getColumn() +
+ "=?";
+
+ JPALiteQueryImpl> query = new JPALiteQueryImpl<>(sqlQuery,
+ QueryLanguage.NATIVE,
+ persistenceContext,
+ jpaEntity._getMetaData().getEntityClass(),
+ hints,
+ lockMode);
+ query.setParameter(1, jpaEntity._getPrimaryKey());
+
+ try {
+ //Lock to row and continue
+ query.getSingleResult();
+ }//try
+ catch (NoResultException ex) {
+ getTransaction().setRollbackOnly();
+ throw new EntityNotFoundException(jpaEntity._getMetaData().getName() + " with key " + jpaEntity._getPrimaryKey() + " not found");
+ }//catch
+ catch (PersistenceException ex) {
+ getTransaction().setRollbackOnly();
+ }//if
+ }//if
+ else {
+ //For optimistic locking we need to flush the entity
+ flush();
+ }//else
+ }//if
+ }//lock
+ //
+
+ //
+ @Override
+ public Query createQuery(String query)
+ {
+ checkOpen();
+ return new QueryImpl(query, persistenceContext, Object[].class, properties);
+ }//createQuery
+
+ @Override
+ public TypedQuery createQuery(String query, Class resultClass)
+ {
+ checkOpen();
+ return new TypedQueryImpl<>(query, QueryLanguage.JPQL, persistenceContext, resultClass, properties);
+ }//createQuery
+
+ @Override
+ public TypedQuery createNamedQuery(String name, Class resultClass)
+ {
+ checkOpen();
+
+ NamedQueries namedQueries = resultClass.getAnnotation(NamedQueries.class);
+ if (namedQueries != null) {
+ for (NamedQuery namedQuery : namedQueries.value()) {
+ if (namedQuery.name().equals(name)) {
+ return new NamedQueryImpl<>(namedQuery, persistenceContext, resultClass, properties);
+ }//if
+ }//for
+ }//if
+
+ NamedQuery namedQuery = resultClass.getAnnotation(NamedQuery.class);
+ if (namedQuery != null && namedQuery.name().equals(name)) {
+ return new NamedQueryImpl<>(namedQuery, persistenceContext, resultClass, properties);
+ }//if
+
+ NamedNativeQueries namedNativeQueries = resultClass.getAnnotation(NamedNativeQueries.class);
+ if (namedNativeQueries != null) {
+ for (NamedNativeQuery nativeQuery : namedNativeQueries.value()) {
+ if (nativeQuery.name().equals(name)) {
+ return new NamedNativeQueryImpl<>(nativeQuery, persistenceContext, resultClass, properties);
+ }//if
+ }//for
+ }//if
+
+ NamedNativeQuery namedNativeQuery = resultClass.getAnnotation(NamedNativeQuery.class);
+ if (namedNativeQuery != null && namedNativeQuery.name().equals(name)) {
+ return new NamedNativeQueryImpl<>(namedNativeQuery, persistenceContext, resultClass, properties);
+ }//if
+
+ throw new IllegalArgumentException("Named query '" + name + "' not found");
+ }//createNamedQuery
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public Query createNativeQuery(String sqlString, Class resultClass)
+ {
+ checkOpen();
+ return new NativeQueryImpl<>(sqlString, persistenceContext, resultClass, properties);
+ }//createNativeQuery
+
+ @Override
+ public Query createNativeQuery(String sqlString)
+ {
+ checkOpen();
+ return new NativeQueryImpl<>(sqlString, persistenceContext, Object.class, properties);
+ }
+
+ @Override
+ public TypedQuery createQuery(CriteriaQuery criteriaQuery)
+ {
+ checkOpen();
+ throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public Query createQuery(CriteriaUpdate updateQuery)
+ {
+ checkOpen();
+ throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public Query createQuery(CriteriaDelete deleteQuery)
+ {
+ checkOpen();
+ throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public Query createNamedQuery(String name)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException("Global Named Queries are not supported");
+ }
+
+ @Override
+ public Query createNativeQuery(String sqlString, String resultSetMapping)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException("ResultSetMapping is not supported");
+ }//createNativeQuery
+
+ @Override
+ public StoredProcedureQuery createNamedStoredProcedureQuery(String name)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public StoredProcedureQuery createStoredProcedureQuery(String procedureName)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings)
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(STORED_PROCEDURE_QUERY_NOT_SUPPORTED);
+ }
+
+ @Override
+ @SuppressWarnings("java:S4144")//Not an error
+ public CriteriaBuilder getCriteriaBuilder()
+ {
+ checkOpen();
+
+ throw new UnsupportedOperationException(CRITERIA_QUERY_NOT_SUPPORTED);
+ }
+ //
+
+ @Override
+ public void joinTransaction()
+ {
+ checkOpen();
+
+ persistenceContext.joinTransaction();
+ }
+
+ @Override
+ public boolean isJoinedToTransaction()
+ {
+ checkOpen();
+
+ return persistenceContext.isJoinedToTransaction();
+ }
+
+ @Override
+ public Metamodel getMetamodel()
+ {
+ checkOpen();
+ return entityManagerFactory.getMetamodel();
+ }
+
+ @Override
+ public Object getDelegate()
+ {
+ checkOpen();
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public T unwrap(Class cls)
+ {
+ checkOpen();
+
+ if (cls.isAssignableFrom(this.getClass())) {
+ return (T) this;
+ }
+
+ if (cls.isAssignableFrom(PersistenceContext.class)) {
+ return (T) persistenceContext;
+ }
+
+ throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
+ }
+}//JPALiteEntityManagerImpl
+
+//--------------------------------------------------------------------[ End ]---
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/caching/EntityCacheImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/caching/EntityCacheImpl.java
new file mode 100644
index 0000000..2027b2d
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/caching/EntityCacheImpl.java
@@ -0,0 +1,304 @@
+/*
+ * 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 org.jpalite.impl.caching;
+
+
+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 org.jpalite.*;
+import org.jpalite.impl.CacheFormat;
+import org.jpalite.impl.JPAConfig;
+
+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/db/ConnectionWrapper.java b/jpalite-core/src/main/java/org/jpalite/impl/db/ConnectionWrapper.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/impl/db/ConnectionWrapper.java
rename to jpalite-core/src/main/java/org/jpalite/impl/db/ConnectionWrapper.java
index 240da72..c232031 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/ConnectionWrapper.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/ConnectionWrapper.java
@@ -15,10 +15,10 @@
* limitations under the License.
*/
-package io.jpalite.impl.db;
+package org.jpalite.impl.db;
-import io.jpalite.DatabasePool;
-import io.jpalite.PersistenceContext;
+import org.jpalite.DatabasePool;
+import org.jpalite.PersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java b/jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolFactory.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java
rename to jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolFactory.java
index d3d9927..f82dbd1 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolFactory.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolFactory.java
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-package io.jpalite.impl.db;
+package org.jpalite.impl.db;
-import io.jpalite.DatabasePool;
+import org.jpalite.DatabasePool;
import jakarta.persistence.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolImpl.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java
rename to jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolImpl.java
index 474272c..17ec363 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/DatabasePoolImpl.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/DatabasePoolImpl.java
@@ -15,12 +15,12 @@
* limitations under the License.
*/
-package io.jpalite.impl.db;
+package org.jpalite.impl.db;
-import io.jpalite.DataSourceProvider;
-import io.jpalite.DatabasePool;
-import io.jpalite.JPALitePersistenceUnit;
-import io.jpalite.PersistenceContext;
+import org.jpalite.DataSourceProvider;
+import org.jpalite.DatabasePool;
+import org.jpalite.JPALitePersistenceUnit;
+import org.jpalite.PersistenceContext;
import jakarta.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +32,7 @@
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
-import static io.jpalite.PersistenceContext.PERSISTENCE_JTA_MANAGED;
+import static org.jpalite.PersistenceContext.PERSISTENCE_JTA_MANAGED;
/**
* The DatabasePoolImpl class is part of the JPA implementation
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/db/PersistenceContextImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/db/PersistenceContextImpl.java
new file mode 100644
index 0000000..0268bb1
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/PersistenceContextImpl.java
@@ -0,0 +1,1156 @@
+/*
+ * 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 org.jpalite.impl.db;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import io.quarkus.runtime.Application;
+import jakarta.annotation.Nonnull;
+import jakarta.enterprise.inject.spi.CDI;
+import jakarta.persistence.*;
+import jakarta.transaction.Status;
+import jakarta.transaction.SystemException;
+import jakarta.transaction.TransactionManager;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.jpalite.PersistenceContext;
+import org.jpalite.*;
+import org.jpalite.impl.EntityL1LocalCacheImpl;
+import org.jpalite.impl.caching.EntityCacheImpl;
+import org.jpalite.impl.queries.EntityDeleteQueryImpl;
+import org.jpalite.impl.queries.EntityInsertQueryImpl;
+import org.jpalite.impl.queries.EntityUpdateQueryImpl;
+import org.jpalite.queries.EntityQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintWriter;
+import java.sql.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.jpalite.JPALiteEntityManager.*;
+import static org.jpalite.PersistenceAction.*;
+
+/**
+ * The persistence context is responsible for managing the connection, persisting entities to the database and keeps
+ * tract of transaction blocks started and needs to do the cleanup on close.
+ */
+public class PersistenceContextImpl implements PersistenceContext
+{
+ private static final Logger LOG = LoggerFactory.getLogger(PersistenceContextImpl.class);
+ private static final Tracer TRACER = GlobalOpenTelemetry.get().getTracer(PersistenceContextImpl.class.getName());
+ /**
+ * The database pool we belong to
+ */
+ private final DatabasePool pool;
+ /**
+ * Control counter to manage transaction depth. Every call to {@link #begin()} will increment it and calls to
+ * {@link #commit()} and {@link #rollback()} will decrement it.
+ */
+ private final AtomicInteger transactionDepth;
+ /**
+ * Control variable to record the current {@link #transactionDepth}.
+ */
+ private final Deque openStack;
+ /**
+ * The connection name used to open a new connection
+ */
+ private final Deque connectionNames;
+ /**
+ * Stack for all save points created by beginTrans()
+ */
+ private final Deque savepoints;
+ /**
+ * The level 1 cache
+ */
+ private final EntityLocalCache entityL1Cache;
+ /**
+ * The level 2 cache
+ */
+ private final EntityCache entityL2Cache;
+ /**
+ * List of all callback listeners
+ */
+ private final List listeners;
+ /**
+ * List of callback listeners to add. This list is populated if a new listener is removed form with in a callback.
+ */
+ private final List pendingAdd;
+ /**
+ * List of callback listeners to delete. This list is populated if a listener is removed form with in a callback.
+ */
+ private final List pendingRemoval;
+ /**
+ * The connection assigned to the manager
+ */
+ private ConnectionWrapper connection;
+ /**
+ * The last query executed in by the connection
+ */
+ private String lastQuery;
+ /**
+ * The current connection name assigned to the connection
+ */
+ private String connectionName;
+ /**
+ * The execution time after which queries are considered run too slowly
+ */
+ private long slowQueryTime;
+ /**
+ * If true create a connection that shows the SQL
+ */
+ private boolean showSql;
+ /**
+ * The cache store mode in effect
+ */
+ private CacheStoreMode cacheStoreMode;
+ /**
+ * Control variable to indicate that we have forced rollback
+ */
+ private boolean rollbackOnly = false;
+ /**
+ * Read only indicator
+ */
+ private boolean readOnly;
+ /**
+ * Control variable to make sure that a transaction callback does not call begin, commit or rollback
+ */
+ private boolean inCallbackHandler;
+ /**
+ * The JTA transaction manager
+ */
+ private TransactionManager transactionManager;
+ /**
+ * True if join to a JTA transaction
+ */
+ private boolean joinedToTransaction;
+ /**
+ * True if the context should automatically detect and join a JTA managed transaction.
+ */
+ private boolean autoJoinTransaction;
+ /**
+ * The persistence context properties
+ */
+ private final Map properties;
+ /**
+ * The persistence unit used to create the context
+ */
+ private final JPALitePersistenceUnit persistenceUnit;
+ private final long threadId;
+ private final long instanceNr;
+ private static final AtomicLong instanceCount = new AtomicLong(0);
+ private boolean released;
+ private final String hostname;
+
+ private enum CallbackMethod
+ {
+ PRE_BEGIN,
+ POST_BEGIN,
+ PRE_COMMIT,
+ POST_COMMIT,
+ PRE_ROLLBACK,
+ POST_ROLLBACK
+ }
+
+ public PersistenceContextImpl(DatabasePool pool, JPALitePersistenceUnit persistenceUnit)
+ {
+ this.pool = pool;
+ readOnly = false;
+ this.persistenceUnit = persistenceUnit;
+ properties = new HashMap<>();
+ listeners = new ArrayList<>();
+ pendingAdd = new ArrayList<>();
+ pendingRemoval = new ArrayList<>();
+ transactionDepth = new AtomicInteger(0);
+ instanceNr = instanceCount.incrementAndGet();
+ openStack = new ArrayDeque<>();
+ connectionNames = new ArrayDeque<>();
+ savepoints = new ArrayDeque<>();
+ connectionName = Thread.currentThread().getName();
+ cacheStoreMode = CacheStoreMode.USE;
+ slowQueryTime = 500L;
+ joinedToTransaction = false;
+ autoJoinTransaction = false;
+ transactionManager = null;
+ showSql = false;
+ released = false;
+
+ hostname = ConfigProvider.getConfig().getOptionalValue("HOSTNAME", String.class).orElse("localhost");
+ threadId = Thread.currentThread().threadId();
+ persistenceUnit.getProperties().forEach((k, v) -> setProperty(k.toString(), v));
+
+ entityL1Cache = new EntityL1LocalCacheImpl(this);
+
+ entityL2Cache = new EntityCacheImpl(this.persistenceUnit);
+
+ LOG.debug("Created {}", this);
+ }//PersistenceContextImpl
+
+ @Override
+ public JPALitePersistenceUnit getPersistenceUnit()
+ {
+ return persistenceUnit;
+ }//getPersistenceUnit
+
+ @Override
+ public void setProperty(String name, Object value)
+ {
+ switch (name) {
+ case PERSISTENCE_CACHE_STOREMODE -> {
+ if (value instanceof String strValue) {
+ value = CacheStoreMode.valueOf(strValue);
+ }//if
+ if (value instanceof CacheStoreMode cacheMode) {
+ cacheStoreMode = cacheMode;
+ }//if
+ }
+ case PERSISTENCE_JTA_MANAGED -> {
+ if (value instanceof String strValue) {
+ value = Boolean.parseBoolean(strValue);
+ }//if
+ if (value instanceof Boolean jtaManaged) {
+ autoJoinTransaction = jtaManaged;
+ }//if
+ }
+ case PERSISTENCE_QUERY_LOG_SLOWTIME -> {
+ if (value instanceof String strValue) {
+ value = Long.parseLong(strValue);
+ }//if
+ if (value instanceof Long slowQuery) {
+ slowQueryTime = slowQuery;
+ }//if
+ }
+ case PERSISTENCE_SHOW_SQL -> {
+ if (value instanceof String strValue) {
+ value = Boolean.parseBoolean(strValue);
+ }//if
+ if (value instanceof Boolean showQuerySql) {
+ this.showSql = showQuerySql;
+ if (connection != null) {
+ connection.setEnableLogging(this.showSql);
+ }//if
+ }//if
+ }
+ default -> {
+ //ignore the rest
+ }
+ }//switch
+
+ properties.put(name, value);
+ }
+
+ @Override
+ public Map getProperties()
+ {
+ return properties;
+ }
+
+ @Override
+ public EntityLocalCache l1Cache()
+ {
+ return entityL1Cache;
+ }//l1Cache
+
+ @Override
+ public EntityCache l2Cache()
+ {
+ return entityL2Cache;
+ }//l2Cache
+
+ private void checkEntityAttached(JPAEntity entity)
+ {
+ if (entity._getEntityState() != EntityState.MANAGED) {
+ throw new IllegalArgumentException("Entity is not current attached to a Persistence Context");
+ }//if
+
+ if (entity._getPersistenceContext() != this) {
+ throw new IllegalArgumentException("Entity is not being managed by this Persistence Context");
+ }//if
+ }//checkEntityAttached
+
+ private void checkRecursiveCallback()
+ {
+ if (inCallbackHandler) {
+ throw new PersistenceException("The EntityTransaction methods begin, commit and rollback cannot be called from within a EntityListener callback");
+ }//if
+ }//checkRecursiveCallback
+
+ private void checkThread()
+ {
+ if (threadId != Thread.currentThread().threadId()) {
+ throw new IllegalStateException("Persistence Context is assigned different thread. Expected " + threadId + ", calling thread is " + Thread.currentThread().threadId());
+ }//if
+ }//checkThread
+
+ private void checkReleaseState()
+ {
+ if (released) {
+ throw new PersistenceException("Persistence Context has detached from the database pool cannot be used");
+ }//if
+ }//checkReleaseState
+
+ private void checkOpen()
+ {
+ checkReleaseState();
+
+ if (connection == null) {
+ throw new IllegalStateException("Persistence Context is closed.");
+ }//if
+ }//checkOpen
+
+ @Override
+ public String toString()
+ {
+ return "Persistence Context " + instanceNr + " [Stack " + openStack.size() + ", " + pool + "]";
+ }//toString
+
+ @Override
+ public void addTransactionListener(EntityTransactionListener listener)
+ {
+ if (inCallbackHandler) {
+ pendingAdd.add(listener);
+ }//if
+ else {
+ listeners.add(listener);
+ }//else
+ }//addTransactionListener
+
+ @Override
+ public void removeTransactionListener(EntityTransactionListener listener)
+ {
+ if (inCallbackHandler) {
+ pendingRemoval.add(listener);
+ }//if
+ else {
+ listeners.remove(listener);
+ }//else
+ }//removeTransactionListener
+
+ @Override
+ public void setLastQuery(String lastQuery)
+ {
+ this.lastQuery = lastQuery;
+ }
+
+ @Override
+ public String getLastQuery()
+ {
+ return lastQuery;
+ }
+
+ @Override
+ public int getTransactionDepth()
+ {
+ return transactionDepth.get();
+ }
+
+ @Override
+ public int getOpenLevel()
+ {
+ return openStack.size();
+ }
+
+ @Override
+ public String getConnectionName()
+ {
+ return connectionName;
+ }
+
+ @Override
+ public void setConnectionName(String connectionName)
+ {
+ this.connectionName = connectionName;
+ }
+
+ @SuppressWarnings({"java:S1141", "java:S2077", "tainting"})
+ //Having try-resource in a bigger try block is allowed. Dynamically formatted SQL is verified to be safe
+ @Override
+ @Nonnull
+ public Connection getConnection(String connectionName)
+ {
+ checkReleaseState();
+ checkThread();
+
+ openStack.push(transactionDepth.get());
+ connectionNames.push(this.connectionName);
+
+ if (connectionName == null) {
+ if (this.connectionName == null) {
+ this.connectionName = Thread.currentThread().getName();
+ }//if
+ }//if
+ else {
+ this.connectionName = connectionName;
+ }//else
+ LOG.trace("Opening persistence context. Level: {} with cursor {}", openStack.size(), this.connectionName);
+
+ if (connection == null) {
+ try {
+ connection = new ConnectionWrapper(this, pool.getConnection(), slowQueryTime);
+
+ try (Statement writeStmt = connection.createStatement()) {
+ String applicationName = Application.currentApplication().getName() + "@" + hostname;
+ if (applicationName.length() > 61) {
+ applicationName = applicationName.substring(0, 61);
+ }//if
+ String applicationNameQry = "set application_name to '" + applicationName + "'";
+ writeStmt.execute(applicationNameQry);
+ }//try
+ catch (SQLException ex) {
+ LOG.error("Error setting the JDBC application name", ex);
+ }//catch
+
+ connection.setEnableLogging(showSql);
+ }//try
+ catch (SQLException ex) {
+ throw new PersistenceException("Error configuring database connection", ex);
+ }//catch
+ }//if
+
+ connection.setName(this.connectionName);
+
+ if (isAutoJoinTransaction()) {
+ joinTransaction();
+ }//if
+
+ return connection;
+ }//getConnection
+
+ @Override
+ public boolean isReleased()
+ {
+ return released;
+ }//if
+
+ @Override
+ public void release()
+ {
+ checkThread();
+
+ if (connection != null) {
+ LOG.warn("Closing unexpected open transaction on {}", connection, new PersistenceException("Possible unhandled exception"));
+ openStack.clear();
+ connectionNames.clear();
+
+ close();
+ }//if
+
+ released = true;
+ }//release
+
+ @Override
+ public void close()
+ {
+ checkOpen();
+ checkThread();
+
+ LOG.trace("Closing connection level: {}", openStack.size());
+ if (!connectionNames.isEmpty()) {
+ connectionName = connectionNames.pop();
+ }//if
+
+ if (!openStack.isEmpty()) {
+ int transDepth = openStack.pop();
+ if (transDepth < this.transactionDepth.get()) {
+ LOG.warn("Closing unexpected open transaction", new PersistenceException("Possible unhandled exception"));
+ rollbackToDepth(transDepth);
+
+ //Check if the rollback closed the connection, if so we are done
+ if (connection == null) {
+ return;
+ }//if
+ }//if
+ }//if
+
+ if (openStack.isEmpty()) {
+ LOG.trace("At level 0, releasing connection {}", connection);
+
+ l1Cache().clear();
+ openStack.clear();
+ connectionNames.clear();
+ savepoints.clear();
+ transactionDepth.set(0);
+ rollbackOnly = false;
+ readOnly = false;
+ try {
+ if (!connection.isClosed() && !connection.getAutoCommit()) {
+ connection.rollback();
+ connection.setAutoCommit(true);
+ }//if
+ connection.realClose();
+ connection = null;
+ }//try
+ catch (SQLException ex) {
+ LOG.error("Error closing connection", ex);
+ }//catch
+ }//if
+
+ }//close
+
+ @Override
+ public X mapResultSet(@Nonnull X entity, ResultSet resultSet)
+ {
+ return mapResultSet(entity, null, resultSet);
+ }
+
+ @Override
+ public X mapResultSet(@Nonnull X entity, String colPrefix, ResultSet resultSet)
+ {
+ ((JPAEntity) entity)._setPersistenceContext(this);
+ ((JPAEntity) entity)._mapResultSet(colPrefix, resultSet);
+ l1Cache().manage((JPAEntity) entity);
+ return entity;
+ }
+
+ private boolean doesNeedFlushing(JPAEntity entity)
+ {
+ if (entity._getPersistenceContext() != this) {
+ throw new PersistenceException("Entity belongs to another persistence context and cannot be updated. I am [" + this + "], Entity [" + entity + "]");
+ }//if
+
+ return entity._getEntityState() == EntityState.MANAGED && (entity._getPendingAction() != PersistenceAction.NONE || entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT);
+ }//doesNeedFlushing
+
+ @Override
+ public void flush()
+ {
+ checkOpen();
+ checkThread();
+
+ l1Cache().foreach(e ->
+ {
+ if (doesNeedFlushing((JPAEntity) e)) {
+ flushEntityInternal((JPAEntity) e);
+ }//if
+ });
+ }//flush
+
+ @Override
+ public void flushOnType(Class> entityClass)
+ {
+ checkOpen();
+ checkThread();
+
+ l1Cache().foreachType(entityClass, e ->
+ {
+ if (doesNeedFlushing((JPAEntity) e)) {
+ flushEntityInternal((JPAEntity) e);
+ }//if
+ });
+ }//flushOnType
+
+ @Override
+ public void flushEntity(@Nonnull JPAEntity entity)
+ {
+ checkOpen();
+ checkThread();
+ checkEntityAttached(entity);
+
+ flushEntityInternal(entity);
+ }
+
+ @SuppressWarnings("java:S6205") //Not a redundant block
+ private void invokeCallbackHandlers(PersistenceAction action, boolean preAction, Object entity)
+ {
+ /*
+ * Callback are not invoked if the transaction is marked for rollback
+ */
+ if (!getRollbackOnly()) {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entity.getClass());
+ try {
+ switch (action) {
+ case INSERT -> {
+ if (preAction) {
+ metaData.getLifecycleListeners().prePersist(entity);
+ } else {
+ metaData.getLifecycleListeners().postPersist(entity);
+ }
+ }
+ case UPDATE -> {
+ if (preAction) {
+ metaData.getLifecycleListeners().preUpdate(entity);
+ } else {
+ metaData.getLifecycleListeners().postUpdate(entity);
+ }
+ }
+ case DELETE -> {
+ if (preAction) {
+ metaData.getLifecycleListeners().preRemove(entity);
+ } else {
+ metaData.getLifecycleListeners().postRemove(entity);
+ }
+ }
+ default -> {//do nothing
+ }
+ }//switch
+ }//try
+ catch (PersistenceException ex) {
+ setRollbackOnly();
+ throw ex;
+ }//catch
+ }//if
+ }//invokeCallbackHandlers
+
+ private void bindParameters(PreparedStatement statement, Object... params)
+ {
+ if (params != null) {
+ int startAt = 0;
+
+ for (Object param : params) {
+ try {
+ startAt++;
+
+ if (param instanceof Boolean) {
+ param = param == Boolean.TRUE ? 1 : 0;
+ }//if
+ if (param instanceof byte[] vBytes) {
+ statement.setBytes(startAt, vBytes);
+ }//if
+ else {
+ statement.setObject(startAt, param, Types.OTHER);
+ }//else
+ }//try
+ catch (SQLException ex) {
+ throw new PersistenceException("Error setting parameter (" + startAt + "=" + param, ex);
+ }//catch
+ }//for
+ }//if
+ }//bindParameters
+
+ private boolean isOptimisticLocked(JPAEntity entity)
+ {
+ return (entity._getLockMode() == LockModeType.OPTIMISTIC || entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT);
+ }//isOptimisticLocked
+
+ @SuppressWarnings("unchecked")
+ private void cascadePersist(Set mappings, @Nonnull JPAEntity entity)
+ {
+ try {
+ for (EntityField field : entity._getMetaData().getEntityFields()) {
+ if ((field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.PERSIST))) {
+
+ if (field.getMappingType() == MappingType.ONE_TO_MANY && mappings.contains(MappingType.ONE_TO_MANY)) {
+ List entityList = (List) field.invokeGetter(entity);
+ if (entityList != null) {
+ entityList.stream()
+ //Check if the entity is new and unattached or was persisted but not flushed
+ .filter(e -> (e._getEntityState() == EntityState.TRANSIENT || e._getPendingAction() == PersistenceAction.INSERT))
+ .forEach(e ->
+ {
+ try {
+ EntityField entityField = e._getMetaData().getEntityField(field.getMappedBy());
+ entityField.invokeSetter(e, entity);
+ e._setPendingAction(PersistenceAction.INSERT);
+ l1Cache().manage(e);
+ flushEntity(e);
+ }//try
+ catch (RuntimeException ex) {
+ setRollbackOnly();
+ throw new PersistenceException("Error cascading persist entity", ex);
+ }//catch
+ });
+ }//if
+ entity._clearField(field.getName());
+ }//if
+ else if ((field.getMappingType() == MappingType.MANY_TO_ONE && mappings.contains(MappingType.MANY_TO_ONE) || (field.getMappingType() == MappingType.ONE_TO_ONE && mappings.contains(MappingType.ONE_TO_ONE)))) {
+ JPAEntity jpaEntity = (JPAEntity) field.invokeGetter(entity);
+ flushEntity(jpaEntity);
+ }//else if
+
+ }//if
+ }//for
+ }//try
+ catch (RuntimeException ex) {
+ setRollbackOnly();
+ throw new PersistenceException("Error cascading persist entity", ex);
+ }//catch
+ }//cascadePersist
+
+ @SuppressWarnings("unchecked")
+ private void cascadeRemove(Set mappings, @Nonnull JPAEntity entity)
+ {
+ try {
+ for (EntityField field : entity._getMetaData().getEntityFields()) {
+
+ if (mappings.contains(field.getMappingType()) && (field.getCascade().contains(CascadeType.ALL) || field.getCascade().contains(CascadeType.REMOVE))) {
+ if (mappings.contains(MappingType.MANY_TO_ONE) || mappings.contains(MappingType.ONE_TO_ONE)) {
+ JPAEntity entityValue = (JPAEntity) field.invokeGetter(entity);
+ if (entityValue != null && !entityValue._isLazyLoaded()) {
+ entityValue._setPendingAction(DELETE);
+ flushEntity(entityValue);
+ }//if
+ }//if
+ else if (mappings.contains(MappingType.ONE_TO_MANY)) {
+ List entityList = (List) field.invokeGetter(entity);
+ if (entityList != null) {
+ entityList.stream()
+ .filter(e -> (!e._isLazyLoaded()))
+ .forEach(e ->
+ {
+ e._setPendingAction(DELETE);
+ flushEntity(e);
+ });
+ }//if
+ }//else if
+ }//if
+ }//for
+ }//try
+ catch (RuntimeException ex) {
+ setRollbackOnly();
+ throw new PersistenceException("Error cascading remove entity", ex);
+ }//catch
+ }//cascadeRemove
+
+ private EntityQuery getFlushQuery(PersistenceAction action, @Nonnull JPAEntity entity)
+ {
+ EntityMetaData> metaData = EntityMetaDataManager.getMetaData(entity.getClass());
+ return switch (action) {
+ case INSERT -> {
+ cascadePersist(Set.of(MappingType.MANY_TO_ONE), entity);
+ yield new EntityInsertQueryImpl(entity, metaData);
+ }
+ case UPDATE -> new EntityUpdateQueryImpl(entity, metaData);
+ case DELETE -> {
+ cascadeRemove(Set.of(MappingType.ONE_TO_MANY, MappingType.ONE_TO_ONE), entity);
+ yield new EntityDeleteQueryImpl(entity, metaData);
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + action);
+ };
+ }//getFlushQuery
+
+ private void flushEntityInternal(@Nonnull JPAEntity entity)
+ {
+ PersistenceAction action = entity._getPendingAction();
+ if (action == NONE) {
+ /*
+ If the entity is not new and is not dirty but is locked optimistically, we need to update the version
+ */
+ if (entity._getLockMode() == LockModeType.OPTIMISTIC_FORCE_INCREMENT) {
+ action = UPDATE;
+ }//if
+ else {
+ return;
+ }//else
+ }//if
+
+ Span span = TRACER.spanBuilder("PersistenceContextImpl::flushEntity").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ span.setAttribute("action", action.name());
+ invokeCallbackHandlers(action, true, entity);
+ if (!getRollbackOnly()) {
+ entity._setPendingAction(NONE);
+ EntityQuery flushQuery = getFlushQuery(action, entity);
+
+ if (flushQuery.getQuery() != null && !flushQuery.getQuery().isBlank()) {
+ String sqlQuery = flushQuery.getQuery();
+ span.setAttribute("query", sqlQuery);
+
+ //noinspection SqlSourceToSinkFlow
+ try (PreparedStatement statement = connection.prepareStatement(sqlQuery, Statement.RETURN_GENERATED_KEYS)) {
+ bindParameters(statement, flushQuery.getParameters());
+
+ int rows = statement.executeUpdate();
+ if (rows > 0) {
+ if (action == PersistenceAction.DELETE) {
+ entity._setEntityState(EntityState.REMOVED);
+ if (entity._getMetaData().isCacheable()) {
+ l2Cache().evict(entity._getEntityClass(), entity._getPrimaryKey());
+ }//if
+
+ cascadeRemove(Set.of(MappingType.MANY_TO_ONE), entity);
+ }//if
+ else {
+ if (action == PersistenceAction.INSERT) {
+ try (ResultSet vResultSet = statement.getGeneratedKeys()) {
+ if (vResultSet.next()) {
+ entity._setPersistenceContext(this);
+ entity._mapResultSet(null, vResultSet);
+ }//if
+ }//try
+
+ if (cacheStoreMode == CacheStoreMode.USE) {
+ l2Cache().add(entity);
+ }//else if
+ }//if
+ else if (entity._getMetaData().isCacheable() && cacheStoreMode != CacheStoreMode.BYPASS) {
+ l2Cache().replace(entity);
+ }//else if
+
+ cascadePersist(Set.of(MappingType.ONE_TO_MANY, MappingType.ONE_TO_ONE), entity);
+ }//else
+ }//if
+ /*
+ * If zero rows were updated or deleted and the entity was optimistic locked, then throw an exception
+ */
+ else if (action != INSERT && isOptimisticLocked(entity)) {
+ setRollbackOnly();
+ throw new OptimisticLockException(entity);
+ }//else if
+ }//try
+ catch (SQLException ex) {
+ setRollbackOnly();
+
+ LOG.error("Failed to flush entity {}, Query: {}", entity._getMetaData().getName(), flushQuery.getQuery(), ex);
+ throw new PersistenceException("Error persisting entity in database");
+ }//catch
+ }//if
+ }//if
+
+ entity._clearModified();
+ invokeCallbackHandlers(action, false, entity);
+ }//try
+ finally {
+ span.end();
+ }//finally
+ }//flushEntity
+
+
+ //
+ @Override
+ public void setAutoJoinTransaction()
+ {
+ autoJoinTransaction = true;
+ }//setAutoJoinTransaction
+
+ @Override
+ public boolean isAutoJoinTransaction()
+ {
+ return autoJoinTransaction;
+ }//isAutoJoinTransactions
+
+ @Override
+ public void joinTransaction()
+ {
+ if (!joinedToTransaction) {
+ try {
+ if (transactionManager == null) {
+ transactionManager = (TransactionManager) CDI.current().select(getClass().getClassLoader().loadClass(TransactionManager.class.getName())).get();
+ if (transactionManager == null) {
+ throw new ClassNotFoundException("Transaction Manager not set");
+ }//if
+ }//if
+
+ //If we not in a JTA transaction, escape here
+ if (!isInJTATransaction()) {
+ return;
+ }//if
+
+ joinedToTransaction = true;
+
+ switch (transactionManager.getStatus()) {
+ case Status.STATUS_ACTIVE, Status.STATUS_PREPARED, Status.STATUS_PREPARING -> begin();
+ case Status.STATUS_MARKED_ROLLBACK -> {
+ begin();
+ setRollbackOnly();
+ }
+ default ->
+ throw new TransactionRequiredException("Explicitly joining a JTA transaction requires a JTA transaction be currently active");
+ }//switch
+ }//try
+ catch (ClassNotFoundException ex) {
+ throw new PersistenceException("No JTA TransactionManager found, mostly likely this is not an EE application", ex);
+ }//catch
+ catch (SystemException ex) {
+ throw new PersistenceException(ex.getMessage(), ex);
+ }//catch
+ }//if
+ }//joinTransaction
+
+ @Override
+ public boolean isJoinedToTransaction()
+ {
+ return joinedToTransaction;
+ }
+
+ private boolean isInJTATransaction()
+ {
+ if (transactionManager != null) {
+ try {
+ int status = transactionManager.getStatus();
+ return (status == Status.STATUS_ACTIVE || status == Status.STATUS_COMMITTING || status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_PREPARED || status == Status.STATUS_PREPARING);
+ }
+ catch (Exception ex) {
+ throw new PersistenceException(ex);
+ }
+ }//if
+ return false;
+ }//joinTransaction
+
+
+ @Override
+ public void afterCompletion(int status)
+ {
+ if (isActive() && status == Status.STATUS_ROLLEDBACK) {
+ setRollbackOnly();
+ rollback();
+ }//if
+ }//afterCompletion
+
+ private void rollbackToDepth(int depth)
+ {
+ while (transactionDepth.get() > depth) {
+ rollback();
+ }//while
+ }//rollbackToDepth
+
+ @Override
+ public EntityTransaction getTransaction()
+ {
+ if (isAutoJoinTransaction() || isJoinedToTransaction()) {
+ throw new IllegalStateException("Transaction is not accessible when using JTA with JPA-compliant transaction access enabled");
+ }//if
+
+ return this;
+ }//getTransaction
+
+ private void transactionCallback(CallbackMethod callback)
+ {
+ checkRecursiveCallback();
+
+ inCallbackHandler = true;
+ for (EntityTransactionListener listener : listeners) {
+ switch (callback) {
+ case PRE_BEGIN -> listener.preTransactionBeginEvent();
+ case POST_BEGIN -> listener.postTransactionBeginEvent();
+ case PRE_COMMIT -> listener.preTransactionCommitEvent();
+ case POST_COMMIT -> listener.postTransactionCommitEvent();
+ case PRE_ROLLBACK -> listener.preTransactionRollbackEvent();
+ case POST_ROLLBACK -> listener.postTransactionRollbackEvent();
+ }//switch
+ }//for
+ inCallbackHandler = false;
+
+ if (!pendingRemoval.isEmpty()) {
+ pendingRemoval.forEach(listeners::remove);
+ }//if
+
+ if (!pendingAdd.isEmpty()) {
+ listeners.addAll(pendingAdd);
+ }//if
+ }//transactionCallback
+
+ @Override
+ public void begin()
+ {
+ checkReleaseState();
+ checkThread();
+ Span span = TRACER.spanBuilder("PersistenceContextImpl::begin").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignore = span.makeCurrent()) {
+ checkRecursiveCallback();
+
+ if (isActive()) {
+ if (getRollbackOnly()) {
+ throw new IllegalStateException("Transaction is current in a rollback only state");
+ }//if
+ LOG.trace("Set a savepoint at depth {}", transactionDepth.get());
+ savepoints.add(connection.setSavepoint());
+ transactionDepth.incrementAndGet();
+ LOG.debug("Legacy support - Transaction is already active, using depth counter");
+ }//if
+ else {
+ LOG.trace("Beginning a new transaction on {}", this);
+ transactionCallback(CallbackMethod.PRE_BEGIN);
+ rollbackOnly = false;
+ getConnection(connectionName).setAutoCommit(false);
+ transactionDepth.set(1);
+ l2Cache().begin();
+ transactionCallback(CallbackMethod.POST_BEGIN);
+ }//else
+ }//try
+ catch (SQLException ex) {
+ throw new PersistenceException("Error beginning a transaction", ex);
+ }//catch
+ catch (SystemException ex) {
+ throw new PersistenceException("Error beginning a transaction in TransactionManager", ex);
+ }//catch
+ finally {
+ span.end();
+ }
+ }//begin
+
+ @Override
+ public void commit()
+ {
+ checkThread();
+
+ if (isActive()) {
+ Span span = TRACER.spanBuilder("PersistenceContextImpl::commit").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (getRollbackOnly()) {
+ span.setStatus(StatusCode.ERROR, "Transaction marked for rollback and cannot be committed");
+ throw new RollbackException("Transaction marked for rollback and cannot be committed");
+ }//if
+
+ if (transactionDepth.decrementAndGet() > 0) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Commit savepoint at depth {}", transactionDepth.get());
+ }//if
+ savepoints.removeLast();
+ return;
+ }//if
+
+ transactionCallback(CallbackMethod.PRE_COMMIT);
+
+ flush();
+ connection.commit();
+ connection.setAutoCommit(true);
+ l1Cache().clear();
+ l2Cache().commit();
+
+ transactionCallback(CallbackMethod.POST_COMMIT);
+ close();
+ LOG.trace("Transaction Committed on {}", this);
+ }//try
+ catch (SQLException ex) {
+ setRollbackOnly();
+ throw new PersistenceException("Error committing transaction", ex);
+ }//catch
+ catch (SystemException ex) {
+ setRollbackOnly();
+ throw new PersistenceException("Error committing transaction in TransactionManager", ex);
+ }//catch
+ finally {
+ span.end();
+ }//finally
+ }//if
+ transactionDepth.set(0);
+ }//commit
+
+ @Override
+ public void rollback()
+ {
+ checkThread();
+ Span span = TRACER.spanBuilder("PersistenceContextImpl::rollback").setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope ignored = span.makeCurrent()) {
+ if (isActive()) {
+ if (transactionDepth.decrementAndGet() > 0) {
+ connection.rollback(savepoints.pop());
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Rolling back to savepoint at depth {}", transactionDepth.get());
+ }//if
+ rollbackOnly = false;
+ return;
+ }//if
+
+ transactionCallback(CallbackMethod.PRE_ROLLBACK);
+
+ rollbackOnly = false;
+ connection.rollback();
+ l2Cache().rollback();
+ connection.setAutoCommit(true);
+
+ l1Cache().clear();
+ transactionCallback(CallbackMethod.POST_ROLLBACK);
+
+ close();
+ LOG.trace("Transaction rolled back on {}", this);
+ }//if
+ }//try
+ catch (SQLException ex) {
+ throw new PersistenceException("Error rolling transaction back", ex);
+ }//catch
+ catch (SystemException ex) {
+ throw new PersistenceException("Error rolling transaction back in TransactionManager", ex);
+ }//catch
+ finally {
+ span.end();
+ }//finally
+ }//rollback
+
+ @Override
+ public void setRollbackOnly()
+ {
+ rollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly()
+ {
+ return rollbackOnly;
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return transactionDepth.get() > 0;
+ }
+ //
+
+ public boolean isReadonly()
+ {
+ return readOnly;
+ }
+
+ //
+ public long getSlowQueryTime()
+ {
+ return slowQueryTime;
+ }
+
+ public void setSlowQueryTime(long pSlowQueryTime)
+ {
+ slowQueryTime = pSlowQueryTime;
+ }
+
+ public boolean isEnableLogging()
+ {
+ return connection.isEnableLogging();
+ }
+
+ public void setEnableLogging(boolean pEnableLogging)
+ {
+ checkOpen();
+ connection.setEnableLogging(pEnableLogging && showSql);
+ }//setEnableLogging
+
+ public void setReadonly(boolean pReadonly)
+ {
+ readOnly = pReadonly;
+ }
+
+ public void setAuditWriter(PrintWriter pAuditWriter)
+ {
+ connection.setAuditWriter(pAuditWriter);
+ }
+
+ public PrintWriter getAuditWriter()
+ {
+ return connection.getAuditWriter();
+ }
+ //
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T unwrap(Class cls)
+ {
+ if (cls.isAssignableFrom(this.getClass())) {
+ return (T) this;
+ }
+
+ if (cls.isAssignableFrom(pool.getClass())) {
+ return (T) pool;
+ }//if
+
+ throw new IllegalArgumentException("Could not unwrap this [" + this + "] as requested Java type [" + cls.getName() + "]");
+ }//unwrap
+}//PersistenceContextImpl
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/PreparedStatementWrapper.java b/jpalite-core/src/main/java/org/jpalite/impl/db/PreparedStatementWrapper.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/impl/db/PreparedStatementWrapper.java
rename to jpalite-core/src/main/java/org/jpalite/impl/db/PreparedStatementWrapper.java
index 8ca00ac..0b7723a 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/PreparedStatementWrapper.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/PreparedStatementWrapper.java
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-package io.jpalite.impl.db;
+package org.jpalite.impl.db;
-import io.jpalite.DatabasePool;
+import org.jpalite.DatabasePool;
import java.io.InputStream;
import java.io.Reader;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/db/StatementWrapper.java b/jpalite-core/src/main/java/org/jpalite/impl/db/StatementWrapper.java
similarity index 99%
rename from jpalite-core/src/main/java/io/jpalite/impl/db/StatementWrapper.java
rename to jpalite-core/src/main/java/org/jpalite/impl/db/StatementWrapper.java
index 8c1384b..9850f78 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/db/StatementWrapper.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/db/StatementWrapper.java
@@ -15,9 +15,9 @@
* limitations under the License.
*/
-package io.jpalite.impl.db;
+package org.jpalite.impl.db;
-import io.jpalite.DatabasePool;
+import org.jpalite.DatabasePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BigDecimalFieldType.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BigDecimalFieldType.java
index 365418e..62ec63a 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BigDecimalFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BigDecimalFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BoolFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BoolFieldType.java
index 934d6b7..a45fce6 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BoolFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BoolFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BooleanFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BooleanFieldType.java
index 4babca2..290fd32 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BooleanFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BooleanFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BytesFieldType.java
similarity index 93%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BytesFieldType.java
index 4f23057..e40e239 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/BytesFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/BytesFieldType.java
@@ -1,9 +1,9 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.CachingException;
-import io.jpalite.FieldConvertType;
+import org.jpalite.CachingException;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
index 805499a..ad1ab99 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleDoubleFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleFieldType.java
index 51d7dac..e2348a3 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/DoubleFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/DoubleFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/EnumFieldType.java
similarity index 93%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/EnumFieldType.java
index c1c9107..3918b52 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/EnumFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/EnumFieldType.java
@@ -1,9 +1,10 @@
-package io.jpalite.impl.fieldtypes;
+package org.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 org.jpalite.FieldConvertType;
+import org.jpalite.impl.EntityFieldImpl;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -12,7 +13,7 @@
import java.sql.SQLException;
/**
- * Special converter type of enums. It is used internally by {@link io.jpalite.impl.EntityFieldImpl}
+ * Special converter type of enums. It is used internally by {@link EntityFieldImpl}
* Note that the type must not have a @Converter annotation
*/
public class EnumFieldType implements FieldConvertType, String>
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntFieldType.java
index 9fc268f..4017f8b 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntegerFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntegerFieldType.java
index 4abc2c5..d1943aa 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/IntegerFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/IntegerFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
index 4a3b71d..022f3c3 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LocalDateTimeFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongFieldType.java
index 7c6327d..4749e37 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongLongFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongLongFieldType.java
index 9a1c320..70b7237 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/LongLongFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/LongLongFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/ObjectFieldType.java
similarity index 92%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/ObjectFieldType.java
index 13689cc..8bc9948 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/ObjectFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/ObjectFieldType.java
@@ -1,16 +1,17 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.CachingException;
-import io.jpalite.FieldConvertType;
+import org.jpalite.CachingException;
+import org.jpalite.FieldConvertType;
+import org.jpalite.impl.EntityFieldImpl;
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}
+ * Special converter type of Object type. It is used internally by {@link EntityFieldImpl}
* Note that the type must not have a @Converter annotation
*/
public class ObjectFieldType implements FieldConvertType
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java
similarity index 92%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java
index bca79f8..eed7700 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/OrdinalEnumFieldType.java
@@ -1,8 +1,9 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
+import org.jpalite.impl.EntityFieldImpl;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -11,7 +12,7 @@
import java.sql.SQLException;
/**
- * Special converter type of ordinal enums. It is used internally by {@link io.jpalite.impl.EntityFieldImpl}
+ * Special converter type of ordinal enums. It is used internally by {@link EntityFieldImpl}
* Note that the type must not have a @Converter annotation
*/
public class OrdinalEnumFieldType implements FieldConvertType, Integer>
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/StringFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/StringFieldType.java
similarity index 94%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/StringFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/StringFieldType.java
index 95c0b17..eefceb5 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/StringFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/StringFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/TimestampFieldType.java b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/TimestampFieldType.java
similarity index 95%
rename from jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/TimestampFieldType.java
rename to jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/TimestampFieldType.java
index 10df047..829fe5b 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/fieldtypes/TimestampFieldType.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/fieldtypes/TimestampFieldType.java
@@ -1,8 +1,8 @@
-package io.jpalite.impl.fieldtypes;
+package org.jpalite.impl.fieldtypes;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
-import io.jpalite.FieldConvertType;
+import org.jpalite.FieldConvertType;
import jakarta.persistence.Converter;
import java.io.DataInputStream;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/parsers/BooleanValue.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/BooleanValue.java
similarity index 97%
rename from jpalite-core/src/main/java/io/jpalite/impl/parsers/BooleanValue.java
rename to jpalite-core/src/main/java/org/jpalite/impl/parsers/BooleanValue.java
index 55cbd18..4122a85 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/BooleanValue.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/BooleanValue.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite.impl.parsers;
+package org.jpalite.impl.parsers;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
diff --git a/jpalite-core/src/main/java/io/jpalite/impl/parsers/ExtraExpressionVisitor.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/ExtraExpressionVisitor.java
similarity index 96%
rename from jpalite-core/src/main/java/io/jpalite/impl/parsers/ExtraExpressionVisitor.java
rename to jpalite-core/src/main/java/org/jpalite/impl/parsers/ExtraExpressionVisitor.java
index 91d8599..a9d7152 100644
--- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/ExtraExpressionVisitor.java
+++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/ExtraExpressionVisitor.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.jpalite.impl.parsers;
+package org.jpalite.impl.parsers;
import net.sf.jsqlparser.expression.ExpressionVisitor;
diff --git a/jpalite-core/src/main/java/org/jpalite/impl/parsers/JPQLParser.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/JPQLParser.java
new file mode 100644
index 0000000..3ce6a85
--- /dev/null
+++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/JPQLParser.java
@@ -0,0 +1,1071 @@
+/*
+ * 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 org.jpalite.impl.parsers;
+
+
+import jakarta.persistence.FetchType;
+import jakarta.persistence.PersistenceException;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.*;
+import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.insert.Insert;
+import net.sf.jsqlparser.statement.select.*;
+import net.sf.jsqlparser.statement.update.Update;
+import net.sf.jsqlparser.statement.update.UpdateSet;
+import org.jpalite.*;
+import org.jpalite.impl.queries.QueryParameterImpl;
+import org.jpalite.parsers.QueryParser;
+import org.jpalite.parsers.QueryStatement;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("java:S1452") //generic wildcard is required
+public class JPQLParser extends JsqlVistorBase implements QueryParser
+{
+
+ private enum Phase
+ {
+ FROM,
+ JOIN,
+ SELECT,
+ WHERE,
+ GROUP_BY,
+ HAVING,
+ ORDERBY
+ }
+
+ /**
+ * The parsed query
+ */
+ private final String query;
+ private QueryStatement queryStatement = QueryStatement.OTHER;
+
+ /**
+ * Starting number to generate unique tables aliases
+ */
+ private int tableNr = 1;
+ /**
+ * List of return types
+ */
+ private final Map> returnTypes;
+ /**
+ * List of tables used
+ */
+ private final List entityInfoList;
+ /**
+ * We may use either positional or named parameters, but we cannot mix them within the same query.
+ */
+ private boolean usingNamedParameters;
+ /**
+ * Map of parameters used in the query
+ */
+ private final List> queryParameters;
+ /**
+ * Instance of the defined joins in the query
+ */
+ private List joins;
+ /**
+ * State variable used to indicate that in section we are processing
+ */
+ private Phase currentPhase = Phase.FROM;
+ /**
+ * The "from" table in the select statement
+ */
+ private Table fromTable = null;
+ /**
+ * If not null the fetchtype settings on the basic fields are ignored and this value is used
+ */
+ private FetchType overrideBasicFetchType = null;
+ /**
+ * If not null the fetchtype settings on the ALL fields are ignored and this value is used
+ */
+ private FetchType overrideAllFetchType = null;
+ private boolean selectUsingPrimaryKey = false;
+ private boolean usingSubSelect = false;
+ private String tableAlias = null;
+
+ public class EntityInfo
+ {
+ private final List aliases;
+ private final EntityMetaData> metadata;
+ private final String tableAlias;
+
+ public EntityInfo(String alias, EntityMetaData> metaData)
+ {
+ aliases = new ArrayList<>();
+ aliases.add(alias);
+ metadata = metaData;
+ tableAlias = "t" + tableNr;
+ tableNr++;
+ }
+
+ public EntityInfo(String alias, EntityMetaData> metaData, String tableAlias)
+ {
+ aliases = new ArrayList<>();
+ aliases.add(alias);
+ metadata = metaData;
+ this.tableAlias = tableAlias;
+ }
+
+ @Override
+ public String toString()
+ {
+ return aliases.getFirst() + "->" + metadata + ", " + metadata.getTable() + " " + tableAlias;
+ }
+
+ public String getColumnAlias()
+ {
+ return aliases.getFirst();
+ }//getColumnAlias
+
+ public void addColAlias(String alias)
+ {
+ aliases.add(alias);
+ }
+
+ public boolean containsEntityAlias(String alias)
+ {
+ return aliases.contains(alias);
+ }
+
+ public String getTableAlias()
+ {
+ return tableAlias;
+ }
+
+ public EntityMetaData> getMetadata()
+ {
+ return metadata;
+ }
+ }//EntityInfo
+
+ /**
+ * Constructor for the class. The method takes as input a JQPL Statement and convert it to a Native Statement. Note
+ * that the original pStatement is modified
+ *
+ * @param rawQuery The JQPL query
+ * @param queryHints The query hints
+ */
+ public JPQLParser(String rawQuery, Map queryHints)
+ {
+ returnTypes = new LinkedHashMap<>();
+ entityInfoList = new ArrayList<>();
+ usingNamedParameters = false;
+ queryParameters = new ArrayList<>();
+
+ if (queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE) != null) {
+ overrideAllFetchType = (FetchType) queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE);
+ }//if
+
+ if (queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE) != null) {
+ overrideBasicFetchType = (FetchType) queryHints.get(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE);
+ }//if
+
+ try {
+ Statement vStatement = CCJSqlParserUtil.parse(rawQuery);
+ vStatement.accept(this);
+ query = vStatement.toString().replace(":?", "?");
+ }//try
+ catch (JSQLParserException ex) {
+ throw new PersistenceException("Error parsing query", ex);
+ }//catch
+ }//JpqlToNative
+
+ @Override
+ public boolean isSelectUsingPrimaryKey()
+ {
+ return selectUsingPrimaryKey;
+ }//isSelectUsingPrimaryKey
+
+ EntityInfo findEntityInfoWithTableAlias(String tableAlias)
+ {
+ for (EntityInfo vInfo : entityInfoList) {
+ if (vInfo.getTableAlias().equals(tableAlias)) {
+ return vInfo;
+ }//if
+ }//for
+ return null;
+ }//findEntityInfoWithTableAlias
+
+ EntityInfo findEntityInfoWithColAlias(String colAlias)
+ {
+ for (EntityInfo vInfo : entityInfoList) {
+ if (vInfo.containsEntityAlias(colAlias)) {
+ return vInfo;
+ }//if
+ }//for
+ return null;
+ }//findEntityInfoWithColAlias
+
+ EntityInfo findEntityInfoWithEntity(Class> entityClass)
+ {
+ for (EntityInfo info : entityInfoList) {
+ if (info.getMetadata().getEntityClass().equals(entityClass)) {
+ return info;
+ }//if
+ }//for
+ return null;
+ }//findEntityInfoWithEntity
+
+ @Override
+ public QueryStatement getStatement()
+ {
+ return queryStatement;
+ }
+
+ /**
+ * Return the Native query
+ *
+ * @return the SQL query
+ */
+ @Override
+ public String getQuery()
+ {
+ return query;
+ }//getNativeStatement
+
+ /**
+ * Return the type of parameter that is used.
+ *
+ * @return True if using named parameters
+ */
+ @Override
+ public boolean isUsingNamedParameters()
+ {
+ return usingNamedParameters;
+ }//isUsingNamedParameters
+
+ @Override
+ public int getNumberOfParameters()
+ {
+ return queryParameters.size();
+ }//getNumberOfParameters
+
+ /**
+ * Return a map of the query parameters defined.
+ *
+ * @return The query parameters
+ */
+ @Override
+ public List> getQueryParameters()
+ {
+ return queryParameters;
+ }
+
+ /**
+ * Return a list of all the return type in the select
+ *
+ * @return the list
+ */
+ @Override
+ public List> getReturnTypes()
+ {
+ return new ArrayList<>(returnTypes.values());
+ }//getReturnTypes
+
+ /**
+ * Check if the given return type is provided by the JQPL guery. If not an IllegalArgumentException exception is
+ * generated
+ *
+ * @param typeToCheck The class to check
+ * @throws IllegalArgumentException if the type is not provided
+ */
+ @Override
+ public void checkType(Class> typeToCheck)
+ {
+ if (queryStatement == QueryStatement.SELECT) {
+ if (!typeToCheck.isArray()) {
+ if (returnTypes.size() > 1) {
+ throw new IllegalArgumentException("Type specified for Query [" + typeToCheck.getName() + "] does not support multiple result set.");
+ }//if
+ if (!returnTypes.get("c1").isAssignableFrom(typeToCheck)) {
+ throw new IllegalArgumentException("Type specified for Query [" + typeToCheck.getName() + "] is incompatible with query return type [" + returnTypes.get("c1").getName() + "]");
+ }//if
+ }//if
+ else {
+ if (typeToCheck != byte[].class && typeToCheck != Object[].class) {
+ throw new IllegalArgumentException("Cannot create TypedQuery for query with more than one return using requested result type " + typeToCheck.arrayType().getName() + "[]");
+ }//if
+ }//else
+ }//if
+ }//checkType
+
+ private void joinAccept(Join join)
+ {
+ if (!join.getOnExpressions().isEmpty()) {
+ throw new IllegalArgumentException("JOIN ON is not supported in JQPL - " + join);
+ }//if
+
+ //JOIN . eg e.department d
+ Table joinTable = (Table) join.getRightItem();
+ String joinField = joinTable.getName(); //