diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java new file mode 100644 index 000000000..fa8581025 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa; + +import jakarta.persistence.TypedQuery; + +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.eclipse.persistence.jpa.JpaQuery; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Slf4j +public class Utils { + + public static String toSql(final TypedQuery typedQuery) { + typedQuery.setParameter(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "DEFAULT"); + // executes the query - otherwise the SQL string is not generated + typedQuery.getResultList(); + return typedQuery.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString(); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java new file mode 100644 index 000000000..80cad6656 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Utils.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import jakarta.persistence.TypedQuery; + +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.QuerySqmImpl; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.SqmTranslator; +import org.hibernate.query.sqm.sql.SqmTranslatorFactory; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Slf4j +public class Utils { + + private static final Method getSqmTranslatorFactory; + + static { + Method method = null; + try { + method = QueryEngine.class.getMethod("getSqmTranslatorFactory"); + } catch (final NoSuchMethodException e) { + log.warn("Can't resolve getSqmTranslatorFactory method (Utils.toString won't work)", e); + } + getSqmTranslatorFactory = method; + } + + public static String toSql(final TypedQuery typedQuery) { + if (getSqmTranslatorFactory == null) { + throw new UnsupportedOperationException("SqmTranslatorFactory resolver is not available"); + } + + final QuerySqmImpl hqlQuery = typedQuery.unwrap(QuerySqmImpl.class); + final SessionFactoryImplementor factory = hqlQuery.getSessionFactory(); + final SharedSessionContractImplementor session = hqlQuery.getSession(); + final SessionFactoryImplementor sessionFactory = session.getFactory(); + + final SqmTranslatorFactory sqmTranslatorFactory; + try { + sqmTranslatorFactory = (SqmTranslatorFactory) getSqmTranslatorFactory.invoke(factory.getQueryEngine()); + } catch (final IllegalAccessException | InvocationTargetException e) { + throw new UnsupportedOperationException("Can't create SqmTranslatorFactory", e); + } + + final SqmTranslator sqmSelectTranslator = + hqlQuery.getSqmStatement() instanceof SqmSelectStatement selectStatement + ? sqmTranslatorFactory.createSelectTranslator(selectStatement, + hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(), + hqlQuery.getLoadQueryInfluencers(), sessionFactory, false) + : sqmTranslatorFactory.createMutationTranslator((SqmDmlStatement) hqlQuery.getSqmStatement(), + hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(), + hqlQuery.getLoadQueryInfluencers(), sessionFactory); + + final SqmTranslation sqmTranslation = sqmSelectTranslator.translate(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + final Map, Map, List>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( + hqlQuery.getDomainParameterXref(), sqmTranslation::getJdbcParamsBySqmParam); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(hqlQuery.getQueryParameterBindings(), + hqlQuery.getDomainParameterXref(), jdbcParamsXref, factory.getRuntimeMetamodels().getMappingMetamodel(), + sqmSelectTranslator.getFromClauseAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() { + + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(final SqmParameter parameter) { + return (MappingModelExpressible) sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter); + } + }, hqlQuery.getSession()); + return (sqmTranslation.getSqlAst() instanceof SelectStatement selectStatement + ? sqlAstTranslatorFactory.buildSelectTranslator(factory, selectStatement) + .translate(jdbcParameterBindings, hqlQuery.getQueryOptions()) + : sqlAstTranslatorFactory.buildMutationTranslator(factory, (MutationStatement) sqmTranslation.getSqlAst()) + .translate(jdbcParameterBindings, hqlQuery.getQueryOptions())) + .getSqlString(); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQL.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQL.java index da4d5758d..7051d40f1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQL.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQL.java @@ -9,8 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.rsql; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; import jakarta.persistence.EntityManager; @@ -25,7 +23,7 @@ import cz.jirutka.rsql.parser.ast.Node; import cz.jirutka.rsql.parser.ast.RSQLOperators; import cz.jirutka.rsql.parser.ast.RSQLVisitor; import org.eclipse.hawkbit.repository.RsqlQueryField; -import org.eclipse.hawkbit.repository.jpa.Jpa; +import org.eclipse.hawkbit.repository.jpa.Utils; import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.springframework.orm.jpa.vendor.Database; @@ -44,25 +42,7 @@ public class RSQLToSQL { final Class domainClass, final Class fieldsClass, final String rsql, final boolean legacyRsqlVisitor) { final CriteriaQuery query = createQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor); final TypedQuery typedQuery = entityManager.createQuery(query); - // executes the query - otherwise the SQL string is not generated - if (Jpa.JPA_VENDOR.equals(Jpa.JpaVendor.ECLIPSELINK)) { - typedQuery.setParameter("eclipselink.tenant-id", "DEFAULT"); - typedQuery.getResultList(); - try { - final Class jpaQueryClass = Class.forName("org.eclipse.persistence.jpa.JpaQuery"); - final Method getDatabaseQueryMethod = jpaQueryClass.getMethod("getDatabaseQuery"); - final Method getSQLString = getDatabaseQueryMethod.getReturnType().getMethod("getSQLString"); - return (String)getSQLString.invoke(getDatabaseQueryMethod.invoke(typedQuery.unwrap(jpaQueryClass))); - } catch (final RuntimeException e) { - throw e; - } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { - throw new UnsupportedOperationException("EclipseLink is not supported", e); - } catch (final InvocationTargetException e) { - throw e.getCause() instanceof RuntimeException ? (RuntimeException)e.getCause() : new RuntimeException(e.getCause()); - } - } else { // hibernate - throw new UnsupportedOperationException("Hibernate is not supported"); - } + return Utils.toSql(typedQuery); } private & RsqlQueryField> CriteriaQuery createQuery( diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java index 24da7db82..0a1230d21 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java @@ -27,7 +27,6 @@ import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter; import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.repository.RolloutApprovalStrategy; import org.eclipse.hawkbit.repository.RolloutStatusCache; -import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.event.ApplicationEventFilter; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; @@ -75,9 +74,9 @@ import org.springframework.security.concurrent.DelegatingSecurityContextSchedule * Spring context configuration required for Dev.Environment. */ @Configuration -@EnableConfigurationProperties({ DdiSecurityProperties.class, - ArtifactUrlHandlerProperties.class, ArtifactFilesystemProperties.class, HawkbitSecurityProperties.class, - ControllerPollProperties.class, TenantConfigurationProperties.class }) +@EnableConfigurationProperties({ + DdiSecurityProperties.class, ArtifactUrlHandlerProperties.class, ArtifactFilesystemProperties.class, + HawkbitSecurityProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class }) @Profile("test") @EnableAutoConfiguration @PropertySource("classpath:/hawkbit-test-defaults.properties") @@ -99,9 +98,7 @@ public class TestConfiguration implements AsyncConfigurer { return new DelegatingSecurityContextScheduledExecutorService( Executors.newScheduledThreadPool(1, runnable -> { final Thread thread = Executors.defaultThreadFactory().newThread(runnable); - thread.setName( - String.format( - Locale.ROOT, "central-scheduled-executor-pool-%d", count.getAndIncrement())); + thread.setName(String.format(Locale.ROOT, "central-scheduled-executor-pool-%d", count.getAndIncrement())); return thread; })); } @@ -135,9 +132,8 @@ public class TestConfiguration implements AsyncConfigurer { } /** - * @return the {@link org.eclipse.hawkbit.repository.test.util.SystemManagementHolder} singleton bean which holds the - * current {@link SystemManagement} service and make it accessible in - * beans which cannot access the service directly, e.g. JPA entities. + * @return the {@link org.eclipse.hawkbit.repository.test.util.SystemManagementHolder} singleton bean which holds the current + * {@link SystemManagement} service and make it accessible in beans which cannot access the service directly, e.g. JPA entities. */ @Bean SystemManagementHolder systemManagementHolder() { @@ -150,8 +146,7 @@ public class TestConfiguration implements AsyncConfigurer { } @Bean - PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler( - final ArtifactUrlHandlerProperties urlHandlerProperties) { + PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties) { return new PropertyBasedArtifactUrlHandler(urlHandlerProperties, ""); } @@ -185,8 +180,8 @@ public class TestConfiguration implements AsyncConfigurer { @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) SimpleApplicationEventMulticaster applicationEventMulticaster(final ApplicationEventFilter applicationEventFilter) { - final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new FilterEnabledApplicationEventPublisher( - applicationEventFilter); + final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = + new FilterEnabledApplicationEventPublisher(applicationEventFilter); simpleApplicationEventMulticaster.setTaskExecutor(asyncExecutor()); return simpleApplicationEventMulticaster; }