From 7e4d087e570ca43f07329061a7ddb185bb4bc685 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 10:43:02 -0400 Subject: [PATCH 1/9] Add Hibernate 7 Forge Feature --- .../en/guide/commandLine/creatingProject.adoc | 4 +- .../templates/buildSrcBuildGradle.rocker.raw | 8 + .../DatabaseDriverConfigurationFeature.java | 2 +- .../database/DatabaseDriverFeature.java | 12 +- ...ateGorm.java => GrailsDataHibernate5.java} | 8 +- .../database/GrailsDataHibernate7.java | 126 +++++++++++ .../GrailsDataHibernateValidator.java | 45 ++++ ...{MongoGorm.java => GrailsDataMongoDB.java} | 6 +- .../forge/feature/database/GraphqlGorm.java | 12 +- .../org/grails/forge/feature/database/H2.java | 3 +- .../grails/forge/feature/database/MySQL.java | 4 +- .../forge/feature/database/PostgreSQL.java | 4 +- .../forge/feature/database/SQLServer.java | 4 +- .../feature/database/TestContainers.java | 2 +- .../forge/feature/grails/GrailsBase.java | 17 +- .../migration/DatabaseMigrationPlugin.java | 8 +- .../org/grails/forge/options/GormImpl.java | 1 + ...groovy => GrailsDataHibernate5Spec.groovy} | 2 +- .../database/GrailsDataHibernate7Spec.groovy | 199 ++++++++++++++++++ ...ec.groovy => GrailsDataMongoDBSpec.groovy} | 2 +- 20 files changed, 432 insertions(+), 37 deletions(-) rename grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/{HibernateGorm.java => GrailsDataHibernate5.java} (91%) create mode 100644 grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java create mode 100644 grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java rename grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/{MongoGorm.java => GrailsDataMongoDB.java} (93%) rename grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/{HibernateGormSpec.groovy => GrailsDataHibernate5Spec.groovy} (97%) create mode 100644 grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy rename grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/{MongoGormSpec.groovy => GrailsDataMongoDBSpec.groovy} (95%) diff --git a/grails-doc/src/en/guide/commandLine/creatingProject.adoc b/grails-doc/src/en/guide/commandLine/creatingProject.adoc index 73091f24d54..7bd657235bb 100644 --- a/grails-doc/src/en/guide/commandLine/creatingProject.adoc +++ b/grails-doc/src/en/guide/commandLine/creatingProject.adoc @@ -159,7 +159,7 @@ The "create-*" commands are used to produce a fundamental Grails project, allowi ---- | -g, --gorm -| Which GORM Implementation to configure. Possible values: hibernate, mongodb, neo4j. +| Which GORM Implementation to configure. Possible values: hibernate, hibernate7, mongodb. | [source,shell] ---- @@ -175,7 +175,7 @@ The "create-*" commands are used to produce a fundamental Grails project, allowi ---- | -f, --features -| The features to use. Possible values: h2, gorm-hibernate5, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties +| The features to use. Possible values: h2, gorm-hibernate5, gorm-hibernate7, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties | [source,shell] ---- diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildSrcBuildGradle.rocker.raw b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildSrcBuildGradle.rocker.raw index 19a117319d2..1dff5a907c7 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildSrcBuildGradle.rocker.raw +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/build/gradle/templates/buildSrcBuildGradle.rocker.raw @@ -51,7 +51,15 @@ repositories { } dependencies { +@if (features.contains("gorm-hibernate7") && features.contains("grails-micronaut")) { + implementation platform("org.apache.grails:grails-hibernate7-micronaut-bom:$grailsVersion") +} else if (features.contains("grails-micronaut")) { + implementation platform("org.apache.grails:grails-micronaut-bom:$grailsVersion") +} else if (features.contains("gorm-hibernate7")) { + implementation platform("org.apache.grails:grails-hibernate7-bom:$grailsVersion") +} else { implementation platform("org.apache.grails:grails-bom:$grailsVersion") +} @for (GradleDependency dependency : gradleBuild.getBuildSrcDependencies()) { @dependency.toSnippet() } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java index 4fc8641e0b5..cc647453407 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java @@ -28,7 +28,7 @@ import static org.grails.forge.feature.config.ConfigurationFeature.PROD_ENVIRONMENT_KEY; import static org.grails.forge.feature.config.ConfigurationFeature.ENVIRONMENTS_KEY; import static org.grails.forge.feature.config.ConfigurationFeature.PROPERTIES_KEY; -import static org.grails.forge.feature.database.HibernateGorm.PREFIX; +import static org.grails.forge.feature.database.GrailsDataHibernate5.PREFIX; /** * A feature that configures a datasource with a driver diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java index ead67ce4ba3..e2272efa673 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java @@ -29,15 +29,15 @@ public abstract class DatabaseDriverFeature implements OneOfFeature { private final TestContainers testContainers; - private final HibernateGorm hibernateGorm; + private final GrailsDataHibernate5 grailsDataHibernate5; public DatabaseDriverFeature() { this.testContainers = null; - this.hibernateGorm = null; + this.grailsDataHibernate5 = null; } - public DatabaseDriverFeature(HibernateGorm hibernateGorm, TestContainers testContainers) { - this.hibernateGorm = hibernateGorm; + public DatabaseDriverFeature(GrailsDataHibernate5 grailsDataHibernate5, TestContainers testContainers) { + this.grailsDataHibernate5 = grailsDataHibernate5; this.testContainers = testContainers; } @@ -56,8 +56,8 @@ public void processSelectedFeatures(FeatureContext featureContext) { if (!featureContext.isPresent(TestContainers.class) && testContainers != null) { featureContext.addFeature(testContainers); } - if (!featureContext.isPresent(HibernateGorm.class) && hibernateGorm != null) { - featureContext.addFeature(hibernateGorm); + if (!featureContext.isPresent(GrailsDataHibernate5.class) && grailsDataHibernate5 != null) { + featureContext.addFeature(grailsDataHibernate5); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/HibernateGorm.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java similarity index 91% rename from grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/HibernateGorm.java rename to grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java index 568030e73f1..280fb3d9b8b 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/HibernateGorm.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java @@ -33,7 +33,7 @@ @Primary @Singleton -public class HibernateGorm extends GormFeature implements DatabaseDriverConfigurationFeature { +public class GrailsDataHibernate5 extends GormFeature implements DatabaseDriverConfigurationFeature { static final String PREFIX = "dataSource."; private static final String URL_KEY = PREFIX + "url"; @@ -44,7 +44,7 @@ public class HibernateGorm extends GormFeature implements DatabaseDriverConfigur private final DatabaseDriverFeature defaultDbFeature; - public HibernateGorm(DatabaseDriverFeature defaultDbFeature) { + public GrailsDataHibernate5(DatabaseDriverFeature defaultDbFeature) { this.defaultDbFeature = defaultDbFeature; } @@ -123,6 +123,8 @@ public String getDbCreateKey() { @Override public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { - return selectedFeatures.stream().anyMatch(f -> f instanceof HibernateGorm) || options.getGormImpl() == GormImpl.HIBERNATE; + return selectedFeatures.stream().anyMatch(f -> f instanceof GrailsDataHibernate5) + || (options.getGormImpl() == GormImpl.HIBERNATE + && selectedFeatures.stream().noneMatch(GrailsDataHibernate7.class::isInstance)); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java new file mode 100644 index 00000000000..43f7c0a4b72 --- /dev/null +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java @@ -0,0 +1,126 @@ +/* + * 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 + * + * https://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.grails.forge.feature.database; + +import jakarta.inject.Singleton; +import org.grails.forge.application.ApplicationType; +import org.grails.forge.application.generator.GeneratorContext; +import org.grails.forge.build.dependencies.Dependency; +import org.grails.forge.feature.Feature; +import org.grails.forge.feature.FeatureContext; +import org.grails.forge.options.GormImpl; +import org.grails.forge.options.Options; + +import java.util.Map; +import java.util.Set; + +@Singleton +public class GrailsDataHibernate7 extends GormFeature implements DatabaseDriverConfigurationFeature { + + static final String PREFIX = "dataSource."; + private static final String URL_KEY = PREFIX + "url"; + private static final String DRIVER_KEY = PREFIX + "driverClassName"; + private static final String USERNAME_KEY = PREFIX + "username"; + private static final String PASSWORD_KEY = PREFIX + "password"; + private static final String DB_CREATE_KEY = PREFIX + "dbCreate"; + + private final DatabaseDriverFeature defaultDbFeature; + + public GrailsDataHibernate7(DatabaseDriverFeature defaultDbFeature) { + this.defaultDbFeature = defaultDbFeature; + } + + @Override + public String getName() { + return "gorm-hibernate7"; + } + + @Override + public String getTitle() { + return "GORM for Hibernate 7"; + } + + @Override + public String getDescription() { + return "Configure GORM for using Hibernate 7."; + } + + @Override + public void processSelectedFeatures(FeatureContext featureContext) { + if (!featureContext.isPresent(DatabaseDriverFeature.class)) { + featureContext.addFeature(defaultDbFeature); + } + } + + @Override + public void apply(GeneratorContext generatorContext) { + Map config = generatorContext.getConfiguration(); + DatabaseDriverFeature dbFeature = generatorContext.getRequiredFeature(DatabaseDriverFeature.class); + applyDefaultConfig(dbFeature, config); + applyDefaultGormConfig(config); + config.put("dataSource.pooled", true); + config.put("dataSource.jmxExport", true); + config.put("hibernate.cache.queries", false); + config.put("hibernate.cache.use_second_level_cache", false); + config.put("hibernate.cache.use_query_cache", false); + + generatorContext.addBuildscriptDependency(Dependency.builder() + .groupId("org.apache.grails") + .artifactId("grails-data-hibernate7") + .buildSrc()); + generatorContext.addDependency(Dependency.builder() + .groupId("org.apache.grails") + .artifactId("grails-data-hibernate7") + .implementation()); + generatorContext.addDependency(Dependency.builder() + .groupId("com.zaxxer") + .artifactId("HikariCP") + .runtimeOnly()); + } + + @Override + public String getUrlKey() { + return URL_KEY; + } + + @Override + public String getDriverKey() { + return DRIVER_KEY; + } + + @Override + public String getUsernameKey() { + return USERNAME_KEY; + } + + @Override + public String getPasswordKey() { + return PASSWORD_KEY; + } + + @Override + public String getDbCreateKey() { + return DB_CREATE_KEY; + } + + @Override + public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { + return selectedFeatures.stream().anyMatch(GrailsDataHibernate7.class::isInstance) || options.getGormImpl() == GormImpl.HIBERNATE7; + } +} diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java new file mode 100644 index 00000000000..912e163c495 --- /dev/null +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java @@ -0,0 +1,45 @@ +/* + * 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 + * + * https://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.grails.forge.feature.database; + +import java.util.Set; + +import jakarta.inject.Singleton; + +import org.grails.forge.application.ApplicationType; +import org.grails.forge.feature.Feature; +import org.grails.forge.feature.validation.FeatureValidator; +import org.grails.forge.options.Options; + +@Singleton +public class GrailsDataHibernateValidator implements FeatureValidator { + + @Override + public void validatePreProcessing(Options options, ApplicationType applicationType, Set features) { + if (features.stream().anyMatch(GrailsDataHibernate5.class::isInstance) + && features.stream().anyMatch(GrailsDataHibernate7.class::isInstance)) { + throw new IllegalArgumentException("Only one GORM for Hibernate implementation can be selected: gorm-hibernate5 or gorm-hibernate7"); + } + } + + @Override + public void validatePostProcessing(Options options, ApplicationType applicationType, Set features) { + + } +} diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MongoGorm.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java similarity index 93% rename from grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MongoGorm.java rename to grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java index d81b0e1523f..94df495e9f8 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MongoGorm.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java @@ -32,11 +32,11 @@ import java.util.Set; @Singleton -public class MongoGorm extends GormOneOfFeature { +public class GrailsDataMongoDB extends GormOneOfFeature { private final TestContainers testContainers; - public MongoGorm(TestContainers testContainers) { + public GrailsDataMongoDB(TestContainers testContainers) { this.testContainers = testContainers; } @@ -76,7 +76,7 @@ public void apply(GeneratorContext generatorContext) { @Override public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { - return selectedFeatures.stream().anyMatch(f -> f instanceof MongoGorm) || options.getGormImpl() == GormImpl.MONGODB; + return selectedFeatures.stream().anyMatch(f -> f instanceof GrailsDataMongoDB) || options.getGormImpl() == GormImpl.MONGODB; } @Nullable diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java index fb958659208..1e06d0975fc 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java @@ -31,18 +31,18 @@ * Adds the {@code grails-data-graphql} plugin to the generated application. * *

GraphQL is a layer on top of GORM rather than a GORM implementation, so - * this feature is selectable in addition to (not instead of) {@link HibernateGorm} - * or {@link MongoGorm}. If the user opts into GraphQL without explicitly + * this feature is selectable in addition to (not instead of) {@link GrailsDataHibernate5} + * or {@link GrailsDataMongoDB}. If the user opts into GraphQL without explicitly * selecting a GORM persistence layer, Hibernate is added as a sensible default * via {@link #processSelectedFeatures(FeatureContext)}.

*/ @Singleton public class GraphqlGorm implements Feature { - private final HibernateGorm hibernateGorm; + private final GrailsDataHibernate5 grailsDataHibernate5; - public GraphqlGorm(HibernateGorm hibernateGorm) { - this.hibernateGorm = hibernateGorm; + public GraphqlGorm(GrailsDataHibernate5 grailsDataHibernate5) { + this.grailsDataHibernate5 = grailsDataHibernate5; } @Override @@ -75,7 +75,7 @@ public void processSelectedFeatures(FeatureContext featureContext) { // GraphQL needs a GORM implementation to introspect; default to Hibernate // when the user has not explicitly chosen a GORM provider. if (!featureContext.isPresent(GormFeature.class) && !featureContext.isPresent(GormOneOfFeature.class)) { - featureContext.addFeature(hibernateGorm); + featureContext.addFeature(grailsDataHibernate5); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java index 93e924e9d70..19be11c6bde 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java @@ -25,6 +25,7 @@ import org.grails.forge.build.dependencies.Dependency; import org.grails.forge.feature.DefaultFeature; import org.grails.forge.feature.Feature; +import org.grails.forge.options.GormImpl; import org.grails.forge.options.Options; import java.util.Set; @@ -99,7 +100,7 @@ public void apply(GeneratorContext generatorContext) { @Override public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { - return options.getGormImpl().getName().equals("gorm-hibernate5") && + return (options.getGormImpl() == GormImpl.HIBERNATE || options.getGormImpl() == GormImpl.HIBERNATE7) && selectedFeatures.stream().noneMatch(f -> f instanceof DatabaseDriverFeature); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MySQL.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MySQL.java index 6518bc3ae06..88fef13f8f8 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MySQL.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/MySQL.java @@ -26,8 +26,8 @@ @Singleton public class MySQL extends DatabaseDriverFeature { - public MySQL(HibernateGorm hibernateGorm, TestContainers testContainers) { - super(hibernateGorm, testContainers); + public MySQL(GrailsDataHibernate5 grailsDataHibernate5, TestContainers testContainers) { + super(grailsDataHibernate5, testContainers); } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/PostgreSQL.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/PostgreSQL.java index 7c06dd46f9d..0858a412b74 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/PostgreSQL.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/PostgreSQL.java @@ -26,8 +26,8 @@ @Singleton public class PostgreSQL extends DatabaseDriverFeature { - public PostgreSQL(HibernateGorm hibernateGorm, TestContainers testContainers) { - super(hibernateGorm, testContainers); + public PostgreSQL(GrailsDataHibernate5 grailsDataHibernate5, TestContainers testContainers) { + super(grailsDataHibernate5, testContainers); } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/SQLServer.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/SQLServer.java index 040905a7906..242c6a0cf05 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/SQLServer.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/SQLServer.java @@ -26,8 +26,8 @@ @Singleton public class SQLServer extends DatabaseDriverFeature { - public SQLServer(HibernateGorm hibernateGorm, TestContainers testContainers) { - super(hibernateGorm, testContainers); + public SQLServer(GrailsDataHibernate5 grailsDataHibernate5, TestContainers testContainers) { + super(grailsDataHibernate5, testContainers); } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/TestContainers.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/TestContainers.java index 10b4b6e8d57..0e545766c34 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/TestContainers.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/TestContainers.java @@ -82,7 +82,7 @@ public void apply(GeneratorContext generatorContext) { generatorContext.addDependency(testContainerTestDependency("testcontainers-spock")); } - if (generatorContext.isFeaturePresent(MongoFeature.class) || generatorContext.isFeaturePresent(MongoGorm.class)) { + if (generatorContext.isFeaturePresent(MongoFeature.class) || generatorContext.isFeaturePresent(GrailsDataMongoDB.class)) { generatorContext.addDependency(testContainerTestDependency("testcontainers-mongodb")); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/grails/GrailsBase.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/grails/GrailsBase.java index a784fc573a5..ae4df33112b 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/grails/GrailsBase.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/grails/GrailsBase.java @@ -24,6 +24,7 @@ import org.grails.forge.build.dependencies.Dependency; import org.grails.forge.feature.DefaultFeature; import org.grails.forge.feature.Feature; +import org.grails.forge.feature.database.GrailsDataHibernate7; import org.grails.forge.feature.micronaut.GrailsMicronaut; import org.grails.forge.options.Options; import org.grails.forge.template.URLTemplate; @@ -56,10 +57,18 @@ public boolean isVisible() { @Override public void apply(GeneratorContext generatorContext) { - // When Micronaut is used, the application must consume grails-micronaut-bom as an - // enforcedPlatform so the Micronaut platform cannot override grails-bom + // When Micronaut is used, the application must consume the Micronaut BOM variant as an + // enforcedPlatform so the Micronaut platform cannot override the Grails BOM. The + // Hibernate 7 BOM variants pin the Hibernate 7 dependency versions (e.g. jandex) that + // conflict with the default (Hibernate 5) grails-bom - see grails-core issue #15942. boolean useMicronautBom = generatorContext.isFeaturePresent(GrailsMicronaut.class); - String bomArtifactId = useMicronautBom ? "grails-micronaut-bom" : "grails-bom"; + boolean useHibernate7Bom = generatorContext.isFeaturePresent(GrailsDataHibernate7.class); + String bomArtifactId; + if (useMicronautBom) { + bomArtifactId = useHibernate7Bom ? "grails-hibernate7-micronaut-bom" : "grails-micronaut-bom"; + } else { + bomArtifactId = useHibernate7Bom ? "grails-hibernate7-bom" : "grails-bom"; + } generatorContext.addDependency(Dependency.builder() .groupId("org.apache.grails") @@ -71,7 +80,7 @@ public void apply(GeneratorContext generatorContext) { generatorContext.addBuildscriptDependency(Dependency.builder() .groupId("org.apache.grails") - .artifactId("grails-bom") + .artifactId(bomArtifactId) .pom(true) .version("$grailsVersion") .classpath()); diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/migration/DatabaseMigrationPlugin.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/migration/DatabaseMigrationPlugin.java index 413d849cbab..70155a58e11 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/migration/DatabaseMigrationPlugin.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/migration/DatabaseMigrationPlugin.java @@ -21,6 +21,7 @@ import jakarta.inject.Singleton; import org.grails.forge.application.generator.GeneratorContext; import org.grails.forge.build.dependencies.Dependency; +import org.grails.forge.feature.database.GrailsDataHibernate7; import org.grails.forge.feature.migration.templates.dbMigrationGradle; import org.grails.forge.template.RockerWritable; import org.grails.forge.template.URLTemplate; @@ -57,14 +58,17 @@ public String getDocumentation() { @Override public void apply(GeneratorContext generatorContext) { final String srcDirPath = getSrcDirPath(); + final String dbMigrationArtifactId = generatorContext.isFeaturePresent(GrailsDataHibernate7.class) + ? "grails-data-hibernate7-dbmigration" + : "grails-data-hibernate5-dbmigration"; generatorContext.addBuildscriptDependency(Dependency.builder() .groupId("org.apache.grails") - .artifactId("grails-data-hibernate5-dbmigration") + .artifactId(dbMigrationArtifactId) .buildSrc() .extension(new RockerWritable(dbMigrationGradle.template(srcDirPath)))); generatorContext.addDependency(Dependency.builder() .groupId("org.apache.grails") - .artifactId("grails-data-hibernate5-dbmigration") + .artifactId(dbMigrationArtifactId) .implementation()); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java index 3bfc9ac5d24..1c53118c8e4 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java @@ -23,6 +23,7 @@ public enum GormImpl { HIBERNATE("gorm-hibernate5", "Hibernate 5"), + HIBERNATE7("gorm-hibernate7", "Hibernate 7"), MONGODB("gorm-mongodb", "MongoDB"); public static final GormImpl DEFAULT_OPTION = HIBERNATE; diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/HibernateGormSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate5Spec.groovy similarity index 97% rename from grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/HibernateGormSpec.groovy rename to grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate5Spec.groovy index d24c6c79360..7aae8c4a2dd 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/HibernateGormSpec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate5Spec.groovy @@ -31,7 +31,7 @@ import org.grails.forge.options.JdkVersion import org.grails.forge.options.Options import org.grails.forge.options.TestFramework -class HibernateGormSpec extends ApplicationContextSpec implements CommandOutputFixture{ +class GrailsDataHibernate5Spec extends ApplicationContextSpec implements CommandOutputFixture{ void "test hibernate gorm features"() { when: diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy new file mode 100644 index 00000000000..c795b6e0394 --- /dev/null +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy @@ -0,0 +1,199 @@ +/* + * 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 + * + * https://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.grails.forge.feature.database + +import groovy.yaml.YamlSlurper +import org.grails.forge.ApplicationContextSpec +import org.grails.forge.BuildBuilder +import org.grails.forge.application.ApplicationType +import org.grails.forge.application.generator.GeneratorContext +import org.grails.forge.feature.Features +import org.grails.forge.fixture.CommandOutputFixture +import org.grails.forge.options.DevelopmentReloading +import org.grails.forge.options.GormImpl +import org.grails.forge.options.JdkVersion +import org.grails.forge.options.Options +import org.grails.forge.options.ServletImpl + +class GrailsDataHibernate7Spec extends ApplicationContextSpec implements CommandOutputFixture { + + private static Options hibernate7Options() { + new Options(DevelopmentReloading.DEVTOOLS, GormImpl.HIBERNATE7, ServletImpl.DEFAULT_OPTION, JdkVersion.DEFAULT_OPTION) + } + + void "test hibernate 7 gorm features"() { + when: + Features features = getFeatures(['gorm-hibernate7']) + + then: + features.contains("h2") + features.contains("gorm-hibernate7") + !features.contains("gorm-hibernate5") + } + + void "test dependencies are present for gradle"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7"]) + .render() + + then: + template.contains('implementation "org.apache.grails:grails-data-hibernate7"') + template.contains('runtimeOnly "com.zaxxer:HikariCP"') + template.contains('runtimeOnly "com.h2database:h2"') + !template.contains('grails-data-hibernate5') + } + + void "test hibernate 7 bom is used for gradle"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7"]) + .render() + + then: + template.contains('org.apache.grails:grails-hibernate7-bom') + !template.contains('org.apache.grails:grails-bom') + } + + void "test hibernate 7 micronaut bom is used when micronaut is added"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7", "grails-micronaut"]) + .jdkVersion(JdkVersion.JDK_25) + .render() + + then: 'the application consumes the Hibernate 7 Micronaut BOM variant as an enforced platform' + template.contains('implementation enforcedPlatform("org.apache.grails:grails-hibernate7-micronaut-bom:$grailsVersion")') + !template.contains('org.apache.grails:grails-micronaut-bom') + + and: 'the buildscript classpath uses the Hibernate 7 Micronaut BOM variant' + template.contains('classpath platform("org.apache.grails:grails-hibernate7-micronaut-bom:$grailsVersion")') + !template.contains('org.apache.grails:grails-hibernate7-bom:') + } + + void "test buildSrc uses the hibernate 7 micronaut bom when micronaut is added"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7", "grails-micronaut"]) + .jdkVersion(JdkVersion.JDK_25) + .renderBuildSrc() + + then: + template.contains('implementation platform("org.apache.grails:grails-hibernate7-micronaut-bom:$grailsVersion")') + !template.contains('org.apache.grails:grails-hibernate7-bom:') + !template.contains('org.apache.grails:grails-bom:') + } + + void "test buildSrc uses the micronaut bom when micronaut is added without hibernate 7"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate5", "grails-micronaut"]) + .jdkVersion(JdkVersion.JDK_25) + .renderBuildSrc() + + then: + template.contains('implementation platform("org.apache.grails:grails-micronaut-bom:$grailsVersion")') + !template.contains('grails-hibernate7-micronaut-bom') + !template.contains('org.apache.grails:grails-bom:') + } + + void "test hibernate 5 micronaut bom is used when micronaut is added without hibernate 7"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate5", "grails-micronaut"]) + .jdkVersion(JdkVersion.JDK_25) + .render() + + then: + template.contains('org.apache.grails:grails-micronaut-bom') + !template.contains('grails-hibernate7-micronaut-bom') + } + + void "test dependencies are present for buildSrc"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7"]) + .renderBuildSrc() + + then: + template.contains('implementation platform("org.apache.grails:grails-hibernate7-bom:$grailsVersion")') + template.contains('implementation "org.apache.grails:grails-data-hibernate7"') + !template.contains('org.apache.grails:grails-bom') + !template.contains('grails-data-hibernate5') + } + + void "test buildSrc is present for buildscript dependencies"() { + given: + final def output = generate(ApplicationType.WEB, hibernate7Options()) + final def buildGradle = output["build.gradle"] + + expect: + buildGradle != null + buildGradle.contains("classpath \"org.apache.grails:grails-data-hibernate7\"") + !buildGradle.contains("grails-data-hibernate5") + } + + void "test database migration uses the hibernate 7 artifact"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7", "database-migration"]) + .render() + + then: + template.contains('grails-data-hibernate7-dbmigration') + !template.contains('grails-data-hibernate5-dbmigration') + } + + void "test selecting both hibernate implementations is rejected"() { + when: + getFeatures(['gorm-hibernate5', 'gorm-hibernate7']) + + then: + IllegalArgumentException e = thrown() + e.message.contains('Only one GORM for Hibernate implementation can be selected') + } + + void "test config"() { + when: + GeneratorContext ctx = buildGeneratorContext(['gorm-hibernate7']) + + then: + ctx.configuration.containsKey("dataSource.pooled") + ctx.configuration.containsKey("dataSource.jmxExport") + ctx.configuration.containsKey("hibernate.cache.queries") + ctx.configuration.containsKey("hibernate.cache.use_second_level_cache") + ctx.configuration.containsKey("hibernate.cache.use_query_cache") + } + + void "test match values of datasource config"() { + when: + final def output = generate(ApplicationType.WEB, hibernate7Options()) + final String applicationYaml = output["grails-app/conf/application.yml"] + def config = new YamlSlurper().parseText(applicationYaml) + + then: + config.environments.development.dataSource.dbCreate == 'create-drop' + config.environments.test.dataSource.dbCreate == 'update' + config.environments.production.dataSource.dbCreate == 'none' + config.environments.development.dataSource.url == 'jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE' + config.environments.test.dataSource.url == 'jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE' + config.environments.production.dataSource.url == 'jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE' + } +} diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/MongoGormSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy similarity index 95% rename from grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/MongoGormSpec.groovy rename to grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy index c8a0eda4087..cd516d01341 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/MongoGormSpec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy @@ -25,7 +25,7 @@ import org.grails.forge.application.generator.GeneratorContext import org.grails.forge.feature.Features import org.grails.forge.fixture.CommandOutputFixture -class MongoGormSpec extends ApplicationContextSpec implements CommandOutputFixture { +class GrailsDataMongoDBSpec extends ApplicationContextSpec implements CommandOutputFixture { void "test Mongo gorm features"() { when: From 4d81ad43af50a74f46dfdeea94e7be3ee666532f Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 10:50:54 -0400 Subject: [PATCH 2/9] updating command switches, renaming Gorm => Grails Data, & renaming Hibernate => Hibernate5 --- .../en/guide/commandLine/creatingProject.adoc | 24 +++---- .../gradleBuild/gradlePlugins.adoc | 2 +- .../en/guide/commandLine/interactiveMode.adoc | 6 +- .../src/en/ref/Command Line/create-app.adoc | 10 +-- .../en/ref/Command Line/create-plugin.adoc | 4 +- .../en/ref/Command Line/create-restapi.adoc | 2 +- .../ref/Command Line/create-web-plugin.adoc | 2 +- grails-doc/src/en/ref/Command Line/help.adoc | 6 +- .../org/grails/forge/api/GormImplDTO.java | 6 +- .../grails/forge/api/SelectOptionsDTO.java | 2 +- .../org/grails/forge/api/ServletImplDTO.java | 2 +- .../forge/cli/command/CreateCommand.java | 2 +- .../forge/cli/command/GormImplConverter.java | 12 ++-- .../cli/command/CreateAppCommandSpec.groovy | 67 ++++++++++++++++++- .../org/grails/forge/analytics/Generated.java | 2 +- .../database/GrailsDataHibernate5.java | 6 +- .../database/GrailsDataHibernate7.java | 4 +- .../GrailsDataHibernateValidator.java | 2 +- .../feature/database/GrailsDataMongoDB.java | 4 +- .../forge/feature/database/GraphqlGorm.java | 4 +- .../org/grails/forge/feature/database/H2.java | 2 +- .../org/grails/forge/options/GormImpl.java | 32 ++++++++- .../forge/options/GormImplTypeConverter.java | 38 +++++++++++ .../database/GrailsDataHibernate7Spec.groovy | 2 +- .../grails/forge/options/GormImplSpec.groovy | 48 +++++++++++++ 25 files changed, 234 insertions(+), 57 deletions(-) create mode 100644 grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java create mode 100644 grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/options/GormImplSpec.groovy diff --git a/grails-doc/src/en/guide/commandLine/creatingProject.adoc b/grails-doc/src/en/guide/commandLine/creatingProject.adoc index 7bd657235bb..87b058a304f 100644 --- a/grails-doc/src/en/guide/commandLine/creatingProject.adoc +++ b/grails-doc/src/en/guide/commandLine/creatingProject.adoc @@ -51,7 +51,7 @@ After generating a project with Forge, switch to the created directory and use t [none] * -jdk, --java-version * -s, --servlet -* -g, --gorm +* -d, --data (-g, --gorm are legacy aliases) * -t, --test * -f, --features * -i, --inplace @@ -61,7 +61,7 @@ After generating a project with Forge, switch to the created directory and use t ./grails -t forge create-app \ --servlet=tomcat \ --jdk=11 \ - --gorm=hibernate \ + --data=hibernate5 \ --test=spock \ --features=github-workflow-java-ci \ com.example.demo @@ -73,7 +73,7 @@ After generating a project with Forge, switch to the created directory and use t [none] * -jdk, --java-version * -s, --servlet -* -g, --gorm +* -d, --data (-g, --gorm are legacy aliases) * -t, --test * -f, --features * -i, --inplace @@ -83,7 +83,7 @@ After generating a project with Forge, switch to the created directory and use t ./grails -t forge create-restapi \ --servlet=tomcat \ --jdk=11 \ - --gorm=hibernate \ + --data=hibernate5 \ --test=spock \ --features=github-workflow-java-ci \ com.example.demo @@ -95,7 +95,7 @@ After generating a project with Forge, switch to the created directory and use t [none] * -jdk, --java-version * -s, --servlet -* -g, --gorm +* -d, --data (-g, --gorm are legacy aliases) * -t, --test * -f, --features * -i, --inplace @@ -105,7 +105,7 @@ After generating a project with Forge, switch to the created directory and use t ./grails -t forge create-plugin \ --servlet=tomcat \ --jdk=11 \ - --gorm=hibernate \ + --data=hibernate5 \ --test=spock \ --features=github-workflow-java-ci \ com.example.demo @@ -117,7 +117,7 @@ After generating a project with Forge, switch to the created directory and use t [none] * -jdk, --java-version * -s, --servlet -* -g, --gorm +* -d, --data (-g, --gorm are legacy aliases) * -t, --test * -f, --features * -i, --inplace @@ -127,7 +127,7 @@ After generating a project with Forge, switch to the created directory and use t ./grails -t forge create-web-plugin \ --servlet=tomcat \ --jdk=11 \ - --gorm=hibernate \ + --data=hibernate5 \ --test=spock \ --features=github-workflow-java-ci \ com.example.demo @@ -136,7 +136,7 @@ After generating a project with Forge, switch to the created directory and use t === The `create-` command flags -The "create-*" commands are used to produce a fundamental Grails project, allowing for the inclusion of optional flags to select additional features, to customize GORM settings, an embedded servlet, the test framework, and the Java version. +The "create-*" commands are used to produce a fundamental Grails project, allowing for the inclusion of optional flags to select additional features, to customize Grails Data settings, an embedded servlet, the test framework, and the Java version. [cols="4,8,6a"] |=== @@ -158,12 +158,12 @@ The "create-*" commands are used to produce a fundamental Grails project, allowi --servlet=tomcat ---- -| -g, --gorm -| Which GORM Implementation to configure. Possible values: hibernate, hibernate7, mongodb. +| -d, --data (-g, --gorm are legacy aliases) +| Which Grails Data implementation to configure. Possible values: hibernate5, hibernate7, mongodb. The value hibernate is accepted as a legacy alias for hibernate5. | [source,shell] ---- ---gorm hibernate +--data hibernate5 ---- | -t, --test diff --git a/grails-doc/src/en/guide/commandLine/gradleBuild/gradlePlugins.adoc b/grails-doc/src/en/guide/commandLine/gradleBuild/gradlePlugins.adoc index 8ee5c8672f4..4c55b7b0813 100644 --- a/grails-doc/src/en/guide/commandLine/gradleBuild/gradlePlugins.adoc +++ b/grails-doc/src/en/guide/commandLine/gradleBuild/gradlePlugins.adoc @@ -20,7 +20,7 @@ under the License. The `create-app` command is available in both the Grails Shell CLI and the Forge CLI. - Use `grails create-app myapp` to generate a default project using the Grails Shell CLI. This is the most common usage. -- Use `grails -t forge create-app myapp` to access advanced application generation options such as `--jdk`, `--gorm`, and other Forge-only flags. +- Use `grails -t forge create-app myapp` to access advanced application generation options such as `--jdk`, `--data`, and other Forge-only flags. The example below assumes you are using the Forge CLI, which is solely intended for **application generation**. It does not support running, building, or managing a Grails application. diff --git a/grails-doc/src/en/guide/commandLine/interactiveMode.adoc b/grails-doc/src/en/guide/commandLine/interactiveMode.adoc index e111e0f25d0..706fc91a96f 100644 --- a/grails-doc/src/en/guide/commandLine/interactiveMode.adoc +++ b/grails-doc/src/en/guide/commandLine/interactiveMode.adoc @@ -75,9 +75,9 @@ Creates an application grails-console, views-markup, asset-pipeline-grails, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, geb, properties - -g, --gorm=GORM Implementation - Which GORM Implementation to configure. Possible values: hibernate, - mongodb, neo4j. + -d, --data, -g, --gorm=Grails Data Implementation + Which Grails Data implementation to configure (-g, --gorm are legacy + aliases). Possible values: hibernate5, hibernate7, mongodb. -h, --help Show this help message and exit. -i, --inplace Create a service using the current directory --jdk, --java-version= diff --git a/grails-doc/src/en/ref/Command Line/create-app.adoc b/grails-doc/src/en/ref/Command Line/create-app.adoc index a12deccbf39..8fec687d539 100644 --- a/grails-doc/src/en/ref/Command Line/create-app.adoc +++ b/grails-doc/src/en/ref/Command Line/create-app.adoc @@ -29,7 +29,7 @@ In Grails {grailsMajorVersion}, `create-app` is available in both the **Grails S - Most users should use the **Grails Shell CLI** by default (`grails create-app bookstore`). This version supports core development commands and project lifecycle management, and includes basic `create-*` flags like `--inplace`, `--features`, and `--profile`. -- The **Forge CLI** (`grails -t forge create-app bookstore`) is intended solely for **application generation** with extended configuration flags such as `--jdk`, `--gorm`, and `--servlet`. +- The **Forge CLI** (`grails -t forge create-app bookstore`) is intended solely for **application generation** with extended configuration flags such as `--jdk`, `--data`, and `--servlet`. Forge-specific flags will result in an error if used with the Shell CLI. ==== @@ -50,7 +50,7 @@ This uses the **Forge CLI** to generate an application with extended configurati ---- $ grails -t forge create-app bookstore \ --jdk=17 \ - --gorm=hibernate \ + --data=hibernate5 \ --servlet=tomcat \ --test=spock \ --features=asciidoctor,github-workflow-java-ci @@ -62,7 +62,7 @@ These options are available only when using `grails -t forge`: - `NAME`: The desired name for the application. - `-f, --features=FEATURE[,FEATURE...]`: Enable features like `gorm-hibernate5`, `postgres`, `asciidoctor`, `grails-gsp`, etc. -- `-g, --gorm=GORM Implementation`: Choose GORM implementation: `hibernate`, `mongodb`, `neo4j`. +- `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Choose the Grails Data implementation: `hibernate5`, `hibernate7`, `mongodb`. - `--jdk=`: Target JDK version (e.g., 11 or 17). - `-s, --servlet=Servlet Implementation`: Choose embedded servlet: `tomcat`, `jetty`, `undertow`, `none`. - `-t, --test=TEST`: Select test framework: `spock`, `junit`. @@ -83,7 +83,7 @@ Available flags: - `--features` _(limited support depending on profile)_ - `--profile` -Forge-only options such as `--jdk`, `--gorm`, or `--servlet` are **not supported** in the Shell CLI. +Forge-only options such as `--jdk`, `--data`, or `--servlet` are **not supported** in the Shell CLI. === Examples @@ -101,7 +101,7 @@ $ cd bookstore ---- $ grails -t forge create-app bookstore \ --jdk=17 \ - --gorm=hibernate \ + --data=hibernate5 \ --features=asciidoctor,grails-gsp ---- diff --git a/grails-doc/src/en/ref/Command Line/create-plugin.adoc b/grails-doc/src/en/ref/Command Line/create-plugin.adoc index ee9f88f0445..d4561a6c4dd 100644 --- a/grails-doc/src/en/ref/Command Line/create-plugin.adoc +++ b/grails-doc/src/en/ref/Command Line/create-plugin.adoc @@ -71,7 +71,7 @@ Options include specifying features, configuring the GORM implementation, select Here are the available options for the create-plugin command: - -f, --features=FEATURE[,FEATURE...]: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. -- -g, --gorm=GORM Implementation: Specifies the GORM Implementation to configure for the plugin. Possible values are hibernate, mongodb, neo4j. +- -d, --data=Grails Data Implementation (-g, --gorm are legacy aliases): Specifies the Grails Data implementation to configure for the plugin. Possible values are hibernate5, hibernate7, mongodb. - -h, --help: Displays the help message and exits. - -i, --inplace: Creates a service within the current directory. - --jdk=: Specifies the JDK version the project should target. @@ -106,7 +106,7 @@ This command will create a Grails plugin project named "minimal-plugin" with the + [source,shell] ---- -grails create-plugin mongodb-plugin --gorm=mongodb +grails create-plugin mongodb-plugin --data=mongodb ---- + This command will generate a Grails plugin project named "mongodb-plugin" configured to use MongoDB as the GORM implementation. diff --git a/grails-doc/src/en/ref/Command Line/create-restapi.adoc b/grails-doc/src/en/ref/Command Line/create-restapi.adoc index 69c47c943ca..ceef4c809c1 100644 --- a/grails-doc/src/en/ref/Command Line/create-restapi.adoc +++ b/grails-doc/src/en/ref/Command Line/create-restapi.adoc @@ -42,7 +42,7 @@ The `create-restapi` command accepts the following options: - `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include. Available options include h2, scaffolding, gorm-hibernate5, spring-boot-starter-jetty, spring-boot-starter-tomcat, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, asset-pipeline-grails, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, geb, properties. -- `-g, --gorm=GORM Implementation`: Specifies the GORM Implementation to configure, with options like hibernate, mongodb, neo4j. +- `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Specifies the Grails Data implementation to configure, with options hibernate5, hibernate7, mongodb. - `-h, --help`: Displays the help message and exits. diff --git a/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc b/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc index 02d3a48c916..51a72ec1910 100644 --- a/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc +++ b/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc @@ -40,7 +40,7 @@ Here are the available options for the `create-web-plugin` command: - `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. -- `-g, --gorm=GORM Implementation`: Specifies the GORM Implementation to configure for the plugin. Possible values are hibernate, mongodb, neo4j. +- `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Specifies the Grails Data implementation to configure for the plugin. Possible values are hibernate5, hibernate7, mongodb. - `-h, --help`: Displays the help message and exits. diff --git a/grails-doc/src/en/ref/Command Line/help.adoc b/grails-doc/src/en/ref/Command Line/help.adoc index d4290d21ce1..de78f614316 100644 --- a/grails-doc/src/en/ref/Command Line/help.adoc +++ b/grails-doc/src/en/ref/Command Line/help.adoc @@ -148,9 +148,9 @@ Creates an application grails-console, views-markup, asset-pipeline-grails, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, geb, properties - -g, --gorm=GORM Implementation - Which GORM Implementation to configure. Possible values: hibernate, - mongodb, neo4j. + -d, --data, -g, --gorm=Grails Data Implementation + Which Grails Data implementation to configure (-g, --gorm are legacy + aliases). Possible values: hibernate5, hibernate7, mongodb. -h, --help Show this help message and exit. -i, --inplace Create a service using the current directory --jdk, --java-version= diff --git a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/GormImplDTO.java b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/GormImplDTO.java index af0e8a9a343..7cfd1c299ce 100644 --- a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/GormImplDTO.java +++ b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/GormImplDTO.java @@ -85,7 +85,7 @@ public GormImplDTO(GormImpl gormImpl) { @NonNull @Override - @Schema(description = "A description of the GORM Implementation") + @Schema(description = "A description of the Grails Data implementation") public String getDescription() { return description; } @@ -98,13 +98,13 @@ public String getName() { } @Override - @Schema(description = "The value of the GORM Implementation for select options") + @Schema(description = "The value of the Grails Data implementation for select options") public GormImpl getValue() { return value; } @Override - @Schema(description = "The label of the GORM Implementation for select options") + @Schema(description = "The label of the Grails Data implementation for select options") public String getLabel() { return value.getLabel(); } diff --git a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/SelectOptionsDTO.java b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/SelectOptionsDTO.java index f25ee04940f..319a9dfd01b 100644 --- a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/SelectOptionsDTO.java +++ b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/SelectOptionsDTO.java @@ -90,7 +90,7 @@ public DevelopmentReloadingSelectOptions getReloading() { return reloading; } - @Schema(description = "supported options for GORM Implementation") + @Schema(description = "supported options for the Grails Data implementation") public GormImplSelectOptions getGorm() { return gorm; } diff --git a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/ServletImplDTO.java b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/ServletImplDTO.java index f1c72e6c598..3af07892efd 100644 --- a/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/ServletImplDTO.java +++ b/grails-forge/grails-forge-api/src/main/java/org/grails/forge/api/ServletImplDTO.java @@ -74,7 +74,7 @@ public ServletImplDTO(ServletImpl servletImpl) { @NonNull @Override - @Schema(description = "A description of the GORM Implementation") + @Schema(description = "A description of the Servlet implementation") public String getDescription() { return description; } diff --git a/grails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/command/CreateCommand.java b/grails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/command/CreateCommand.java index aa478ae6749..a65722da2c4 100644 --- a/grails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/command/CreateCommand.java +++ b/grails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/command/CreateCommand.java @@ -50,7 +50,7 @@ public abstract class CreateCommand extends BaseCommand implements Callable { public GormImpl convert(String value) throws Exception { if (value == null) { return GormImpl.DEFAULT_OPTION; - } else { - for (GormImpl impl : GormImpl.values()) { - if (value.equalsIgnoreCase(impl.toString())) { - return impl; - } - } } - throw new CommandLine.TypeConversionException("Invalid GORM implementation selection: " + value); + GormImpl impl = GormImpl.parse(value); + if (impl != null) { + return impl; + } + throw new CommandLine.TypeConversionException("Invalid Grails Data implementation selection: " + value); } } diff --git a/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy b/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy index 23f7adc642c..7aceef22145 100644 --- a/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy +++ b/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy @@ -64,7 +64,72 @@ class CreateAppCommandSpec extends CommandSpec implements CommandFixture { then: noExceptionThrown() - baos.toString().contains("Invalid GORM implementation selection: xyz") + baos.toString().contains("Invalid Grails Data implementation selection: xyz") + } + + void "test creating a project with the data switch"() { + given: + ByteArrayOutputStream out = new ByteArrayOutputStream() + System.setOut(new PrintStream(out)) + + when: + PicocliRunner.run(CreateAppCommand, ctx, "dataswitch", "--data", "hibernate7") + + then: + noExceptionThrown() + out.toString().contains("Application created") + } + + void "test creating a project with the -d short alias"() { + given: + ByteArrayOutputStream out = new ByteArrayOutputStream() + System.setOut(new PrintStream(out)) + + when: + PicocliRunner.run(CreateAppCommand, ctx, "dshort", "-d", "hibernate7") + + then: + noExceptionThrown() + out.toString().contains("Application created") + } + + void "test creating a project with the legacy -g short alias"() { + given: + ByteArrayOutputStream out = new ByteArrayOutputStream() + System.setOut(new PrintStream(out)) + + when: + PicocliRunner.run(CreateAppCommand, ctx, "gshort", "-g", "hibernate5") + + then: + noExceptionThrown() + out.toString().contains("Application created") + } + + void "test creating a project with the legacy hibernate gorm value"() { + given: + ByteArrayOutputStream out = new ByteArrayOutputStream() + System.setOut(new PrintStream(out)) + + when: + PicocliRunner.run(CreateAppCommand, ctx, "legacygorm", "--gorm", "hibernate") + + then: + noExceptionThrown() + out.toString().contains("Application created") + } + + void "test creating a project with the hibernate5 gorm value"() { + given: + ByteArrayOutputStream out = new ByteArrayOutputStream() + System.setOut(new PrintStream(out)) + + when: + PicocliRunner.run(CreateAppCommand, ctx, "hib5gorm", "--gorm", "hibernate5") + + then: + noExceptionThrown() + out.toString().contains("Application created") } void "community and preview features are labelled as such"() { diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/analytics/Generated.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/analytics/Generated.java index 408933655ae..e37e2431f0d 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/analytics/Generated.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/analytics/Generated.java @@ -47,7 +47,7 @@ public Generated( @NonNull DevelopmentReloading reloading, @NonNull JdkVersion jdkVersion) { this.type = Objects.requireNonNull(type, "Type cannot be null"); - this.gorm = Objects.requireNonNull(gorm, "GORM cannot be null"); + this.gorm = Objects.requireNonNull(gorm, "Grails Data implementation cannot be null"); this.servlet = Objects.requireNonNull(servlet, "Embedded Servlet cannot be null"); this.reloading = Objects.requireNonNull(reloading, "Development reloading cannot be null"); this.jdkVersion = Objects.requireNonNull(jdkVersion, "JDK version cannot be null"); diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java index 280fb3d9b8b..9bc9d538a66 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java @@ -55,12 +55,12 @@ public String getName() { @Override public String getTitle() { - return "GORM for Hibernate 5"; + return "Grails Data for Hibernate 5"; } @Override public String getDescription() { - return "Configure GORM for using Hibernate 5."; + return "Configure Grails Data for using Hibernate 5."; } @Override @@ -124,7 +124,7 @@ public String getDbCreateKey() { @Override public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { return selectedFeatures.stream().anyMatch(f -> f instanceof GrailsDataHibernate5) - || (options.getGormImpl() == GormImpl.HIBERNATE + || (options.getGormImpl() == GormImpl.HIBERNATE5 && selectedFeatures.stream().noneMatch(GrailsDataHibernate7.class::isInstance)); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java index 43f7c0a4b72..834a3b2cfa5 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java @@ -53,12 +53,12 @@ public String getName() { @Override public String getTitle() { - return "GORM for Hibernate 7"; + return "Grails Data for Hibernate 7"; } @Override public String getDescription() { - return "Configure GORM for using Hibernate 7."; + return "Configure Grails Data for using Hibernate 7."; } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java index 912e163c495..f69ce47e0e4 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java @@ -34,7 +34,7 @@ public class GrailsDataHibernateValidator implements FeatureValidator { public void validatePreProcessing(Options options, ApplicationType applicationType, Set features) { if (features.stream().anyMatch(GrailsDataHibernate5.class::isInstance) && features.stream().anyMatch(GrailsDataHibernate7.class::isInstance)) { - throw new IllegalArgumentException("Only one GORM for Hibernate implementation can be selected: gorm-hibernate5 or gorm-hibernate7"); + throw new IllegalArgumentException("Only one Grails Data for Hibernate implementation can be selected: gorm-hibernate5 or gorm-hibernate7"); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java index 94df495e9f8..254863aac79 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataMongoDB.java @@ -47,12 +47,12 @@ public String getName() { @Override public String getTitle() { - return "GORM for MongoDB"; + return "Grails Data for MongoDB"; } @Override public String getDescription() { - return "Configure GORM for using MongoDB."; + return "Configure Grails Data for using MongoDB."; } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java index 1e06d0975fc..81650a688f0 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GraphqlGorm.java @@ -52,12 +52,12 @@ public String getName() { @Override public String getTitle() { - return "GORM for GraphQL"; + return "Grails Data for GraphQL"; } @Override public String getDescription() { - return "Generates a GraphQL schema based on entities in GORM."; + return "Generates a GraphQL schema based on entities in Grails Data."; } @Override diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java index 19be11c6bde..5cf1b5ac166 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/H2.java @@ -100,7 +100,7 @@ public void apply(GeneratorContext generatorContext) { @Override public boolean shouldApply(ApplicationType applicationType, Options options, Set selectedFeatures) { - return (options.getGormImpl() == GormImpl.HIBERNATE || options.getGormImpl() == GormImpl.HIBERNATE7) && + return (options.getGormImpl() == GormImpl.HIBERNATE5 || options.getGormImpl() == GormImpl.HIBERNATE7) && selectedFeatures.stream().noneMatch(f -> f instanceof DatabaseDriverFeature); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java index 1c53118c8e4..1f071e35d58 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImpl.java @@ -19,14 +19,19 @@ package org.grails.forge.options; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; public enum GormImpl { - HIBERNATE("gorm-hibernate5", "Hibernate 5"), + HIBERNATE5("gorm-hibernate5", "Hibernate 5"), HIBERNATE7("gorm-hibernate7", "Hibernate 7"), MONGODB("gorm-mongodb", "MongoDB"); - public static final GormImpl DEFAULT_OPTION = HIBERNATE; + public static final GormImpl DEFAULT_OPTION = HIBERNATE5; + + // Selection value accepted before HIBERNATE5 replaced the HIBERNATE constant + private static final String LEGACY_HIBERNATE_VALUE = "hibernate"; + private final String featureName; private final String label; @@ -45,4 +50,27 @@ public String getLabel() { return label; } + /** + * Resolves a user-supplied selection value to a {@link GormImpl}. + * + * @param value the selection value (case-insensitive), e.g. {@code hibernate5}; + * the legacy value {@code hibernate} resolves to {@link #HIBERNATE5} + * @return the matching implementation, or {@code null} when the value is unknown + */ + @Nullable + public static GormImpl parse(@Nullable String value) { + if (value == null) { + return null; + } + if (LEGACY_HIBERNATE_VALUE.equalsIgnoreCase(value)) { + return HIBERNATE5; + } + for (GormImpl impl : values()) { + if (value.equalsIgnoreCase(impl.name())) { + return impl; + } + } + return null; + } + } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java new file mode 100644 index 00000000000..eb8624fe771 --- /dev/null +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java @@ -0,0 +1,38 @@ +/* + * 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 + * + * https://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.grails.forge.options; + +import java.util.Optional; + +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.TypeConverter; +import jakarta.inject.Singleton; + +/** + * Converts user-supplied selection values (including the legacy {@code hibernate} + * value) to {@link GormImpl} for HTTP parameter binding. + */ +@Singleton +public class GormImplTypeConverter implements TypeConverter { + + @Override + public Optional convert(CharSequence object, Class targetType, ConversionContext context) { + return Optional.ofNullable(GormImpl.parse(object.toString())); + } +} diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy index c795b6e0394..5c22075f5aa 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy @@ -167,7 +167,7 @@ class GrailsDataHibernate7Spec extends ApplicationContextSpec implements Command then: IllegalArgumentException e = thrown() - e.message.contains('Only one GORM for Hibernate implementation can be selected') + e.message.contains('Only one Grails Data for Hibernate implementation can be selected') } void "test config"() { diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/options/GormImplSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/options/GormImplSpec.groovy new file mode 100644 index 00000000000..83343239188 --- /dev/null +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/options/GormImplSpec.groovy @@ -0,0 +1,48 @@ +/* + * 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 + * + * https://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.grails.forge.options + +import spock.lang.Specification +import spock.lang.Unroll + +class GormImplSpec extends Specification { + + @Unroll + void "parse resolves #value to #expected"() { + expect: + GormImpl.parse(value) == expected + + where: + value | expected + 'hibernate5' | GormImpl.HIBERNATE5 + 'HIBERNATE5' | GormImpl.HIBERNATE5 + 'hibernate' | GormImpl.HIBERNATE5 + 'HIBERNATE' | GormImpl.HIBERNATE5 + 'hibernate7' | GormImpl.HIBERNATE7 + 'mongodb' | GormImpl.MONGODB + 'neo4j' | null + null | null + } + + void "the default option is Hibernate 5"() { + expect: + GormImpl.DEFAULT_OPTION == GormImpl.HIBERNATE5 + } +} From 3b39d9c313c488fe87e833133e5ee7af7b648903 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 11:07:33 -0400 Subject: [PATCH 3/9] Add what's new --- grails-doc/src/en/guide/introduction/whatsNew.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/grails-doc/src/en/guide/introduction/whatsNew.adoc b/grails-doc/src/en/guide/introduction/whatsNew.adoc index 482b962915d..e0ccfdc4f1c 100644 --- a/grails-doc/src/en/guide/introduction/whatsNew.adoc +++ b/grails-doc/src/en/guide/introduction/whatsNew.adoc @@ -39,6 +39,20 @@ This brings the Spring Boot 4 modular artifact layout, Spring Framework 7 API re The Grails 8 upgrade guide calls out the major application-impacting changes and links to the Spring Boot 4.0 migration guide, Spring Boot 4.1 release notes and Spring Framework 7.0 release notes. +==== Hibernate 7 Application Generation + +Grails Forge can now generate applications configured for Grails Data with Hibernate 7. +The `create-*` commands also accept a new `-d, --data` option (the previous `-g` and `--gorm` flags remain supported as legacy aliases) with the values `hibernate5`, `hibernate7`, and `mongodb`; the legacy value `hibernate` is still accepted and selects Hibernate 5: + +[source,console] +---- +grails -t forge create-app --data=hibernate7 com.example.demo +---- + +Applications generated for Hibernate 7 consume `grails-hibernate7-bom` — or `grails-hibernate7-micronaut-bom` when combined with the `grails-micronaut` feature — consistently across the application dependencies, the buildscript classpath, and `buildSrc`. +This avoids the dependency resolution conflicts that occur when an application generated for Hibernate 5 is manually switched to `grails-data-hibernate7` while the default `grails-bom` remains on the build classpath. +The `database-migration` feature also selects the matching `grails-data-hibernate7-dbmigration` plugin automatically. + ==== GSP Tag Library Improvements Grails {grailsMajorVersion} continues the move toward method-based TagLib handlers while preserving compatibility with existing closure-based tags. From 3d10537b86aed7733c1bc0e1aa4fbdeada5e4d9e Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 14:08:09 -0400 Subject: [PATCH 4/9] Address review feedback - Do not auto-add the Hibernate 5 feature from database driver features when another Grails Data implementation (Hibernate 7 or MongoDB) is already selected - Validate the Hibernate 5/7 mutual exclusion after feature processing as well as before - Guard against null input in GormImplTypeConverter - Restore System.out/System.err after each CreateAppCommandSpec test --- .../cli/command/CreateAppCommandSpec.groovy | 17 +++++++++++---- .../database/DatabaseDriverFeature.java | 2 +- .../GrailsDataHibernateValidator.java | 12 +++++++---- .../forge/options/GormImplTypeConverter.java | 3 +++ .../database/GrailsDataHibernate7Spec.groovy | 21 +++++++++++++++++++ 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy b/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy index 7aceef22145..a842071ee69 100644 --- a/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy +++ b/grails-forge/grails-forge-cli/src/test/groovy/org/grails/forge/cli/command/CreateAppCommandSpec.groovy @@ -41,6 +41,19 @@ class CreateAppCommandSpec extends CommandSpec implements CommandFixture { @AutoCleanup ApplicationContext beanContext = ApplicationContext.run() + PrintStream originalOut + PrintStream originalErr + + void setup() { + originalOut = System.out + originalErr = System.err + } + + void cleanup() { + System.setOut(originalOut) + System.setErr(originalErr) + } + void "test creating project with defaults"() { given: ByteArrayOutputStream out = new ByteArrayOutputStream() @@ -135,7 +148,6 @@ class CreateAppCommandSpec extends CommandSpec implements CommandFixture { void "community and preview features are labelled as such"() { given: ByteArrayOutputStream baos = new ByteArrayOutputStream() - PrintStream old = System.out System.setOut(new PrintStream(baos)) and: @@ -152,8 +164,5 @@ class CreateAppCommandSpec extends CommandSpec implements CommandFixture { //baos.toString().contains("$previewFeature.name [PREVIEW]") communityFeature == null //baos.toString().contains("$communityFeature.name [COMMUNITY]") - - cleanup: - System.setOut(old) } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java index e2272efa673..958c8b47242 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java @@ -56,7 +56,7 @@ public void processSelectedFeatures(FeatureContext featureContext) { if (!featureContext.isPresent(TestContainers.class) && testContainers != null) { featureContext.addFeature(testContainers); } - if (!featureContext.isPresent(GrailsDataHibernate5.class) && grailsDataHibernate5 != null) { + if (!featureContext.isPresent(GormFeature.class) && grailsDataHibernate5 != null) { featureContext.addFeature(grailsDataHibernate5); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java index f69ce47e0e4..ce9db884294 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernateValidator.java @@ -32,14 +32,18 @@ public class GrailsDataHibernateValidator implements FeatureValidator { @Override public void validatePreProcessing(Options options, ApplicationType applicationType, Set features) { - if (features.stream().anyMatch(GrailsDataHibernate5.class::isInstance) - && features.stream().anyMatch(GrailsDataHibernate7.class::isInstance)) { - throw new IllegalArgumentException("Only one Grails Data for Hibernate implementation can be selected: gorm-hibernate5 or gorm-hibernate7"); - } + validateOnlyOneHibernateImplementation(features); } @Override public void validatePostProcessing(Options options, ApplicationType applicationType, Set features) { + validateOnlyOneHibernateImplementation(features); + } + private static void validateOnlyOneHibernateImplementation(Set features) { + if (features.stream().anyMatch(GrailsDataHibernate5.class::isInstance) + && features.stream().anyMatch(GrailsDataHibernate7.class::isInstance)) { + throw new IllegalArgumentException("Only one Grails Data for Hibernate implementation can be selected: gorm-hibernate5 or gorm-hibernate7"); + } } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java index eb8624fe771..0f2a4b0dbea 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java @@ -33,6 +33,9 @@ public class GormImplTypeConverter implements TypeConverter convert(CharSequence object, Class targetType, ConversionContext context) { + if (object == null) { + return Optional.empty(); + } return Optional.ofNullable(GormImpl.parse(object.toString())); } } diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy index 5c22075f5aa..d27b2e9e326 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataHibernate7Spec.groovy @@ -48,6 +48,27 @@ class GrailsDataHibernate7Spec extends ApplicationContextSpec implements Command !features.contains("gorm-hibernate5") } + void "test a database driver feature does not add hibernate 5 when hibernate 7 is selected"() { + when: + Features features = getFeatures(['gorm-hibernate7', 'postgres']) + + then: + features.contains("postgres") + features.contains("gorm-hibernate7") + !features.contains("gorm-hibernate5") + } + + void "test a database driver feature dependencies with hibernate 7 for gradle"() { + when: + final String template = new BuildBuilder(beanContext) + .features(["gorm-hibernate7", "postgres"]) + .render() + + then: + template.contains('implementation "org.apache.grails:grails-data-hibernate7"') + !template.contains('grails-data-hibernate5') + } + void "test dependencies are present for gradle"() { when: final String template = new BuildBuilder(beanContext) From eb8fc5480c139bd711eec904f2067e252fba0fb1 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 14:20:02 -0400 Subject: [PATCH 5/9] Update analytics spec for the GormImpl.HIBERNATE to HIBERNATE5 rename --- .../analytics/postgres/StoreGeneratedProjectStatsSpec.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-forge/grails-forge-analytics-postgres/src/test/groovy/org/grails/forge/analytics/postgres/StoreGeneratedProjectStatsSpec.groovy b/grails-forge/grails-forge-analytics-postgres/src/test/groovy/org/grails/forge/analytics/postgres/StoreGeneratedProjectStatsSpec.groovy index 681b7696424..517a6edd340 100644 --- a/grails-forge/grails-forge-analytics-postgres/src/test/groovy/org/grails/forge/analytics/postgres/StoreGeneratedProjectStatsSpec.groovy +++ b/grails-forge/grails-forge-analytics-postgres/src/test/groovy/org/grails/forge/analytics/postgres/StoreGeneratedProjectStatsSpec.groovy @@ -73,7 +73,7 @@ class StoreGeneratedProjectStatsSpec extends Specification implements TestProper given: def generated = new Generated( ApplicationType.WEB, - GormImpl.HIBERNATE, + GormImpl.HIBERNATE5, ServletImpl.TOMCAT, DevelopmentReloading.DEVTOOLS, JdkVersion.DEFAULT_OPTION From c68ab8410fec564f882537f0fbc22a7e26c401f7 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 14:43:32 -0400 Subject: [PATCH 6/9] Update command line docs for the gorm-hibernate7 feature and Grails Data terminology --- grails-doc/src/en/guide/commandLine/interactiveMode.adoc | 9 +++++---- grails-doc/src/en/ref/Command Line/create-app.adoc | 2 +- grails-doc/src/en/ref/Command Line/create-plugin.adoc | 8 ++++---- grails-doc/src/en/ref/Command Line/create-restapi.adoc | 2 +- .../src/en/ref/Command Line/create-web-plugin.adoc | 2 +- grails-doc/src/en/ref/Command Line/help.adoc | 4 ++-- grails-doc/src/en/ref/Command Line/list-features.adoc | 5 +++-- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/grails-doc/src/en/guide/commandLine/interactiveMode.adoc b/grails-doc/src/en/guide/commandLine/interactiveMode.adoc index 706fc91a96f..6d34d69b1ba 100644 --- a/grails-doc/src/en/guide/commandLine/interactiveMode.adoc +++ b/grails-doc/src/en/guide/commandLine/interactiveMode.adoc @@ -61,13 +61,13 @@ You can access general usage information for Grails commands using the help flag [source,shell] ---- grails> create-app -h -Usage: grails create-app [-hivVx] [--list-features] [-g=GORM Implementation] [--jdk=] +Usage: grails create-app [-hivVx] [--list-features] [-d=Grails Data Implementation] [--jdk=] [-s=Servlet Implementation] [-t=TEST] [-f=FEATURE[,FEATURE...]]... [NAME] Creates an application [NAME] The name of the application to create. -f, --features=FEATURE[,FEATURE...] The features to use. Possible values: h2, scaffolding, gorm-hibernate5, - spring-boot-starter-jetty, spring-boot-starter-tomcat, + gorm-hibernate7, spring-boot-starter-jetty, spring-boot-starter-tomcat, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, @@ -116,8 +116,9 @@ Available Features Database database-migration Adds support for Liquibase database migrations. The Database Migration plugin helps you manage database changes while developing Grails applications. - gorm-hibernate5 (+) Adds support for Hibernate5 using GORM - gorm-mongodb Configures GORM for MongoDB for Groovy applications + gorm-hibernate5 (+) Configure Grails Data for using Hibernate 5. + gorm-hibernate7 Configure Grails Data for using Hibernate 7. + gorm-mongodb Configure Grails Data for using MongoDB. gorm-neo4j Configures GORM for Neo4j for Groovy applications h2 (+) Adds the H2 driver and default config mongo-sync Adds support for the MongoDB Synchronous Driver diff --git a/grails-doc/src/en/ref/Command Line/create-app.adoc b/grails-doc/src/en/ref/Command Line/create-app.adoc index 8fec687d539..274a76e0095 100644 --- a/grails-doc/src/en/ref/Command Line/create-app.adoc +++ b/grails-doc/src/en/ref/Command Line/create-app.adoc @@ -61,7 +61,7 @@ $ grails -t forge create-app bookstore \ These options are available only when using `grails -t forge`: - `NAME`: The desired name for the application. -- `-f, --features=FEATURE[,FEATURE...]`: Enable features like `gorm-hibernate5`, `postgres`, `asciidoctor`, `grails-gsp`, etc. +- `-f, --features=FEATURE[,FEATURE...]`: Enable features like `gorm-hibernate5`, `gorm-hibernate7`, `postgres`, `asciidoctor`, `grails-gsp`, etc. - `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Choose the Grails Data implementation: `hibernate5`, `hibernate7`, `mongodb`. - `--jdk=`: Target JDK version (e.g., 11 or 17). - `-s, --servlet=Servlet Implementation`: Choose embedded servlet: `tomcat`, `jetty`, `undertow`, `none`. diff --git a/grails-doc/src/en/ref/Command Line/create-plugin.adoc b/grails-doc/src/en/ref/Command Line/create-plugin.adoc index d4561a6c4dd..a8b73e24580 100644 --- a/grails-doc/src/en/ref/Command Line/create-plugin.adoc +++ b/grails-doc/src/en/ref/Command Line/create-plugin.adoc @@ -64,13 +64,13 @@ grails create-plugin [OPTIONS] NAME - `NAME`: The desired name for the Grails plugin. -Options include specifying features, configuring the GORM implementation, selecting a servlet implementation, choosing a test framework, setting the JDK version, and more. You can tailor your Grails plugin to your specific requirements using these options. +Options include specifying features, configuring the Grails Data implementation, selecting a servlet implementation, choosing a test framework, setting the JDK version, and more. You can tailor your Grails plugin to your specific requirements using these options. === Options Here are the available options for the create-plugin command: -- -f, --features=FEATURE[,FEATURE...]: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. +- -f, --features=FEATURE[,FEATURE...]: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, gorm-hibernate7, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. - -d, --data=Grails Data Implementation (-g, --gorm are legacy aliases): Specifies the Grails Data implementation to configure for the plugin. Possible values are hibernate5, hibernate7, mongodb. - -h, --help: Displays the help message and exits. - -i, --inplace: Creates a service within the current directory. @@ -109,7 +109,7 @@ This command will create a Grails plugin project named "minimal-plugin" with the grails create-plugin mongodb-plugin --data=mongodb ---- + -This command will generate a Grails plugin project named "mongodb-plugin" configured to use MongoDB as the GORM implementation. +This command will generate a Grails plugin project named "mongodb-plugin" configured to use MongoDB as the Grails Data implementation. 3. Create a plugin with embedded H2 database and view support: @@ -121,4 +121,4 @@ grails create-plugin h2-views-plugin --features h2,views-markup + This command will create a Grails plugin project named "h2-views-plugin" with the H2 database and views markup feature enabled, making it useful for projects that require an embedded database and view rendering capabilities. -These additional examples showcase different use cases for the `create-plugin` command, such as creating minimal plugins, customizing GORM implementations, and enabling specific features to meet project requirements. +These additional examples showcase different use cases for the `create-plugin` command, such as creating minimal plugins, customizing Grails Data implementations, and enabling specific features to meet project requirements. diff --git a/grails-doc/src/en/ref/Command Line/create-restapi.adoc b/grails-doc/src/en/ref/Command Line/create-restapi.adoc index ceef4c809c1..7625d5e8ba2 100644 --- a/grails-doc/src/en/ref/Command Line/create-restapi.adoc +++ b/grails-doc/src/en/ref/Command Line/create-restapi.adoc @@ -40,7 +40,7 @@ The `create-restapi` command accepts the following options: - `NAME`: The desired name for the REST API application. -- `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include. Available options include h2, scaffolding, gorm-hibernate5, spring-boot-starter-jetty, spring-boot-starter-tomcat, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, asset-pipeline-grails, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, geb, properties. +- `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include. Available options include h2, scaffolding, gorm-hibernate5, gorm-hibernate7, spring-boot-starter-jetty, spring-boot-starter-tomcat, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, asset-pipeline-grails, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, geb, properties. - `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Specifies the Grails Data implementation to configure, with options hibernate5, hibernate7, mongodb. diff --git a/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc b/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc index 51a72ec1910..989a50903d8 100644 --- a/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc +++ b/grails-doc/src/en/ref/Command Line/create-web-plugin.adoc @@ -38,7 +38,7 @@ grails create-web-plugin [OPTIONS] NAME Here are the available options for the `create-web-plugin` command: -- `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. +- `-f, --features=FEATURE[,FEATURE...]`: Specifies the features to include in the plugin. Available options include h2, gorm-hibernate5, gorm-hibernate7, spring-boot-starter-jetty, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, github-workflow-java-ci, jrebel, testcontainers, sqlserver, grails-console, views-markup, views-json, gorm-neo4j, asciidoctor, grails-web-console, logbackGroovy, mongo-sync, shade, properties. - `-d, --data=Grails Data Implementation` (`-g`, `--gorm` are legacy aliases): Specifies the Grails Data implementation to configure for the plugin. Possible values are hibernate5, hibernate7, mongodb. diff --git a/grails-doc/src/en/ref/Command Line/help.adoc b/grails-doc/src/en/ref/Command Line/help.adoc index de78f614316..868fd675292 100644 --- a/grails-doc/src/en/ref/Command Line/help.adoc +++ b/grails-doc/src/en/ref/Command Line/help.adoc @@ -134,13 +134,13 @@ NOTE: To access help for Forge-only options such as `--features`, `--jdk`, or `- [source,shell] ---- grails> create-app --help -Usage: grails create-app [-hivVx] [--list-features] [-g=GORM Implementation] [--jdk=] +Usage: grails create-app [-hivVx] [--list-features] [-d=Grails Data Implementation] [--jdk=] [-s=Servlet Implementation] [-t=TEST] [-f=FEATURE[,FEATURE...]]... [NAME] Creates an application [NAME] The name of the application to create. -f, --features=FEATURE[,FEATURE...] The features to use. Possible values: h2, scaffolding, gorm-hibernate5, - spring-boot-starter-jetty, spring-boot-starter-tomcat, + gorm-hibernate7, spring-boot-starter-jetty, spring-boot-starter-tomcat, micronaut-http-client, cache-ehcache, hibernate-validator, postgres, mysql, cache, database-migration, grails-gsp, hamcrest, gorm-mongodb, assertj, mockito, spring-boot-starter-undertow, diff --git a/grails-doc/src/en/ref/Command Line/list-features.adoc b/grails-doc/src/en/ref/Command Line/list-features.adoc index 807e011c021..8c65cc96aa7 100644 --- a/grails-doc/src/en/ref/Command Line/list-features.adoc +++ b/grails-doc/src/en/ref/Command Line/list-features.adoc @@ -65,8 +65,9 @@ Available Features Database database-migration Adds support for Liquibase database migrations. The Database Migration plugin helps you manage database changes while developing Grails applications. - gorm-hibernate5 (+) Adds support for Hibernate5 using GORM - gorm-mongodb Configures GORM for MongoDB for Groovy applications + gorm-hibernate5 (+) Configure Grails Data for using Hibernate 5. + gorm-hibernate7 Configure Grails Data for using Hibernate 7. + gorm-mongodb Configure Grails Data for using MongoDB. gorm-neo4j Configures GORM for Neo4j for Groovy applications h2 (+) Adds the H2 driver and default config mongo-sync Adds support for the MongoDB Synchronous Driver From e738e8fe3f1fa72b324ae509feacd4c68348c61f Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 14:50:49 -0400 Subject: [PATCH 7/9] Move the datasource prefix constant to DatabaseDriverConfigurationFeature --- .../database/DatabaseDriverConfigurationFeature.java | 5 +++-- .../forge/feature/database/GrailsDataHibernate5.java | 11 +++++------ .../forge/feature/database/GrailsDataHibernate7.java | 11 +++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java index cc647453407..40645cacb5c 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverConfigurationFeature.java @@ -28,13 +28,14 @@ import static org.grails.forge.feature.config.ConfigurationFeature.PROD_ENVIRONMENT_KEY; import static org.grails.forge.feature.config.ConfigurationFeature.ENVIRONMENTS_KEY; import static org.grails.forge.feature.config.ConfigurationFeature.PROPERTIES_KEY; -import static org.grails.forge.feature.database.GrailsDataHibernate5.PREFIX; /** * A feature that configures a datasource with a driver */ public interface DatabaseDriverConfigurationFeature extends Feature { + String DATASOURCE_PREFIX = "dataSource."; + String getUrlKey(); String getDriverKey(); @@ -64,6 +65,6 @@ default void applyDefaultConfig(DatabaseDriverFeature dbFeature, Map config, String key, Object value) { - config.put(ENVIRONMENTS_KEY + "." + PROD_ENVIRONMENT_KEY + "." + PREFIX + PROPERTIES_KEY + "." + key, value); + config.put(ENVIRONMENTS_KEY + "." + PROD_ENVIRONMENT_KEY + "." + DATASOURCE_PREFIX + PROPERTIES_KEY + "." + key, value); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java index 9bc9d538a66..7b8ebf84008 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate5.java @@ -35,12 +35,11 @@ @Singleton public class GrailsDataHibernate5 extends GormFeature implements DatabaseDriverConfigurationFeature { - static final String PREFIX = "dataSource."; - private static final String URL_KEY = PREFIX + "url"; - private static final String DRIVER_KEY = PREFIX + "driverClassName"; - private static final String USERNAME_KEY = PREFIX + "username"; - private static final String PASSWORD_KEY = PREFIX + "password"; - private static final String DB_CREATE_KEY = PREFIX + "dbCreate"; + private static final String URL_KEY = DATASOURCE_PREFIX + "url"; + private static final String DRIVER_KEY = DATASOURCE_PREFIX + "driverClassName"; + private static final String USERNAME_KEY = DATASOURCE_PREFIX + "username"; + private static final String PASSWORD_KEY = DATASOURCE_PREFIX + "password"; + private static final String DB_CREATE_KEY = DATASOURCE_PREFIX + "dbCreate"; private final DatabaseDriverFeature defaultDbFeature; diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java index 834a3b2cfa5..4fb30ce28bd 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/GrailsDataHibernate7.java @@ -33,12 +33,11 @@ @Singleton public class GrailsDataHibernate7 extends GormFeature implements DatabaseDriverConfigurationFeature { - static final String PREFIX = "dataSource."; - private static final String URL_KEY = PREFIX + "url"; - private static final String DRIVER_KEY = PREFIX + "driverClassName"; - private static final String USERNAME_KEY = PREFIX + "username"; - private static final String PASSWORD_KEY = PREFIX + "password"; - private static final String DB_CREATE_KEY = PREFIX + "dbCreate"; + private static final String URL_KEY = DATASOURCE_PREFIX + "url"; + private static final String DRIVER_KEY = DATASOURCE_PREFIX + "driverClassName"; + private static final String USERNAME_KEY = DATASOURCE_PREFIX + "username"; + private static final String PASSWORD_KEY = DATASOURCE_PREFIX + "password"; + private static final String DB_CREATE_KEY = DATASOURCE_PREFIX + "dbCreate"; private final DatabaseDriverFeature defaultDbFeature; From e5bd4d1b9ce9691303216e054f53d17c8af7f726 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 15:18:55 -0400 Subject: [PATCH 8/9] Reject invalid Grails Data implementation values on the create endpoints An unknown gorm/data query parameter value now produces a 400 with a clear message instead of silently generating an app with the default implementation. The feature filter endpoints keep their existing lenient fallback behavior for consistency with the other filter options. --- .../forge/api/FeatureControllerSpec.groovy | 30 ++++++++++++++++-- .../forge/api/ZipCreateControllerSpec.groovy | 31 +++++++++++++++++++ .../forge/options/GormImplTypeConverter.java | 9 +++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy index 9c1f3715387..aecf9585b16 100644 --- a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy +++ b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy @@ -21,7 +21,9 @@ package org.grails.forge.api import com.fasterxml.jackson.databind.ObjectMapper import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.http.client.annotation.Client import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject @@ -173,9 +175,8 @@ class FeatureControllerSpec extends Specification { void "test feature filter - invalid option as query parameter"() { when: - String response = httpClient.toBlocking().withCloseable { client -> - client.exchange(HttpRequest.GET('/application-types/' + ApplicationType.WEB.name + '/features?javaVersion=invalid'), String.class).body() - } + String response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/application-types/' + ApplicationType.WEB.name + '/features?javaVersion=invalid'), String.class).body() then: response @@ -186,4 +187,27 @@ class FeatureControllerSpec extends Specification { map.features.collect { it -> it.name }.find { it == 'gorm-mongodb'} } + + void "test feature filter - Grails Data implementation as query parameter"() { + when: 'a valid Grails Data implementation is accepted' + String response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/application-types/' + ApplicationType.WEB.name + '/features?gorm=hibernate7'), String.class).body() + + then: + response + + when: 'the legacy hibernate value is accepted' + response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/application-types/' + ApplicationType.WEB.name + '/features?gorm=hibernate'), String.class).body() + + then: + response + + when: 'an invalid value falls back to the default like other filter options' + response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/application-types/' + ApplicationType.WEB.name + '/features?gorm=invalid'), String.class).body() + + then: + response + } } diff --git a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/ZipCreateControllerSpec.groovy b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/ZipCreateControllerSpec.groovy index 598e781ee59..99922d4a157 100644 --- a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/ZipCreateControllerSpec.groovy +++ b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/ZipCreateControllerSpec.groovy @@ -21,7 +21,10 @@ package org.grails.forge.api import io.micronaut.http.HttpHeaders +import io.micronaut.http.HttpRequest import io.micronaut.http.HttpStatus +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject @@ -36,6 +39,10 @@ class ZipCreateControllerSpec extends Specification { @Inject CreateClient client + @Inject + @Client("/") + HttpClient httpClient + @Inject MyEventListener eventListener @@ -111,4 +118,28 @@ class ZipCreateControllerSpec extends Specification { then: ZipUtil.containsFileWithContents(bytes, "test/build.gradle", "spring-boot-devtools") } + + void "test create app with an invalid Grails Data implementation is rejected"() { + when: + httpClient.toBlocking() + .exchange(HttpRequest.GET('/create/web/test?gorm=invalid'), byte[].class) + + then: + def e = thrown(HttpClientResponseException) + e.status == HttpStatus.BAD_REQUEST + + when: 'a valid Grails Data implementation is accepted' + def response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/create/web/test?gorm=hibernate7'), byte[].class) + + then: + ZipUtil.isZip(response.body()) + + when: 'the legacy hibernate value is accepted' + response = httpClient.toBlocking() + .exchange(HttpRequest.GET('/create/web/test?gorm=hibernate'), byte[].class) + + then: + ZipUtil.isZip(response.body()) + } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java index 0f2a4b0dbea..2daa7a6bf71 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java @@ -22,6 +22,8 @@ import io.micronaut.core.convert.ConversionContext; import io.micronaut.core.convert.TypeConverter; +import io.micronaut.core.convert.exceptions.ConversionErrorException; +import io.micronaut.core.type.Argument; import jakarta.inject.Singleton; /** @@ -36,6 +38,11 @@ public Optional convert(CharSequence object, Class targetTyp if (object == null) { return Optional.empty(); } - return Optional.ofNullable(GormImpl.parse(object.toString())); + GormImpl gormImpl = GormImpl.parse(object.toString()); + if (gormImpl == null) { + throw new ConversionErrorException(Argument.of(GormImpl.class), + new IllegalArgumentException("Invalid Grails Data implementation selection: " + object)); + } + return Optional.of(gormImpl); } } From b668d64db4f0c3a35ac6c3d9f6b2785d7d7e9473 Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 9 Jul 2026 19:23:09 -0400 Subject: [PATCH 9/9] Address review feedback: restore Hibernate 5 auto-add for SQL drivers with MongoDB, use ConversionContext#reject, remove unused imports --- .../forge/api/FeatureControllerSpec.groovy | 2 -- .../database/DatabaseDriverFeature.java | 2 +- .../forge/options/GormImplTypeConverter.java | 5 ++- .../database/GrailsDataMongoDBSpec.groovy | 36 +++++++++++++++++++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy index aecf9585b16..3967fd42aa3 100644 --- a/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy +++ b/grails-forge/grails-forge-api/src/test/groovy/org/grails/forge/api/FeatureControllerSpec.groovy @@ -21,9 +21,7 @@ package org.grails.forge.api import com.fasterxml.jackson.databind.ObjectMapper import io.micronaut.http.HttpRequest -import io.micronaut.http.HttpStatus import io.micronaut.http.client.HttpClient -import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.http.client.annotation.Client import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java index 958c8b47242..7d04b11e77e 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/database/DatabaseDriverFeature.java @@ -56,7 +56,7 @@ public void processSelectedFeatures(FeatureContext featureContext) { if (!featureContext.isPresent(TestContainers.class) && testContainers != null) { featureContext.addFeature(testContainers); } - if (!featureContext.isPresent(GormFeature.class) && grailsDataHibernate5 != null) { + if (!featureContext.isPresent(DatabaseDriverConfigurationFeature.class) && grailsDataHibernate5 != null) { featureContext.addFeature(grailsDataHibernate5); } } diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java index 2daa7a6bf71..7530a9a9ca4 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/options/GormImplTypeConverter.java @@ -22,8 +22,6 @@ import io.micronaut.core.convert.ConversionContext; import io.micronaut.core.convert.TypeConverter; -import io.micronaut.core.convert.exceptions.ConversionErrorException; -import io.micronaut.core.type.Argument; import jakarta.inject.Singleton; /** @@ -40,8 +38,9 @@ public Optional convert(CharSequence object, Class targetTyp } GormImpl gormImpl = GormImpl.parse(object.toString()); if (gormImpl == null) { - throw new ConversionErrorException(Argument.of(GormImpl.class), + context.reject(object, new IllegalArgumentException("Invalid Grails Data implementation selection: " + object)); + return Optional.empty(); } return Optional.of(gormImpl); } diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy index cd516d01341..b2a62b23d61 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/database/GrailsDataMongoDBSpec.groovy @@ -24,6 +24,11 @@ import org.grails.forge.BuildBuilder import org.grails.forge.application.generator.GeneratorContext import org.grails.forge.feature.Features import org.grails.forge.fixture.CommandOutputFixture +import org.grails.forge.options.DevelopmentReloading +import org.grails.forge.options.GormImpl +import org.grails.forge.options.JdkVersion +import org.grails.forge.options.Options +import org.grails.forge.options.ServletImpl class GrailsDataMongoDBSpec extends ApplicationContextSpec implements CommandOutputFixture { @@ -61,5 +66,36 @@ class GrailsDataMongoDBSpec extends ApplicationContextSpec implements CommandOut ctx.configuration.containsKey("grails.mongodb.url") } + void "test a SQL driver combined with MongoDB still adds Hibernate 5 as the default SQL implementation"() { + given: + Options options = new Options(DevelopmentReloading.DEFAULT_OPTION, GormImpl.MONGODB, ServletImpl.DEFAULT_OPTION, JdkVersion.DEFAULT_OPTION) + + when: + Features features = getFeatures(['gorm-mongodb', 'postgres'], options) + + then: + features.contains("gorm-mongodb") + features.contains("postgres") + features.contains("gorm-hibernate5") + !features.contains("gorm-hibernate7") + + when: + String template = new BuildBuilder(beanContext) + .features(['gorm-mongodb', 'postgres']) + .gormImpl(GormImpl.MONGODB) + .render() + + then: + template.contains("implementation \"org.apache.grails:grails-data-mongodb\"") + template.contains("implementation \"org.apache.grails:grails-data-hibernate5\"") + template.contains("runtimeOnly \"org.postgresql:postgresql\"") + + when: + GeneratorContext ctx = buildGeneratorContext(['gorm-mongodb', 'postgres'], options) + + then: + ctx.configuration.containsKey("grails.mongodb.url") + ctx.configuration.get("dataSource.driverClassName") == 'org.postgresql.Driver' + } }