diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b9a08b7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# More details are here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# The '*' pattern is global owners. + +# Order is important. The last matching pattern has the most precedence. +# The folders are ordered as follows: + +# In each subsection folders are ordered first by depth, then alphabetically. +# This should make it easy to add new rules without breaking existing ones. + +* @eddiecarpenter/jpalite diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..05fc58a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily \ No newline at end of file diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 0000000..37101f9 --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,3 @@ +release: + current-version: "3.0.0" + next-version: "3.0.0-SNAPSHOT" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5d70ea7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,66 @@ +name: JPALite Build + +on: + push: + branches: + - "main" + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Build on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # os: [windows-latest, macos-latest, ubuntu-latest] + os: [ ubuntu-latest ] + runs-on: ${{ matrix.os }} + steps: + - name: Prepare git + run: git config --global core.autocrlf false + if: startsWith(matrix.os, 'windows') + + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + + - name: Display settings.xml + run: | + cat /home/runner/.m2/settings.xml + + - name: Set up Maven 3.9.6 + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 + + - name: Build with Maven + run: mvn -B clean install -Dno-format diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000..e2e8b7c --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,33 @@ +name: JPALite Pre Release + +on: + pull_request: + paths: + - '.github/project.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + release: + runs-on: ubuntu-latest + name: pre release + + steps: + - uses: radcortez/project-metadata-action@master + name: retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - name: Validate version + if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') + run: | + echo '::error::Cannot release a SNAPSHOT version.' + exit 1 diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 02d5a85..0000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - - initial - -jobs: - qodana: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2024.1 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..919af78 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,76 @@ +name: JPALite Release + +on: + pull_request: + types: [ closed ] + paths: + - '.github/project.yml' + - '.github/workflows/release.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + release: + runs-on: ubuntu-latest + name: release + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/project-metadata-action@main + name: Retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4.2.2 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Set up Maven 3.9.6 + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 + + - name: Configure Git author + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Update latest release version in docs + run: | + mvn -B -ntp -pl docs -am package -DskipTests -DskipITs -Denforcer.skip -Dformatter.skip -Dimpsort.skip + if ! git diff --quiet docs/modules/ROOT/pages/includes; then + git add docs/modules/ROOT/pages/includes + git commit -m "Update the latest release version ${{steps.metadata.outputs.current-version}} in documentation" + fi + + - name: Maven release ${{steps.metadata.outputs.current-version}} + run: | + mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + mvn -B release:perform -Darguments=-DperformRelease -DperformRelease -Prelease + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Push changes to ${{github.base_ref}} branch + run: | + git push + git push origin ${{steps.metadata.outputs.current-version}} diff --git a/docs/modules/ROOT/pages/using/Database Connections.adoc b/docs/modules/ROOT/pages/using/Database Connections.adoc index e5a0783..14b6014 100644 --- a/docs/modules/ROOT/pages/using/Database Connections.adoc +++ b/docs/modules/ROOT/pages/using/Database Connections.adoc @@ -57,7 +57,7 @@ In the case of dependency injection, the Java Transaction API will control if a == EntityManagerFactory The entity manager is constructed by passing it the name of the Persistence Unit name it should use to create the database connection. -The EntityManagerFactory resolves the PersistenceUnit name using the SPI service looking implementers of the `io.jpalite.PersistenceUnitProvider` interface. +The EntityManagerFactory resolves the PersistenceUnit name using the SPI service looking implementers of the `org.jpalite.PersistenceUnitProvider` interface. The interface will return an object that implements the `jakarta.persistence.spi.PersistenceUnitInfo` interface. === Entity Transaction diff --git a/jpalite-cache-infinispan/pom.xml b/jpalite-cache-infinispan/pom.xml new file mode 100644 index 0000000..31e1b8f --- /dev/null +++ b/jpalite-cache-infinispan/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + io.jpalite + jpalite-parent + 3.0.0-SNAPSHOT + ../pom.xml + + + jpalite-cache-infinispan + JPALite Infinispan Caching Provider + + + 21 + 21 + UTF-8 + + + + + io.jpalite + jpalite-core + ${project.version} + + + io.quarkus + quarkus-infinispan-client + provided + + + org.infinispan + infinispan-api + + + org.infinispan + infinispan-client-hotrod + + + org.infinispan + infinispan-query-dsl + + + + diff --git a/jpalite-cache-infinispan/src/main/java/org/jpalite/caching/infinispan/JPALiteInfinispanCache.java b/jpalite-cache-infinispan/src/main/java/org/jpalite/caching/infinispan/JPALiteInfinispanCache.java new file mode 100644 index 0000000..36dc312 --- /dev/null +++ b/jpalite-cache-infinispan/src/main/java/org/jpalite/caching/infinispan/JPALiteInfinispanCache.java @@ -0,0 +1,120 @@ +package org.jpalite.caching.infinispan; + +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 org.jpalite.CachingException; +import org.jpalite.JPACache; + +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/pom.xml b/jpalite-core/pom.xml index c4bfb69..202f8ca 100644 --- a/jpalite-core/pom.xml +++ b/jpalite-core/pom.xml @@ -1,22 +1,5 @@ - - @@ -24,7 +7,7 @@ io.jpalite jpalite-parent - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml @@ -63,27 +46,6 @@ org.projectlombok lombok - - io.quarkus - quarkus-infinispan-client - provided - - - org.infinispan - infinispan-api - - - org.infinispan - infinispan-client-hotrod - - - org.infinispan - infinispan-query-dsl - - - org.infinispan.protostream - protostream-processor - org.graalvm.sdk graal-sdk diff --git a/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java deleted file mode 100644 index 5654edc..0000000 --- a/jpalite-core/src/main/java/io/jpalite/impl/EntityFieldImpl.java +++ /dev/null @@ -1,437 +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.*; -import io.jpalite.impl.fieldtypes.EnumFieldType; -import io.jpalite.impl.fieldtypes.ObjectFieldType; -import io.jpalite.impl.fieldtypes.OrdinalEnumFieldType; -import jakarta.persistence.*; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.graalvm.nativeimage.ImageInfo; - -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/EntityMetaDataImpl.java b/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java deleted file mode 100644 index 8fcce27..0000000 --- a/jpalite-core/src/main/java/io/jpalite/impl/EntityMetaDataImpl.java +++ /dev/null @@ -1,382 +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.*; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; -import jakarta.persistence.*; -import lombok.extern.slf4j.Slf4j; - -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/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(); //
=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/parsers/JsqlVistorBase.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/JsqlVistorBase.java similarity index 99% rename from jpalite-core/src/main/java/io/jpalite/impl/parsers/JsqlVistorBase.java rename to jpalite-core/src/main/java/org/jpalite/impl/parsers/JsqlVistorBase.java index 6520294..999080f 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/JsqlVistorBase.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/JsqlVistorBase.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.impl.parsers; +package org.jpalite.impl.parsers; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.*; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/parsers/QueryParserFactory.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/QueryParserFactory.java similarity index 88% rename from jpalite-core/src/main/java/io/jpalite/impl/parsers/QueryParserFactory.java rename to jpalite-core/src/main/java/org/jpalite/impl/parsers/QueryParserFactory.java index c1f5ba7..a29b20f 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/QueryParserFactory.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/QueryParserFactory.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package io.jpalite.impl.parsers; +package org.jpalite.impl.parsers; -import io.jpalite.parsers.QueryParser; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.parsers.QueryParser; +import org.jpalite.queries.QueryLanguage; import jakarta.persistence.FetchType; import jakarta.persistence.PersistenceException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static io.jpalite.JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE; -import static io.jpalite.JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE; +import static org.jpalite.JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE; +import static org.jpalite.JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE; public class QueryParserFactory { diff --git a/jpalite-core/src/main/java/io/jpalite/impl/parsers/SQLParser.java b/jpalite-core/src/main/java/org/jpalite/impl/parsers/SQLParser.java similarity index 97% rename from jpalite-core/src/main/java/io/jpalite/impl/parsers/SQLParser.java rename to jpalite-core/src/main/java/org/jpalite/impl/parsers/SQLParser.java index 61902e2..a0dfb4e 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/parsers/SQLParser.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/parsers/SQLParser.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package io.jpalite.impl.parsers; +package org.jpalite.impl.parsers; -import io.jpalite.impl.queries.QueryParameterImpl; -import io.jpalite.parsers.QueryParser; -import io.jpalite.parsers.QueryStatement; +import org.jpalite.impl.queries.QueryParameterImpl; +import org.jpalite.parsers.QueryParser; +import org.jpalite.parsers.QueryStatement; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; diff --git a/jpalite-core/src/main/java/org/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java new file mode 100644 index 0000000..53c87f5 --- /dev/null +++ b/jpalite-core/src/main/java/org/jpalite/impl/providers/JPALiteEntityManagerFactoryImpl.java @@ -0,0 +1,227 @@ +/* + * 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.providers; + +import jakarta.persistence.*; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.metamodel.Metamodel; +import lombok.extern.slf4j.Slf4j; +import org.jpalite.PersistenceContext; +import org.jpalite.*; +import org.jpalite.impl.JPAConfig; +import org.jpalite.impl.JPALiteEntityManagerImpl; +import org.jpalite.impl.caching.EntityCacheImpl; +import org.jpalite.impl.db.DatabasePoolFactory; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; + +import static org.jpalite.JPALiteEntityManager.PERSISTENCE_QUERY_LOG_SLOWTIME; +import static org.jpalite.JPALiteEntityManager.PERSISTENCE_SHOW_SQL; +import static org.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/providers/JPALitePersistenceProviderImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/providers/JPALitePersistenceProviderImpl.java similarity index 97% rename from jpalite-core/src/main/java/io/jpalite/impl/providers/JPALitePersistenceProviderImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/providers/JPALitePersistenceProviderImpl.java index 1b15a92..48d5714 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/providers/JPALitePersistenceProviderImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/providers/JPALitePersistenceProviderImpl.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.impl.providers; +package org.jpalite.impl.providers; -import io.jpalite.JPAEntity; +import org.jpalite.JPAEntity; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.spi.LoadState; import jakarta.persistence.spi.PersistenceProvider; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityDeleteQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityDeleteQueryImpl.java similarity index 93% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/EntityDeleteQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/EntityDeleteQueryImpl.java index d6aa777..4f0ff71 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityDeleteQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityDeleteQueryImpl.java @@ -15,13 +15,13 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.EntityField; -import io.jpalite.EntityMetaData; -import io.jpalite.JPAEntity; -import io.jpalite.queries.EntityQuery; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.EntityField; +import org.jpalite.EntityMetaData; +import org.jpalite.JPAEntity; +import org.jpalite.queries.EntityQuery; +import org.jpalite.queries.QueryLanguage; import jakarta.persistence.PersistenceException; import java.util.ArrayList; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityInsertQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityInsertQueryImpl.java similarity index 93% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/EntityInsertQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/EntityInsertQueryImpl.java index d3e5a89..ee498d1 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityInsertQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityInsertQueryImpl.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package io.jpalite.impl.queries; - -import io.jpalite.EntityField; -import io.jpalite.EntityMetaData; -import io.jpalite.JPAEntity; -import io.jpalite.MappingType; -import io.jpalite.queries.EntityQuery; -import io.jpalite.queries.QueryLanguage; +package org.jpalite.impl.queries; + +import org.jpalite.EntityField; +import org.jpalite.EntityMetaData; +import org.jpalite.JPAEntity; +import org.jpalite.MappingType; +import org.jpalite.queries.EntityQuery; +import org.jpalite.queries.QueryLanguage; import java.sql.Timestamp; import java.util.ArrayList; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntitySelectQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntitySelectQueryImpl.java similarity index 92% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/EntitySelectQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/EntitySelectQueryImpl.java index 111e100..4bfdc60 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntitySelectQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntitySelectQueryImpl.java @@ -15,11 +15,11 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.EntityMetaData; -import io.jpalite.queries.EntityQuery; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.EntityMetaData; +import org.jpalite.queries.EntityQuery; +import org.jpalite.queries.QueryLanguage; import java.util.ArrayList; import java.util.List; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityUpdateQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityUpdateQueryImpl.java similarity index 94% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/EntityUpdateQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/EntityUpdateQueryImpl.java index 059403e..4e0c418 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/EntityUpdateQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/EntityUpdateQueryImpl.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package io.jpalite.impl.queries; - -import io.jpalite.EntityField; -import io.jpalite.EntityMetaData; -import io.jpalite.JPAEntity; -import io.jpalite.MappingType; -import io.jpalite.queries.EntityQuery; -import io.jpalite.queries.QueryLanguage; +package org.jpalite.impl.queries; + +import org.jpalite.EntityField; +import org.jpalite.EntityMetaData; +import org.jpalite.JPAEntity; +import org.jpalite.MappingType; +import org.jpalite.queries.EntityQuery; +import org.jpalite.queries.QueryLanguage; import jakarta.persistence.PersistenceException; import java.sql.Timestamp; diff --git a/jpalite-core/src/main/java/org/jpalite/impl/queries/JPALiteQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/JPALiteQueryImpl.java new file mode 100644 index 0000000..f3cb3ae --- /dev/null +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/JPALiteQueryImpl.java @@ -0,0 +1,1080 @@ +/* + * 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.queries; + +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 org.jpalite.PersistenceContext; +import org.jpalite.*; +import org.jpalite.impl.db.ConnectionWrapper; +import org.jpalite.impl.parsers.QueryParserFactory; +import org.jpalite.parsers.QueryParser; +import org.jpalite.parsers.QueryStatement; +import org.jpalite.queries.QueryLanguage; + +import java.lang.reflect.InvocationTargetException; +import java.sql.*; +import java.util.Date; +import java.util.*; + +import static jakarta.persistence.LockModeType.*; +import static org.jpalite.JPALiteEntityManager.*; + +@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/impl/queries/NamedNativeQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/NamedNativeQueryImpl.java similarity index 92% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/NamedNativeQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/NamedNativeQueryImpl.java index 702d31f..4283601 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/NamedNativeQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/NamedNativeQueryImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.PersistenceContext; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.PersistenceContext; +import org.jpalite.queries.QueryLanguage; import jakarta.annotation.Nonnull; import jakarta.persistence.NamedNativeQuery; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/NamedQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/NamedQueryImpl.java similarity index 93% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/NamedQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/NamedQueryImpl.java index 28ed138..57f5912 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/NamedQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/NamedQueryImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.PersistenceContext; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.PersistenceContext; +import org.jpalite.queries.QueryLanguage; import jakarta.annotation.Nonnull; import jakarta.persistence.NamedQuery; import jakarta.persistence.QueryHint; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/NativeQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/NativeQueryImpl.java similarity index 91% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/NativeQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/NativeQueryImpl.java index db4c8db..fb6f267 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/NativeQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/NativeQueryImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.PersistenceContext; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.PersistenceContext; +import org.jpalite.queries.QueryLanguage; import jakarta.annotation.Nonnull; import java.util.Map; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/QueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/QueryImpl.java similarity index 91% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/QueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/QueryImpl.java index 72407f3..6f3a556 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/QueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/QueryImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.PersistenceContext; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.PersistenceContext; +import org.jpalite.queries.QueryLanguage; import jakarta.annotation.Nonnull; import java.util.Map; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/QueryParameterImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/QueryParameterImpl.java similarity index 98% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/QueryParameterImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/QueryParameterImpl.java index 2635ac1..a707d08 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/QueryParameterImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/QueryParameterImpl.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; import jakarta.persistence.Parameter; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/SQLFunction.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/SQLFunction.java similarity index 96% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/SQLFunction.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/SQLFunction.java index 5ab768e..173c645 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/SQLFunction.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/SQLFunction.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; import java.sql.SQLException; diff --git a/jpalite-core/src/main/java/io/jpalite/impl/queries/TypedQueryImpl.java b/jpalite-core/src/main/java/org/jpalite/impl/queries/TypedQueryImpl.java similarity index 97% rename from jpalite-core/src/main/java/io/jpalite/impl/queries/TypedQueryImpl.java rename to jpalite-core/src/main/java/org/jpalite/impl/queries/TypedQueryImpl.java index 337b688..d0808f7 100644 --- a/jpalite-core/src/main/java/io/jpalite/impl/queries/TypedQueryImpl.java +++ b/jpalite-core/src/main/java/org/jpalite/impl/queries/TypedQueryImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl.queries; +package org.jpalite.impl.queries; -import io.jpalite.PersistenceContext; -import io.jpalite.queries.QueryLanguage; +import org.jpalite.PersistenceContext; +import org.jpalite.queries.QueryLanguage; import jakarta.annotation.Nonnull; import jakarta.persistence.*; diff --git a/jpalite-core/src/main/java/io/jpalite/parsers/QueryParser.java b/jpalite-core/src/main/java/org/jpalite/parsers/QueryParser.java similarity index 95% rename from jpalite-core/src/main/java/io/jpalite/parsers/QueryParser.java rename to jpalite-core/src/main/java/org/jpalite/parsers/QueryParser.java index 0daed7b..aa3dd6d 100644 --- a/jpalite-core/src/main/java/io/jpalite/parsers/QueryParser.java +++ b/jpalite-core/src/main/java/org/jpalite/parsers/QueryParser.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.parsers; +package org.jpalite.parsers; -import io.jpalite.impl.queries.JPALiteQueryImpl; -import io.jpalite.impl.queries.QueryParameterImpl; +import org.jpalite.impl.queries.JPALiteQueryImpl; +import org.jpalite.impl.queries.QueryParameterImpl; import java.util.Collections; import java.util.List; diff --git a/jpalite-core/src/main/java/io/jpalite/parsers/QueryStatement.java b/jpalite-core/src/main/java/org/jpalite/parsers/QueryStatement.java similarity index 96% rename from jpalite-core/src/main/java/io/jpalite/parsers/QueryStatement.java rename to jpalite-core/src/main/java/org/jpalite/parsers/QueryStatement.java index d496be0..c43f0df 100644 --- a/jpalite-core/src/main/java/io/jpalite/parsers/QueryStatement.java +++ b/jpalite-core/src/main/java/org/jpalite/parsers/QueryStatement.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.parsers; +package org.jpalite.parsers; public enum QueryStatement { diff --git a/jpalite-core/src/main/java/io/jpalite/queries/EntityQuery.java b/jpalite-core/src/main/java/org/jpalite/queries/EntityQuery.java similarity index 96% rename from jpalite-core/src/main/java/io/jpalite/queries/EntityQuery.java rename to jpalite-core/src/main/java/org/jpalite/queries/EntityQuery.java index f7d2934..ff261e9 100644 --- a/jpalite-core/src/main/java/io/jpalite/queries/EntityQuery.java +++ b/jpalite-core/src/main/java/org/jpalite/queries/EntityQuery.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.queries; +package org.jpalite.queries; public interface EntityQuery { diff --git a/jpalite-core/src/main/java/io/jpalite/queries/QueryLanguage.java b/jpalite-core/src/main/java/org/jpalite/queries/QueryLanguage.java similarity index 96% rename from jpalite-core/src/main/java/io/jpalite/queries/QueryLanguage.java rename to jpalite-core/src/main/java/org/jpalite/queries/QueryLanguage.java index a97656e..ef70bd8 100644 --- a/jpalite-core/src/main/java/io/jpalite/queries/QueryLanguage.java +++ b/jpalite-core/src/main/java/org/jpalite/queries/QueryLanguage.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.queries; +package org.jpalite.queries; public enum QueryLanguage { diff --git a/jpalite-core/src/main/resources/META-INF/services/io.jpalite.FieldConvertType b/jpalite-core/src/main/resources/META-INF/services/io.jpalite.FieldConvertType deleted file mode 100644 index 85a2184..0000000 --- a/jpalite-core/src/main/resources/META-INF/services/io.jpalite.FieldConvertType +++ /dev/null @@ -1,13 +0,0 @@ -io.jpalite.impl.fieldtypes.BooleanFieldType -io.jpalite.impl.fieldtypes.IntegerFieldType -io.jpalite.impl.fieldtypes.LongLongFieldType -io.jpalite.impl.fieldtypes.DoubleDoubleFieldType -io.jpalite.impl.fieldtypes.BoolFieldType -io.jpalite.impl.fieldtypes.IntFieldType -io.jpalite.impl.fieldtypes.LongFieldType -io.jpalite.impl.fieldtypes.DoubleFieldType -io.jpalite.impl.fieldtypes.StringFieldType -io.jpalite.impl.fieldtypes.BytesFieldType -io.jpalite.impl.fieldtypes.TimestampFieldType -io.jpalite.impl.fieldtypes.LocalDateTimeFieldType -io.jpalite.impl.fieldtypes.BigDecimalFieldType diff --git a/jpalite-core/src/main/resources/META-INF/services/jakarta.persistence.spi.PersistenceProvider b/jpalite-core/src/main/resources/META-INF/services/jakarta.persistence.spi.PersistenceProvider index cf10318..fb60cd2 100644 --- a/jpalite-core/src/main/resources/META-INF/services/jakarta.persistence.spi.PersistenceProvider +++ b/jpalite-core/src/main/resources/META-INF/services/jakarta.persistence.spi.PersistenceProvider @@ -15,4 +15,4 @@ # limitations under the License. # -io.jpalite.impl.providers.JPALitePersistenceProviderImpl +org.jpalite.impl.providers.JPALitePersistenceProviderImpl diff --git a/jpalite-core/src/main/resources/META-INF/services/org.jpalite.FieldConvertType b/jpalite-core/src/main/resources/META-INF/services/org.jpalite.FieldConvertType new file mode 100644 index 0000000..d85e915 --- /dev/null +++ b/jpalite-core/src/main/resources/META-INF/services/org.jpalite.FieldConvertType @@ -0,0 +1,13 @@ +org.jpalite.impl.fieldtypes.BooleanFieldType +org.jpalite.impl.fieldtypes.IntegerFieldType +org.jpalite.impl.fieldtypes.LongLongFieldType +org.jpalite.impl.fieldtypes.DoubleDoubleFieldType +org.jpalite.impl.fieldtypes.BoolFieldType +org.jpalite.impl.fieldtypes.IntFieldType +org.jpalite.impl.fieldtypes.LongFieldType +org.jpalite.impl.fieldtypes.DoubleFieldType +org.jpalite.impl.fieldtypes.StringFieldType +org.jpalite.impl.fieldtypes.BytesFieldType +org.jpalite.impl.fieldtypes.TimestampFieldType +org.jpalite.impl.fieldtypes.LocalDateTimeFieldType +org.jpalite.impl.fieldtypes.BigDecimalFieldType diff --git a/jpalite-core/src/test/java/io/jpalite/impl/JPAEntityImplTest.java b/jpalite-core/src/test/java/io/jpalite/impl/JPAEntityImplTest.java deleted file mode 100644 index cfb8593..0000000 --- a/jpalite-core/src/test/java/io/jpalite/impl/JPAEntityImplTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.jpalite.impl; - -import io.jpalite.test.*; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class JPAEntityImplTest -{ - - @BeforeAll - static void beforeAll() - { - TestEntityMetaDataManager.init(); - } - - @Test - void testToJson() - { - Company c = new Company(); - c.setId(3); - c.setName("Test Company"); - - Department d = new Department(); - d.setId(2); - d.setName("Test Dept"); - d.setCompany(c); - - Employee e = new Employee(); - e.setAge(10); - e.setId(1); - e.setSalary(BigDecimal.valueOf(4000.00).setScale(4, RoundingMode.HALF_DOWN)); - e.setFullName(new FullName("Test", "Employee")); - e.setDepartment(d); - - Phone p1 = new Phone(); - p1.setId(4); - p1.setNumber("1234567890"); - p1.setEmployee(e); - - Phone p2 = new Phone(); - p2.setId(5); - p2.setNumber("0987654321"); - p2.setEmployee(e); - - e.setPhones(List.of(p1, p2)); - - String json = e._toJson(); - - Employee e2 = new Employee(); - e2._fromJson(json); - - assertEquals(e.getId(), e2.getId()); - assertEquals(e.getAge(), e2.getAge()); - assertEquals(e.getFullName().getName(), e2.getFullName().getName()); - assertEquals(e.getFullName().getSurname(), e2.getFullName().getSurname()); - assertEquals(e.getDepartment().getId(), e2.getDepartment().getId()); - assertEquals(e.getSalary(), e2.getSalary()); - } -} diff --git a/jpalite-core/src/test/java/io/jpalite/jqpl/JPQLParserTest.java b/jpalite-core/src/test/java/io/jpalite/jqpl/JPQLParserTest.java deleted file mode 100644 index 9a0fb69..0000000 --- a/jpalite-core/src/test/java/io/jpalite/jqpl/JPQLParserTest.java +++ /dev/null @@ -1,601 +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.jqpl; - -import io.jpalite.EntityMetaData; -import io.jpalite.EntityMetaDataManager; -import io.jpalite.JPALiteEntityManager; -import io.jpalite.impl.parsers.JPQLParser; -import io.jpalite.test.*; -import jakarta.persistence.FetchType; -import net.sf.jsqlparser.JSQLParserException; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -class JPQLParserTest -{ - @BeforeAll - static void beforeAll() - { - TestEntityMetaDataManager.init(); - } - - @Test - void testUsingSelectIn() - { - JPQLParser vParser = new JPQLParser("select RatePlan from RatePlan where (uid, resourceVersion) in (select e.uid, max(e.resourceVersion) from RatePlan e group by e.uid)", new HashMap<>()); - Assertions.assertEquals("SELECT t1.ID \"c1-1\", t1.UID \"c1-2\", t1.RESOURCE_VERSION \"c1-3\", t1.OPERATOR_ID \"c1-4\", t1.PLAN_NAME \"c1-5\", t1.CREATED_BY \"c1-6\", t1.APPROVED_BY \"c1-7\", t1.EFFECTIVE_DATE \"c1-8\", " + - "t1.RATE_PLAN_CONFIG \"c1-9\", t1.MODIFIED_ON \"c1-10\", t1.CREATED_DATE \"c1-11\" " + - "FROM RATE_PLAN t1 " + - "WHERE (t1.UID, t1.RESOURCE_VERSION) IN (SELECT t2.UID \"c1\", max(t2.RESOURCE_VERSION) \"c2\" " + - "FROM RATE_PLAN t2 GROUP BY t2.UID)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(RatePlan.class, vParser.getReturnTypes().get(0)); - } - - @Test - void testUsingBitAndOperators() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("select E from Employee E where bitand(E.department.id, :flag) = :flag", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE bitand(t2.IRN, ?) = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("select Employee from Employee where bitand(department.id, :flag) = :flag", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE bitand(t2.IRN, ?) = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("select Employee1 from Employee1 where bitand(department.id, :flag) = :flag", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + - "FROM EMPLOYEE t1 " + - "LEFT JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "WHERE bitand(t2.IRN, ?) = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenUsingBracketsInWhereClauses() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("select E from Employee E where (E.department.id = :val)", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE (t2.IRN = ?)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("select Employee from Employee where (department.id = :val)", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE (t2.IRN = ?)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerWithLazyJoin() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee1 e", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + - "FROM EMPLOYEE t1", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("SELECT e FROM Employee1 e JOIN e.department", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", t2.COMP \"c1-5-3\" " + - "FROM EMPLOYEE t1 " + - "LEFT JOIN DEPT t2 ON t1.DEPT = t2.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenOverridingFetchTypes_thenGenerateQueryCorrect() throws JSQLParserException - { - Map vHints = new HashMap<>(); - vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE, FetchType.EAGER); - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e", vHints); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", t1.SALARY \"c1-4\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vHints = new HashMap<>(); - vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE, FetchType.LAZY); - vParser = new JPQLParser("SELECT e FROM Employee e", vHints); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + - "FROM EMPLOYEE t1", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vHints = new HashMap<>(); - vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE, FetchType.LAZY); - vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE, FetchType.EAGER); - vParser = new JPQLParser("SELECT e FROM Employee e", vHints); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t1.SALARY \"c1-4\", t1.DEPT \"c1-5\" " + - "FROM EMPLOYEE t1", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerJoin() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("SELECT Employee FROM Employee", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("SELECT e FROM Employee e JOIN e.department", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("select e.department.id, e.department.company from Employee e", new HashMap<>()); - Assertions.assertEquals("SELECT t2.IRN \"c1\", " + - "t3.IRN \"c2-1\", t3.NAME \"c2-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(2, vParser.getReturnTypes().size()); - Assertions.assertEquals(Integer.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(Company.class, vParser.getReturnTypes().get(1)); - - vParser = new JPQLParser("select e.fullName.name, e.department.name, e.department.id from Employee e", new HashMap<>()); - Assertions.assertEquals("SELECT t1.NAME \"c1\", t2.NAME \"c2\", t2.IRN \"c3\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN", - vParser.getQuery()); - Assertions.assertEquals(3, vParser.getReturnTypes().size()); - Assertions.assertEquals(String.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(String.class, vParser.getReturnTypes().get(1)); - Assertions.assertEquals(Integer.class, vParser.getReturnTypes().get(2)); - - vParser = new JPQLParser("select e.fullName, e.department from Employee e", new HashMap<>()); - Assertions.assertEquals("SELECT t1.NAME \"c1-1\", t1.SURNAME \"c1-2\", t2.IRN \"c2-1\", t2.NAME \"c2-2\", " + - "t3.IRN \"c2-3-1\", t3.NAME \"c2-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(2, vParser.getReturnTypes().size()); - Assertions.assertEquals(FullName.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(1)); - } - - @Test - void whenJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("select case when exists(select 1 from Employee where id=:id) then 1 else 0 end", new HashMap<>()); - - Assertions.assertEquals("SELECT CASE WHEN EXISTS (SELECT 1 \"c1\" FROM EMPLOYEE t1 WHERE t1.IRN = ?) THEN 1 ELSE 0 END \"c1\"", - vParser.getQuery()); - - vParser = new JPQLParser("select count(Employee) from Employee where department.company=:id", new HashMap<>()); - - Assertions.assertEquals("SELECT count(t1.IRN) \"c1\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t3.IRN = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Object.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(1, vParser.getQueryParameters().size()); - Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); - Assertions.assertFalse(vParser.isSelectUsingPrimaryKey()); - } - - @Test - void whenWhereINIsUsed() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e where e.age in (11,22,33)", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.AGE IN (11, 22, 33)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("SELECT e FROM Employee e where e.age in (select e1.age from Employee1 e1)", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.AGE IN (SELECT t4.AGE \"c1\" FROM EMPLOYEE t4)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - } - - @Test - void whenIsNullIsUsed() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e where e.fullName.name is null", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.NAME IS NULL", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - - vParser = new JPQLParser("SELECT e FROM Employee e where e.fullName.name is not null", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.NAME IS NOT NULL", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenExistIsUsed_thenCreatesExplicitInnerJoin() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT d FROM Employee e JOIN e.department d", new HashMap<>()); - - Assertions.assertEquals("SELECT t2.IRN \"c1-1\", t2.NAME \"c1-2\", " + - "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenLeftKeywordIsSpecified_thenCreatesOuterJoinAndIncludesNonMatched() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e", new HashMap<>()); - - Assertions.assertEquals("SELECT DISTINCT t1.IRN \"c1-1\", t1.NAME \"c1-2\", " + - "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + - "FROM DEPT t1 " + - "LEFT JOIN EMPLOYEE t2 ON t1.IRN = t2.DEPT " + - "INNER JOIN COMPANY t3 ON t1.COMP = t3.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenEntitiesAreListedInFrom_ThenCreatesCartesianProduct() throws JSQLParserException - { - //Mark the company field as lazy for this test - EntityMetaData vMetaData = EntityMetaDataManager.getMetaData(Department.class); - vMetaData.getEntityField("company").setFetchType(FetchType.LAZY); - - JPQLParser vParser = new JPQLParser("SELECT e, d FROM Employee e, Department d", new HashMap<>()); - vMetaData.getEntityField("company").setFetchType(FetchType.EAGER); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t2.COMP \"c1-5-3\", " + - "t2.IRN \"c2-1\", t2.NAME \"c2-2\", t2.COMP \"c2-3\" " + - "FROM EMPLOYEE t1, DEPT t2", - vParser.getQuery()); - Assertions.assertEquals(2, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(1)); - } - - @Test - void whenEntitiesAreListedInFromAndMatchedInWhere_ThenCreatesJoin() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT d FROM Employee e, Department d WHERE e.department = d", new HashMap<>()); - - Assertions.assertEquals("SELECT t2.IRN \"c1-1\", t2.NAME \"c1-2\", " + - "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + - "FROM EMPLOYEE t1, DEPT t2 " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.DEPT = t2.IRN", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenEntitiesAreOrderedByFK() throws JSQLParserException - { - - JPQLParser vParser = new JPQLParser("SELECT Employee FROM Employee Order by department.name asc", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "ORDER BY t2.NAME ASC", vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenCollectionValuedAssociationIsJoined_ThenCanSelect() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e JOIN e.phones ph WHERE ph LIKE '1%'", new HashMap<>()); - - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t3.IRN \"c1-5-1\", t3.NAME \"c1-5-2\", " + - "t4.IRN \"c1-5-3-1\", t4.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + - "INNER JOIN DEPT t3 ON t1.DEPT = t3.IRN " + - "INNER JOIN COMPANY t4 ON t3.COMP = t4.IRN " + - "WHERE t2.IRN LIKE '1%'", - vParser.getQuery()); - - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenMultipleEntitiesAreListedWithJoin_ThenCreatesMultipleJoins() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT ph FROM Employee e " + - "JOIN e.department d " + - "JOIN e.phones ph " + - "WHERE d.name IS NOT NULL", new HashMap<>()); - Assertions.assertEquals("SELECT t3.IRN \"c1-1\", t3.NUM \"c1-2\", " + - "t1.IRN \"c1-3-1\", t1.NAME \"c1-3-2-1\", t1.SURNAME \"c1-3-2-2\", t1.AGE \"c1-3-3\", " + - "t2.IRN \"c1-3-5-1\", t2.NAME \"c1-3-5-2\", " + - "t4.IRN \"c1-3-5-3-1\", t4.NAME \"c1-3-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "JOIN PHONE t3 ON t1.IRN = t3.EMPL " + - "INNER JOIN COMPANY t4 ON t2.COMP = t4.IRN " + - "WHERE t2.NAME IS NOT NULL", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Phone.class, vParser.getReturnTypes().get(0)); - } - - @Test - void whenGroupByOrderBy_ThenCreatesJoins() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e.fullName.name, count(ph.id) FROM Employee e " + - "JOIN e.phones ph " + - "group by e.fullName.name " + - "order by e.fullName.name, ph", new HashMap<>()); - Assertions.assertEquals("SELECT t1.NAME \"c1\", count(t2.IRN) \"c2\" " + - "FROM EMPLOYEE t1 " + - "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + - "GROUP BY t1.NAME " + - "ORDER BY t1.NAME, t2.IRN", - vParser.getQuery()); - Assertions.assertEquals(2, vParser.getReturnTypes().size()); - Assertions.assertEquals(String.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(Object.class, vParser.getReturnTypes().get(1)); - } - - @Test - void whenGroupByHaving_ThenCreatesJoins() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e.fullName.name, count(ph.id),:p1 FROM Employee e " + - "JOIN e.phones ph " + - "group by e.fullName.name " + - "having count(ph.id) > 1", new HashMap<>()); - Assertions.assertEquals("SELECT t1.NAME \"c1\", count(t2.IRN) \"c2\", ? \"c3\" " + - "FROM EMPLOYEE t1 " + - "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + - "GROUP BY t1.NAME " + - "HAVING count(t2.IRN) > 1", - vParser.getQuery()); - assertDoesNotThrow(() -> vParser.checkType(Object[].class)); - assertThrows(IllegalArgumentException.class, () -> vParser.checkType(Employee.class)); - } - - @Test - void whenWhereByEntity_ThenCreateWhereOnPK() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e " + - "where e.fullName=:FullName", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE (t1.NAME, t1.SURNAME) = (?, ?)", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(1, vParser.getQueryParameters().size()); - Assertions.assertEquals(FullName.class, vParser.getQueryParameters().get(0).getParameterType()); - - vParser = new JPQLParser("SELECT Employee FROM Employee " + - "where fullName=:FullName and age>:age", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE (t1.NAME, t1.SURNAME) = (?, ?) " + - "AND t1.AGE > ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(2, vParser.getQueryParameters().size()); - Assertions.assertEquals(FullName.class, vParser.getQueryParameters().get(0).getParameterType()); - - vParser = new JPQLParser("SELECT e FROM Employee e where e=?1", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t1.IRN = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(1, vParser.getQueryParameters().size()); - Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); - Assertions.assertTrue(vParser.isSelectUsingPrimaryKey()); - - vParser = new JPQLParser("SELECT e FROM Employee e where e.department.company=?1", new HashMap<>()); - Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + - "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + - "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + - "FROM EMPLOYEE t1 " + - "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + - "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + - "WHERE t3.IRN = ?", - vParser.getQuery()); - Assertions.assertEquals(1, vParser.getReturnTypes().size()); - Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); - Assertions.assertEquals(1, vParser.getQueryParameters().size()); - Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); - Assertions.assertFalse(vParser.isSelectUsingPrimaryKey()); - } - - @Test - void deleteEntityUsingPK() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("DELETE Employee e where e.id=:id", new HashMap<>()); - Assertions.assertEquals("DELETE EMPLOYEE t1 WHERE t1.IRN = ?", - vParser.getQuery()); - - vParser = new JPQLParser("DELETE Employee where age>?", new HashMap<>()); - Assertions.assertEquals("DELETE EMPLOYEE t1 WHERE t1.AGE > ?", - vParser.getQuery()); - } - - @Test - void updateEntityUsingPK() throws JSQLParserException - { - JPQLParser vParser = new JPQLParser("UPDATE Employee e set e.salary=e.salary*1.05 " + - "where e.id=:id", new HashMap<>()); - Assertions.assertEquals("UPDATE EMPLOYEE t1 SET t1.SALARY = t1.SALARY * 1.05 WHERE t1.IRN = ?", - vParser.getQuery()); - - vParser = new JPQLParser("UPDATE Employee set salary=salary*1.05 " + - "where id=:id", new HashMap<>()); - Assertions.assertEquals("UPDATE EMPLOYEE t1 SET t1.SALARY = t1.SALARY * 1.05 WHERE t1.IRN = ?", - vParser.getQuery()); - - vParser = new JPQLParser("UPDATE Employee e set (e.salary,e.age)=(e.salary*1.05,e.age+1) " + - "where e.id=:id", new HashMap<>()); - Assertions.assertEquals("UPDATE EMPLOYEE t1 SET (t1.SALARY, t1.AGE) = (t1.SALARY * 1.05, t1.AGE + 1) WHERE t1.IRN = ?", - vParser.getQuery()); - - vParser = new JPQLParser("UPDATE Employee set (salary,age)=(salary*1.05,age+1) " + - "where id=:id", new HashMap<>()); - Assertions.assertEquals("UPDATE EMPLOYEE t1 SET (t1.SALARY, t1.AGE) = (t1.SALARY * 1.05, t1.AGE + 1) WHERE t1.IRN = ?", - vParser.getQuery()); - } - - @Test - void whenUsingNamedParameters_thenCheckIfNamesReused() - { - JPQLParser vParser = new JPQLParser("select Employee from Employee " + - "where id = :num " + - "and age= :num", - new HashMap<>()); - assertEquals(2, vParser.getNumberOfParameters()); - } -} diff --git a/jpalite-core/src/test/java/org/jpalite/impl/JPAEntityImplTest.java b/jpalite-core/src/test/java/org/jpalite/impl/JPAEntityImplTest.java new file mode 100644 index 0000000..fc1ffaf --- /dev/null +++ b/jpalite-core/src/test/java/org/jpalite/impl/JPAEntityImplTest.java @@ -0,0 +1,65 @@ +package org.jpalite.impl; + +import org.jpalite.test.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class JPAEntityImplTest +{ + + @BeforeAll + static void beforeAll() + { + TestEntityMetaDataManager.init(); + } + + @Test + void testToJson() + { + Company c = new Company(); + c.setId(3); + c.setName("Test Company"); + + Department d = new Department(); + d.setId(2); + d.setName("Test Dept"); + d.setCompany(c); + + Employee e = new Employee(); + e.setAge(10); + e.setId(1); + e.setSalary(BigDecimal.valueOf(4000.00).setScale(4, RoundingMode.HALF_DOWN)); + e.setFullName(new FullName("Test", "Employee")); + e.setDepartment(d); + + Phone p1 = new Phone(); + p1.setId(4); + p1.setNumber("1234567890"); + p1.setEmployee(e); + + Phone p2 = new Phone(); + p2.setId(5); + p2.setNumber("0987654321"); + p2.setEmployee(e); + + e.setPhones(List.of(p1, p2)); + + String json = e._toJson(); + + Employee e2 = new Employee(); + e2._fromJson(json); + + assertEquals(e.getId(), e2.getId()); + assertEquals(e.getAge(), e2.getAge()); + assertEquals(e.getFullName().getName(), e2.getFullName().getName()); + assertEquals(e.getFullName().getSurname(), e2.getFullName().getSurname()); + assertEquals(e.getDepartment().getId(), e2.getDepartment().getId()); + assertEquals(e.getSalary(), e2.getSalary()); + } +} diff --git a/jpalite-core/src/test/java/org/jpalite/jqpl/JPQLParserTest.java b/jpalite-core/src/test/java/org/jpalite/jqpl/JPQLParserTest.java new file mode 100644 index 0000000..2e4ebff --- /dev/null +++ b/jpalite-core/src/test/java/org/jpalite/jqpl/JPQLParserTest.java @@ -0,0 +1,601 @@ +/* + * 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.jqpl; + +import jakarta.persistence.FetchType; +import net.sf.jsqlparser.JSQLParserException; +import org.jpalite.EntityMetaData; +import org.jpalite.EntityMetaDataManager; +import org.jpalite.JPALiteEntityManager; +import org.jpalite.impl.parsers.JPQLParser; +import org.jpalite.test.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class JPQLParserTest +{ + @BeforeAll + static void beforeAll() + { + TestEntityMetaDataManager.init(); + } + + @Test + void testUsingSelectIn() + { + JPQLParser vParser = new JPQLParser("select RatePlan from RatePlan where (uid, resourceVersion) in (select e.uid, max(e.resourceVersion) from RatePlan e group by e.uid)", new HashMap<>()); + Assertions.assertEquals("SELECT t1.ID \"c1-1\", t1.UID \"c1-2\", t1.RESOURCE_VERSION \"c1-3\", t1.OPERATOR_ID \"c1-4\", t1.PLAN_NAME \"c1-5\", t1.CREATED_BY \"c1-6\", t1.APPROVED_BY \"c1-7\", t1.EFFECTIVE_DATE \"c1-8\", " + + "t1.RATE_PLAN_CONFIG \"c1-9\", t1.MODIFIED_ON \"c1-10\", t1.CREATED_DATE \"c1-11\" " + + "FROM RATE_PLAN t1 " + + "WHERE (t1.UID, t1.RESOURCE_VERSION) IN (SELECT t2.UID \"c1\", max(t2.RESOURCE_VERSION) \"c2\" " + + "FROM RATE_PLAN t2 GROUP BY t2.UID)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(RatePlan.class, vParser.getReturnTypes().get(0)); + } + + @Test + void testUsingBitAndOperators() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("select E from Employee E where bitand(E.department.id, :flag) = :flag", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE bitand(t2.IRN, ?) = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("select Employee from Employee where bitand(department.id, :flag) = :flag", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE bitand(t2.IRN, ?) = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("select Employee1 from Employee1 where bitand(department.id, :flag) = :flag", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + + "FROM EMPLOYEE t1 " + + "LEFT JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "WHERE bitand(t2.IRN, ?) = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenUsingBracketsInWhereClauses() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("select E from Employee E where (E.department.id = :val)", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE (t2.IRN = ?)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("select Employee from Employee where (department.id = :val)", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE (t2.IRN = ?)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerWithLazyJoin() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee1 e", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + + "FROM EMPLOYEE t1", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("SELECT e FROM Employee1 e JOIN e.department", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", t2.COMP \"c1-5-3\" " + + "FROM EMPLOYEE t1 " + + "LEFT JOIN DEPT t2 ON t1.DEPT = t2.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee1.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenOverridingFetchTypes_thenGenerateQueryCorrect() throws JSQLParserException + { + Map vHints = new HashMap<>(); + vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE, FetchType.EAGER); + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e", vHints); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", t1.SALARY \"c1-4\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vHints = new HashMap<>(); + vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE, FetchType.LAZY); + vParser = new JPQLParser("SELECT e FROM Employee e", vHints); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", t1.DEPT \"c1-5\" " + + "FROM EMPLOYEE t1", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vHints = new HashMap<>(); + vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_FETCHTYPE, FetchType.LAZY); + vHints.put(JPALiteEntityManager.PERSISTENCE_OVERRIDE_BASIC_FETCHTYPE, FetchType.EAGER); + vParser = new JPQLParser("SELECT e FROM Employee e", vHints); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t1.SALARY \"c1-4\", t1.DEPT \"c1-5\" " + + "FROM EMPLOYEE t1", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenPathExpressionIsUsedForSingleValuedAssociation_thenCreatesImplicitInnerJoin() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("SELECT Employee FROM Employee", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("SELECT e FROM Employee e JOIN e.department", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("select e.department.id, e.department.company from Employee e", new HashMap<>()); + Assertions.assertEquals("SELECT t2.IRN \"c1\", " + + "t3.IRN \"c2-1\", t3.NAME \"c2-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(2, vParser.getReturnTypes().size()); + Assertions.assertEquals(Integer.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(Company.class, vParser.getReturnTypes().get(1)); + + vParser = new JPQLParser("select e.fullName.name, e.department.name, e.department.id from Employee e", new HashMap<>()); + Assertions.assertEquals("SELECT t1.NAME \"c1\", t2.NAME \"c2\", t2.IRN \"c3\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN", + vParser.getQuery()); + Assertions.assertEquals(3, vParser.getReturnTypes().size()); + Assertions.assertEquals(String.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(String.class, vParser.getReturnTypes().get(1)); + Assertions.assertEquals(Integer.class, vParser.getReturnTypes().get(2)); + + vParser = new JPQLParser("select e.fullName, e.department from Employee e", new HashMap<>()); + Assertions.assertEquals("SELECT t1.NAME \"c1-1\", t1.SURNAME \"c1-2\", t2.IRN \"c2-1\", t2.NAME \"c2-2\", " + + "t3.IRN \"c2-3-1\", t3.NAME \"c2-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(2, vParser.getReturnTypes().size()); + Assertions.assertEquals(FullName.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(1)); + } + + @Test + void whenJoinKeywordIsUsed_thenCreatesExplicitInnerJoin() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("select case when exists(select 1 from Employee where id=:id) then 1 else 0 end", new HashMap<>()); + + Assertions.assertEquals("SELECT CASE WHEN EXISTS (SELECT 1 \"c1\" FROM EMPLOYEE t1 WHERE t1.IRN = ?) THEN 1 ELSE 0 END \"c1\"", + vParser.getQuery()); + + vParser = new JPQLParser("select count(Employee) from Employee where department.company=:id", new HashMap<>()); + + Assertions.assertEquals("SELECT count(t1.IRN) \"c1\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t3.IRN = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Object.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(1, vParser.getQueryParameters().size()); + Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); + Assertions.assertFalse(vParser.isSelectUsingPrimaryKey()); + } + + @Test + void whenWhereINIsUsed() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e where e.age in (11,22,33)", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.AGE IN (11, 22, 33)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("SELECT e FROM Employee e where e.age in (select e1.age from Employee1 e1)", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.AGE IN (SELECT t4.AGE \"c1\" FROM EMPLOYEE t4)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + } + + @Test + void whenIsNullIsUsed() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e where e.fullName.name is null", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.NAME IS NULL", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + + vParser = new JPQLParser("SELECT e FROM Employee e where e.fullName.name is not null", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.NAME IS NOT NULL", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenExistIsUsed_thenCreatesExplicitInnerJoin() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT d FROM Employee e JOIN e.department d", new HashMap<>()); + + Assertions.assertEquals("SELECT t2.IRN \"c1-1\", t2.NAME \"c1-2\", " + + "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenLeftKeywordIsSpecified_thenCreatesOuterJoinAndIncludesNonMatched() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT DISTINCT d FROM Department d LEFT JOIN d.employees e", new HashMap<>()); + + Assertions.assertEquals("SELECT DISTINCT t1.IRN \"c1-1\", t1.NAME \"c1-2\", " + + "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + + "FROM DEPT t1 " + + "LEFT JOIN EMPLOYEE t2 ON t1.IRN = t2.DEPT " + + "INNER JOIN COMPANY t3 ON t1.COMP = t3.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenEntitiesAreListedInFrom_ThenCreatesCartesianProduct() throws JSQLParserException + { + //Mark the company field as lazy for this test + EntityMetaData vMetaData = EntityMetaDataManager.getMetaData(Department.class); + vMetaData.getEntityField("company").setFetchType(FetchType.LAZY); + + JPQLParser vParser = new JPQLParser("SELECT e, d FROM Employee e, Department d", new HashMap<>()); + vMetaData.getEntityField("company").setFetchType(FetchType.EAGER); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t2.COMP \"c1-5-3\", " + + "t2.IRN \"c2-1\", t2.NAME \"c2-2\", t2.COMP \"c2-3\" " + + "FROM EMPLOYEE t1, DEPT t2", + vParser.getQuery()); + Assertions.assertEquals(2, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(1)); + } + + @Test + void whenEntitiesAreListedInFromAndMatchedInWhere_ThenCreatesJoin() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT d FROM Employee e, Department d WHERE e.department = d", new HashMap<>()); + + Assertions.assertEquals("SELECT t2.IRN \"c1-1\", t2.NAME \"c1-2\", " + + "t3.IRN \"c1-3-1\", t3.NAME \"c1-3-2\" " + + "FROM EMPLOYEE t1, DEPT t2 " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.DEPT = t2.IRN", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Department.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenEntitiesAreOrderedByFK() throws JSQLParserException + { + + JPQLParser vParser = new JPQLParser("SELECT Employee FROM Employee Order by department.name asc", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "ORDER BY t2.NAME ASC", vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenCollectionValuedAssociationIsJoined_ThenCanSelect() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e JOIN e.phones ph WHERE ph LIKE '1%'", new HashMap<>()); + + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t3.IRN \"c1-5-1\", t3.NAME \"c1-5-2\", " + + "t4.IRN \"c1-5-3-1\", t4.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + + "INNER JOIN DEPT t3 ON t1.DEPT = t3.IRN " + + "INNER JOIN COMPANY t4 ON t3.COMP = t4.IRN " + + "WHERE t2.IRN LIKE '1%'", + vParser.getQuery()); + + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenMultipleEntitiesAreListedWithJoin_ThenCreatesMultipleJoins() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT ph FROM Employee e " + + "JOIN e.department d " + + "JOIN e.phones ph " + + "WHERE d.name IS NOT NULL", new HashMap<>()); + Assertions.assertEquals("SELECT t3.IRN \"c1-1\", t3.NUM \"c1-2\", " + + "t1.IRN \"c1-3-1\", t1.NAME \"c1-3-2-1\", t1.SURNAME \"c1-3-2-2\", t1.AGE \"c1-3-3\", " + + "t2.IRN \"c1-3-5-1\", t2.NAME \"c1-3-5-2\", " + + "t4.IRN \"c1-3-5-3-1\", t4.NAME \"c1-3-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "JOIN PHONE t3 ON t1.IRN = t3.EMPL " + + "INNER JOIN COMPANY t4 ON t2.COMP = t4.IRN " + + "WHERE t2.NAME IS NOT NULL", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Phone.class, vParser.getReturnTypes().get(0)); + } + + @Test + void whenGroupByOrderBy_ThenCreatesJoins() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e.fullName.name, count(ph.id) FROM Employee e " + + "JOIN e.phones ph " + + "group by e.fullName.name " + + "order by e.fullName.name, ph", new HashMap<>()); + Assertions.assertEquals("SELECT t1.NAME \"c1\", count(t2.IRN) \"c2\" " + + "FROM EMPLOYEE t1 " + + "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + + "GROUP BY t1.NAME " + + "ORDER BY t1.NAME, t2.IRN", + vParser.getQuery()); + Assertions.assertEquals(2, vParser.getReturnTypes().size()); + Assertions.assertEquals(String.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(Object.class, vParser.getReturnTypes().get(1)); + } + + @Test + void whenGroupByHaving_ThenCreatesJoins() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e.fullName.name, count(ph.id),:p1 FROM Employee e " + + "JOIN e.phones ph " + + "group by e.fullName.name " + + "having count(ph.id) > 1", new HashMap<>()); + Assertions.assertEquals("SELECT t1.NAME \"c1\", count(t2.IRN) \"c2\", ? \"c3\" " + + "FROM EMPLOYEE t1 " + + "JOIN PHONE t2 ON t1.IRN = t2.EMPL " + + "GROUP BY t1.NAME " + + "HAVING count(t2.IRN) > 1", + vParser.getQuery()); + assertDoesNotThrow(() -> vParser.checkType(Object[].class)); + assertThrows(IllegalArgumentException.class, () -> vParser.checkType(Employee.class)); + } + + @Test + void whenWhereByEntity_ThenCreateWhereOnPK() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("SELECT e FROM Employee e " + + "where e.fullName=:FullName", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE (t1.NAME, t1.SURNAME) = (?, ?)", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(1, vParser.getQueryParameters().size()); + Assertions.assertEquals(FullName.class, vParser.getQueryParameters().get(0).getParameterType()); + + vParser = new JPQLParser("SELECT Employee FROM Employee " + + "where fullName=:FullName and age>:age", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE (t1.NAME, t1.SURNAME) = (?, ?) " + + "AND t1.AGE > ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(2, vParser.getQueryParameters().size()); + Assertions.assertEquals(FullName.class, vParser.getQueryParameters().get(0).getParameterType()); + + vParser = new JPQLParser("SELECT e FROM Employee e where e=?1", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t1.IRN = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(1, vParser.getQueryParameters().size()); + Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); + Assertions.assertTrue(vParser.isSelectUsingPrimaryKey()); + + vParser = new JPQLParser("SELECT e FROM Employee e where e.department.company=?1", new HashMap<>()); + Assertions.assertEquals("SELECT t1.IRN \"c1-1\", t1.NAME \"c1-2-1\", t1.SURNAME \"c1-2-2\", t1.AGE \"c1-3\", " + + "t2.IRN \"c1-5-1\", t2.NAME \"c1-5-2\", " + + "t3.IRN \"c1-5-3-1\", t3.NAME \"c1-5-3-2\" " + + "FROM EMPLOYEE t1 " + + "INNER JOIN DEPT t2 ON t1.DEPT = t2.IRN " + + "INNER JOIN COMPANY t3 ON t2.COMP = t3.IRN " + + "WHERE t3.IRN = ?", + vParser.getQuery()); + Assertions.assertEquals(1, vParser.getReturnTypes().size()); + Assertions.assertEquals(Employee.class, vParser.getReturnTypes().get(0)); + Assertions.assertEquals(1, vParser.getQueryParameters().size()); + Assertions.assertEquals(Object.class, vParser.getQueryParameters().get(0).getParameterType()); + Assertions.assertFalse(vParser.isSelectUsingPrimaryKey()); + } + + @Test + void deleteEntityUsingPK() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("DELETE Employee e where e.id=:id", new HashMap<>()); + Assertions.assertEquals("DELETE EMPLOYEE t1 WHERE t1.IRN = ?", + vParser.getQuery()); + + vParser = new JPQLParser("DELETE Employee where age>?", new HashMap<>()); + Assertions.assertEquals("DELETE EMPLOYEE t1 WHERE t1.AGE > ?", + vParser.getQuery()); + } + + @Test + void updateEntityUsingPK() throws JSQLParserException + { + JPQLParser vParser = new JPQLParser("UPDATE Employee e set e.salary=e.salary*1.05 " + + "where e.id=:id", new HashMap<>()); + Assertions.assertEquals("UPDATE EMPLOYEE t1 SET t1.SALARY = t1.SALARY * 1.05 WHERE t1.IRN = ?", + vParser.getQuery()); + + vParser = new JPQLParser("UPDATE Employee set salary=salary*1.05 " + + "where id=:id", new HashMap<>()); + Assertions.assertEquals("UPDATE EMPLOYEE t1 SET t1.SALARY = t1.SALARY * 1.05 WHERE t1.IRN = ?", + vParser.getQuery()); + + vParser = new JPQLParser("UPDATE Employee e set (e.salary,e.age)=(e.salary*1.05,e.age+1) " + + "where e.id=:id", new HashMap<>()); + Assertions.assertEquals("UPDATE EMPLOYEE t1 SET (t1.SALARY, t1.AGE) = (t1.SALARY * 1.05, t1.AGE + 1) WHERE t1.IRN = ?", + vParser.getQuery()); + + vParser = new JPQLParser("UPDATE Employee set (salary,age)=(salary*1.05,age+1) " + + "where id=:id", new HashMap<>()); + Assertions.assertEquals("UPDATE EMPLOYEE t1 SET (t1.SALARY, t1.AGE) = (t1.SALARY * 1.05, t1.AGE + 1) WHERE t1.IRN = ?", + vParser.getQuery()); + } + + @Test + void whenUsingNamedParameters_thenCheckIfNamesReused() + { + JPQLParser vParser = new JPQLParser("select Employee from Employee " + + "where id = :num " + + "and age= :num", + new HashMap<>()); + assertEquals(2, vParser.getNumberOfParameters()); + } +} diff --git a/jpalite-core/src/test/java/io/jpalite/test/Company.java b/jpalite-core/src/test/java/org/jpalite/test/Company.java similarity index 94% rename from jpalite-core/src/test/java/io/jpalite/test/Company.java rename to jpalite-core/src/test/java/org/jpalite/test/Company.java index 9f1e4a9..3c059db 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Company.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Company.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/Department.java b/jpalite-core/src/test/java/org/jpalite/test/Department.java similarity index 95% rename from jpalite-core/src/test/java/io/jpalite/test/Department.java rename to jpalite-core/src/test/java/org/jpalite/test/Department.java index c665c28..fd0e13c 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Department.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Department.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/Department1.java b/jpalite-core/src/test/java/org/jpalite/test/Department1.java similarity index 95% rename from jpalite-core/src/test/java/io/jpalite/test/Department1.java rename to jpalite-core/src/test/java/org/jpalite/test/Department1.java index 1711090..0c1ab27 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Department1.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Department1.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/Employee.java b/jpalite-core/src/test/java/org/jpalite/test/Employee.java similarity index 95% rename from jpalite-core/src/test/java/io/jpalite/test/Employee.java rename to jpalite-core/src/test/java/org/jpalite/test/Employee.java index 809e43a..14fa36d 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Employee.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Employee.java @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/Employee1.java b/jpalite-core/src/test/java/org/jpalite/test/Employee1.java similarity index 95% rename from jpalite-core/src/test/java/io/jpalite/test/Employee1.java rename to jpalite-core/src/test/java/org/jpalite/test/Employee1.java index 6ed0885..8ddad7f 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Employee1.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Employee1.java @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/FullName.java b/jpalite-core/src/test/java/org/jpalite/test/FullName.java similarity index 95% rename from jpalite-core/src/test/java/io/jpalite/test/FullName.java rename to jpalite-core/src/test/java/org/jpalite/test/FullName.java index 1ed4bec..03d628c 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/FullName.java +++ b/jpalite-core/src/test/java/org/jpalite/test/FullName.java @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.Getter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/Phone.java b/jpalite-core/src/test/java/org/jpalite/test/Phone.java similarity index 94% rename from jpalite-core/src/test/java/io/jpalite/test/Phone.java rename to jpalite-core/src/test/java/org/jpalite/test/Phone.java index a8c8c45..ffd1ba1 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/Phone.java +++ b/jpalite-core/src/test/java/org/jpalite/test/Phone.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/RatePlan.java b/jpalite-core/src/test/java/org/jpalite/test/RatePlan.java similarity index 96% rename from jpalite-core/src/test/java/io/jpalite/test/RatePlan.java rename to jpalite-core/src/test/java/org/jpalite/test/RatePlan.java index e9bf37d..a601913 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/RatePlan.java +++ b/jpalite-core/src/test/java/org/jpalite/test/RatePlan.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.impl.JPAEntityImpl; +import org.jpalite.impl.JPAEntityImpl; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; diff --git a/jpalite-core/src/test/java/io/jpalite/test/TestEntityMetaDataManager.java b/jpalite-core/src/test/java/org/jpalite/test/TestEntityMetaDataManager.java similarity index 86% rename from jpalite-core/src/test/java/io/jpalite/test/TestEntityMetaDataManager.java rename to jpalite-core/src/test/java/org/jpalite/test/TestEntityMetaDataManager.java index eb9916d..6658df1 100644 --- a/jpalite-core/src/test/java/io/jpalite/test/TestEntityMetaDataManager.java +++ b/jpalite-core/src/test/java/org/jpalite/test/TestEntityMetaDataManager.java @@ -1,7 +1,7 @@ -package io.jpalite.test; +package org.jpalite.test; -import io.jpalite.EntityMetaDataManager; -import io.jpalite.impl.EntityMetaDataImpl; +import org.jpalite.EntityMetaDataManager; +import org.jpalite.impl.EntityMetaDataImpl; public class TestEntityMetaDataManager { diff --git a/jpalite-core/src/test/resources/META-INF/services/io.jpalite.MultiTenantProvider b/jpalite-core/src/test/resources/META-INF/services/org.jpalite.MultiTenantProvider similarity index 100% rename from jpalite-core/src/test/resources/META-INF/services/io.jpalite.MultiTenantProvider rename to jpalite-core/src/test/resources/META-INF/services/org.jpalite.MultiTenantProvider diff --git a/jpalite-maven-plugin/pom.xml b/jpalite-maven-plugin/pom.xml index d73c0ee..5fba53a 100644 --- a/jpalite-maven-plugin/pom.xml +++ b/jpalite-maven-plugin/pom.xml @@ -21,7 +21,7 @@ io.jpalite jpalite-parent - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml diff --git a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteMojo.java b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteMojo.java similarity index 98% rename from jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteMojo.java rename to jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteMojo.java index e788bf3..764a937 100644 --- a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteMojo.java +++ b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteMojo.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; -import io.jpalite.impl.JPALiteToolingImpl; +import org.jpalite.impl.JPALiteToolingImpl; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; diff --git a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteTooling.java b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteTooling.java similarity index 98% rename from jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteTooling.java rename to jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteTooling.java index 540797b..9ed9d58 100644 --- a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteTooling.java +++ b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteTooling.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; import java.util.List; diff --git a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteToolingException.java b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteToolingException.java similarity index 98% rename from jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteToolingException.java rename to jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteToolingException.java index 42e8a96..8d04aa2 100644 --- a/jpalite-maven-plugin/src/main/java/io/jpalite/JPALiteToolingException.java +++ b/jpalite-maven-plugin/src/main/java/org/jpalite/JPALiteToolingException.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; public class JPALiteToolingException extends Exception { diff --git a/jpalite-maven-plugin/src/main/java/io/jpalite/impl/JPALiteToolingImpl.java b/jpalite-maven-plugin/src/main/java/org/jpalite/impl/JPALiteToolingImpl.java similarity index 98% rename from jpalite-maven-plugin/src/main/java/io/jpalite/impl/JPALiteToolingImpl.java rename to jpalite-maven-plugin/src/main/java/org/jpalite/impl/JPALiteToolingImpl.java index 11db6e9..9a30f86 100644 --- a/jpalite-maven-plugin/src/main/java/io/jpalite/impl/JPALiteToolingImpl.java +++ b/jpalite-maven-plugin/src/main/java/org/jpalite/impl/JPALiteToolingImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.impl; +package org.jpalite.impl; -import io.jpalite.JPALiteTooling; -import io.jpalite.JPALiteToolingException; +import org.jpalite.JPALiteTooling; +import org.jpalite.JPALiteToolingException; import jakarta.persistence.*; import javassist.*; import org.slf4j.Logger; @@ -115,7 +115,7 @@ private void applyTooling(CtClass entityClass) throws JPALiteToolingException { try { LOG.debug("Applying JPA Tooling to {}", entityClass.getName()); - Class jpaEntityClass = Thread.currentThread().getContextClassLoader().loadClass("io.jpalite.impl.JPAEntityImpl"); + Class jpaEntityClass = Thread.currentThread().getContextClassLoader().loadClass("org.jpalite.impl.JPAEntityImpl"); CtClass jpaEntityImpl = classPool.get(jpaEntityClass.getName()); entityClass.setSuperclass(jpaEntityImpl); diff --git a/jpalite-quarkus-extension/deployment/pom.xml b/jpalite-quarkus-extension/deployment/pom.xml index 27abd39..133bc0e 100644 --- a/jpalite-quarkus-extension/deployment/pom.xml +++ b/jpalite-quarkus-extension/deployment/pom.xml @@ -23,7 +23,7 @@ io.jpalite jpalite-quarkus-extension - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml jpalite-extension-deployment diff --git a/jpalite-quarkus-extension/deployment/src/main/java/io/jpalite/extension/deployment/JPALiteExtensionProcessor.java b/jpalite-quarkus-extension/deployment/src/main/java/io/jpalite/extension/deployment/JPALiteExtensionProcessor.java deleted file mode 100644 index babc5b6..0000000 --- a/jpalite-quarkus-extension/deployment/src/main/java/io/jpalite/extension/deployment/JPALiteExtensionProcessor.java +++ /dev/null @@ -1,170 +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.extension.deployment; - -import io.jpalite.*; -import io.jpalite.extension.JPALiteConfigMapping; -import io.jpalite.extension.JPALiteRecorder; -import io.quarkus.arc.deployment.SyntheticBeanBuildItem; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Default; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.transaction.TransactionManager; -import jakarta.transaction.TransactionSynchronizationRegistry; -import org.jboss.jandex.ClassType; -import org.jboss.jandex.DotName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; - -class JPALiteExtensionProcessor -{ - private static final DotName ENTITY_MANAGER_FACTORY = DotName.createSimple("jakarta.persistence.EntityManagerFactory"); - public static final DotName ENTITY_MANAGER = DotName.createSimple("jakarta.persistence.EntityManager"); - private static final Logger LOG = LoggerFactory.getLogger(JPALiteExtensionProcessor.class); - - private static final String DEFAULT_NAME = ""; - - private static final String FEATURE = "jpalite-extension"; - - @BuildStep - FeatureBuildItem feature() - { - return new FeatureBuildItem(FEATURE); - } - - @BuildStep - NativeImageResourceBuildItem nativeImageResourceBuildItem() - { - return new NativeImageResourceBuildItem("META-INF/services/io.jpalite.DataSourceProvider", - "META-INF/services/io.jpalite.PersistenceUnitProvider", - "META-INF/services/io.jpalite.FieldConvertType"); - } - - @BuildStep - ReflectiveClassBuildItem reflection() - { - return ReflectiveClassBuildItem.builder(DataSourceProvider.class, - PersistenceUnitProvider.class, - FieldConvertType.class, - EntityMetaDataManager.class) - .build(); - } - - @Record(RUNTIME_INIT) - @BuildStep - void generateJPABeans(JPALiteRecorder recorder, - JPALiteConfigMapping jpaConfigMapping, - BuildProducer syntheticBeanBuildItemBuildProducer) - { - if (jpaConfigMapping.defaultPersistenceUnit() != null) { - //Define the default EntityManagerFactory producer - syntheticBeanBuildItemBuildProducer - .produce(createSyntheticBean(DEFAULT_NAME, - false, - EntityManagerFactory.class, - List.of(ENTITY_MANAGER_FACTORY) - , true) - .createWith(recorder.entityManagerFactorySupplier(DEFAULT_NAME)) - .done()); - - //Define the default EntityManager producer - syntheticBeanBuildItemBuildProducer - .produce(createSyntheticBean(DEFAULT_NAME, - false, - EntityManager.class, - List.of(ENTITY_MANAGER) - , true) - .createWith(recorder.entityManagerSupplier(DEFAULT_NAME)) - .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionManager.class))) - .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSynchronizationRegistry.class))) - .done()); - }//if - - - if (jpaConfigMapping.namedPersistenceUnits() != null) { - for (String unitName : jpaConfigMapping.namedPersistenceUnits().keySet()) { - //Define the named EntityManagerFactory producer - syntheticBeanBuildItemBuildProducer - .produce(createSyntheticBean(unitName, - true, - EntityManagerFactory.class, - List.of(ENTITY_MANAGER_FACTORY) - , false) - .createWith(recorder.entityManagerFactorySupplier(unitName)) - .done()); - - //Define the named EntityManager producer - syntheticBeanBuildItemBuildProducer - .produce(createSyntheticBean(unitName, - true, - EntityManager.class, - List.of(ENTITY_MANAGER) - , false) - .createWith(recorder.entityManagerSupplier(unitName)) - .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionManager.class))) - .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSynchronizationRegistry.class))) - .done()); - - } - }//if - } - - private static SyntheticBeanBuildItem.ExtendedBeanConfigurator createSyntheticBean(String persistenceUnitName, - boolean isNamedPersistenceUnit, - Class type, - List allExposedTypes, - boolean defaultBean) - { - LOG.info("Creating Synthetic Bean for {}(\"{}\")", type.getSimpleName(), persistenceUnitName); - SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem - .configure(type) - .scope(ApplicationScoped.class) - .unremovable() - .setRuntimeInit(); - - for (DotName exposedType : allExposedTypes) { - configurator.addType(exposedType); - } - - if (defaultBean) { - configurator.defaultBean(); - } - - if (isNamedPersistenceUnit) { -// configurator.addQualifier(Default.class); - configurator.addQualifier().annotation(PersistenceUnit.class).addValue("value", persistenceUnitName).done(); - } - else { - configurator.addQualifier(Default.class); - configurator.addQualifier().annotation(PersistenceUnit.class).done(); - } - - return configurator; - } -} diff --git a/jpalite-quarkus-extension/deployment/src/main/java/org/jpalite/extension/deployment/JPALiteExtensionProcessor.java b/jpalite-quarkus-extension/deployment/src/main/java/org/jpalite/extension/deployment/JPALiteExtensionProcessor.java new file mode 100644 index 0000000..93990f6 --- /dev/null +++ b/jpalite-quarkus-extension/deployment/src/main/java/org/jpalite/extension/deployment/JPALiteExtensionProcessor.java @@ -0,0 +1,168 @@ +/* + * 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.extension.deployment; + +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Default; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.transaction.TransactionManager; +import jakarta.transaction.TransactionSynchronizationRegistry; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jpalite.*; +import org.jpalite.extension.JPALiteConfigMapping; +import org.jpalite.extension.JPALiteRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; + +class JPALiteExtensionProcessor +{ + private static final DotName ENTITY_MANAGER_FACTORY = DotName.createSimple("jakarta.persistence.EntityManagerFactory"); + public static final DotName ENTITY_MANAGER = DotName.createSimple("jakarta.persistence.EntityManager"); + private static final Logger LOG = LoggerFactory.getLogger(JPALiteExtensionProcessor.class); + + private static final String DEFAULT_NAME = ""; + + private static final String FEATURE = "jpalite-extension"; + + @BuildStep + FeatureBuildItem feature() + { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + NativeImageResourceBuildItem nativeImageResourceBuildItem() + { + return new NativeImageResourceBuildItem("META-INF/services/io.jpalite.DataSourceProvider", + "META-INF/services/io.jpalite.PersistenceUnitProvider", + "META-INF/services/io.jpalite.FieldConvertType"); + } + + @BuildStep + ReflectiveClassBuildItem reflection() + { + return ReflectiveClassBuildItem.builder(DataSourceProvider.class, + PersistenceUnitProvider.class, + FieldConvertType.class, + EntityMetaDataManager.class) + .build(); + } + + @Record(RUNTIME_INIT) + @BuildStep + void generateJPABeans(JPALiteRecorder recorder, + JPALiteConfigMapping jpaConfigMapping, + BuildProducer syntheticBeanBuildItemBuildProducer) + { + if (jpaConfigMapping.defaultPersistenceUnit() != null) { + //Define the default EntityManagerFactory producer + syntheticBeanBuildItemBuildProducer + .produce(createSyntheticBean(DEFAULT_NAME, + false, + EntityManagerFactory.class, + List.of(ENTITY_MANAGER_FACTORY) + , true) + .createWith(recorder.entityManagerFactorySupplier(DEFAULT_NAME)) + .done()); + + //Define the default EntityManager producer + syntheticBeanBuildItemBuildProducer + .produce(createSyntheticBean(DEFAULT_NAME, + false, + EntityManager.class, + List.of(ENTITY_MANAGER) + , true) + .createWith(recorder.entityManagerSupplier(DEFAULT_NAME)) + .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionManager.class))) + .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSynchronizationRegistry.class))) + .done()); + }//if + + + if (jpaConfigMapping.namedPersistenceUnits() != null) { + for (String unitName : jpaConfigMapping.namedPersistenceUnits().keySet()) { + //Define the named EntityManagerFactory producer + syntheticBeanBuildItemBuildProducer + .produce(createSyntheticBean(unitName, + true, + EntityManagerFactory.class, + List.of(ENTITY_MANAGER_FACTORY) + , false) + .createWith(recorder.entityManagerFactorySupplier(unitName)) + .done()); + + //Define the named EntityManager producer + syntheticBeanBuildItemBuildProducer + .produce(createSyntheticBean(unitName, + true, + EntityManager.class, + List.of(ENTITY_MANAGER) + , false) + .createWith(recorder.entityManagerSupplier(unitName)) + .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionManager.class))) + .addInjectionPoint(ClassType.create(DotName.createSimple(TransactionSynchronizationRegistry.class))) + .done()); + + } + }//if + } + + private static SyntheticBeanBuildItem.ExtendedBeanConfigurator createSyntheticBean(String persistenceUnitName, + boolean isNamedPersistenceUnit, + Class type, + List allExposedTypes, + boolean defaultBean) + { + LOG.info("Creating Synthetic Bean for {}(\"{}\")", type.getSimpleName(), persistenceUnitName); + SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem + .configure(type) + .scope(ApplicationScoped.class) + .unremovable() + .setRuntimeInit(); + + for (DotName exposedType : allExposedTypes) { + configurator.addType(exposedType); + } + + if (defaultBean) { + configurator.defaultBean(); + } + + if (isNamedPersistenceUnit) { + configurator.addQualifier().annotation(PersistenceUnit.class).addValue("value", persistenceUnitName).done(); + } else { + configurator.addQualifier(Default.class); + configurator.addQualifier().annotation(PersistenceUnit.class).done(); + } + + return configurator; + } +} diff --git a/jpalite-quarkus-extension/pom.xml b/jpalite-quarkus-extension/pom.xml index c129662..db81e86 100644 --- a/jpalite-quarkus-extension/pom.xml +++ b/jpalite-quarkus-extension/pom.xml @@ -23,7 +23,7 @@ io.jpalite jpalite-parent - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml diff --git a/jpalite-quarkus-extension/runtime/pom.xml b/jpalite-quarkus-extension/runtime/pom.xml index 991216c..35dd069 100644 --- a/jpalite-quarkus-extension/runtime/pom.xml +++ b/jpalite-quarkus-extension/runtime/pom.xml @@ -23,7 +23,7 @@ io.jpalite jpalite-quarkus-extension - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml jpalite-extension diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSources.java b/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSources.java deleted file mode 100644 index 40485f8..0000000 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSources.java +++ /dev/null @@ -1,130 +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.agroal; - -import io.agroal.api.AgroalDataSource; -import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; -import io.agroal.api.configuration.AgroalDataSourceConfiguration; -import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier; -import io.agroal.api.configuration.supplier.AgroalConnectionPoolConfigurationSupplier; -import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier; -import io.agroal.api.security.NamePrincipal; -import io.agroal.api.security.SimplePassword; -import io.agroal.pool.DataSource; -import io.jpalite.agroal.configuration.DataSourceConfigMapping; -import io.quarkus.arc.Unremovable; -import io.smallrye.config.SmallRyeConfig; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.persistence.PersistenceException; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.microprofile.config.Config; - -import java.sql.Statement; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Singleton -@Unremovable -@Slf4j -public class AgroalDataSources -{ - private final Map dataSources = new ConcurrentHashMap<>(); - - @Inject - Config configProvider; - - public AgroalDataSource getDataSource(String dataSourceName) - { - return dataSources.computeIfAbsent(dataSourceName, this::loadDataSource); - }//getDataSource - - private AgroalDataSource loadDataSource(String dataSourceName) - { - SmallRyeConfig config = configProvider.unwrap(SmallRyeConfig.class); - - DataSourceConfigMapping dataSourceConfigMapping = config.getConfigMapping(DataSourceConfigMapping.class); - - DataSourceConfigMapping.DataSourceConfig dataSourceConfig = dataSourceConfigMapping.getDataSourceConfig(dataSourceName); - if (dataSourceConfig == null) { - throw new PersistenceException("DataSource '" + dataSourceName + "' not found"); - }//if - - if (dataSourceConfig.jdbc().url().isEmpty()) { - throw new PersistenceException("The jdbc.url property is missing for DataSource '" + dataSourceName + "'"); - }//if - - LOG.info("Creating Agroal data source ({}) for url {}", dataSourceName, dataSourceConfig.jdbc().url().get()); - AgroalConnectionFactoryConfigurationSupplier connectionConfiguration = new AgroalConnectionFactoryConfigurationSupplier() - .jdbcUrl(dataSourceConfig.jdbc().url().get()) - .connectionProviderClassName(dataSourceConfig.jdbc().driver()) - .autoCommit(true) - .jdbcTransactionIsolation(dataSourceConfig.jdbc().transactionIsolationLevel()); - - - dataSourceConfig.jdbc().jdbcProperties().forEach((k, v) -> connectionConfiguration.jdbcProperty(k, v)); - if (dataSourceConfig.jdbc().currentSchema().isPresent()) { - connectionConfiguration.jdbcProperty("currentSchema", dataSourceConfig.jdbc().currentSchema().get()); - }//if - - if (dataSourceConfig.username().isPresent()) { - NamePrincipal username = new NamePrincipal(dataSourceConfig.username().get()); - connectionConfiguration.principal(username).recoveryPrincipal(username); - }//if - if (dataSourceConfig.password().isPresent()) { - SimplePassword password = new SimplePassword(dataSourceConfig.password().get()); - connectionConfiguration.credential(password).credential(password); - }//if - - AgroalConnectionPoolConfigurationSupplier poolConfiguration = new AgroalConnectionPoolConfigurationSupplier() - .minSize(dataSourceConfig.jdbc().minSize()) - .maxSize(dataSourceConfig.jdbc().maxSize()) - .initialSize(dataSourceConfig.jdbc().initialSize()) - .connectionValidator(selectValidator(dataSourceConfig.jdbc().validationQuery())) - .acquisitionTimeout(dataSourceConfig.jdbc().acquisitionTimeout()) - .leakTimeout(dataSourceConfig.jdbc().leakDetectionInterval()) - .validationTimeout(dataSourceConfig.jdbc().validationInterval()) - .reapTimeout(dataSourceConfig.jdbc().idleRemovalInterval()) - .maxLifetime(dataSourceConfig.jdbc().maxLifetime()) - .enhancedLeakReport(dataSourceConfig.jdbc().leakReport()) - .connectionFactoryConfiguration(connectionConfiguration); - - - AgroalDataSourceConfigurationSupplier agroalConfig = new AgroalDataSourceConfigurationSupplier() - .dataSourceImplementation(AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL) - .metricsEnabled(dataSourceConfig.jdbc().enableMetrics()) - .connectionPoolConfiguration(poolConfiguration); - - LOG.debug("Creating agroal pool {} with URI {}", dataSourceName, dataSourceConfig.jdbc().url()); - return new DataSource(agroalConfig.get()); - }//loadDataSource - - private AgroalConnectionPoolConfiguration.ConnectionValidator selectValidator(final String validationQuery) - { - return connection -> - { - try (Statement check = connection.createStatement()) { - check.execute(validationQuery); - return connection.isValid(0); - } - catch (Exception t) { - return false; - } - }; - }//SelectValidator -}//AgroalDataSources diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteRecorder.java b/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteRecorder.java deleted file mode 100644 index d25730f..0000000 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteRecorder.java +++ /dev/null @@ -1,70 +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.extension; - -import io.quarkus.arc.SyntheticCreationalContext; -import io.quarkus.runtime.annotations.Recorder; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.Persistence; -import jakarta.transaction.TransactionManager; -import jakarta.transaction.TransactionSynchronizationRegistry; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -@Slf4j -@Recorder -public class JPALiteRecorder -{ - private final Map entityManagerFactoryList = new ConcurrentHashMap<>(); - - @Inject - TransactionManager transactionManager; - - @Inject - TransactionSynchronizationRegistry transactionSynchronizationRegistry; - - public Function, EntityManagerFactory> entityManagerFactorySupplier(String persistenceUnitName) - { - return context -> entityManagerFactoryList.computeIfAbsent(persistenceUnitName, Persistence::createEntityManagerFactory); - } - - public EntityManager getEntityManager(String persistenceUnit) - { - EntityManagerFactory factory = entityManagerFactoryList.computeIfAbsent(persistenceUnit, Persistence::createEntityManagerFactory); - return new TransactionScopedEntityManagerImpl(factory, - transactionManager, - transactionSynchronizationRegistry); - }//getEntityManager - - public Function, EntityManager> entityManagerSupplier(String persistenceUnitName) - { - return context -> { - TransactionManager transactionManager = context.getInjectedReference(TransactionManager.class); - TransactionSynchronizationRegistry transactionSynchronizationRegistry = context.getInjectedReference(TransactionSynchronizationRegistry.class); - EntityManagerFactory factory = entityManagerFactoryList.computeIfAbsent(persistenceUnitName, Persistence::createEntityManagerFactory); - return new TransactionScopedEntityManagerImpl(factory, - transactionManager, - transactionSynchronizationRegistry); - }; - } -} diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanup.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanup.java similarity index 98% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanup.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanup.java index af3759a..6449fb3 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanup.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanup.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; import jakarta.interceptor.InterceptorBinding; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanupHandler.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanupHandler.java similarity index 95% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanupHandler.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanupHandler.java index 5cbffdf..5165d31 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/DatabaseCleanupHandler.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/DatabaseCleanupHandler.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; -import io.jpalite.impl.db.DatabasePoolFactory; +import org.jpalite.impl.db.DatabasePoolFactory; import jakarta.annotation.Priority; import jakarta.interceptor.AroundInvoke; import jakarta.interceptor.Interceptor; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/PersistenceUnit.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/PersistenceUnit.java similarity index 98% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/PersistenceUnit.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/PersistenceUnit.java index db5f4e6..d46f052 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/PersistenceUnit.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/PersistenceUnit.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite; +package org.jpalite; import jakarta.enterprise.util.AnnotationLiteral; import jakarta.inject.Qualifier; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSourceProvider.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSourceProvider.java similarity index 94% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSourceProvider.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSourceProvider.java index e81208f..ebb3177 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/AgroalDataSourceProvider.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSourceProvider.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.agroal; +package org.jpalite.agroal; -import io.jpalite.DataSourceProvider; +import org.jpalite.DataSourceProvider; import io.quarkus.arc.Arc; import javax.sql.DataSource; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSources.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSources.java new file mode 100644 index 0000000..f9a0237 --- /dev/null +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/AgroalDataSources.java @@ -0,0 +1,130 @@ +/* + * 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.agroal; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.agroal.api.configuration.AgroalDataSourceConfiguration; +import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier; +import io.agroal.api.configuration.supplier.AgroalConnectionPoolConfigurationSupplier; +import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier; +import io.agroal.api.security.NamePrincipal; +import io.agroal.api.security.SimplePassword; +import io.agroal.pool.DataSource; +import io.quarkus.arc.Unremovable; +import io.smallrye.config.SmallRyeConfig; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.persistence.PersistenceException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.Config; +import org.jpalite.agroal.configuration.DataSourceConfigMapping; + +import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Singleton +@Unremovable +@Slf4j +public class AgroalDataSources +{ + private final Map dataSources = new ConcurrentHashMap<>(); + + @Inject + Config configProvider; + + public AgroalDataSource getDataSource(String dataSourceName) + { + return dataSources.computeIfAbsent(dataSourceName, this::loadDataSource); + }//getDataSource + + private AgroalDataSource loadDataSource(String dataSourceName) + { + SmallRyeConfig config = configProvider.unwrap(SmallRyeConfig.class); + + DataSourceConfigMapping dataSourceConfigMapping = config.getConfigMapping(DataSourceConfigMapping.class); + + DataSourceConfigMapping.DataSourceConfig dataSourceConfig = dataSourceConfigMapping.getDataSourceConfig(dataSourceName); + if (dataSourceConfig == null) { + throw new PersistenceException("DataSource '" + dataSourceName + "' not found"); + }//if + + if (dataSourceConfig.jdbc().url().isEmpty()) { + throw new PersistenceException("The jdbc.url property is missing for DataSource '" + dataSourceName + "'"); + }//if + + LOG.info("Creating Agroal data source ({}) for url {}", dataSourceName, dataSourceConfig.jdbc().url().get()); + AgroalConnectionFactoryConfigurationSupplier connectionConfiguration = new AgroalConnectionFactoryConfigurationSupplier() + .jdbcUrl(dataSourceConfig.jdbc().url().get()) + .connectionProviderClassName(dataSourceConfig.jdbc().driver()) + .autoCommit(true) + .jdbcTransactionIsolation(dataSourceConfig.jdbc().transactionIsolationLevel()); + + + dataSourceConfig.jdbc().jdbcProperties().forEach(connectionConfiguration::jdbcProperty); + if (dataSourceConfig.jdbc().currentSchema().isPresent()) { + connectionConfiguration.jdbcProperty("currentSchema", dataSourceConfig.jdbc().currentSchema().get()); + }//if + + if (dataSourceConfig.username().isPresent()) { + NamePrincipal username = new NamePrincipal(dataSourceConfig.username().get()); + connectionConfiguration.principal(username).recoveryPrincipal(username); + }//if + if (dataSourceConfig.password().isPresent()) { + SimplePassword password = new SimplePassword(dataSourceConfig.password().get()); + connectionConfiguration.credential(password).credential(password); + }//if + + AgroalConnectionPoolConfigurationSupplier poolConfiguration = new AgroalConnectionPoolConfigurationSupplier() + .minSize(dataSourceConfig.jdbc().minSize()) + .maxSize(dataSourceConfig.jdbc().maxSize()) + .initialSize(dataSourceConfig.jdbc().initialSize()) + .connectionValidator(selectValidator(dataSourceConfig.jdbc().validationQuery())) + .acquisitionTimeout(dataSourceConfig.jdbc().acquisitionTimeout()) + .leakTimeout(dataSourceConfig.jdbc().leakDetectionInterval()) + .validationTimeout(dataSourceConfig.jdbc().validationInterval()) + .reapTimeout(dataSourceConfig.jdbc().idleRemovalInterval()) + .maxLifetime(dataSourceConfig.jdbc().maxLifetime()) + .enhancedLeakReport(dataSourceConfig.jdbc().leakReport()) + .connectionFactoryConfiguration(connectionConfiguration); + + + AgroalDataSourceConfigurationSupplier agroalConfig = new AgroalDataSourceConfigurationSupplier() + .dataSourceImplementation(AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL) + .metricsEnabled(dataSourceConfig.jdbc().enableMetrics()) + .connectionPoolConfiguration(poolConfiguration); + + LOG.debug("Creating agroal pool {} with URI {}", dataSourceName, dataSourceConfig.jdbc().url()); + return new DataSource(agroalConfig.get()); + }//loadDataSource + + private AgroalConnectionPoolConfiguration.ConnectionValidator selectValidator(final String validationQuery) + { + return connection -> + { + try (Statement check = connection.createStatement()) { + check.execute(validationQuery); + return connection.isValid(0); + } + catch (Exception t) { + return false; + } + }; + }//SelectValidator +}//AgroalDataSources diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DataSourceConfigMapping.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DataSourceConfigMapping.java similarity index 99% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DataSourceConfigMapping.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DataSourceConfigMapping.java index a08e989..9038fe8 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DataSourceConfigMapping.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DataSourceConfigMapping.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.agroal.configuration; +package org.jpalite.agroal.configuration; import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; import io.quarkus.runtime.annotations.ConfigDocMapKey; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DurationConverter.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DurationConverter.java similarity index 96% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DurationConverter.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DurationConverter.java index 9924029..23abf27 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/DurationConverter.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/DurationConverter.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.agroal.configuration; +package org.jpalite.agroal.configuration; import org.eclipse.microprofile.config.spi.Converter; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/TransactionIsolationConverter.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/TransactionIsolationConverter.java similarity index 96% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/TransactionIsolationConverter.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/TransactionIsolationConverter.java index c12f72c..9ff4d2f 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/agroal/configuration/TransactionIsolationConverter.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/agroal/configuration/TransactionIsolationConverter.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.agroal.configuration; +package org.jpalite.agroal.configuration; import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; import org.eclipse.microprofile.config.spi.Converter; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteConfigMapping.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteConfigMapping.java similarity index 98% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteConfigMapping.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteConfigMapping.java index 878a346..49d61ec 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteConfigMapping.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteConfigMapping.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.extension; +package org.jpalite.extension; -import io.jpalite.JPALiteEntityManager; -import io.jpalite.impl.CacheFormat; +import org.jpalite.JPALiteEntityManager; +import org.jpalite.impl.CacheFormat; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigPhase; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteRecorder.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteRecorder.java new file mode 100644 index 0000000..1396207 --- /dev/null +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/JPALiteRecorder.java @@ -0,0 +1,70 @@ +/* + * 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.extension; + +import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.runtime.annotations.Recorder; +import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Persistence; +import jakarta.transaction.TransactionManager; +import jakarta.transaction.TransactionSynchronizationRegistry; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@Slf4j +@Recorder +public class JPALiteRecorder +{ + private final Map entityManagerFactoryList = new ConcurrentHashMap<>(); + + @Inject + TransactionManager transactionManager; + + @Inject + TransactionSynchronizationRegistry transactionSynchronizationRegistry; + + public Function, EntityManagerFactory> entityManagerFactorySupplier(String persistenceUnitName) + { + return context -> entityManagerFactoryList.computeIfAbsent(persistenceUnitName, Persistence::createEntityManagerFactory); + } + + public EntityManager getEntityManager(String persistenceUnit) + { + EntityManagerFactory factory = entityManagerFactoryList.computeIfAbsent(persistenceUnit, Persistence::createEntityManagerFactory); + return new TransactionScopedEntityManagerImpl(factory, + transactionManager, + transactionSynchronizationRegistry); + }//getEntityManager + + public Function, EntityManager> entityManagerSupplier(String persistenceUnitName) + { + return context -> { + TransactionManager txnManager = context.getInjectedReference(TransactionManager.class); + TransactionSynchronizationRegistry txnSyncReg = context.getInjectedReference(TransactionSynchronizationRegistry.class); + EntityManagerFactory factory = entityManagerFactoryList.computeIfAbsent(persistenceUnitName, Persistence::createEntityManagerFactory); + return new TransactionScopedEntityManagerImpl(factory, + txnManager, + txnSyncReg); + }; + } +} diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PersistenceUnitProperties.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PersistenceUnitProperties.java similarity index 96% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PersistenceUnitProperties.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PersistenceUnitProperties.java index 6fb0df4..833a8d5 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PersistenceUnitProperties.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PersistenceUnitProperties.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package io.jpalite.extension; +package org.jpalite.extension; -import io.jpalite.impl.CustomPersistenceUnit; +import org.jpalite.impl.CustomPersistenceUnit; import io.smallrye.config.SmallRyeConfig; import jakarta.persistence.PersistenceException; import org.eclipse.microprofile.config.Config; diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PropertyPersistenceUnitProvider.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PropertyPersistenceUnitProvider.java similarity index 89% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PropertyPersistenceUnitProvider.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PropertyPersistenceUnitProvider.java index b6b1cb0..307bdd9 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/PropertyPersistenceUnitProvider.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/PropertyPersistenceUnitProvider.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.extension; +package org.jpalite.extension; -import io.jpalite.JPALitePersistenceUnit; -import io.jpalite.PersistenceUnitProvider; +import org.jpalite.JPALitePersistenceUnit; +import org.jpalite.PersistenceUnitProvider; public class PropertyPersistenceUnitProvider implements PersistenceUnitProvider { diff --git a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/TransactionScopedEntityManagerImpl.java b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/TransactionScopedEntityManagerImpl.java similarity index 99% rename from jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/TransactionScopedEntityManagerImpl.java rename to jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/TransactionScopedEntityManagerImpl.java index 71f0bfb..543781e 100644 --- a/jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/TransactionScopedEntityManagerImpl.java +++ b/jpalite-quarkus-extension/runtime/src/main/java/org/jpalite/extension/TransactionScopedEntityManagerImpl.java @@ -15,10 +15,10 @@ * limitations under the License. */ -package io.jpalite.extension; +package org.jpalite.extension; -import io.jpalite.JPALiteEntityManager; -import io.jpalite.PersistenceContext; +import org.jpalite.JPALiteEntityManager; +import org.jpalite.PersistenceContext; import io.quarkus.arc.Arc; import io.quarkus.runtime.BlockingOperationControl; import io.quarkus.runtime.BlockingOperationNotAllowedException; diff --git a/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.DataSourceProvider b/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.DataSourceProvider similarity index 94% rename from jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.DataSourceProvider rename to jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.DataSourceProvider index 6dc438d..ddbf1f7 100644 --- a/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.DataSourceProvider +++ b/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.DataSourceProvider @@ -15,4 +15,4 @@ # limitations under the License. # -io.jpalite.agroal.AgroalDataSourceProvider +org.jpalite.agroal.AgroalDataSourceProvider diff --git a/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.PersistenceUnitProvider b/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.PersistenceUnitProvider similarity index 93% rename from jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.PersistenceUnitProvider rename to jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.PersistenceUnitProvider index 86fec63..15aaa2a 100644 --- a/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/io.jpalite.PersistenceUnitProvider +++ b/jpalite-quarkus-extension/runtime/src/main/resources/META-INF/services/org.jpalite.PersistenceUnitProvider @@ -15,4 +15,4 @@ # limitations under the License. # -io.jpalite.extension.PropertyPersistenceUnitProvider +org.jpalite.extension.PropertyPersistenceUnitProvider diff --git a/jpalite-repository/pom.xml b/jpalite-repository/pom.xml index cba57b7..04c11c5 100644 --- a/jpalite-repository/pom.xml +++ b/jpalite-repository/pom.xml @@ -23,7 +23,7 @@ io.jpalite jpalite-parent - 3.0.0 + 3.0.0-SNAPSHOT ../pom.xml diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/FilterParser.java b/jpalite-repository/src/main/java/io/jpalite/repository/FilterParser.java deleted file mode 100644 index d280fa0..0000000 --- a/jpalite-repository/src/main/java/io/jpalite/repository/FilterParser.java +++ /dev/null @@ -1,295 +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.repository; - -import jakarta.persistence.PersistenceException; -import net.sf.jsqlparser.JSQLParserException; -import net.sf.jsqlparser.expression.*; -import net.sf.jsqlparser.expression.operators.arithmetic.Addition; -import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction; -import net.sf.jsqlparser.expression.operators.conditional.AndExpression; -import net.sf.jsqlparser.expression.operators.conditional.OrExpression; -import net.sf.jsqlparser.expression.operators.relational.*; -import net.sf.jsqlparser.parser.CCJSqlParserUtil; -import net.sf.jsqlparser.schema.Column; -import net.sf.jsqlparser.statement.Statement; -import net.sf.jsqlparser.statement.select.PlainSelect; - -import java.util.ArrayList; -import java.util.List; - -class FilterParser extends ParserBase -{ - private Filter filter = Filter.noFilter(); - private final Object[] parameters; - private int paramNr = 0; - private Filter currentFilter; - private String col = null; - private String operationType = null; - private final List params = new ArrayList<>(); - - public FilterParser(String whereExpression, Object... params) - { - parameters = params; - currentFilter = filter; - try { - Statement statement = CCJSqlParserUtil.parse("select * from t where " + whereExpression); - statement.accept(this); - }//try - catch (JSQLParserException ex) { - throw new PersistenceException("Error parsing query", ex); - }//catch - } - - public Filter getFilter() - { - return filter; - } - - @Override - public void visit(PlainSelect plainSelect) - { - if (plainSelect.getWhere() != null) { - plainSelect.getWhere().accept(this); - } - } - - @Override - public void visit(Addition addition) - { - addition.getLeftExpression().accept(this); - operationType = addition.getStringExpression(); - addition.getRightExpression().accept(this); - } - - @Override - public void visit(Subtraction subtraction) - { - subtraction.getLeftExpression().accept(this); - operationType = subtraction.getStringExpression(); - subtraction.getRightExpression().accept(this); - } - - @Override - public void visit(JdbcParameter jdbcParameter) - { - params.add(parameters[paramNr++]); - } - - @Override - public void visit(JdbcNamedParameter jdbcNamedParameter) - { - params.add(parameters[paramNr++]); - } - - - @Override - public void visit(DoubleValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(LongValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(HexValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(DateValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(TimeValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(TimestampValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(StringValue pValue) - { - params.add(pValue.getValue()); - } - - @Override - public void visit(Column tableColumn) - { - col = tableColumn.getFullyQualifiedName(); - } - - @Override - public void visit(Parenthesis parenthesis) - { - parenthesis.getExpression().accept(this); - } - - @Override - public void visit(AndExpression andExpression) - { - andExpression.getLeftExpression().accept(this); - andExpression.getRightExpression().accept(this); - } - - @Override - public void visit(OrExpression orExpression) - { - boolean vRoot = (currentFilter.isUnfiltered() && currentFilter == filter); - - orExpression.getLeftExpression().accept(this); - Filter filter = currentFilter; - currentFilter = Filter.noFilter(); - orExpression.getRightExpression().accept(this); - - currentFilter = Filter.of(filter).orWhere(currentFilter); - if (vRoot) { - this.filter = currentFilter; - }//if - } - - private void addFilter(Operators pOperators) - { - Filter filter = Filter.of(col, pOperators, params.toArray()); - - if (currentFilter.isUnfiltered() && currentFilter == this.filter) { - currentFilter = filter; - this.filter = filter; - }//if - else { - if (currentFilter.isUnfiltered()) { - currentFilter = filter; - }//if - else { - currentFilter.andWhere(filter); - }//else - }//else - - col = null; - params.clear(); - } - - @Override - public void visit(Between between) - { - between.getLeftExpression().accept(this); - between.getBetweenExpressionStart().accept(this); - between.getBetweenExpressionEnd().accept(this); - addFilter(Operators.BETWEEN); - } - - @Override - public void visit(EqualsTo equalsTo) - { - equalsTo.getLeftExpression().accept(this); - equalsTo.getRightExpression().accept(this); - addFilter(Operators.EQUALS); - } - - @Override - public void visit(GreaterThan greaterThan) - { - greaterThan.getLeftExpression().accept(this); - greaterThan.getRightExpression().accept(this); - addFilter(Operators.BIGGER_THAN); - } - - @Override - public void visit(GreaterThanEquals greaterThanEquals) - { - greaterThanEquals.getLeftExpression().accept(this); - greaterThanEquals.getRightExpression().accept(this); - addFilter(Operators.BIGGER_OR_EQUAL); - } - - @Override - public void visit(InExpression inExpression) - { - if (inExpression.getLeftExpression() != null) { - inExpression.getLeftExpression().accept(this); - }//if - - if (inExpression.getRightItemsList() != null) { - inExpression.getRightItemsList().accept(this); - }//if - - addFilter(inExpression.isNot() ? Operators.NOTIN : Operators.IN); - } - - - @Override - public void visit(IsNullExpression isNullExpression) - { - isNullExpression.getLeftExpression().accept(this); - addFilter(isNullExpression.isNot() ? Operators.ISNOTNULL : Operators.ISNULL); - } - - @Override - public void visit(LikeExpression likeExpression) - { - likeExpression.getLeftExpression().accept(this); - likeExpression.getRightExpression().accept(this); - addFilter(likeExpression.isNot() ? Operators.CONTAINS_NOT : Operators.CONTAINS); - } - - @Override - public void visit(MinorThan minorThan) - { - minorThan.getLeftExpression().accept(this); - minorThan.getRightExpression().accept(this); - addFilter(Operators.SMALLER_THAN); - } - - @Override - public void visit(MinorThanEquals minorThanEquals) - { - minorThanEquals.getLeftExpression().accept(this); - minorThanEquals.getRightExpression().accept(this); - addFilter(Operators.SMALLER_OR_EQUAL); - } - - @Override - public void visit(NotEqualsTo notEqualsTo) - { - notEqualsTo.getLeftExpression().accept(this); - notEqualsTo.getRightExpression().accept(this); - addFilter(Operators.NOTEQUALS); - } - - @Override - public void visit(IntervalExpression intervalExpression) - { - if (operationType != null) { - params.add(intervalExpression.getParameter()); - addFilter(operationType.equals("+") ? Operators.PLUS_INTERVAL : Operators.MINUS_INTERVAL); - operationType = null; - }//if - } -} diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/JPALiteRepositoryProcessor.java b/jpalite-repository/src/main/java/io/jpalite/repository/JPALiteRepositoryProcessor.java deleted file mode 100644 index c6acda4..0000000 --- a/jpalite-repository/src/main/java/io/jpalite/repository/JPALiteRepositoryProcessor.java +++ /dev/null @@ -1,473 +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.repository; - -import com.google.auto.service.AutoService; -import jakarta.persistence.LockModeType; -import jakarta.persistence.QueryHint; - -import javax.annotation.processing.*; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.tools.Diagnostic; -import javax.tools.FileObject; -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -@SupportedAnnotationTypes("io.jpalite.repository.Repository") -@SupportedSourceVersion(SourceVersion.RELEASE_21) -@AutoService(Processor.class) -@SuppressWarnings("java:S1192") //We are generating code here. Defining statics will impair the readability -public class JPALiteRepositoryProcessor extends AbstractProcessor -{ - private static final String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; - - //---------------------------------------------------------------[ note ]--- - private void note(String msg) - { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg); - }//note - - //------------------------------------------------------------[ process ]--- - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) - { - - if (roundEnv.processingOver()) { - return false; - }//if - - note("Creating JPALite Repositories"); - try { - Set set = roundEnv.getElementsAnnotatedWith(Repository.class); - - for (Element element : set) { - Repository annotation = element.getAnnotation(Repository.class); - if (annotation != null) { - generateRepo((TypeElement) element, annotation); - }//if - }//for - return true; - }//try - catch (Exception ex) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getMessage()); - }//catch - - return false; - }//process - - private void addJpaRepository(PrintWriter out, DeclaredType jpaRepository) - { - - String argType = jpaRepository.getTypeArguments().get(0).toString(); - String idType = jpaRepository.getTypeArguments().get(1).toString(); - - out.println("@Transactional"); - out.println("public void persist(" + argType + " entity) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" if (!em.contains(entity)) {"); - out.println(" em.persist(entity);"); - out.println(" }"); - out.println("}"); - out.println(""); - - out.println("@Transactional"); - out.println("public void save(" + argType + " entity) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" if (!em.contains(entity)) {"); - out.println(" if (em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity) != null) {"); - out.println(" em.merge(entity);"); - out.println(" } else {"); - out.println(" em.persist(entity);"); - out.println(" }"); - out.println(" }"); - out.println("}"); - out.println(""); - - out.println("@Transactional"); - out.println("public " + argType + " merge(" + argType + " entity) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" if (!em.contains(entity)) {"); - out.println(" return (" + argType + ") em.merge(entity);"); - out.println(" }"); - out.println(" return (" + argType + ")entity;"); - out.println("}"); - out.println(""); - - out.println("public void refresh(" + argType + " entity) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" em.refresh(entity);"); - out.println("}"); - out.println(""); - - out.println("public void lock(" + argType + " entity, LockModeType mode) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" em.lock(entity, mode);"); - out.println("}"); - out.println(""); - - out.println("public " + argType + " findById(" + idType + " id) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" return em.find(" + argType + ".class,id);"); - out.println("}"); - out.println(""); - - out.println("public " + argType + " findById(" + idType + " id, LockModeType mode) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" return em.find(" + argType + ".class,id, mode);"); - out.println("}"); - out.println(""); - - out.println("public " + argType + " getReference(" + idType + " id) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" return em.getReference(" + argType + ".class,id);"); - out.println("}"); - out.println(""); - - out.println("public void delete(" + argType + " entity) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" em.remove(entity);"); - out.println("}"); - out.println(""); - }//addJpaRepository - - private void addPagingRepository(PrintWriter out, DeclaredType pagingRepository) - { - String vArgType = pagingRepository.getTypeArguments().getFirst().toString(); - String vEntityType = vArgType.substring(vArgType.lastIndexOf(".") + 1); - - out.println("public long count(Filter filter, Map hints) {"); - out.println(" EntityManager em = getEntityManager();"); - out.print(" String q=\"select count("); - out.print(vEntityType); - out.print(") from "); - out.print(vEntityType); - out.println("\";"); - out.println(" Map parameters = new HashMap<>();"); - out.println(" if (!filter.isUnfiltered()) q = q+\" where \"+filter.getExpression(parameters); "); - out.println(" TypedQuery query = em.createQuery(q,Long.class);"); - out.println(" if (hints != null && !hints.isEmpty()) {"); - out.println(" hints.entrySet().forEach(entry -> query.setHint(entry.getKey(), entry.getValue()));"); - out.println(" }"); - out.println(" if (!parameters.isEmpty()) parameters.entrySet().stream().forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));"); - out.println(" return query.getSingleResult();"); - out.println("}"); - out.println(""); - - out.print("public List<"); - out.print(vArgType); - out.println("> findAll(Pageable pageable, Filter filter, Map hints) {"); - out.println(" EntityManager em = getEntityManager();"); - out.println(" if (hints != null && !hints.isEmpty()) {"); - out.println(" hints.entrySet().stream().forEach(entry -> em.setProperty(entry.getKey(), entry.getValue()));"); - out.println(" }"); - - out.print(" String q=\"select "); - out.print(vEntityType); - out.print(" from "); - out.print(vEntityType); - out.println("\";"); - out.println(" Map parameters = new HashMap<>();"); - out.println(" if (!filter.isUnfiltered()) q = q+\" where \"+filter.getExpression(parameters); "); - out.println(" if (!pageable.getSort().isUnsorted()) q = q+\" order by \"+pageable.getSort().getExpression();"); - out.print(" TypedQuery<"); - out.print(vArgType); - out.print("> query = em.createQuery(q,"); - out.print(vArgType); - out.println(".class);"); - out.println(" if (hints != null && !hints.isEmpty()) {"); - out.println(" hints.entrySet().forEach(entry -> query.setHint(entry.getKey(), entry.getValue()));"); - out.println(" }"); - out.println(" if (!parameters.isEmpty()) parameters.entrySet().stream().forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));"); - out.println(" if (!pageable.isUnpaged()) query.setFirstResult(pageable.getPageIndex())"); - out.println(" .setMaxResults(pageable.getPageSize());"); - out.println(" return query.getResultList();"); - out.println("}"); - out.println(""); - }//addPagingRepository - - @SuppressWarnings({"java:S3776", "java:S6541"}) //Complexity cannot be reduced further - private void createMethod(PrintWriter out, ExecutableElement method) - { - Query[] queries = method.getAnnotationsByType(Query.class); - if (queries.length > 0) { - method.getAnnotationMirrors() - .forEach(p -> - { - if (!p.getAnnotationType().toString().equals(Query.class.getName())) { - out.println(p); - }//if - }); - - out.print("public final "); - - //The return type can either be a List or an object - - out.print(method.getReturnType()); - out.print(" "); - out.print(method.getSimpleName().toString()); - out.print("("); - - out.print(method.getParameters() - .stream() - .map(p -> p.asType() + " " + p.getSimpleName()) - .collect(Collectors.joining(","))); - out.println(") {"); - out.println(" EntityManager em = getEntityManager();"); - boolean returnCollection = false; - String returnType = method.getReturnType().toString(); - if (method.getReturnType() instanceof DeclaredType declaredType && !declaredType.getTypeArguments().isEmpty()) { - returnCollection = true; - returnType = declaredType.getTypeArguments().getFirst().toString(); - }//if - - StringBuilder pageAndSort = new StringBuilder(); - Query query = queries[0]; - if (query.namedQuery()) { - out.print("TypedQuery<"); - out.print(returnType); - out.print("> query = em.createNamedQuery(\""); - out.print(query.value()); - out.print("\","); - out.print(returnType); - out.println(".class);"); - - method.getParameters() - .stream() - .filter(p -> p.asType().toString().equals(Pageable.class.getName())) - .forEach(p -> pageAndSort.append("query.setFirstResult(") - .append(p.getSimpleName()) - .append(".getPageIndex())\n") - .append(".setMaxResults(") - .append(p.getSimpleName()) - .append(".getPageSize());\n")); - }//if - else { - out.println("String queryStr = \"" + query.value() + "\";"); - - //Pageable and Sorting is only support for JPQL queries - if (!query.nativeQuery() && !query.updateQuery()) { - //Check for Pageable parameter - method.getParameters() - .stream() - .filter(p -> p.asType().toString().equals(Pageable.class.getName())) - .forEach(p -> - { - pageAndSort.append("query.setFirstResult(") - .append(p.getSimpleName()) - .append(".getPageIndex())\n") - .append(".setMaxResults(") - .append(p.getSimpleName()) - .append(".getPageSize());\n"); - out.println("queryStr += " + p.getSimpleName() + ".getSort().getOrderBy();"); - }); - method.getParameters() - .stream() - .filter(p -> p.asType().toString().equals(Sort.class.getName())) - .forEach(p -> out.println("queryStr += " + p.getSimpleName() + ".getOrderBy();")); - }//if - - if (query.nativeQuery()) { - out.println("jakarta.persistence.Query query = em.createNativeQuery(queryStr);"); - }//if - else if (query.updateQuery()) { - out.println("jakarta.persistence.Query query = em.createQuery(queryStr);"); - }//if - else { - out.print("TypedQuery<"); - out.print(returnType); - out.print("> query = em.createQuery(queryStr,"); - - out.print(returnType); - out.println(".class);"); - }//else - }//else - - if (!query.updateQuery() && query.lockMode() != LockModeType.NONE) { - out.print(" query.setLockMode("); - out.print("LockModeType." + query.lockMode().name()); - out.println(");"); - }//if - - if (method.getParameters() - .stream() - .anyMatch(p -> p.getAnnotation(QueryParam.class) != null)) { - - method.getParameters() - .stream() - .filter(p -> p.getAnnotation(QueryParam.class) != null) - .map(p -> "\"" + p.getAnnotation(QueryParam.class).value() + "\"," + p.getSimpleName()) - .forEach(p -> out.println("query.setParameter(" + p + ");")); - }//if - else { - AtomicInteger paramNr = new AtomicInteger(0); - method.getParameters() - .stream() - .filter(p -> !p.asType().toString().equals(Pageable.class.getName())) - .map(p -> paramNr.incrementAndGet() + "," + p.getSimpleName()) - .forEach(p -> out.println("query.setParameter(" + p + ");")); - }//else - - for (QueryHint hint : query.hints()) { - out.print("query.setHint(\""); - out.print(hint.name()); - out.print("\",\""); - out.print(hint.value()); - out.println("\");"); - }//for - - if (query.updateQuery()) { - out.println("return query.executeUpdate();"); - }//if - else { - out.println(pageAndSort); - - if (!returnCollection && query.catchNoResult()) { - out.println("try {"); - }//if - out.print(" return "); - if (query.nativeQuery()) { - out.print("("); - out.print(returnType); - out.print(") "); - }//if - out.print(" query."); - if (returnCollection) { - out.println("getResultList();"); - }//if - else { - out.println("getSingleResult();"); - }//else - - if (!returnCollection && query.catchNoResult()) { - out.println("}"); - out.println("catch (NoResultException ex) {"); - out.println(" return null;"); - out.println("}"); - }//if - }//else - - out.println("}"); - }//if - }//createMethod - - private void generateRepo(TypeElement repoElement, Repository annotation) throws IOException - { - - String packageName = annotation.name(); - if (packageName.isBlank()) { - packageName = repoElement.getQualifiedName() + "Impl"; - }//if - - int lastDot = packageName.lastIndexOf('.'); - if (lastDot == 0) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Repository.name is invalid, expected qualified name"); - return; - }//if - - FileObject repoFile = processingEnv.getFiler().createSourceFile(packageName); - String repoClassName = packageName.substring(lastDot + 1); - packageName = packageName.substring(0, lastDot); - - try (PrintWriter out = new PrintWriter(repoFile.openWriter())) { - out.print("package "); - out.print(packageName); - out.println(";"); - out.println(); - out.println("import io.quarkus.arc.Arc;"); - out.println("import jakarta.enterprise.context.RequestScoped;"); - out.println("import jakarta.inject.Inject;"); - out.println("import jakarta.transaction.Transactional;"); - out.println("import jakarta.annotation.Generated;"); - out.println("import jakarta.persistence.*;"); - out.println("import java.util.*;"); - out.println("import io.jpalite.PersistenceUnit;"); - out.println("import io.jpalite.repository.*;"); -// out.println("import io.jpalite.EntityState;"); - out.println(); - - SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); - out.println("@Generated(value={\"Generated by " + getClass().getName() + "\"}," + - "date=\"" + dateFormat.format(new Date()) + "\"," + - "comments=\"JPALite Repository Generation\")"); - out.println("@RequestScoped"); - out.print("public class "); - out.print(repoClassName); - if (!annotation.abstractClass().isBlank()) { - out.print(" extends "); - out.print(annotation.abstractClass()); - out.print(" "); - }//if - out.print(" implements "); - out.print(repoElement.getQualifiedName()); - - out.println(" {"); - - if (!annotation.persistenceUnit().startsWith("${")) { - out.println(" @Inject"); - out.print(" @PersistenceUnit(\""); - out.print(annotation.persistenceUnit()); - out.println("\")"); - out.println(" EntityManager em;"); - }//if - - out.println("public EntityManager getEntityManager() {"); - if (annotation.persistenceUnit().startsWith("${")) { - out.println(" io.jpalite.extension.JPALiteRecorder producer = Arc.container().instance(io.jpalite.extension.JPALiteRecorder.class).get();"); - out.print(" return producer.getEntityManager(JpaRepositoryUtil.getPersistenceUnitName(\""); - out.print(annotation.persistenceUnit()); - out.println("\"));"); - }//if - else { - out.println(" return em;"); - }//else - out.println("}"); - - repoElement.getInterfaces() - .forEach(t -> - { - switch (((DeclaredType) t).asElement().toString()) { - case "io.jpalite.repository.JpaRepository" -> - addJpaRepository(out, (DeclaredType) t); - case "io.jpalite.repository.PagingRepository" -> - addPagingRepository(out, (DeclaredType) t); - default -> { - //Ignore the rest - } - } - }); - - for (Element vMethod : repoElement.getEnclosedElements()) { - createMethod(out, (ExecutableElement) vMethod); - }//for - - out.println("}"); - out.flush(); - }//try - catch (Exception ex) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Process Error:" + ex.getMessage()); - }//catch - }//generateRepo -}//JPALiteRepositoryProcessor diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Direction.java b/jpalite-repository/src/main/java/org/jpalite/repository/Direction.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/Direction.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Direction.java index 8e557ab..1c0a0b1 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Direction.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Direction.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; public enum Direction { diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Filter.java b/jpalite-repository/src/main/java/org/jpalite/repository/Filter.java similarity index 99% rename from jpalite-repository/src/main/java/io/jpalite/repository/Filter.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Filter.java index 65ffbad..9925b24 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Filter.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Filter.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import java.util.*; import java.util.stream.Stream; diff --git a/jpalite-repository/src/main/java/org/jpalite/repository/FilterParser.java b/jpalite-repository/src/main/java/org/jpalite/repository/FilterParser.java new file mode 100644 index 0000000..f746990 --- /dev/null +++ b/jpalite-repository/src/main/java/org/jpalite/repository/FilterParser.java @@ -0,0 +1,295 @@ +/* + * 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.repository; + +import jakarta.persistence.PersistenceException; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.operators.arithmetic.Addition; +import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.*; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.PlainSelect; + +import java.util.ArrayList; +import java.util.List; + +class FilterParser extends ParserBase +{ + private Filter filter = Filter.noFilter(); + private final Object[] parameters; + private int paramNr = 0; + private Filter currentFilter; + private String col = null; + private String operationType = null; + private final List params = new ArrayList<>(); + + public FilterParser(String whereExpression, Object... params) + { + parameters = params; + currentFilter = filter; + try { + Statement statement = CCJSqlParserUtil.parse("select * from t where " + whereExpression); + statement.accept(this); + }//try + catch (JSQLParserException ex) { + throw new PersistenceException("Error parsing query", ex); + }//catch + } + + public Filter getFilter() + { + return filter; + } + + @Override + public void visit(PlainSelect plainSelect) + { + if (plainSelect.getWhere() != null) { + plainSelect.getWhere().accept(this); + } + } + + @Override + public void visit(Addition addition) + { + addition.getLeftExpression().accept(this); + operationType = addition.getStringExpression(); + addition.getRightExpression().accept(this); + } + + @Override + public void visit(Subtraction subtraction) + { + subtraction.getLeftExpression().accept(this); + operationType = subtraction.getStringExpression(); + subtraction.getRightExpression().accept(this); + } + + @Override + public void visit(JdbcParameter jdbcParameter) + { + params.add(parameters[paramNr++]); + } + + @Override + public void visit(JdbcNamedParameter jdbcNamedParameter) + { + params.add(parameters[paramNr++]); + } + + + @Override + public void visit(DoubleValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(LongValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(HexValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(DateValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(TimeValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(TimestampValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(StringValue pValue) + { + params.add(pValue.getValue()); + } + + @Override + public void visit(Column tableColumn) + { + col = tableColumn.getFullyQualifiedName(); + } + + @Override + public void visit(Parenthesis parenthesis) + { + parenthesis.getExpression().accept(this); + } + + @Override + public void visit(AndExpression andExpression) + { + andExpression.getLeftExpression().accept(this); + andExpression.getRightExpression().accept(this); + } + + @Override + public void visit(OrExpression orExpression) + { + boolean vRoot = (currentFilter.isUnfiltered() && currentFilter == filter); + + orExpression.getLeftExpression().accept(this); + Filter newFilter = currentFilter; + currentFilter = Filter.noFilter(); + orExpression.getRightExpression().accept(this); + + currentFilter = Filter.of(newFilter).orWhere(currentFilter); + if (vRoot) { + this.filter = currentFilter; + }//if + } + + private void addFilter(Operators pOperators) + { + Filter newFilter = Filter.of(col, pOperators, params.toArray()); + + if (currentFilter.isUnfiltered() && currentFilter == this.filter) { + currentFilter = newFilter; + this.filter = newFilter; + }//if + else { + if (currentFilter.isUnfiltered()) { + currentFilter = newFilter; + }//if + else { + currentFilter.andWhere(newFilter); + }//else + }//else + + col = null; + params.clear(); + } + + @Override + public void visit(Between between) + { + between.getLeftExpression().accept(this); + between.getBetweenExpressionStart().accept(this); + between.getBetweenExpressionEnd().accept(this); + addFilter(Operators.BETWEEN); + } + + @Override + public void visit(EqualsTo equalsTo) + { + equalsTo.getLeftExpression().accept(this); + equalsTo.getRightExpression().accept(this); + addFilter(Operators.EQUALS); + } + + @Override + public void visit(GreaterThan greaterThan) + { + greaterThan.getLeftExpression().accept(this); + greaterThan.getRightExpression().accept(this); + addFilter(Operators.BIGGER_THAN); + } + + @Override + public void visit(GreaterThanEquals greaterThanEquals) + { + greaterThanEquals.getLeftExpression().accept(this); + greaterThanEquals.getRightExpression().accept(this); + addFilter(Operators.BIGGER_OR_EQUAL); + } + + @Override + public void visit(InExpression inExpression) + { + if (inExpression.getLeftExpression() != null) { + inExpression.getLeftExpression().accept(this); + }//if + + if (inExpression.getRightItemsList() != null) { + inExpression.getRightItemsList().accept(this); + }//if + + addFilter(inExpression.isNot() ? Operators.NOTIN : Operators.IN); + } + + + @Override + public void visit(IsNullExpression isNullExpression) + { + isNullExpression.getLeftExpression().accept(this); + addFilter(isNullExpression.isNot() ? Operators.ISNOTNULL : Operators.ISNULL); + } + + @Override + public void visit(LikeExpression likeExpression) + { + likeExpression.getLeftExpression().accept(this); + likeExpression.getRightExpression().accept(this); + addFilter(likeExpression.isNot() ? Operators.CONTAINS_NOT : Operators.CONTAINS); + } + + @Override + public void visit(MinorThan minorThan) + { + minorThan.getLeftExpression().accept(this); + minorThan.getRightExpression().accept(this); + addFilter(Operators.SMALLER_THAN); + } + + @Override + public void visit(MinorThanEquals minorThanEquals) + { + minorThanEquals.getLeftExpression().accept(this); + minorThanEquals.getRightExpression().accept(this); + addFilter(Operators.SMALLER_OR_EQUAL); + } + + @Override + public void visit(NotEqualsTo notEqualsTo) + { + notEqualsTo.getLeftExpression().accept(this); + notEqualsTo.getRightExpression().accept(this); + addFilter(Operators.NOTEQUALS); + } + + @Override + public void visit(IntervalExpression intervalExpression) + { + if (operationType != null) { + params.add(intervalExpression.getParameter()); + addFilter(operationType.equals("+") ? Operators.PLUS_INTERVAL : Operators.MINUS_INTERVAL); + operationType = null; + }//if + } +} diff --git a/jpalite-repository/src/main/java/org/jpalite/repository/JPALiteRepositoryProcessor.java b/jpalite-repository/src/main/java/org/jpalite/repository/JPALiteRepositoryProcessor.java new file mode 100644 index 0000000..8adc7b0 --- /dev/null +++ b/jpalite-repository/src/main/java/org/jpalite/repository/JPALiteRepositoryProcessor.java @@ -0,0 +1,470 @@ +/* + * 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.repository; + +import com.google.auto.service.AutoService; +import jakarta.persistence.LockModeType; +import jakarta.persistence.QueryHint; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@SupportedAnnotationTypes("io.jpalite.repository.Repository") +@SupportedSourceVersion(SourceVersion.RELEASE_21) +@AutoService(Processor.class) +@SuppressWarnings("java:S1192") //We are generating code here. Defining statics will impair the readability +public class JPALiteRepositoryProcessor extends AbstractProcessor +{ + private static final String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; + + //---------------------------------------------------------------[ note ]--- + private void note(String msg) + { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg); + }//note + + //------------------------------------------------------------[ process ]--- + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) + { + + if (roundEnv.processingOver()) { + return false; + }//if + + note("Creating JPALite Repositories"); + try { + Set set = roundEnv.getElementsAnnotatedWith(Repository.class); + + for (Element element : set) { + Repository annotation = element.getAnnotation(Repository.class); + if (annotation != null) { + generateRepo((TypeElement) element, annotation); + }//if + }//for + return true; + }//try + catch (Exception ex) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getMessage()); + }//catch + + return false; + }//process + + private void addJpaRepository(PrintWriter out, DeclaredType jpaRepository) + { + + String argType = jpaRepository.getTypeArguments().get(0).toString(); + String idType = jpaRepository.getTypeArguments().get(1).toString(); + + out.println("@Transactional"); + out.println("public void persist(" + argType + " entity) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" if (!em.contains(entity)) {"); + out.println(" em.persist(entity);"); + out.println(" }"); + out.println("}"); + out.println(""); + + out.println("@Transactional"); + out.println("public void save(" + argType + " entity) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" if (!em.contains(entity)) {"); + out.println(" if (em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity) != null) {"); + out.println(" em.merge(entity);"); + out.println(" } else {"); + out.println(" em.persist(entity);"); + out.println(" }"); + out.println(" }"); + out.println("}"); + out.println(""); + + out.println("@Transactional"); + out.println("public " + argType + " merge(" + argType + " entity) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" if (!em.contains(entity)) {"); + out.println(" return (" + argType + ") em.merge(entity);"); + out.println(" }"); + out.println(" return (" + argType + ")entity;"); + out.println("}"); + out.println(""); + + out.println("public void refresh(" + argType + " entity) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" em.refresh(entity);"); + out.println("}"); + out.println(""); + + out.println("public void lock(" + argType + " entity, LockModeType mode) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" em.lock(entity, mode);"); + out.println("}"); + out.println(""); + + out.println("public " + argType + " findById(" + idType + " id) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" return em.find(" + argType + ".class,id);"); + out.println("}"); + out.println(""); + + out.println("public " + argType + " findById(" + idType + " id, LockModeType mode) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" return em.find(" + argType + ".class,id, mode);"); + out.println("}"); + out.println(""); + + out.println("public " + argType + " getReference(" + idType + " id) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" return em.getReference(" + argType + ".class,id);"); + out.println("}"); + out.println(""); + + out.println("public void delete(" + argType + " entity) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" em.remove(entity);"); + out.println("}"); + out.println(""); + }//addJpaRepository + + private void addPagingRepository(PrintWriter out, DeclaredType pagingRepository) + { + String vArgType = pagingRepository.getTypeArguments().getFirst().toString(); + String vEntityType = vArgType.substring(vArgType.lastIndexOf(".") + 1); + + out.println("public long count(Filter filter, Map hints) {"); + out.println(" EntityManager em = getEntityManager();"); + out.print(" String q=\"select count("); + out.print(vEntityType); + out.print(") from "); + out.print(vEntityType); + out.println("\";"); + out.println(" Map parameters = new HashMap<>();"); + out.println(" if (!filter.isUnfiltered()) q = q+\" where \"+filter.getExpression(parameters); "); + out.println(" TypedQuery query = em.createQuery(q,Long.class);"); + out.println(" if (hints != null && !hints.isEmpty()) {"); + out.println(" hints.entrySet().forEach(entry -> query.setHint(entry.getKey(), entry.getValue()));"); + out.println(" }"); + out.println(" if (!parameters.isEmpty()) parameters.entrySet().stream().forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));"); + out.println(" return query.getSingleResult();"); + out.println("}"); + out.println(""); + + out.print("public List<"); + out.print(vArgType); + out.println("> findAll(Pageable pageable, Filter filter, Map hints) {"); + out.println(" EntityManager em = getEntityManager();"); + out.println(" if (hints != null && !hints.isEmpty()) {"); + out.println(" hints.entrySet().stream().forEach(entry -> em.setProperty(entry.getKey(), entry.getValue()));"); + out.println(" }"); + + out.print(" String q=\"select "); + out.print(vEntityType); + out.print(" from "); + out.print(vEntityType); + out.println("\";"); + out.println(" Map parameters = new HashMap<>();"); + out.println(" if (!filter.isUnfiltered()) q = q+\" where \"+filter.getExpression(parameters); "); + out.println(" if (!pageable.getSort().isUnsorted()) q = q+\" order by \"+pageable.getSort().getExpression();"); + out.print(" TypedQuery<"); + out.print(vArgType); + out.print("> query = em.createQuery(q,"); + out.print(vArgType); + out.println(".class);"); + out.println(" if (hints != null && !hints.isEmpty()) {"); + out.println(" hints.entrySet().forEach(entry -> query.setHint(entry.getKey(), entry.getValue()));"); + out.println(" }"); + out.println(" if (!parameters.isEmpty()) parameters.entrySet().stream().forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));"); + out.println(" if (!pageable.isUnpaged()) query.setFirstResult(pageable.getPageIndex())"); + out.println(" .setMaxResults(pageable.getPageSize());"); + out.println(" return query.getResultList();"); + out.println("}"); + out.println(""); + }//addPagingRepository + + @SuppressWarnings({"java:S3776", "java:S6541"}) //Complexity cannot be reduced further + private void createMethod(PrintWriter out, ExecutableElement method) + { + Query[] queries = method.getAnnotationsByType(Query.class); + if (queries.length > 0) { + method.getAnnotationMirrors() + .forEach(p -> + { + if (!p.getAnnotationType().toString().equals(Query.class.getName())) { + out.println(p); + }//if + }); + + out.print("public final "); + + //The return type can either be a List or an object + + out.print(method.getReturnType()); + out.print(" "); + out.print(method.getSimpleName().toString()); + out.print("("); + + out.print(method.getParameters() + .stream() + .map(p -> p.asType() + " " + p.getSimpleName()) + .collect(Collectors.joining(","))); + out.println(") {"); + out.println(" EntityManager em = getEntityManager();"); + boolean returnCollection = false; + String returnType = method.getReturnType().toString(); + if (method.getReturnType() instanceof DeclaredType declaredType && !declaredType.getTypeArguments().isEmpty()) { + returnCollection = true; + returnType = declaredType.getTypeArguments().getFirst().toString(); + }//if + + StringBuilder pageAndSort = new StringBuilder(); + Query query = queries[0]; + if (query.namedQuery()) { + out.print("TypedQuery<"); + out.print(returnType); + out.print("> query = em.createNamedQuery(\""); + out.print(query.value()); + out.print("\","); + out.print(returnType); + out.println(".class);"); + + method.getParameters() + .stream() + .filter(p -> p.asType().toString().equals(Pageable.class.getName())) + .forEach(p -> pageAndSort.append("query.setFirstResult(") + .append(p.getSimpleName()) + .append(".getPageIndex())\n") + .append(".setMaxResults(") + .append(p.getSimpleName()) + .append(".getPageSize());\n")); + }//if + else { + out.println("String queryStr = \"" + query.value() + "\";"); + + //Pageable and Sorting is only support for JPQL queries + if (!query.nativeQuery() && !query.updateQuery()) { + //Check for Pageable parameter + method.getParameters() + .stream() + .filter(p -> p.asType().toString().equals(Pageable.class.getName())) + .forEach(p -> + { + pageAndSort.append("query.setFirstResult(") + .append(p.getSimpleName()) + .append(".getPageIndex())\n") + .append(".setMaxResults(") + .append(p.getSimpleName()) + .append(".getPageSize());\n"); + out.println("queryStr += " + p.getSimpleName() + ".getSort().getOrderBy();"); + }); + method.getParameters() + .stream() + .filter(p -> p.asType().toString().equals(Sort.class.getName())) + .forEach(p -> out.println("queryStr += " + p.getSimpleName() + ".getOrderBy();")); + }//if + + if (query.nativeQuery()) { + out.println("jakarta.persistence.Query query = em.createNativeQuery(queryStr);"); + }//if + else if (query.updateQuery()) { + out.println("jakarta.persistence.Query query = em.createQuery(queryStr);"); + }//if + else { + out.print("TypedQuery<"); + out.print(returnType); + out.print("> query = em.createQuery(queryStr,"); + + out.print(returnType); + out.println(".class);"); + }//else + }//else + + if (!query.updateQuery() && query.lockMode() != LockModeType.NONE) { + out.print(" query.setLockMode("); + out.print("LockModeType." + query.lockMode().name()); + out.println(");"); + }//if + + if (method.getParameters() + .stream() + .anyMatch(p -> p.getAnnotation(QueryParam.class) != null)) { + + method.getParameters() + .stream() + .filter(p -> p.getAnnotation(QueryParam.class) != null) + .map(p -> "\"" + p.getAnnotation(QueryParam.class).value() + "\"," + p.getSimpleName()) + .forEach(p -> out.println("query.setParameter(" + p + ");")); + }//if + else { + AtomicInteger paramNr = new AtomicInteger(0); + method.getParameters() + .stream() + .filter(p -> !p.asType().toString().equals(Pageable.class.getName())) + .map(p -> paramNr.incrementAndGet() + "," + p.getSimpleName()) + .forEach(p -> out.println("query.setParameter(" + p + ");")); + }//else + + for (QueryHint hint : query.hints()) { + out.print("query.setHint(\""); + out.print(hint.name()); + out.print("\",\""); + out.print(hint.value()); + out.println("\");"); + }//for + + if (query.updateQuery()) { + out.println("return query.executeUpdate();"); + }//if + else { + out.println(pageAndSort); + + if (!returnCollection && query.catchNoResult()) { + out.println("try {"); + }//if + out.print(" return "); + if (query.nativeQuery()) { + out.print("("); + out.print(returnType); + out.print(") "); + }//if + out.print(" query."); + if (returnCollection) { + out.println("getResultList();"); + }//if + else { + out.println("getSingleResult();"); + }//else + + if (!returnCollection && query.catchNoResult()) { + out.println("}"); + out.println("catch (NoResultException ex) {"); + out.println(" return null;"); + out.println("}"); + }//if + }//else + + out.println("}"); + }//if + }//createMethod + + private void generateRepo(TypeElement repoElement, Repository annotation) throws IOException + { + + String packageName = annotation.name(); + if (packageName.isBlank()) { + packageName = repoElement.getQualifiedName() + "Impl"; + }//if + + int lastDot = packageName.lastIndexOf('.'); + if (lastDot == 0) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Repository.name is invalid, expected qualified name"); + return; + }//if + + FileObject repoFile = processingEnv.getFiler().createSourceFile(packageName); + String repoClassName = packageName.substring(lastDot + 1); + packageName = packageName.substring(0, lastDot); + + try (PrintWriter out = new PrintWriter(repoFile.openWriter())) { + out.print("package "); + out.print(packageName); + out.println(";"); + out.println(); + out.println("import io.quarkus.arc.Arc;"); + out.println("import jakarta.enterprise.context.RequestScoped;"); + out.println("import jakarta.inject.Inject;"); + out.println("import jakarta.transaction.Transactional;"); + out.println("import jakarta.annotation.Generated;"); + out.println("import jakarta.persistence.*;"); + out.println("import java.util.*;"); + out.println("import org.jpalite.PersistenceUnit;"); + out.println("import org.jpalite.repository.*;"); + out.println(); + + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + out.println("@Generated(value={\"Generated by " + getClass().getName() + "\"}," + + "date=\"" + dateFormat.format(new Date()) + "\"," + + "comments=\"JPALite Repository Generation\")"); + out.println("@RequestScoped"); + out.print("public class "); + out.print(repoClassName); + if (!annotation.abstractClass().isBlank()) { + out.print(" extends "); + out.print(annotation.abstractClass()); + out.print(" "); + }//if + out.print(" implements "); + out.print(repoElement.getQualifiedName()); + + out.println(" {"); + + if (!annotation.persistenceUnit().startsWith("${")) { + out.println(" @Inject"); + out.print(" @PersistenceUnit(\""); + out.print(annotation.persistenceUnit()); + out.println("\")"); + out.println(" EntityManager em;"); + }//if + + out.println("public EntityManager getEntityManager() {"); + if (annotation.persistenceUnit().startsWith("${")) { + out.println(" io.jpalite.extension.JPALiteRecorder producer = Arc.container().instance(io.jpalite.extension.JPALiteRecorder.class).get();"); + out.print(" return producer.getEntityManager(JpaRepositoryUtil.getPersistenceUnitName(\""); + out.print(annotation.persistenceUnit()); + out.println("\"));"); + }//if + else { + out.println(" return em;"); + }//else + out.println("}"); + + repoElement.getInterfaces() + .forEach(t -> + { + switch (((DeclaredType) t).asElement().toString()) { + case "io.jpalite.repository.JpaRepository" -> addJpaRepository(out, (DeclaredType) t); + case "io.jpalite.repository.PagingRepository" -> addPagingRepository(out, (DeclaredType) t); + default -> { + //Ignore the rest + } + } + }); + + for (Element vMethod : repoElement.getEnclosedElements()) { + createMethod(out, (ExecutableElement) vMethod); + }//for + + out.println("}"); + out.flush(); + }//try + catch (Exception ex) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Process Error:" + ex.getMessage()); + }//catch + }//generateRepo +}//JPALiteRepositoryProcessor diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/JpaRepository.java b/jpalite-repository/src/main/java/org/jpalite/repository/JpaRepository.java similarity index 98% rename from jpalite-repository/src/main/java/io/jpalite/repository/JpaRepository.java rename to jpalite-repository/src/main/java/org/jpalite/repository/JpaRepository.java index 37c5b54..9509ef1 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/JpaRepository.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/JpaRepository.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import jakarta.persistence.LockModeType; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/JpaRepositoryUtil.java b/jpalite-repository/src/main/java/org/jpalite/repository/JpaRepositoryUtil.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/JpaRepositoryUtil.java rename to jpalite-repository/src/main/java/org/jpalite/repository/JpaRepositoryUtil.java index e200ab4..9ca7edb 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/JpaRepositoryUtil.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/JpaRepositoryUtil.java @@ -1,4 +1,4 @@ -package io.jpalite.repository; +package org.jpalite.repository; import lombok.extern.slf4j.Slf4j; import org.eclipse.microprofile.config.Config; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/NullHandling.java b/jpalite-repository/src/main/java/org/jpalite/repository/NullHandling.java similarity index 96% rename from jpalite-repository/src/main/java/io/jpalite/repository/NullHandling.java rename to jpalite-repository/src/main/java/org/jpalite/repository/NullHandling.java index 5ba7c83..feedc69 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/NullHandling.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/NullHandling.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; public enum NullHandling { diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Operators.java b/jpalite-repository/src/main/java/org/jpalite/repository/Operators.java similarity index 98% rename from jpalite-repository/src/main/java/io/jpalite/repository/Operators.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Operators.java index 75a0ef3..75c6a42 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Operators.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Operators.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; public enum Operators { diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Pageable.java b/jpalite-repository/src/main/java/org/jpalite/repository/Pageable.java similarity index 98% rename from jpalite-repository/src/main/java/io/jpalite/repository/Pageable.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Pageable.java index 8ffd24d..1d31d5c 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Pageable.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Pageable.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; public class Pageable { diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/PagingRepository.java b/jpalite-repository/src/main/java/org/jpalite/repository/PagingRepository.java similarity index 99% rename from jpalite-repository/src/main/java/io/jpalite/repository/PagingRepository.java rename to jpalite-repository/src/main/java/org/jpalite/repository/PagingRepository.java index 608aebb..a3d53ba 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/PagingRepository.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/PagingRepository.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import java.util.Collections; import java.util.List; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/ParserBase.java b/jpalite-repository/src/main/java/org/jpalite/repository/ParserBase.java similarity index 99% rename from jpalite-repository/src/main/java/io/jpalite/repository/ParserBase.java rename to jpalite-repository/src/main/java/org/jpalite/repository/ParserBase.java index 393c73c..5df1207 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/ParserBase.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/ParserBase.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.*; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Query.java b/jpalite-repository/src/main/java/org/jpalite/repository/Query.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/Query.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Query.java index dc1ab68..4cc5aa5 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Query.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Query.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import jakarta.persistence.LockModeType; import jakarta.persistence.QueryHint; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/QueryParam.java b/jpalite-repository/src/main/java/org/jpalite/repository/QueryParam.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/QueryParam.java rename to jpalite-repository/src/main/java/org/jpalite/repository/QueryParam.java index 8da8c8d..ace0dd8 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/QueryParam.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/QueryParam.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Repository.java b/jpalite-repository/src/main/java/org/jpalite/repository/Repository.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/Repository.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Repository.java index 78392d4..d001d96 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Repository.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Repository.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/RepositoryBase.java b/jpalite-repository/src/main/java/org/jpalite/repository/RepositoryBase.java similarity index 97% rename from jpalite-repository/src/main/java/io/jpalite/repository/RepositoryBase.java rename to jpalite-repository/src/main/java/org/jpalite/repository/RepositoryBase.java index a231aed..f8b298e 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/RepositoryBase.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/RepositoryBase.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/Sort.java b/jpalite-repository/src/main/java/org/jpalite/repository/Sort.java similarity index 99% rename from jpalite-repository/src/main/java/io/jpalite/repository/Sort.java rename to jpalite-repository/src/main/java/org/jpalite/repository/Sort.java index 398e36d..92f167e 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/Sort.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/Sort.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import java.util.ArrayList; import java.util.Arrays; diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/SortOrder.java b/jpalite-repository/src/main/java/org/jpalite/repository/SortOrder.java similarity index 98% rename from jpalite-repository/src/main/java/io/jpalite/repository/SortOrder.java rename to jpalite-repository/src/main/java/org/jpalite/repository/SortOrder.java index 03f6cea..44f0089 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/SortOrder.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/SortOrder.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; public class SortOrder { diff --git a/jpalite-repository/src/main/java/io/jpalite/repository/SortParser.java b/jpalite-repository/src/main/java/org/jpalite/repository/SortParser.java similarity index 98% rename from jpalite-repository/src/main/java/io/jpalite/repository/SortParser.java rename to jpalite-repository/src/main/java/org/jpalite/repository/SortParser.java index b7c2ff8..c3d6eb0 100644 --- a/jpalite-repository/src/main/java/io/jpalite/repository/SortParser.java +++ b/jpalite-repository/src/main/java/org/jpalite/repository/SortParser.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import jakarta.persistence.PersistenceException; import net.sf.jsqlparser.JSQLParserException; diff --git a/jpalite-repository/src/test/java/io/jpalite/repository/FilterParserTest.java b/jpalite-repository/src/test/java/org/jpalite/repository/FilterParserTest.java similarity index 99% rename from jpalite-repository/src/test/java/io/jpalite/repository/FilterParserTest.java rename to jpalite-repository/src/test/java/org/jpalite/repository/FilterParserTest.java index fc45e7b..3b9daf6 100644 --- a/jpalite-repository/src/test/java/io/jpalite/repository/FilterParserTest.java +++ b/jpalite-repository/src/test/java/org/jpalite/repository/FilterParserTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import org.junit.jupiter.api.Test; diff --git a/jpalite-repository/src/test/java/io/jpalite/repository/FilterTest.java b/jpalite-repository/src/test/java/org/jpalite/repository/FilterTest.java similarity index 99% rename from jpalite-repository/src/test/java/io/jpalite/repository/FilterTest.java rename to jpalite-repository/src/test/java/org/jpalite/repository/FilterTest.java index ebdbbba..580e94a 100644 --- a/jpalite-repository/src/test/java/io/jpalite/repository/FilterTest.java +++ b/jpalite-repository/src/test/java/org/jpalite/repository/FilterTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import org.junit.jupiter.api.Test; diff --git a/jpalite-repository/src/test/java/io/jpalite/repository/PageableTest.java b/jpalite-repository/src/test/java/org/jpalite/repository/PageableTest.java similarity index 98% rename from jpalite-repository/src/test/java/io/jpalite/repository/PageableTest.java rename to jpalite-repository/src/test/java/org/jpalite/repository/PageableTest.java index 7ae264a..2c9d2c4 100644 --- a/jpalite-repository/src/test/java/io/jpalite/repository/PageableTest.java +++ b/jpalite-repository/src/test/java/org/jpalite/repository/PageableTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import org.junit.jupiter.api.Test; diff --git a/jpalite-repository/src/test/java/io/jpalite/repository/SortParserTest.java b/jpalite-repository/src/test/java/org/jpalite/repository/SortParserTest.java similarity index 98% rename from jpalite-repository/src/test/java/io/jpalite/repository/SortParserTest.java rename to jpalite-repository/src/test/java/org/jpalite/repository/SortParserTest.java index 79f0007..135f372 100644 --- a/jpalite-repository/src/test/java/io/jpalite/repository/SortParserTest.java +++ b/jpalite-repository/src/test/java/org/jpalite/repository/SortParserTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import org.junit.jupiter.api.Test; diff --git a/jpalite-repository/src/test/java/io/jpalite/repository/SortTest.java b/jpalite-repository/src/test/java/org/jpalite/repository/SortTest.java similarity index 98% rename from jpalite-repository/src/test/java/io/jpalite/repository/SortTest.java rename to jpalite-repository/src/test/java/org/jpalite/repository/SortTest.java index cae0854..51935bc 100644 --- a/jpalite-repository/src/test/java/io/jpalite/repository/SortTest.java +++ b/jpalite-repository/src/test/java/org/jpalite/repository/SortTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package io.jpalite.repository; +package org.jpalite.repository; import org.junit.jupiter.api.Test; diff --git a/pom.xml b/pom.xml index f8a80e6..7528840 100644 --- a/pom.xml +++ b/pom.xml @@ -1,21 +1,4 @@ - - @@ -24,7 +7,7 @@ io.jpalite jpalite-parent - 3.0.0 + 3.0.0-SNAPSHOT pom JPALite Parent @@ -35,7 +18,7 @@ 21 21 - 3.10.2 + 3.13.2 2.0.7 1.18.30 @@ -51,6 +34,7 @@ jpalite-core + jpalite-cache-infinispan jpalite-repository jpalite-maven-plugin jpalite-quarkus-extension @@ -113,21 +97,6 @@ ${maven-deploy-plugin.version} - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - install - - sign - - - - - org.apache.maven.plugins maven-source-plugin @@ -156,20 +125,39 @@ default - TradeSwitch - TradeSwitch Repo - https://nexus.frei.dev/repository/tradeswitch.release/ + common + Common Repo + https://nexus.frei.dev/repository/tradeswitch.common/ default - TradeSwitch - Internal TradeSwitch Release Repository - https://nexus.frei.dev/repository/tradeswitch.release/ + common + Internal TradeSwitch Common Repository + https://nexus.frei.dev/repository/tradeswitch.common/ + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.5 + + + sign-artifacts + install + + sign + + + + + + @@ -186,8 +174,8 @@ default - devrepo - Dev Repo + common + Common Repo http://localhost:8046/artifactory/devrepo/ default @@ -195,10 +183,9 @@ - devrepo - Dev Repo - http://localhost:8046/artifactory/devrepo/ - default + common + Internal TradeSwitch Common Repository + https://nexus.frei.dev/repository/tradeswitch.common/ diff --git a/qodana.yaml b/qodana.yaml deleted file mode 100644 index c130725..0000000 --- a/qodana.yaml +++ /dev/null @@ -1,59 +0,0 @@ -#-------------------------------------------------------------------------------# -# Qodana analysis is configured by qodana.yaml file # -# https://www.jetbrains.com/help/qodana/qodana-yaml.html # -#-------------------------------------------------------------------------------# -version: "1.0" - -#Specify inspection profile for code analysis -profile: - name: qodana.starter - -#Enable inspections -#include: -# - name: - -#Disable inspections -#exclude: -# - name: -# paths: -# - - -projectJDK: 21 #(Applied in CI/CD pipeline) - -#Execute shell command before Qodana execution (Applied in CI/CD pipeline) -#bootstrap: sh ./prepare-qodana.sh - -#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) -#plugins: -# - id: #(plugin id can be found at https://plugins.jetbrains.com) - -#Specify Qodana linter for analysis (Applied in CI/CD pipeline) -linter: jetbrains/qodana-jvm:latest -exclude: - - name: AutoCloseableResource - - paths: - - docs - - name: All - paths: - - jpalite-quarkus-extension/runtime/src/main/java/io/jpalite/extension/JPALiteRecorder.java # false CDI issue - -licenseRules: - - keys: - - "PROPRIETARY-LICENSE" - - "MIT" - prohibited: - - "BSD-3-CLAUSE-NO-CHANGE" - allowed: - - "ISC" - - - keys: ["Apache-2.0"] - allowed: - - "MIT" - - "NONE" - - "BSD-3-CLAUSE-NO-TRADEMARK" - - "EPL-2.0" - - "GPL-2.0-only" - - "GPL-1.0-or-later" - - "LGPL-2.1-only" - - "LGPL-2.0-or-later" - - "MPL-1.1"