From 29848c49dba8551d9a76e3c6eac3375a8059a88b Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 15 Oct 2020 17:20:30 -0500 Subject: [PATCH] ORM + Gradle HHH-14285 - project template HHH-14286 - Gradle plugin --- .travis.yml | 2 +- build.gradle | 1 + documentation/documentation.gradle | 2 + gradle/javadoc.gradle | 60 +++ gradle/libraries.gradle | 4 +- gradle/published-java-module.gradle | 56 +-- .../internal/AggregatedServiceLoader.java | 3 +- .../EntityManagerFactoryBuilderImpl.java | 34 +- .../boot/spi/EntityManagerFactoryBuilder.java | 17 +- .../org/hibernate/mapping/DependantValue.java | 4 + release/release.gradle | 24 +- settings.gradle | 5 +- tooling/hibernate-gradle-plugin/README.adoc | 51 +++ tooling/hibernate-gradle-plugin/README.md | 6 - .../hibernate-gradle-plugin.gradle | 122 +++++- .../tooling/gradle/EnhanceExtension.groovy | 24 -- .../orm/tooling/gradle/EnhanceTask.groovy | 37 -- .../orm/tooling/gradle/EnhancementHelper.java | 178 --------- .../tooling/gradle/HibernateExtension.groovy | 54 --- .../orm/tooling/gradle/HibernatePlugin.java | 78 ---- .../hibernate/orm/tooling/gradle/Helper.java | 48 +++ .../tooling/gradle/HibernateOrmPlugin.java | 52 +++ .../orm/tooling/gradle/HibernateOrmSpec.java | 98 +++++ .../orm/tooling/gradle/HibernateVersion.java | 50 +++ .../gradle/enhance/EnhancementHelper.java | 167 +++++++++ .../gradle/enhance/EnhancementSpec.java | 129 +++++++ .../gradle/enhance/EnhancementTask.java | 119 ++++++ .../metamodel/JpaMetamodelGenerationSpec.java | 88 +++++ .../metamodel/JpaMetamodelGenerationTask.java | 346 ++++++++++++++++++ .../gradle/metamodel/model/AttributeBag.java | 34 ++ .../gradle/metamodel/model/AttributeList.java | 34 ++ .../gradle/metamodel/model/AttributeMap.java | 38 ++ .../gradle/metamodel/model/AttributeSet.java | 34 ++ .../metamodel/model/AttributeSingular.java | 29 ++ .../metamodel/model/AttributeSupport.java | 75 ++++ .../model/JpaStaticMetamodelGenerator.java | 105 ++++++ .../metamodel/model/MetamodelAttribute.java | 19 + .../metamodel/model/MetamodelClass.java | 156 ++++++++ .../gradle/metamodel/model/ObjectFactory.java | 237 ++++++++++++ .../org.hibernate.orm.properties | 7 - .../resources/META-INF/hibernate-orm.version | 1 + .../tooling/gradle/HibernatePluginTest.groovy | 89 ----- .../gradle/HibernateOrmPluginTest.java | 91 +++++ .../src/testKit/resources/simple/build.gradle | 29 ++ .../testKit/resources/simple/settings.gradle | 0 .../simple/src/main/java/TheEmbeddable.java | 23 ++ .../simple/src/main/java/TheEntity.java | 79 ++++ tooling/project-template/README.adoc | 6 + .../project-template/project-template.gradle | 112 ++++++ .../src/template/resources/README.adoc | 5 + .../src/template/resources/build.gradle.kts | 80 ++++ .../template/resources/settings.gradle.kts | 32 ++ .../java/org/your/domain/SimpleEntity.java | 38 ++ .../java/org/your/domain/EntityTests.java | 53 +++ .../src/test/resources/log4j.properties | 7 + 55 files changed, 2696 insertions(+), 576 deletions(-) create mode 100644 gradle/javadoc.gradle create mode 100644 tooling/hibernate-gradle-plugin/README.adoc delete mode 100644 tooling/hibernate-gradle-plugin/README.md delete mode 100644 tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceExtension.groovy delete mode 100644 tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceTask.groovy delete mode 100644 tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhancementHelper.java delete mode 100644 tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernateExtension.groovy delete mode 100644 tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/Helper.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmSpec.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateVersion.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementHelper.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementSpec.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementTask.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationSpec.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationTask.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeBag.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeList.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeMap.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSet.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSingular.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSupport.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/JpaStaticMetamodelGenerator.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelAttribute.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelClass.java create mode 100644 tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/ObjectFactory.java delete mode 100644 tooling/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.hibernate.orm.properties create mode 100644 tooling/hibernate-gradle-plugin/src/main/resources/META-INF/hibernate-orm.version delete mode 100644 tooling/hibernate-gradle-plugin/src/test/groovy/org/hibernate/orm/tooling/gradle/HibernatePluginTest.groovy create mode 100644 tooling/hibernate-gradle-plugin/src/testKit/java/org/hibernate/orm/tooling/gradle/HibernateOrmPluginTest.java create mode 100644 tooling/hibernate-gradle-plugin/src/testKit/resources/simple/build.gradle create mode 100644 tooling/hibernate-gradle-plugin/src/testKit/resources/simple/settings.gradle create mode 100644 tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEmbeddable.java create mode 100644 tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEntity.java create mode 100644 tooling/project-template/README.adoc create mode 100644 tooling/project-template/project-template.gradle create mode 100644 tooling/project-template/src/template/resources/README.adoc create mode 100644 tooling/project-template/src/template/resources/build.gradle.kts create mode 100644 tooling/project-template/src/template/resources/settings.gradle.kts create mode 100644 tooling/project-template/src/template/resources/src/main/java/org/your/domain/SimpleEntity.java create mode 100644 tooling/project-template/src/template/resources/src/test/java/org/your/domain/EntityTests.java create mode 100644 tooling/project-template/src/template/resources/src/test/resources/log4j.properties diff --git a/.travis.yml b/.travis.yml index 6fc0e918dc..437679a4be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_script: - java -version - ./gradlew assemble script: - - ./gradlew check -Plog-test-progress=true + - ./gradlew check -Plog-test-progress=true --stacktrace before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ diff --git a/build.gradle b/build.gradle index 7a2923b1eb..cdcb0e2e9a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ buildscript { jcenter() mavenCentral() } + dependencies { classpath 'org.hibernate.build.gradle:hibernate-matrix-testing:3.0.0.Final' classpath 'org.hibernate.build.gradle:version-injection-plugin:1.0.0' diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index a473decd0e..b1fc929bf0 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -12,6 +12,8 @@ ext { projectsToSkipWhenAggregatingJavadocs = [ 'documentation', 'hibernate-entitymanager', + 'hibernate-gradle-plugin', + 'project-template', 'hibernate-infinispan', 'hibernate-ehcache', 'hibernate-java8', diff --git a/gradle/javadoc.gradle b/gradle/javadoc.gradle new file mode 100644 index 0000000000..9c8930f66e --- /dev/null +++ b/gradle/javadoc.gradle @@ -0,0 +1,60 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +// make sure Java plugin is applied +apply plugin : 'java' + +apply from: rootProject.file( 'gradle/base-information.gradle' ) + +javadoc { + exclude( "**/internal/*" ) + exclude( "**/generated-src/**" ) + + final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) + + configure( options ) { + // this is the config needed to use asciidoclet for Javadoc rendering. It relies on a build from John's PR @ https://github.com/asciidoctor/asciidoclet/pull/91 + // however, the PR does not work for me in that Javadocs with `@asciidoclet` are not rendered using asciidoc(tor/let). Also tried the preferable `@asciidoc` + // with the same result. Leaving all this config in place however as the outcome is the same as not enabling it. + // todo (6.0) : need to find out why the asciidoclet PR does not work + // + // Travis CI JDK 11 build did not like this +// docletpath = configurations.asciidoclet.files.asType(List) +// doclet = 'org.asciidoctor.Asciidoclet' + windowTitle = "$project.name JavaDocs" + docTitle = "$project.name JavaDocs ($project.version)" + bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." + use = true + encoding = 'UTF-8' + links += [ + 'https://docs.oracle.com/javase/8/docs/api/', + 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', + 'http://docs.jboss.org/cdi/api/2.0/', + 'https://javaee.github.io/javaee-spec/javadocs/' + ] + tags = [ "apiNote", 'implSpec', 'implNote', 'todo' ] + + if ( JavaVersion.current().isJava11Compatible() ) { + //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 + // but after excluding the first two builds; see also specific comments on + // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 + // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people + // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. + System.out.println("Forcing Javadoc in Java 8 compatible mode"); + options.source = project.baselineJavaVersion + } + + addStringOption( 'Xdoclint:none', '-quiet' ) + + tags( + 'todo:X"', + 'apiNote:a:"API Note:"', + 'implSpec:a:"Implementation Specification:"', + 'implNote:a:"Implementation Note:"' + ) + } +} \ No newline at end of file diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 7c94f74a13..a7c8ba0d99 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -10,8 +10,8 @@ ext { junitVersion = '4.12' - junitVintageVersion = '5.3.1' - junit5Version = '5.3.1' + junitVintageVersion = '5.4.2' + junit5Version = '5.4.2' h2Version = '1.4.199' bytemanVersion = '4.0.13' //Compatible with JDK16 diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle index 9f1a181764..d971564513 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -117,61 +117,9 @@ task javadocJar(type: Jar) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Javadoc +apply from: rootProject.file( 'gradle/javadoc.gradle' ) + javadoc { - exclude( "**/internal/*" ) - exclude( "**/generated-src/**" ) - - final int currentYear = new GregorianCalendar().get( Calendar.YEAR ) - - configure( options ) { - // this is the config needed to use asciidoclet for Javadoc rendering. It relies on a build from John's PR @ https://github.com/asciidoctor/asciidoclet/pull/91 - // however, the PR does not work for me in that Javadocs with `@asciidoclet` are not rendered using asciidoc(tor/let). Also tried the preferable `@asciidoc` - // with the same result. Leaving all this config in place however as the outcome is the same as not enabling it. - // todo (6.0) : need to find out why the asciidoclet PR does not work - // - // Travis CI JDK 11 build did not like this -// docletpath = configurations.asciidoclet.files.asType(List) -// doclet = 'org.asciidoctor.Asciidoclet' - windowTitle = "$project.name JavaDocs" - docTitle = "$project.name JavaDocs ($project.version)" - bottom = "Copyright © 2001-$currentYear Red Hat, Inc. All Rights Reserved." - use = true - encoding = 'UTF-8' - links += [ - 'https://docs.oracle.com/javase/8/docs/api/', - 'http://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/', - 'http://docs.jboss.org/cdi/api/2.0/', - 'https://javaee.github.io/javaee-spec/javadocs/' - ] - tags = [ "apiNote", 'implSpec', 'implNote', 'todo' ] - - if ( JavaVersion.current().isJava11Compatible() ) { - //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 - // but after excluding the first two builds; see also specific comments on - // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 - // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people - // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. - System.out.println("Forcing Javadoc in Java 8 compatible mode"); - options.source = project.baselineJavaVersion - } - - if ( JavaVersion.current().isJava8Compatible() ) { - addStringOption( 'Xdoclint:none', '-quiet' ) - } - -// // by default, exclude the files from Asciidoclet processing -// // add the @asciidoclet tag to enable Asciidoclet on a particular file -// options.addStringOption( '-exclude-asciidoclet-process', '**' ) - - tags( - 'todo:X"', - 'apiNote:a:"API Note:"', - 'implSpec:a:"Implementation Specification:"', - 'implNote:a:"Implementation Note:"' - ) - } - options.addStringOption( 'Xdoclint:none', '-quiet' ) - doFirst { // ordering problems if we try to do this during config phase :( classpath += project.sourceSets.main.output.classesDirs + project.sourceSets.main.compileClasspath + project.configurations.provided diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java index 6d902d046d..59c195782d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java @@ -194,8 +194,7 @@ abstract class AggregatedServiceLoader { Set result = new LinkedHashSet<>(); // Always try the aggregated class loader first - Iterator> providerIterator = providerStream( aggregatedClassLoaderServiceLoader ) - .iterator(); + Iterator> providerIterator = providerStream( aggregatedClassLoaderServiceLoader ).iterator(); while ( providerIterator.hasNext() ) { Supplier provider = providerIterator.next(); collectServiceIfNotDuplicate( result, alreadyEncountered, provider ); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index 2cf9064368..7f105070ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -1198,6 +1198,26 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil return metadata; } + @Override + public ManagedResources getManagedResources() { + return managedResources; + } + + /** + * Used by extensions : Hibernate Reactive + */ + @Override + public MetadataImplementor metadata() { + if ( this.metadata == null ) { + this.metadata = MetadataBuildingProcess.complete( + managedResources, + metamodelBuilder.getBootstrapContext(), + metamodelBuilder.getMetadataBuildingOptions() + ); + } + return metadata; + } + @Override public EntityManagerFactoryBuilder withValidatorFactory(Object validatorFactory) { this.validatorFactory = validatorFactory; @@ -1220,20 +1240,6 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil // todo : close the bootstrap registry (not critical, but nice to do) } - /** - * Used by extensions : Hibernate Reactive - */ - protected MetadataImplementor metadata() { - if ( this.metadata == null ) { - this.metadata = MetadataBuildingProcess.complete( - managedResources, - metamodelBuilder.getBootstrapContext(), - metamodelBuilder.getMetadataBuildingOptions() - ); - } - return metadata; - } - @Override public void generateSchema() { // This seems overkill, but building the SF is necessary to get the Integrators to kick in. diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java index 212b8e429e..5c9ad9de57 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/EntityManagerFactoryBuilder.java @@ -9,6 +9,9 @@ package org.hibernate.jpa.boot.spi; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import org.hibernate.boot.model.process.spi.ManagedResources; +import org.hibernate.boot.spi.MetadataImplementor; + /** * Represents a 2-phase JPA bootstrap process for building a Hibernate EntityManagerFactory. * @@ -24,6 +27,10 @@ import javax.sql.DataSource; * @author Scott Marlow */ public interface EntityManagerFactoryBuilder { + ManagedResources getManagedResources(); + + MetadataImplementor metadata(); + /** * Allows passing in a Java EE ValidatorFactory (delayed from constructing the builder, AKA phase 2) to be used * in building the EntityManagerFactory @@ -32,7 +39,7 @@ public interface EntityManagerFactoryBuilder { * * @return {@code this}, for method chaining */ - public EntityManagerFactoryBuilder withValidatorFactory(Object validatorFactory); + EntityManagerFactoryBuilder withValidatorFactory(Object validatorFactory); /** * Allows passing in a DataSource (delayed from constructing the builder, AKA phase 2) to be used @@ -42,23 +49,23 @@ public interface EntityManagerFactoryBuilder { * * @return {@code this}, for method chaining */ - public EntityManagerFactoryBuilder withDataSource(DataSource dataSource); + EntityManagerFactoryBuilder withDataSource(DataSource dataSource); /** * Build {@link EntityManagerFactory} instance * * @return The built {@link EntityManagerFactory} */ - public EntityManagerFactory build(); + EntityManagerFactory build(); /** * Cancel the building processing. This is used to signal the builder to release any resources in the case of * something having gone wrong during the bootstrap process */ - public void cancel(); + void cancel(); /** * Perform an explicit schema generation (rather than an "auto" one) based on the */ - public void generateSchema(); + void generateSchema(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java index 9bc6ac5345..69fcd02bd2 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/DependantValue.java @@ -27,6 +27,10 @@ public class DependantValue extends SimpleValue implements Resolvable { this.wrappedValue = prototype; } + public KeyValue getWrappedValue() { + return wrappedValue; + } + public Type getType() throws MappingException { return wrappedValue.getType(); } diff --git a/release/release.gradle b/release/release.gradle index c016d4235e..85bee50c81 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -22,7 +22,8 @@ ext { idea.module { } -final File documentationDir = mkdir( "${project.buildDir}/documentation" ); +final File documentationDir = mkdir( "${project.buildDir}/documentation" ) +final File projectTemplateStagingDir = mkdir( "${project.buildDir}/projectTemplate" ) task releaseChecks() { doFirst { @@ -104,6 +105,12 @@ task assembleDocumentation(type: Task, dependsOn: [rootProject.project( 'documen } } +task assembleProjectTemplates(type:Copy, dependsOn: project( ":project-template" ).tasks.assembleDist) { + def templateProject = project( ":project-template" ) + from templateProject.layout.buildDirectory.dir( "distributions" ) + into projectTemplateStagingDir +} + /** * Upload the documentation to the JBoss doc server */ @@ -150,9 +157,10 @@ distributions { from parent.project( 'hibernate-core' ).configurations.provided.files { dep -> dep.name == 'javassist' } } -// into( 'lib/jpa' ) { -// from parent.project( 'hibernate-entitymanager' ).configurations.archives.allArtifacts.files.filter{ file -> !file.name.endsWith('-sources.jar') } -// } + into( 'project-template' ) { + // todo : hook in some form of variable replacement - especially for version + from project( ':project-template' ).files( 'src/main/dist' ) + } // todo (6.0) - add back spatial // into( 'lib/spatial' ) { @@ -241,11 +249,9 @@ distributions { } } -distZip.dependsOn assembleDocumentation -distTar.dependsOn assembleDocumentation -distTar { - compression = Compression.GZIP -} +// this is the common task between distTar and distZip +assembleDist.dependsOn assembleDocumentation +distTar.compression = Compression.GZIP /** * "virtual" task for building both types of dist bundles diff --git a/settings.gradle b/settings.gradle index eef2aa5596..5662d9c003 100644 --- a/settings.gradle +++ b/settings.gradle @@ -82,6 +82,9 @@ project(':metamodel-generator').name = 'hibernate-jpamodelgen' include 'hibernate-gradle-plugin' project(':hibernate-gradle-plugin').projectDir = new File(rootProject.projectDir, "tooling/hibernate-gradle-plugin") +include 'project-template' +project(':project-template').projectDir = new File(rootProject.projectDir, "tooling/project-template") + include 'hibernate-enhance-maven-plugin' project(':hibernate-enhance-maven-plugin').projectDir = new File(rootProject.projectDir, "tooling/hibernate-enhance-maven-plugin") @@ -90,4 +93,4 @@ rootProject.children.each { project -> assert project.projectDir.isDirectory() assert project.buildFile.exists() assert project.buildFile.isFile() -} +} \ No newline at end of file diff --git a/tooling/hibernate-gradle-plugin/README.adoc b/tooling/hibernate-gradle-plugin/README.adoc new file mode 100644 index 0000000000..0fcb6e84e8 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/README.adoc @@ -0,0 +1,51 @@ += Hibernate ORM Gradle Plugin + +A Gradle plugin for introducing Hibernate tasks and capabilities into a build. + + +== Set up + +``` +plugins { + id 'org.hibernate.orm' version='X' +} + +// HibernateOrmSpec +hibernate { + ... +} +``` + +== Bytecode Enhancement + +The plugin can perform build-time enhancement of the domain classes. This is controlled +by the `enhancement` portion of the `hibernate` extension: + +``` +hibernate { + // EnhancementSpec + enhancement { + // available options - all default to false + lazyInitialization( true ) + } +} +``` + +== DSL + +The `hibernate` DSL extension is the main entry into configuring the plugin + +``` +hibernate { +} +``` + +At this time, there is no configuration at this level. + +== Capa + +== Tasks + +== Additional Resources + +* https://plugins.gradle.org/plugin/org.hibernate.orm diff --git a/tooling/hibernate-gradle-plugin/README.md b/tooling/hibernate-gradle-plugin/README.md deleted file mode 100644 index 39b1daf8ac..0000000000 --- a/tooling/hibernate-gradle-plugin/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Defines a Gradle plugin for introducing Hibernate specific tasks and capabilities into and end-user build. - -Currently the only capability added is for bytecode enhancement of the user domain model, although other capabilities are -planned. - -todo : usage \ No newline at end of file diff --git a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle index f7f018bcd8..e5d52b76b0 100644 --- a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle +++ b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle @@ -1,29 +1,121 @@ +import org.apache.tools.ant.filters.ReplaceTokens + /* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -apply plugin: 'groovy' -description = "Gradle plugin for integrating Hibernate functionality into your build" +plugins { + id 'java-gradle-plugin' + id 'com.github.sebersole.testkit-junit5' version '0.9.5' -apply from: rootProject.file( 'gradle/published-java-module.gradle' ) -apply plugin: 'java-gradle-plugin' -apply plugin: 'maven' + // for portal publishing + id "com.gradle.plugin-publish" version "0.12.0" + id "nu.studer.credentials" version "2.1" + // for publishing snapshots + id 'maven-publish' + id 'org.hibernate.build.maven-repo-auth' + + id 'idea' + id 'eclipse' +} + +description = "Gradle plugin for integrating Hibernate aspects into your build" + +apply from: rootProject.file( 'gradle/base-information.gradle' ) +apply from: rootProject.file( 'gradle/libraries.gradle' ) +apply from: rootProject.file( 'gradle/javadoc.gradle' ) + +ext { + pluginId = 'org.hibernate.orm' + pluginVersion = project.version + + //noinspection GrUnresolvedAccess + publishKey = credentials.'hibernate.gradle.publish.key' + //noinspection GrUnresolvedAccess + publishSecret = credentials.'hibernate.gradle.publish.secret' + + if ( publishKey != null && publishSecret != null ) { + project.'gradle.publish.key' = publishKey + project.'gradle.publish.secret' = publishSecret + } +} dependencies { - compile( project( ':hibernate-core' ) ) - compile( libraries.jpa ) - compile( libraries.javassist ) - compile( libraries.byteBuddy ) - compile gradleApi() - compile localGroovy() + implementation( project( ':hibernate-core' ) ) + implementation project( ':hibernate-testing' ) + implementation( libraries.jpa ) + implementation( libraries.javassist ) + implementation( libraries.byteBuddy ) } -tasks.withType( GroovyCompile ) { - options.encoding = 'UTF-8' - sourceCompatibility = project.baselineJavaVersion - targetCompatibility = project.baselineJavaVersion +gradlePlugin { + plugins { + ormPlugin { + id = project.pluginId + implementationClass = 'org.hibernate.orm.tooling.gradle.HibernateOrmPlugin' + } + } } + +pluginBundle { + website = 'https://github.com/hibernate/hibernate-orm/tree/master/tooling/hibernate-gradle-plugin' + vcsUrl = 'https://github.com/hibernate/hibernate-orm/tree/master/tooling/hibernate-gradle-plugin' + tags = ['hibernate','orm','bytecode','enhancement','bytebuddy'] + + plugins { + ormPlugin { + id = project.pluginId + displayName = 'Gradle plugin for Hibernate ORM' + description = 'Applies Hibernate aspects into the build' + } + } +} + +processResources { + filter( ReplaceTokens, tokens: [ 'hibernateVersion': getVersion() ] ) +} + + +task release + +if ( project.version.toString().endsWith( '-SNAPSHOT' ) ) { + tasks.publishPlugins.enabled = false + tasks.release.dependsOn tasks.publish +} +else { + tasks.publish.enabled = false + tasks.release.dependsOn( tasks.publishPlugins ) +} + +tasks.publishPlugins { + doFirst { + if ( project.'gradle.publish.key' == null ) { + throw new RuntimeException( "`gradle.publish.key` not found" ) + } + if ( project.'gradle.publish.secret' == null ) { + throw new RuntimeException( "`gradle.publish.secret` not found" ) + } + } +} + + +// for SNAPSHOT version +publishing { + publications { + plugin( MavenPublication ) { + from components.java + } + } + + repositories { + maven { + name 'jboss-snapshots-repository' + url 'https://repository.jboss.org/nexus/content/repositories/snapshots' + } + } +} + diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceExtension.groovy b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceExtension.groovy deleted file mode 100644 index 6c2638f2c9..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceExtension.groovy +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.tooling.gradle - -/** - * Gradle DSL extension for configuring various Hibernate bytecode enhancement. Registered - * under "hibernate.enhance". - * - * @author Steve Ebersole - */ -class EnhanceExtension implements Serializable { - def boolean enableLazyInitialization = false - def boolean enableDirtyTracking = false - def boolean enableAssociationManagement = false - def boolean enableExtendedEnhancement = false - - boolean shouldApply() { - return enableLazyInitialization || enableDirtyTracking || enableAssociationManagement || enableExtendedEnhancement; - } -} diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceTask.groovy b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceTask.groovy deleted file mode 100644 index 9179de58bb..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhanceTask.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.orm.tooling.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.TaskAction -import org.gradle.util.ConfigureUtil - -/** - * @author Steve Ebersole - */ -@SuppressWarnings("unused") -class EnhanceTask extends DefaultTask { - @Input - EnhanceExtension options - @InputFiles - SourceSet[] sourceSets - - @TaskAction - void enhance() { - for ( SourceSet sourceSet: sourceSets ) { - EnhancementHelper.enhance( sourceSet, options, project ) - } - } - - void options(Closure closure) { - options = new EnhanceExtension() - ConfigureUtil.configure( closure, options ) - } -} diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhancementHelper.java b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhancementHelper.java deleted file mode 100644 index 927fa254e5..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/EnhancementHelper.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.orm.tooling.gradle; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; - -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.gradle.api.file.FileTree; -import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.SourceSet; - -import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; -import org.hibernate.bytecode.enhance.spi.EnhancementContext; -import org.hibernate.bytecode.enhance.spi.Enhancer; -import org.hibernate.bytecode.enhance.spi.UnloadedClass; -import org.hibernate.bytecode.enhance.spi.UnloadedField; -import org.hibernate.cfg.Environment; - -/** - * @author Steve Ebersole - */ -public class EnhancementHelper { - static void enhance(SourceSet sourceSet, EnhanceExtension options, Project project) { - final ClassLoader classLoader = toClassLoader( sourceSet.getRuntimeClasspath() ); - - final EnhancementContext enhancementContext = new DefaultEnhancementContext() { - @Override - public ClassLoader getLoadingClassLoader() { - return classLoader; - } - - @Override - public boolean doBiDirectionalAssociationManagement(UnloadedField field) { - return options.getEnableAssociationManagement(); - } - - @Override - public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { - return options.getEnableDirtyTracking(); - } - - @Override - public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { - return options.getEnableLazyInitialization(); - } - - @Override - public boolean isLazyLoadable(UnloadedField field) { - return options.getEnableLazyInitialization(); - } - - @Override - public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { - return options.getEnableExtendedEnhancement(); - } - }; - - if ( options.getEnableExtendedEnhancement() ) { - project.getLogger().warn("Extended enhancement is enabled. Classes other than entities may be modified. You should consider access the entities using getter/setter methods and disable this property. Use at your own risk." ); - } - - final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( enhancementContext ); - - for ( File classesDir: sourceSet.getOutput().getClassesDirs() ) { - final FileTree fileTree = project.fileTree( classesDir ); - for ( File file : fileTree ) { - if ( !file.getName().endsWith( ".class" ) ) { - continue; - } - - final byte[] enhancedBytecode = doEnhancement( classesDir, file, enhancer ); - if ( enhancedBytecode != null ) { - writeOutEnhancedClass( enhancedBytecode, file, project.getLogger() ); - project.getLogger().info( "Successfully enhanced class [" + file + "]" ); - } - else { - project.getLogger().info( "Skipping class [" + file.getAbsolutePath() + "], not an entity nor embeddable" ); - } - } - } - } - - public static ClassLoader toClassLoader(FileCollection runtimeClasspath) { - List urls = new ArrayList<>(); - for ( File file : runtimeClasspath ) { - try { - urls.add( file.toURI().toURL() ); - } - catch (MalformedURLException e) { - throw new GradleException( "Unable to resolve classpath entry to URL : " + file.getAbsolutePath(), e ); - } - } - return new URLClassLoader( urls.toArray( new URL[0] ), Enhancer.class.getClassLoader() ); - } - - @SuppressWarnings("WeakerAccess") - static byte[] doEnhancement(File root, File javaClassFile, Enhancer enhancer) { - try { - final String className = determineClassName( root, javaClassFile ); - final ByteArrayOutputStream originalBytes = new ByteArrayOutputStream(); - try (final FileInputStream fileInputStream = new FileInputStream( javaClassFile )) { - byte[] buffer = new byte[1024]; - int length; - while ( ( length = fileInputStream.read( buffer ) ) != -1 ) { - originalBytes.write( buffer, 0, length ); - } - } - return enhancer.enhance( className, originalBytes.toByteArray() ); - } - catch (Exception e) { - throw new GradleException( "Unable to enhance class : " + javaClassFile, e ); - } - } - - private static String determineClassName(File root, File javaClassFile) { - return javaClassFile.getAbsolutePath().substring( - root.getAbsolutePath().length() + 1, - javaClassFile.getAbsolutePath().length() - ".class".length() - ).replace( File.separatorChar, '.' ); - } - - private static void writeOutEnhancedClass(byte[] enhancedBytecode, File file, Logger logger) { - try { - if ( file.delete() ) { - if ( !file.createNewFile() ) { - logger.error( "Unable to recreate class file [" + file.getName() + "]" ); - } - } - else { - logger.error( "Unable to delete class file [" + file.getName() + "]" ); - } - } - catch (IOException e) { - logger.warn( "Problem preparing class file for writing out enhancements [" + file.getName() + "]" ); - } - - try { - FileOutputStream outputStream = new FileOutputStream( file, false ); - try { - outputStream.write( enhancedBytecode ); - outputStream.flush(); - } - catch (IOException e) { - throw new GradleException( "Error writing to enhanced class [" + file.getName() + "] to file [" + file.getAbsolutePath() + "]", e ); - } - finally { - try { - outputStream.close(); - } - catch (IOException ignore) { - } - } - } - catch (FileNotFoundException e) { - throw new GradleException( "Error opening class file for writing : " + file.getAbsolutePath(), e ); - } - - } - - private EnhancementHelper() { - } -} diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernateExtension.groovy b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernateExtension.groovy deleted file mode 100644 index 5cb900c9c3..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernateExtension.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.tooling.gradle - -import org.gradle.api.Project -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.SourceSet -import org.gradle.util.ConfigureUtil - -/** - * Gradle DSL extension for configuring various Hibernate built-time tasks. Registered - * under "hibernate". - * - * @author Steve Ebersole - */ -class HibernateExtension { - private final Project project - - /** - * The source sets that hold persistent model. Default is project.sourceSets.main - */ - def SourceSet[] sourceSets - - /** - * Configuration for bytecode enhancement. Private; see instead {@link #enhance(groovy.lang.Closure)} - */ - protected EnhanceExtension enhance - - HibernateExtension(Project project) { - this.project = project - this.sourceSet( project.getConvention().getPlugin( JavaPluginConvention ).sourceSets.main ) - } - - /** - * Add a single SourceSet. - * - * @param sourceSet The SourceSet to add - */ - void sourceSet(SourceSet sourceSet) { - if ( sourceSets == null ) { - sourceSets = [] - } - sourceSets += sourceSet - } - - void enhance(Closure closure) { - enhance = new EnhanceExtension() - ConfigureUtil.configure( closure, enhance ) - } -} diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java deleted file mode 100644 index d35037fc40..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.tooling.gradle; - -import org.gradle.api.Action; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.tasks.SourceSet; - -/** - * The Hibernate Gradle plugin. Adds Hibernate build-time capabilities into your Gradle-based build. - * - * @author Jeremy Whiting - * @author Steve Ebersole - */ -@SuppressWarnings("serial") -public class HibernatePlugin implements Plugin { - public void apply(Project project) { - project.getPlugins().apply( "java" ); - - final HibernateExtension hibernateExtension = new HibernateExtension( project ); - - project.getLogger().debug( "Adding Hibernate extensions to the build [{}]", project.getName() ); - project.getExtensions().add( "hibernate", hibernateExtension ); - - project.afterEvaluate( - p -> applyEnhancement( p, hibernateExtension ) - ); - } - - private void applyEnhancement(final Project project, final HibernateExtension hibernateExtension) { - if ( hibernateExtension.enhance == null || ! hibernateExtension.enhance.shouldApply() ) { - project.getLogger().warn( "Skipping Hibernate bytecode enhancement since no feature is enabled" ); - return; - } - - for ( final SourceSet sourceSet : hibernateExtension.getSourceSets() ) { - project.getLogger().debug( "Applying Hibernate enhancement action to SourceSet.{}", sourceSet.getName() ); - - final Task compileTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() ); - assert compileTask != null; - compileTask.doLast(new EnhancerAction( sourceSet, hibernateExtension, project )); - } - } - - /** - * Gradle doesn't allow lambdas in doLast or doFirst configurations and causing up-to-date checks - * to fail. Extracting the lambda to an inner class works around this issue. - * - * @link https://github.com/gradle/gradle/issues/5510 - */ - private static class EnhancerAction implements Action { - - private final SourceSet sourceSet; - - private final HibernateExtension hibernateExtension; - - private final Project project; - - private EnhancerAction(SourceSet sourceSet, HibernateExtension hibernateExtension, Project project) { - this.sourceSet = sourceSet; - this.hibernateExtension = hibernateExtension; - this.project = project; - } - - @Override - public void execute(Task task) { - EnhancementHelper.enhance( sourceSet, hibernateExtension.enhance, project ); - } - - } - -} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/Helper.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/Helper.java new file mode 100644 index 0000000000..5c59274aaf --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/Helper.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.Directory; + +import org.hibernate.bytecode.enhance.spi.Enhancer; + +/** + * @author Steve Ebersole + */ +public class Helper { + + public static ClassLoader toClassLoader(Directory classesDir) { + final File classesDirFile = classesDir.getAsFile(); + final URI classesDirUri = classesDirFile.toURI(); + try { + final URL url = classesDirUri.toURL(); + return new URLClassLoader( new URL[] { url }, Enhancer.class.getClassLoader() ); + } + catch (MalformedURLException e) { + throw new GradleException( "Unable to resolve classpath entry to URL : " + classesDirFile.getAbsolutePath(), e ); + } + } + + public static String determineClassName(File root, File javaClassFile) { + final Path relativeClassPath = root.toPath().relativize( javaClassFile.toPath() ); + final String relativeClassPathString = relativeClassPath.toString(); + final String classNameBase = relativeClassPathString.substring( + 0, + relativeClassPathString.length() - ".class".length() + ); + return classNameBase.replace( File.separatorChar, '.' ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java new file mode 100644 index 0000000000..356334eb67 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; + +import org.hibernate.orm.tooling.gradle.enhance.EnhancementTask; +import org.hibernate.orm.tooling.gradle.metamodel.JpaMetamodelGenerationTask; + +/** + * Hibernate ORM Gradle plugin + */ +public class HibernateOrmPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPlugins().apply( JavaPlugin.class ); + + project.getLogger().debug( "Adding Hibernate extensions to the build [{}]", project.getPath() ); + final HibernateOrmSpec ormDsl = project.getExtensions().create( HibernateOrmSpec.DSL_NAME, HibernateOrmSpec.class, project ); + + final Configuration hibernateOrm = project.getConfigurations().maybeCreate( "hibernateOrm" ); + project.getDependencies().add( + "hibernateOrm", + project.provider( () -> "org.hibernate.orm:hibernate-core:" + HibernateVersion.version ) + ); + project.getConfigurations().getByName( "implementation" ).extendsFrom( hibernateOrm ); + + final JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin( JavaPluginConvention.class ); + assert javaPluginConvention != null; + + final SourceSetContainer sourceSets = javaPluginConvention.getSourceSets(); + final SourceSet mainSourceSet = sourceSets.getByName( SourceSet.MAIN_SOURCE_SET_NAME ); + + EnhancementTask.apply( ormDsl, mainSourceSet, project ); + JpaMetamodelGenerationTask.apply( ormDsl, mainSourceSet, project ); + + project.getDependencies().add( + "implementation", + project.provider( () -> "org.hibernate.orm:hibernate-core:" + ormDsl.getHibernateVersionProperty().get() ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmSpec.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmSpec.java new file mode 100644 index 0000000000..fd238be553 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmSpec.java @@ -0,0 +1,98 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.provider.Property; + +import org.hibernate.orm.tooling.gradle.enhance.EnhancementSpec; +import org.hibernate.orm.tooling.gradle.metamodel.JpaMetamodelGenerationSpec; + +/** + * Main DSL extension for Hibernate ORM. Available as `project.hibernate` + */ +public abstract class HibernateOrmSpec implements ExtensionAware { + public static final String HIBERNATE = "hibernate"; + + public static final String DSL_NAME = HIBERNATE; + + private final Property hibernateVersionProperty; + private final Property supportEnhancementProperty; + private final Property supportJpaMetamodelProperty; + + private final EnhancementSpec enhancementDsl; + private final JpaMetamodelGenerationSpec jpaMetamodelDsl; + + + @Inject + @SuppressWarnings( "UnstableApiUsage" ) + public HibernateOrmSpec(Project project) { + hibernateVersionProperty = project.getObjects().property( String.class ); + hibernateVersionProperty.convention( HibernateVersion.version ); + + supportEnhancementProperty = project.getObjects().property( Boolean.class ); + supportEnhancementProperty.convention( true ); + + supportJpaMetamodelProperty = project.getObjects().property( Boolean.class ); + supportJpaMetamodelProperty.convention( true ); + + enhancementDsl = getExtensions().create( EnhancementSpec.DSL_NAME, EnhancementSpec.class, this, project ); + jpaMetamodelDsl = getExtensions().create( JpaMetamodelGenerationSpec.DSL_NAME, JpaMetamodelGenerationSpec.class, this, project ); + } + + public Property getHibernateVersionProperty() { + return hibernateVersionProperty; + } + + public void hibernateVersion(String version) { + setHibernateVersion( version ); + } + + public void setHibernateVersion(String version) { + hibernateVersionProperty.set( version ); + } + + public Property getSupportEnhancementProperty() { + return supportEnhancementProperty; + } + + public void disableEnhancement() { + supportEnhancementProperty.set( false ); + } + + public Property getSupportJpaMetamodelProperty() { + return supportJpaMetamodelProperty; + } + + public void disableJpaMetamodel() { + supportJpaMetamodelProperty.set( false ); + } + + public EnhancementSpec getEnhancementSpec() { + return enhancementDsl; + } + + public void enhancement(Action action) { + action.execute( enhancementDsl ); + } + + public JpaMetamodelGenerationSpec getJpaMetamodelSpec() { + return jpaMetamodelDsl; + } + + public void jpaMetamodel(Actionaction) { + action.execute( jpaMetamodelDsl ); + } + + @Override + public abstract ExtensionContainer getExtensions(); +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateVersion.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateVersion.java new file mode 100644 index 0000000000..7589ebf142 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateVersion.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.net.URL; + +import org.gradle.api.GradleException; + +/** + * @author Steve Ebersole + */ +public class HibernateVersion { + public static volatile String version; + + static { + version = determineHibernateVersion(); + } + + private static String determineHibernateVersion() { + final URL versionFileUrl = findVersionFile(); + try ( final InputStream inputStream = versionFileUrl.openStream() ) { + try ( final InputStreamReader inputStreamReader = new InputStreamReader( inputStream ) ) { + return new LineNumberReader( inputStreamReader ).readLine(); + } + } + catch (IOException e) { + throw new GradleException( "Unable to read `META-INF/hibernate-orm.version` resource" ); + } + } + + private static URL findVersionFile() { + final URL badGunsAndRoses = HibernateOrmSpec.class.getClassLoader().getResource( "META-INF/hibernate-orm.version" ); + if ( badGunsAndRoses != null ) { + return badGunsAndRoses; + } + + //noinspection UnnecessaryLocalVariable + final URL goodGunsAndRoses = HibernateOrmSpec.class.getClassLoader().getResource( "/META-INF/hibernate-orm.version" ); + + return goodGunsAndRoses; + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementHelper.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementHelper.java new file mode 100644 index 0000000000..bc8022e252 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementHelper.java @@ -0,0 +1,167 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.enhance; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.logging.Logger; +import org.gradle.work.InputChanges; + +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; +import org.hibernate.cfg.Environment; +import org.hibernate.orm.tooling.gradle.Helper; + +import static org.hibernate.orm.tooling.gradle.Helper.determineClassName; + +/** + * @author Steve Ebersole + */ +public class EnhancementHelper { + public static void enhance( + DirectoryProperty classesDirectoryProperty, + InputChanges inputChanges, + EnhancementSpec enhancementDsl, + Project project) { + final Directory classesDir = classesDirectoryProperty.get(); + final File classesDirFile = classesDir.getAsFile(); + + final Enhancer enhancer = generateEnhancer( classesDir, enhancementDsl ); + + final String classesDirPath = classesDirFile.getAbsolutePath(); + + inputChanges.getFileChanges( classesDirectoryProperty ).forEach( + change -> { + switch ( change.getChangeType() ) { + case ADDED: + case MODIFIED: { + final File changedFile = change.getFile(); + if ( changedFile.getName().endsWith( ".class" ) ) { + final String classFilePath = changedFile.getAbsolutePath(); + if ( classFilePath.startsWith( classesDirPath ) ) { + // we found the directory it came from + // -use that to determine the class name + enhance( changedFile, determineClassName( classesDirFile, changedFile ), enhancer, project ); + break; + } + } + break; + } + case REMOVED: { + // nothing to do + break; + } + default: { + throw new UnsupportedOperationException( "Unexpected ChangeType : " + change.getChangeType().name() ); + } + } + } + ); + } + + private static void enhance( + File javaClassFile, + String className, + Enhancer enhancer, + Project project) { + final byte[] enhancedBytecode = doEnhancement( javaClassFile, className, enhancer ); + if ( enhancedBytecode != null ) { + writeOutEnhancedClass( enhancedBytecode, javaClassFile, project.getLogger() ); + project.getLogger().info( "Successfully enhanced class : " + className ); + } + else { + project.getLogger().info( "Skipping class : " + className ); + } + } + + private static byte[] doEnhancement(File javaClassFile, String className, Enhancer enhancer) { + try { + return enhancer.enhance( className, Files.readAllBytes( javaClassFile.toPath() ) ); + } + catch (Exception e) { + throw new GradleException( "Unable to enhance class : " + className, e ); + } + } + + private static Enhancer generateEnhancer(Directory classesDir, EnhancementSpec enhancementDsl) { + final ClassLoader classLoader = Helper.toClassLoader( classesDir ); + + final EnhancementContext enhancementContext = new DefaultEnhancementContext() { + @Override + public ClassLoader getLoadingClassLoader() { + return classLoader; + } + + @Override + public boolean doBiDirectionalAssociationManagement(UnloadedField field) { + return enhancementDsl.getEnableAssociationManagement().get(); + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return enhancementDsl.getEnableDirtyTracking().get(); + } + + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return enhancementDsl.getEnableLazyInitialization().get(); + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return enhancementDsl.getEnableLazyInitialization().get(); + } + + @Override + public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { + return enhancementDsl.getEnableExtendedEnhancement().get(); + } + }; + + //noinspection deprecation + return Environment.getBytecodeProvider().getEnhancer( enhancementContext ); + } + + private static void writeOutEnhancedClass(byte[] enhancedBytecode, File file, Logger logger) { + try { + if ( file.delete() ) { + if ( !file.createNewFile() ) { + logger.error( "Unable to recreate class file : " + file.getAbsolutePath() ); + } + } + else { + logger.error( "Unable to delete class file : " + file.getAbsolutePath() ); + } + } + catch (IOException e) { + logger.warn( "Problem preparing class file for writing out enhancements [" + file.getAbsolutePath() + "]" ); + } + + try { + Files.write( file.toPath(), enhancedBytecode ); + } + catch (FileNotFoundException e) { + throw new GradleException( "Error opening class file for writing : " + file.getAbsolutePath(), e ); + } + catch (IOException e) { + throw new GradleException( "Error writing enhanced class to file [" + file.getAbsolutePath() + "]", e ); + } + } + + private EnhancementHelper() { + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementSpec.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementSpec.java new file mode 100644 index 0000000000..ab46075d59 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementSpec.java @@ -0,0 +1,129 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.enhance; + +import javax.inject.Inject; + +import org.gradle.api.Project; +import org.gradle.api.provider.Property; + +import org.hibernate.orm.tooling.gradle.HibernateOrmSpec; + +/** + * DSL extension for configuring bytecode enhancement - available as `project.hibernateOrm.enhancement` + */ +@SuppressWarnings( { "unused", "RedundantSuppression" } ) +public class EnhancementSpec { + public static final String ENHANCE = "enhance"; + public static final String ENHANCEMENT = "enhancement"; + + public static final String DSL_NAME = ENHANCEMENT; + + private final Property enableLazyInitialization; + private final Property enableDirtyTracking; + private final Property enableAssociationManagement; + private final Property enableExtendedEnhancement; + + + @Inject + public EnhancementSpec(HibernateOrmSpec ormDsl, Project project) { + enableLazyInitialization = makeProperty( project ); + enableDirtyTracking = makeProperty( project ); + enableAssociationManagement = makeProperty( project ); + enableExtendedEnhancement = makeProperty( project ); + } + + public boolean hasAnythingToDo() { + return enableLazyInitialization.get() + || enableDirtyTracking.get() + || enableAssociationManagement.get() + || enableExtendedEnhancement.get(); + } + + public Property getEnableLazyInitialization() { + return enableLazyInitialization; + } + + public void setEnableLazyInitialization(boolean enable) { + enableLazyInitialization.set( enable ); + } + + public void enableLazyInitialization(boolean enable) { + setEnableLazyInitialization( enable ); + } + + public void lazyInitialization(boolean enable) { + setEnableLazyInitialization( enable ); + } + + public void setLazyInitialization(boolean enable) { + setEnableLazyInitialization( enable ); + } + + + public Property getEnableDirtyTracking() { + return enableDirtyTracking; + } + + public void setEnableDirtyTracking(boolean enable) { + enableDirtyTracking.set( enable ); + } + + public void enableDirtyTracking(boolean enable) { + setEnableDirtyTracking( enable ); + } + + public void dirtyTracking(boolean enable) { + setEnableDirtyTracking( enable ); + } + + public void setDirtyTracking(boolean enable) { + setEnableDirtyTracking( enable ); + } + + + public Property getEnableAssociationManagement() { + return enableAssociationManagement; + } + + public void setEnableAssociationManagement(boolean enable) { + enableAssociationManagement.set( enable ); + } + + public void enableAssociationManagement(boolean enable) { + setEnableAssociationManagement( enable ); + } + + public void associationManagement(boolean enable) { + setEnableAssociationManagement( enable ); + } + + + public Property getEnableExtendedEnhancement() { + return enableExtendedEnhancement; + } + + public void setEnableExtendedEnhancement(boolean enable) { + enableExtendedEnhancement.set( enable ); + } + + public void enableExtendedEnhancement(boolean enable) { + setEnableExtendedEnhancement( enable ); + } + + public void extendedEnhancement(boolean enable) { + setEnableExtendedEnhancement( enable ); + } + + @SuppressWarnings( "UnstableApiUsage" ) + public static Property makeProperty(Project project) { + final Property createdProperty = project.getObjects().property( Boolean.class ); + // default to false + createdProperty.convention( false ); + return createdProperty; + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementTask.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementTask.java new file mode 100644 index 0000000000..d56048936d --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/enhance/EnhancementTask.java @@ -0,0 +1,119 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.enhance; + +import java.util.concurrent.atomic.AtomicBoolean; +import javax.inject.Inject; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.execution.TaskExecutionAdapter; +import org.gradle.api.execution.TaskExecutionGraph; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskState; +import org.gradle.work.Incremental; +import org.gradle.work.InputChanges; + +import org.hibernate.orm.tooling.gradle.HibernateOrmSpec; + +import static org.hibernate.orm.tooling.gradle.HibernateOrmSpec.HIBERNATE; + +/** + * @author Steve Ebersole + */ +public class EnhancementTask extends DefaultTask { + public static final String DSL_NAME = "hibernateEnhance"; + + public static void apply(HibernateOrmSpec ormDsl, SourceSet mainSourceSet, Project project) { + final EnhancementTask enhancementTask = project.getTasks().create( + DSL_NAME, + EnhancementTask.class, + ormDsl, + mainSourceSet, + project + ); + enhancementTask.setGroup( HIBERNATE ); + enhancementTask.setDescription( "Performs Hibernate ORM enhancement of the project's compiled classes" ); + + final String compileJavaTaskName = mainSourceSet.getCompileJavaTaskName(); + final Task compileJavaTask = project.getTasks().getByName( compileJavaTaskName ); + enhancementTask.dependsOn( compileJavaTask ); + compileJavaTask.finalizedBy( enhancementTask ); + } + + private final EnhancementSpec enhancementDsl; + private final DirectoryProperty javaCompileOutputDirectory; + private final DirectoryProperty outputDirectory; + + @Inject + @SuppressWarnings( "UnstableApiUsage" ) + public EnhancementTask(HibernateOrmSpec ormSpec, SourceSet mainSourceSet, Project project) { + this.enhancementDsl = ormSpec.getEnhancementSpec(); + + javaCompileOutputDirectory = mainSourceSet.getJava().getDestinationDirectory(); + + outputDirectory = project.getObjects().directoryProperty(); + outputDirectory.set( project.getLayout().getBuildDirectory().dir( "tmp/hibernateEnhancement" ) ); + + final AtomicBoolean didCompileRun = new AtomicBoolean( false ); + + final TaskExecutionGraph taskGraph = project.getGradle().getTaskGraph(); + + taskGraph.addTaskExecutionListener( + new TaskExecutionAdapter() { + @Override + public void afterExecute(Task task, TaskState state) { + super.afterExecute( task, state ); + if ( "compileJava".equals( task.getName() ) ) { + if ( state.getDidWork() ) { + didCompileRun.set( true ); + } + } + + taskGraph.removeTaskExecutionListener( this ); + } + } + ); + + getOutputs().upToDateWhen( (task) -> ! didCompileRun.get() ); + } + + @InputDirectory + @Incremental + public DirectoryProperty getJavaCompileDirectory() { + return javaCompileOutputDirectory; + } + + @OutputDirectory + public DirectoryProperty getOutputDirectory() { + return outputDirectory; + } + + @TaskAction + public void enhanceClasses(InputChanges inputChanges) { + if ( !enhancementDsl.hasAnythingToDo() ) { + return; + } + + if ( !inputChanges.isIncremental() ) { + getProject().getLogger().debug( "EnhancementTask inputs were not incremental" ); + } + + if ( enhancementDsl.getEnableExtendedEnhancement().get() ) { + // for extended enhancement, we may need to enhance everything... + // for now, assume we don't + getProject().getLogger().info( "Performing extended enhancement" ); + } + + EnhancementHelper.enhance( javaCompileOutputDirectory, inputChanges, enhancementDsl, getProject() ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationSpec.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationSpec.java new file mode 100644 index 0000000000..5fc18fc4c2 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationSpec.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel; + +import java.util.Arrays; +import javax.inject.Inject; + +import org.gradle.api.JavaVersion; +import org.gradle.api.Project; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.compile.JavaCompile; + +import org.hibernate.orm.tooling.gradle.HibernateOrmSpec; + +/** + * @author Steve Ebersole + */ +public class JpaMetamodelGenerationSpec { + public static final String JPA_METAMODEL = "jpaMetamodel"; + public static final String DSL_NAME = JPA_METAMODEL; + + private final Property applyGeneratedAnnotation; + private final SetProperty suppressions; + private final DirectoryProperty generationOutputDirectory; + private final DirectoryProperty compileOutputDirectory; + + private final Provider targetJavaVersionAccess; + + @Inject + @SuppressWarnings( "UnstableApiUsage" ) + public JpaMetamodelGenerationSpec(HibernateOrmSpec ormDsl, Project project) { + applyGeneratedAnnotation = project.getObjects().property( Boolean.class ); + applyGeneratedAnnotation.convention( true ); + + suppressions = project.getObjects().setProperty( String.class ); + suppressions.convention( Arrays.asList( "raw", "deprecation" ) ); + + generationOutputDirectory = project.getObjects().directoryProperty(); + generationOutputDirectory.convention( + project.getLayout().getBuildDirectory().dir( "generated/sources/" + JPA_METAMODEL ) + ); + + compileOutputDirectory = project.getObjects().directoryProperty(); + compileOutputDirectory.convention( + project.getLayout().getBuildDirectory().dir( "classes/java/" + JPA_METAMODEL ) + ); + + targetJavaVersionAccess = project.provider( + () -> { + final JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin( JavaPluginConvention.class ); + assert javaPluginConvention != null; + final SourceSet sourceSet = javaPluginConvention.getSourceSets().getByName( SourceSet.MAIN_SOURCE_SET_NAME ); + final String compileTaskName = sourceSet.getCompileJavaTaskName(); + final JavaCompile compileTask = (JavaCompile) project.getTasks().getByName( compileTaskName ); + return JavaVersion.toVersion( compileTask.getTargetCompatibility() ); + } + ); + } + + public Provider getTargetJavaVersionAccess() { + return targetJavaVersionAccess; + } + + public Property getApplyGeneratedAnnotation() { + return applyGeneratedAnnotation; + } + + public SetProperty getSuppressions() { + return suppressions; + } + + public DirectoryProperty getGenerationOutputDirectory() { + return generationOutputDirectory; + } + + public DirectoryProperty getCompileOutputDirectory() { + return compileOutputDirectory; + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationTask.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationTask.java new file mode 100644 index 0000000000..abdf36bf2e --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/JpaMetamodelGenerationTask.java @@ -0,0 +1,346 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import javax.inject.Inject; +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; +import javax.sql.DataSource; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.file.ConfigurableFileTree; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetOutput; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.compile.JavaCompile; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.orm.tooling.gradle.Helper; +import org.hibernate.orm.tooling.gradle.HibernateOrmSpec; +import org.hibernate.orm.tooling.gradle.metamodel.model.JpaStaticMetamodelGenerator; +import org.hibernate.orm.tooling.gradle.metamodel.model.MetamodelClass; + +import static org.hibernate.orm.tooling.gradle.HibernateOrmSpec.HIBERNATE; + +/** + * Generates the "JPA static metamodel" from the domain model defined by the project + * via classes and possibly XML mappings + * + * @apiNote While there is also an annotation-processor that performs the same + * general function, that approach is limited in that it can only process compiled + * classes based on annotations. This task accounts for both classes and XML mappings + */ +public class JpaMetamodelGenerationTask extends DefaultTask { + public static final String DSL_NAME = "generateJpaMetamodel"; + public static final String COMPILE_DSL_NAME = "compileJpaMetamodel"; + + + private final HibernateOrmSpec ormSpec; + private final DirectoryProperty resourcesOutputDir; + private final SourceSet mainSourceSet; + + @Inject + @SuppressWarnings( "UnstableApiUsage" ) + public JpaMetamodelGenerationTask( + HibernateOrmSpec ormSpec, + SourceSet mainSourceSet, + JavaCompile mainCompileTask, + Project project) { + this.ormSpec = ormSpec; + dependsOn( mainCompileTask ); + + this.mainSourceSet = mainSourceSet; + + final SourceSetOutput mainSourceSetOutput = mainSourceSet.getOutput(); + + resourcesOutputDir = project.getObjects().directoryProperty(); + resourcesOutputDir.set( project.getLayout().dir( project.provider( mainSourceSetOutput::getResourcesDir ) ) ); + + } + + @InputFiles + @SkipWhenEmpty + public FileCollection getJavaClassDirs() { + return mainSourceSet.getOutput(); + } + + @InputFiles + @SkipWhenEmpty + public DirectoryProperty getResourcesOutputDir() { + // for access to XML mappings + return resourcesOutputDir; + } + + @OutputDirectory + public DirectoryProperty getGenerationOutputDirectory() { + return ormSpec.getJpaMetamodelSpec().getGenerationOutputDirectory(); + } + + @TaskAction + public void generateJpaMetamodel() { + final ClassLoader classLoader = determineUnitClassLoader( getProject(), mainSourceSet ); + final PersistenceUnitInfoImpl unitInfo = new PersistenceUnitInfoImpl( + determineUnitUrl(), + generateIntegrationSettings(), + classLoader + ); + + getJavaClassDirs().forEach( + classesDir -> { + final ConfigurableFileTree files = getProject().fileTree( classesDir ); + files.forEach( + file -> { + if ( file.getName().endsWith( ".class" ) ) { + final String className = Helper.determineClassName( classesDir, file ); + unitInfo.addManagedClassName( className ); + } + else if ( isMappingFile( file ) ) { + unitInfo.addMappingFile( file.getName() ); + } + } + ); + } + ); + + resourcesOutputDir.getAsFileTree().forEach( + file -> { + if ( isMappingFile( file ) ) { + unitInfo.addMappingFile( file.getName() ); + } + } + ); + + JpaStaticMetamodelGenerator.processMetamodel( unitInfo, ormSpec.getJpaMetamodelSpec() ); + } + + private URL determineUnitUrl() { + try { + // NOTE : we just need *a* URL - we used the project dir + return getProject().getProjectDir().toURI().toURL(); + } + catch (MalformedURLException e) { + throw new IllegalStateException( "Could not interpret project directory as URL" ); + } + } + + @SuppressWarnings( "UnstableApiUsage" ) + private static ClassLoader determineUnitClassLoader(Project project, SourceSet mainSourceSet) { + final String compileJavaTaskName = mainSourceSet.getCompileJavaTaskName(); + final JavaCompile javaCompileTask = (JavaCompile) project.getTasks().getByName( compileJavaTaskName ); + final URL projectClassesDirUrl = toUrl( javaCompileTask.getDestinationDirectory().get().getAsFile() ); + + return new URLClassLoader( new URL[] { projectClassesDirUrl }, MetamodelClass.class.getClassLoader() ); + } + + private static URL toUrl(File file) { + final URI uri = file.toURI(); + try { + return uri.toURL(); + } + catch (MalformedURLException e) { + throw new GradleException( "Could not convert classpath entry into URL : " + file.getAbsolutePath(), e ); + } + } + + private Properties generateIntegrationSettings() { + final Properties settings = new Properties(); + + settings.put( "hibernate.temp.use_jdbc_metadata_defaults", "false" ); + settings.put( AvailableSettings.DIALECT, "H2" ); + settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, false ); + settings.put( AvailableSettings.USE_QUERY_CACHE, false ); + + return settings; + } + + private boolean isMappingFile(File file) { + final String fileName = file.getName(); + + // convention + // - we could allow filters for flexibility? + return fileName.endsWith( ".hbm.xml" ) || fileName.endsWith( ".orm.xml" ); + } + + private static class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + private final URL unitRoot; + private final Properties properties; + private final ClassLoader classLoader; + private final List managedClassNames = new ArrayList<>(); + private final List mappingFileNames = new ArrayList<>(); + + public PersistenceUnitInfoImpl(URL unitRoot, Properties properties, ClassLoader classLoader) { + this.unitRoot = unitRoot; + this.properties = properties; + this.classLoader = classLoader; + } + + @Override + public String getPersistenceUnitName() { + return "jpa-static-metamodel-gen"; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return unitRoot; + } + + @Override + public Properties getProperties() { + return properties; + } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public List getManagedClassNames() { + return managedClassNames; + } + + public void addManagedClassName(String className) { + getManagedClassNames().add( className ); + } + + @Override + public List getMappingFileNames() { + return mappingFileNames; + } + + public void addMappingFile(String fileName) { + getMappingFileNames().add( fileName ); + } + + + + + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public DataSource getJtaDataSource() { + return null; + } + + @Override + public DataSource getNonJtaDataSource() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public boolean excludeUnlistedClasses() { + return true; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return null; + } + + @Override + public void addTransformer(ClassTransformer transformer) { + + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } + } + + @SuppressWarnings( "UnstableApiUsage" ) + public static void apply(HibernateOrmSpec ormDsl, SourceSet mainSourceSet, Project project) { + final String mainCompileTaskName = mainSourceSet.getCompileJavaTaskName(); + final JavaCompile mainCompileTask = (JavaCompile) project.getTasks().getByName( mainCompileTaskName ); + + final JpaMetamodelGenerationTask genTask = project.getTasks().create( + DSL_NAME, + JpaMetamodelGenerationTask.class, + ormDsl, + mainSourceSet, + mainCompileTask, + project + ); + genTask.setGroup( HIBERNATE ); + genTask.setDescription( "Generates the JPA 'static metamodel'" ); + + genTask.dependsOn( mainCompileTask ); + + final Task compileResourcesTask = project.getTasks().getByName( "processResources" ); + genTask.dependsOn( compileResourcesTask ); + + final JavaCompile compileJpaMetamodelTask = project.getTasks().create( COMPILE_DSL_NAME, JavaCompile.class ); + compileJpaMetamodelTask.setGroup( HIBERNATE ); + compileJpaMetamodelTask.setDescription( "Compiles the JPA static metamodel generated by `" + DSL_NAME + "`" ); + compileJpaMetamodelTask.setSourceCompatibility( mainCompileTask.getSourceCompatibility() ); + compileJpaMetamodelTask.setTargetCompatibility( mainCompileTask.getTargetCompatibility() ); + genTask.finalizedBy( compileJpaMetamodelTask ); + compileJpaMetamodelTask.dependsOn( genTask ); + compileJpaMetamodelTask.source( project.files( ormDsl.getJpaMetamodelSpec().getGenerationOutputDirectory() ) ); + compileJpaMetamodelTask.getDestinationDirectory().set( ormDsl.getJpaMetamodelSpec().getCompileOutputDirectory() ); + compileJpaMetamodelTask.setClasspath( + project.getConfigurations().getByName( "runtimeClasspath" ).plus( mainSourceSet.getRuntimeClasspath() ) + ); + + compileJpaMetamodelTask.doFirst( + (task) -> { + project.getLogger().lifecycle( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); + project.getLogger().lifecycle( "compileJpaMetamodel classpath" ); + project.getLogger().lifecycle( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); + ( (JavaCompile) task ).getClasspath().forEach( + entry -> project.getLogger().lifecycle( " > {}", entry.getAbsolutePath() ) + ); + project.getLogger().lifecycle( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" ); + } + ); + } + +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeBag.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeBag.java new file mode 100644 index 0000000000..6cdf182035 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeBag.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Collection; + +public class AttributeBag extends AttributeSupport { + private final Class elementJavaType; + + public AttributeBag( + MetamodelClass metamodelClass, + String attributeName, + Class elementJavaType) { + super( metamodelClass, attributeName, Collection.class ); + this.elementJavaType = elementJavaType; + } + + @Override + public void renderAttributeType(BufferedWriter writer) throws IOException { + writer.write( + format( + "CollectionAttribute<%s,%s>", + getOwnerDomainClassName(), + elementJavaType.getName() + ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeList.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeList.java new file mode 100644 index 0000000000..d9f4ef4db8 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeList.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Set; + +public class AttributeList extends AttributeSupport { + private final Class elementJavaType; + + public AttributeList( + MetamodelClass metamodelClass, + String attributeName, + Class elementJavaType) { + super( metamodelClass, attributeName, Set.class ); + this.elementJavaType = elementJavaType; + } + + @Override + public void renderAttributeType(BufferedWriter writer) throws IOException { + writer.write( + format( + "ListAttribute<%s,%s>", + getOwnerDomainClassName(), + elementJavaType.getName() + ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeMap.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeMap.java new file mode 100644 index 0000000000..c1d47e2c1c --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeMap.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Set; + +public class AttributeMap extends AttributeSupport { + private final Class keyJavaType; + private final Class elementJavaType; + + public AttributeMap( + MetamodelClass metamodelClass, + String attributeName, + Class keyJavaType, + Class elementJavaType) { + super( metamodelClass, attributeName, Set.class ); + this.keyJavaType = keyJavaType; + this.elementJavaType = elementJavaType; + } + + @Override + public void renderAttributeType(BufferedWriter writer) throws IOException { + writer.write( + format( + "MapAttribute<%s,%s,%s>", + getOwnerDomainClassName(), + keyJavaType.getName(), + elementJavaType.getName() + ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSet.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSet.java new file mode 100644 index 0000000000..4cfb15fc9e --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSet.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Set; + +public class AttributeSet extends AttributeSupport { + private final Class elementJavaType; + + public AttributeSet( + MetamodelClass metamodelClass, + String attributeName, + Class elementJavaType) { + super( metamodelClass, attributeName, Set.class ); + this.elementJavaType = elementJavaType; + } + + @Override + public void renderAttributeType(BufferedWriter writer) throws IOException { + writer.write( + format( + "SetAttribute<%s,%s>", + getOwnerDomainClassName(), + elementJavaType.getName() + ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSingular.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSingular.java new file mode 100644 index 0000000000..95cb61c33b --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSingular.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; + +public class AttributeSingular extends AttributeSupport { + + public AttributeSingular(MetamodelClass metamodelClass, String name, Class javaType) { + super( metamodelClass, name, javaType ); + } + + @Override + public void renderAttributeType(BufferedWriter writer) throws IOException { + // JPA stuff already imported + writer.write( + format( + "SingularAttribute<%s,%s>", + getOwnerDomainClassName(), + getAttributeJavaType().getName() + ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSupport.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSupport.java new file mode 100644 index 0000000000..0c0055ff15 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/AttributeSupport.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Locale; + +import static java.lang.Character.LINE_SEPARATOR; + +public abstract class AttributeSupport implements MetamodelAttribute { + private final MetamodelClass metamodelClass; + private final String name; + private final Class javaType; + + public AttributeSupport( + MetamodelClass metamodelClass, + String name, + Class javaType) { + this.metamodelClass = metamodelClass; + this.name = name; + this.javaType = javaType; + } + + @Override + public String getName() { + return name; + } + + @Override + public Class getAttributeJavaType() { + return javaType; + } + + public String getOwnerDomainClassName() { + return metamodelClass.getMetamodelClassName(); + } + + @Override + public void renderJpaMembers(BufferedWriter writer) { + try { + writer.write( " public static volatile " ); + renderAttributeType( writer ); + writer.write( " " + name ); + writer.write( ';' ); + writer.write( LINE_SEPARATOR ); + } + catch (IOException e) { + throw new IllegalStateException( "Problem writing attribute `" + metamodelClass.getMetamodelClassName() + "#" + name + "` to output stream", e ); + } + } + + public abstract void renderAttributeType(BufferedWriter writer) throws IOException; + + protected String format(String pattern, Object... args) { + return String.format( Locale.ROOT, pattern, args ); + } + + @Override + public void renderNameConstant(BufferedWriter writer) { + try { + writer.write( " public static final String " ); + writer.write( getName().toUpperCase( Locale.ROOT ) ); + writer.write( " = \"" + getName() + "\";" ); + writer.write( LINE_SEPARATOR ); + } + catch (IOException e) { + throw new IllegalStateException( "Problem writing attribute `" + metamodelClass.getMetamodelClassName() + "#" + name + "` to output stream", e ); + } + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/JpaStaticMetamodelGenerator.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/JpaStaticMetamodelGenerator.java new file mode 100644 index 0000000000..645d766a51 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/JpaStaticMetamodelGenerator.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.persistence.spi.PersistenceUnitInfo; + +import org.gradle.api.file.Directory; +import org.gradle.api.file.RegularFile; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.orm.tooling.gradle.metamodel.JpaMetamodelGenerationSpec; + +public class JpaStaticMetamodelGenerator { + public static void processMetamodel( + PersistenceUnitInfo persistenceUnitInfo, + JpaMetamodelGenerationSpec spec) { + EntityManagerFactoryBuilder target = Bootstrap.getEntityManagerFactoryBuilder( persistenceUnitInfo, Collections.emptyMap() ); + try { + new JpaStaticMetamodelGenerator( spec, target.metadata() ).process(); + } + finally { + target.cancel(); + } + } + + private final JpaMetamodelGenerationSpec spec; + private final MetadataImplementor metadata; + + private final Directory generationOutputDirectory; + private final ObjectFactory objectFactory; + + private final Set processedDomainTypeNames = new HashSet<>(); + + private JpaStaticMetamodelGenerator(JpaMetamodelGenerationSpec spec, MetadataImplementor metadata) { + this.spec = spec; + this.metadata = metadata; + this.generationOutputDirectory = spec.getGenerationOutputDirectory().get(); + this.objectFactory = new ObjectFactory( metadata ); + } + + private void process() { + final Set mappedSuperclasses = metadata.getMappedSuperclassMappingsCopy(); + if ( mappedSuperclasses != null ) { + mappedSuperclasses.forEach( this::handleMappedClass ); + } + + final java.util.Collection entityBindings = metadata.getEntityBindings(); + if ( entityBindings != null ) { + entityBindings.forEach( this::handlePersistentClass ); + } + } + + @SuppressWarnings( "unchecked" ) + private void handleMappedClass(MappedSuperclass mappingDescriptor) { + final MetamodelClass metamodelClass = objectFactory.metamodelClass( mappingDescriptor ); + handleManagedClass( metamodelClass, mappingDescriptor.getDeclaredPropertyIterator() ); + } + + @SuppressWarnings( "unchecked" ) + private void handlePersistentClass(PersistentClass persistentClass) { + final MetamodelClass metamodelClass = objectFactory.metamodelClass( persistentClass ); + handleManagedClass( metamodelClass, persistentClass.getDeclaredPropertyIterator() ); + } + + private void handleManagedClass(MetamodelClass metamodelClass, Iterator propertyIterator) { + if ( ! processedDomainTypeNames.add( metamodelClass.getDomainClassName() ) ) { + // already processed + return; + } + + propertyIterator.forEachRemaining( + property -> metamodelClass.addAttribute( + objectFactory.attribute( property, property.getValue(), metamodelClass, this::handleEmbeddable ) + ) + ); + + final String replaced = metamodelClass.getMetamodelClassName().replace( '.', '/' ); + final String metamodelClassJavaFileName = replaced + ".java"; + final RegularFile metamodelClassJavaFile = generationOutputDirectory.file( metamodelClassJavaFileName ); + + final File metamodelClassJavaFileAsFile = metamodelClassJavaFile.getAsFile(); + metamodelClass.writeToFile( metamodelClassJavaFileAsFile, spec ); + } + + @SuppressWarnings( "unchecked" ) + private void handleEmbeddable(Component embeddedValueMapping) { + final MetamodelClass metamodelClass = objectFactory.metamodelClass( embeddedValueMapping ); + handleManagedClass( metamodelClass, embeddedValueMapping.getPropertyIterator() ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelAttribute.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelAttribute.java new file mode 100644 index 0000000000..c3ec077149 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelAttribute.java @@ -0,0 +1,19 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; + +public interface MetamodelAttribute { + String getName(); + + Class getAttributeJavaType(); + + void renderJpaMembers(BufferedWriter writer); + + void renderNameConstant(BufferedWriter writer); +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelClass.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelClass.java new file mode 100644 index 0000000000..bcde5938fd --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/MetamodelClass.java @@ -0,0 +1,156 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.text.DateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; + +import org.gradle.api.GradleException; +import org.gradle.api.JavaVersion; + +import org.hibernate.orm.tooling.gradle.metamodel.JpaMetamodelGenerationSpec; + +import static java.lang.Character.LINE_SEPARATOR; + +/** + * Descriptor for a class in the JPA static metamodel being generated + */ +public class MetamodelClass { + + private final String domainClassName; + private final String metamodelClassName; + + private final String metamodelSuperClassName; + + private final TreeSet attributes = new TreeSet<>( Comparator.comparing( MetamodelAttribute::getName ) ); + + public MetamodelClass(String domainClassName, String superTypeName) { + this.domainClassName = domainClassName; + this.metamodelClassName = domainClassName + "_"; + this.metamodelSuperClassName = superTypeName == null ? null : superTypeName + "_"; + } + + public String getMetamodelClassName() { + return metamodelClassName; + } + + public String getDomainClassName() { + return domainClassName; + } + + public void addAttribute(MetamodelAttribute attribute) { + assert attribute != null; + attributes.add( attribute ); + } + + public void writeToFile(File outputFile, JpaMetamodelGenerationSpec spec) { + prepareOutputFile( outputFile ); + + final Path path = outputFile.toPath(); + + try ( final BufferedWriter writer = Files.newBufferedWriter( path, StandardCharsets.UTF_8, StandardOpenOption.WRITE ) ) { + + renderClassPreamble( writer, spec ); + writer.write( LINE_SEPARATOR ); + + writer.write( "public abstract class " + metamodelClassName ); + if ( metamodelSuperClassName != null ) { + writer.write( " extends " + metamodelSuperClassName ); + } + writer.write( " {" ); + writer.write( LINE_SEPARATOR ); + writer.write( LINE_SEPARATOR ); + + writer.write( " // Attribute name constants" ); + writer.write( LINE_SEPARATOR ); + attributes.forEach( attribute -> attribute.renderNameConstant( writer ) ); + writer.write( LINE_SEPARATOR ); + writer.write( LINE_SEPARATOR ); + + writer.write( " // JPA static metamodel fields" ); + writer.write( LINE_SEPARATOR ); + attributes.forEach( attribute -> attribute.renderJpaMembers( writer ) ); + writer.write( LINE_SEPARATOR ); + + writer.write( "}" ); + writer.write( LINE_SEPARATOR ); + } + catch (IOException e) { + throw new IllegalStateException( "Unable to open file : " + outputFile.getAbsolutePath(), e ); + } + } + + private void renderClassPreamble(BufferedWriter writer, JpaMetamodelGenerationSpec spec) throws IOException { + final String nowFormatted = DateFormat.getDateInstance().format( new Date() ); + + writer.write( "// Generated by Hibernate ORM Gradle tooling - " + nowFormatted ); + writer.write( LINE_SEPARATOR ); + writer.write( LINE_SEPARATOR ); + + writer.write( "import javax.persistence.*;" ); + writer.write( LINE_SEPARATOR ); + writer.write( "import javax.persistence.metamodel.*;" ); + writer.write( LINE_SEPARATOR ); + writer.write( LINE_SEPARATOR ); + + writer.write( "/** JPA static metamodel descriptor for the `" + domainClassName + "` domain class */" ); + writer.write( LINE_SEPARATOR ); + + // first, the generated annotation + if ( spec.getApplyGeneratedAnnotation().getOrElse( true ) ) { + final JavaVersion javaVersion = spec.getTargetJavaVersionAccess().getOrElse( JavaVersion.current() ); + final String qualifiedAnnotationName = javaVersion.isJava9Compatible() + ? "javax.annotation.processing.Generated" + : "javax.annotation.Generated"; + final String generatedAnnotationFragment = String.format( + Locale.ROOT, + "@%s( value=\"%s\", date=\"%s\", comments=\"%s\" )", + qualifiedAnnotationName, + JpaStaticMetamodelGenerator.class.getName(), + nowFormatted, + "Generated by Hibernate ORM Gradle tooling" + ); + writer.write( generatedAnnotationFragment ); + writer.write( LINE_SEPARATOR ); + } + + final Set suppressions = spec.getSuppressions().getOrElse( Collections.emptySet() ); + if ( ! suppressions.isEmpty() ) { + writer.write( "@SuppressWarnings( { " ); + for ( String suppression : suppressions ) { + writer.write( "\"" + suppression + "\", " ); + } + writer.write( " } )" ); + writer.write( LINE_SEPARATOR ); + } + + writer.write( "@StaticMetamodel( " + domainClassName + ".class )" ); + } + + @SuppressWarnings( "ResultOfMethodCallIgnored" ) + public void prepareOutputFile(File outputFile) { + try { + outputFile.getParentFile().mkdirs(); + outputFile.createNewFile(); + } + catch (IOException e) { + throw new GradleException( "Unable to prepare output file `" + outputFile.getAbsolutePath() + "`", e ); + } + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/ObjectFactory.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/ObjectFactory.java new file mode 100644 index 0000000000..51b3e785e9 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/metamodel/model/ObjectFactory.java @@ -0,0 +1,237 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle.metamodel.model; + +import java.util.Locale; +import java.util.function.Consumer; + +import org.gradle.api.GradleException; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.Any; +import org.hibernate.mapping.Bag; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Component; +import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.KeyValue; +import org.hibernate.mapping.List; +import org.hibernate.mapping.Map; +import org.hibernate.mapping.MappedSuperclass; +import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.Set; +import org.hibernate.mapping.ToOne; +import org.hibernate.mapping.Value; + +/** + * @author Steve Ebersole + */ +public class ObjectFactory { + private final MetadataImplementor metadata; + + public ObjectFactory(MetadataImplementor metadata) { + this.metadata = metadata; + } + + public MetamodelClass metamodelClass(PersistentClass entityDescriptor) { + return new MetamodelClass( entityDescriptor.getMappedClass().getName(), determineSuperTypeName( entityDescriptor ) ); + } + + private String determineSuperTypeName(PersistentClass entityDescriptor) { + if ( entityDescriptor.getSuperMappedSuperclass() != null ) { + return entityDescriptor.getSuperMappedSuperclass().getMappedClass().getName(); + } + + if ( entityDescriptor.getSuperclass() != null ) { + return entityDescriptor.getSuperclass().getMappedClass().getName(); + } + + return null; + } + + public MetamodelClass metamodelClass(MappedSuperclass mappedSuperclassDescriptor) { + return new MetamodelClass( mappedSuperclassDescriptor.getMappedClass().getName(), determineSuperTypeName( mappedSuperclassDescriptor ) ); + } + + private String determineSuperTypeName(MappedSuperclass mappedSuperclassDescriptor) { + if ( mappedSuperclassDescriptor.getSuperMappedSuperclass() != null ) { + return mappedSuperclassDescriptor.getSuperMappedSuperclass().getMappedClass().getName(); + } + + if ( mappedSuperclassDescriptor.getSuperPersistentClass() != null ) { + return mappedSuperclassDescriptor.getSuperPersistentClass().getMappedClass().getName(); + } + + + return null; + } + + public MetamodelClass metamodelClass(Component embeddedMapping) { + return new MetamodelClass( embeddedMapping.getComponentClassName(), null ); + } + + public MetamodelAttribute attribute( + Property property, + Value propertyValueMapping, + MetamodelClass metamodelClass, + Consumer componentConsumer) { + if ( propertyValueMapping instanceof DependantValue ) { + final DependantValue dependantValue = (DependantValue) propertyValueMapping; + final KeyValue wrappedValue = dependantValue.getWrappedValue(); + return attribute( property, wrappedValue, metamodelClass, componentConsumer ); + } + + if ( propertyValueMapping instanceof Collection ) { + return pluralAttribute( property, (Collection) propertyValueMapping, metamodelClass, componentConsumer ); + } + + final Class propertyJavaType = determineSingularJavaType( property, propertyValueMapping, metamodelClass, componentConsumer ); + return new AttributeSingular( metamodelClass, property.getName(), propertyJavaType ); + } + + private Class determineSingularJavaType( + Property property, + Value propertyValueMapping, + MetamodelClass metamodelClass, + Consumer componentConsumer) { + if ( propertyValueMapping instanceof BasicValue ) { + final BasicValue basicValue = (BasicValue) propertyValueMapping; + return basicValue.resolve().getDomainJavaDescriptor().getJavaType(); + } + + if ( propertyValueMapping instanceof Component ) { + final Component component = (Component) propertyValueMapping; + componentConsumer.accept( component ); + return component.getComponentClass(); + } + + if ( propertyValueMapping instanceof Any ) { + return Object.class; + } + + if ( propertyValueMapping instanceof ToOne ) { + final ToOne toOne = (ToOne) propertyValueMapping; + final String referencedEntityName = toOne.getReferencedEntityName(); + final PersistentClass entityBinding = metadata.getEntityBinding( referencedEntityName ); + final Class mappedClass = entityBinding.getMappedClass(); + if ( mappedClass == null ) { + throw new GradleException( + String.format( + Locale.ROOT, + "Could not determine ToOne java type : %s#%s", + metamodelClass.getDomainClassName(), + property.getName() + ) + ); + } + return mappedClass; + } + + propertyValueMapping.setTypeUsingReflection( metamodelClass.getDomainClassName(), property.getName() ); + return propertyValueMapping.getType().getReturnedClass(); + } + + private MetamodelAttribute pluralAttribute( + Property property, + Collection collectionMapping, + MetamodelClass metamodelClass, + Consumer componentConsumer) { + if ( collectionMapping instanceof Set ) { + return new AttributeSet( + metamodelClass, + property.getName(), + determineCollectionPartJavaType( property, collectionMapping.getElement(), metamodelClass, componentConsumer ) + ); + } + + if ( collectionMapping instanceof Bag ) { + return new AttributeBag( + metamodelClass, + property.getName(), + determineCollectionPartJavaType( property, collectionMapping.getElement(), metamodelClass, componentConsumer ) + ); + } + + if ( collectionMapping instanceof List ) { + return new AttributeList( + metamodelClass, + property.getName(), + determineCollectionPartJavaType( property, collectionMapping.getElement(), metamodelClass, componentConsumer ) + ); + } + + if ( collectionMapping instanceof Map ) { + return new AttributeMap( + metamodelClass, + property.getName(), + determineCollectionPartJavaType( property, ( (Map) collectionMapping ).getIndex(), metamodelClass, componentConsumer ), + determineCollectionPartJavaType( property, collectionMapping.getElement(), metamodelClass, componentConsumer ) + ); + } + + throw new UnsupportedOperationException( "Unsupported plural value type : " + collectionMapping.getClass().getName() ); + } + + private Class determineCollectionPartJavaType( + Property property, + Value partJavaType, + MetamodelClass metamodelClass, + Consumer componentConsumer) { + if ( partJavaType instanceof DependantValue ) { + final DependantValue dependantValue = (DependantValue) partJavaType; + final KeyValue wrappedValue = dependantValue.getWrappedValue(); + return determineCollectionPartJavaType( property, wrappedValue, metamodelClass, componentConsumer ); + } + + if ( partJavaType instanceof BasicValue ) { + final BasicValue basicValue = (BasicValue) partJavaType; + return basicValue.resolve().getDomainJavaDescriptor().getJavaType(); + } + + if ( partJavaType instanceof Component ) { + final Component component = (Component) partJavaType; + componentConsumer.accept( component ); + return component.getComponentClass(); + } + + if ( partJavaType instanceof Any ) { + return Object.class; + } + + if ( partJavaType instanceof OneToMany ) { + final OneToMany oneToMany = (OneToMany) partJavaType; + final PersistentClass associatedClass = oneToMany.getAssociatedClass(); + return associatedClass.getMappedClass(); + } + + if ( partJavaType instanceof ToOne ) { + final ToOne toOne = (ToOne) partJavaType; + final String referencedEntityName = toOne.getReferencedEntityName(); + if ( referencedEntityName != null ) { + final PersistentClass entityBinding = metadata.getEntityBinding( referencedEntityName ); + if ( entityBinding != null ) { + final Class mappedClass = entityBinding.getMappedClass(); + if ( mappedClass != null ) { + return mappedClass; + } + } + } + throw new GradleException( + String.format( + Locale.ROOT, + "Could not determine ToOne java type : %s#%s", + metamodelClass.getDomainClassName(), + property.getName() + ) + ); + } + + return partJavaType.getType().getReturnedClass(); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.hibernate.orm.properties b/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.hibernate.orm.properties deleted file mode 100644 index 0fb1d5fd7b..0000000000 --- a/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.hibernate.orm.properties +++ /dev/null @@ -1,7 +0,0 @@ -# -# Hibernate, Relational Persistence for Idiomatic Java -# -# License: GNU Lesser General Public License (LGPL), version 2.1 or later. -# See the lgpl.txt file in the root directory or . -# -implementation-class=org.hibernate.orm.tooling.gradle.HibernatePlugin diff --git a/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/hibernate-orm.version b/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/hibernate-orm.version new file mode 100644 index 0000000000..a20c6c25ae --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/main/resources/META-INF/hibernate-orm.version @@ -0,0 +1 @@ +@hibernateVersion@ \ No newline at end of file diff --git a/tooling/hibernate-gradle-plugin/src/test/groovy/org/hibernate/orm/tooling/gradle/HibernatePluginTest.groovy b/tooling/hibernate-gradle-plugin/src/test/groovy/org/hibernate/orm/tooling/gradle/HibernatePluginTest.groovy deleted file mode 100644 index 845e4a681b..0000000000 --- a/tooling/hibernate-gradle-plugin/src/test/groovy/org/hibernate/orm/tooling/gradle/HibernatePluginTest.groovy +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.tooling.gradle -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.testfixtures.ProjectBuilder - -import org.junit.Test - -import static org.junit.Assert.assertNotNull -/** - * Test what we can. ProjectBuilder is better than nothing, but still quited limited in what - * you can test (e.g. you cannot test task execution). - * - * @author Steve Ebersole - */ -class HibernatePluginTest { - @Test - public void testHibernatePluginAddsExtension() { - Project project = ProjectBuilder.builder().build() - project.plugins.apply 'org.hibernate.orm' - - assertNotNull( project.extensions.findByName( "hibernate" ) ) - } - - @Test - public void testHibernateExtensionConfig() { - Project project = ProjectBuilder.builder().build() - project.plugins.apply 'org.hibernate.orm' - - project.extensions.findByType( HibernateExtension.class ).enhance { - enableLazyInitialization = true - enableDirtyTracking = true - enableAssociationManagement = false - enableExtendedEnhancement = false - } - } - - @Test - public void testEnhanceTask() { - Project project = ProjectBuilder.builder().build() - project.plugins.apply 'org.hibernate.orm' - - def task = project.tasks.create( "finishHim", EnhanceTask ) - - task.options { - enableLazyInitialization = true - enableDirtyTracking = true - enableAssociationManagement = false - enableExtendedEnhancement = false - } - - task.sourceSets = project.getConvention().getPlugin( JavaPluginConvention ).sourceSets.main - - task.enhance() - } - - @Test - public void testTaskAction() { - Project project = ProjectBuilder.builder().build() - project.plugins.apply 'org.hibernate.orm' - - // the test sourceSet - def sourceSet = project.getConvention().getPlugin( JavaPluginConvention ).sourceSets.test; - - // The compile task for the test sourceSet - final JavaCompile compileTestTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() ); - - // Lets add our enhancer to enhance the test classes after the test are compiled - compileTestTask.doLast { - EnhancementHelper.enhance( - sourceSet, - project.extensions.findByType( HibernateExtension.class ).enhance, - project - ) - } - - // TODO find how to do this in Gradle 5 - // the class-level javadoc says it's not possible, and it was there in Gradle 4.x... - //compileTestTask.execute() - } -} diff --git a/tooling/hibernate-gradle-plugin/src/testKit/java/org/hibernate/orm/tooling/gradle/HibernateOrmPluginTest.java b/tooling/hibernate-gradle-plugin/src/testKit/java/org/hibernate/orm/tooling/gradle/HibernateOrmPluginTest.java new file mode 100644 index 0000000000..e31eea697b --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/testKit/java/org/hibernate/orm/tooling/gradle/HibernateOrmPluginTest.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.tooling.gradle; + +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import com.github.sebersole.testkit.Project; +import com.github.sebersole.testkit.ProjectScope; +import com.github.sebersole.testkit.TestKit; + +import static org.hamcrest.CoreMatchers.anyOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test what we can. TestKit is better than nothing, but still somewhat limited in what + * you can test in my experience + * + * @author Steve Ebersole + */ +@TestKit +class HibernateOrmPluginTest { + @Test + public void testEnhancementTaskAsFinalizer(@Project( "simple" ) ProjectScope projectScope) { + final GradleRunner gradleRunner = projectScope.createGradleRunner( "clean", "compileJava" ); + final BuildResult result = gradleRunner.build(); + final BuildTask task = result.task( ":hibernateEnhance" ); + assert task != null; + + assertThat( + task.getOutcome(), + anyOf( is( TaskOutcome.SUCCESS ), is( TaskOutcome.UP_TO_DATE ) ) + ); + } + + @Test + public void testEnhancementTask(@Project( "simple" ) ProjectScope projectScope) { + final GradleRunner gradleRunner = projectScope.createGradleRunner( + "clean", + "hibernateEnhance" + ); + final BuildResult result = gradleRunner.build(); + final BuildTask task = result.task( ":hibernateEnhance" ); + assert task != null; + + assertThat( task.getOutcome(), is( TaskOutcome.SUCCESS ) ); + } + + @Test + public void testEnhancementTaskUpToDate(@Project( "simple" ) ProjectScope projectScope) { + final GradleRunner gradleRunner = projectScope.createGradleRunner( + "clean", + "hibernateEnhance" + ); + final BuildResult result = gradleRunner.build(); + final BuildTask task = result.task( ":hibernateEnhance" ); + assert task != null; + + assertThat( + task.getOutcome(), + anyOf( is( TaskOutcome.SUCCESS ), is( TaskOutcome.UP_TO_DATE ) ) + ); + } + + @Test +// @Disabled( "Problem with ClassPathAndModulePathAggregatedServiceLoader and loading Java services" ) + public void testJpaMetamodelGen(@Project( "simple" ) ProjectScope projectScope) { + final GradleRunner gradleRunner = projectScope.createGradleRunner( + "clean", + "generateJpaMetamodel" + ); + final BuildResult result = gradleRunner.build(); + final BuildTask task = result.task( ":generateJpaMetamodel" ); + assert task != null; + + assertThat( + task.getOutcome(), + anyOf( is( TaskOutcome.SUCCESS ), is( TaskOutcome.UP_TO_DATE ) ) + ); + } +} diff --git a/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/build.gradle b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/build.gradle new file mode 100644 index 0000000000..7ef328e56e --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/build.gradle @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +plugins { + id 'java' + id 'org.hibernate.orm' +} + +repositories { + mavenCentral() + + maven { + name 'jboss-snapshots-repository' + url 'https://repository.jboss.org/nexus/content/repositories/snapshots' + } +} + +hibernate { + enhancement { + lazyInitialization( true ) + dirtyTracking = true + } + jpaMetamodel { + } +} diff --git a/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/settings.gradle b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/settings.gradle new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEmbeddable.java b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEmbeddable.java new file mode 100644 index 0000000000..78827d75f9 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEmbeddable.java @@ -0,0 +1,23 @@ +import javax.persistence.Embeddable; + +@Embeddable +public class TheEmbeddable { + private String valueOne; + private String valueTwo; + + public String getValueOne() { + return valueOne; + } + + public void setValueOne(String valueOne) { + this.valueOne = valueOne; + } + + public String getValueTwo() { + return valueTwo; + } + + public void setValueTwo(String valueTwo) { + this.valueTwo = valueTwo; + } +} \ No newline at end of file diff --git a/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEntity.java b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEntity.java new file mode 100644 index 0000000000..4e84a53205 --- /dev/null +++ b/tooling/hibernate-gradle-plugin/src/testKit/resources/simple/src/main/java/TheEntity.java @@ -0,0 +1,79 @@ +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import java.util.Set; + +@Entity +public class TheEntity { + @Id + private Integer id; + private String name; + + @Embedded + private TheEmbeddable theEmbeddable; + + @ManyToOne + @JoinColumn + private TheEntity theManyToOne; + + @OneToMany( mappedBy = "theManyToOne" ) + private Set theOneToMany; + + @ElementCollection + @JoinColumn( name = "owner_id" ) + private Set theEmbeddableCollection; + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TheEmbeddable getTheEmbeddable() { + return theEmbeddable; + } + + public void setTheEmbeddable(TheEmbeddable theEmbeddable) { + this.theEmbeddable = theEmbeddable; + } + + public TheEntity getTheManyToOne() { + return theManyToOne; + } + + public void setTheManyToOne(TheEntity theManyToOne) { + this.theManyToOne = theManyToOne; + } + + public Set getTheOneToMany() { + return theOneToMany; + } + + public void setTheOneToMany(Set theOneToMany) { + this.theOneToMany = theOneToMany; + } + + public Set getTheEmbeddableCollection() { + return theEmbeddableCollection; + } + + public void setTheEmbeddableCollection(Set theEmbeddableCollection) { + this.theEmbeddableCollection = theEmbeddableCollection; + } +} \ No newline at end of file diff --git a/tooling/project-template/README.adoc b/tooling/project-template/README.adoc new file mode 100644 index 0000000000..ee9a399e11 --- /dev/null +++ b/tooling/project-template/README.adoc @@ -0,0 +1,6 @@ +A template for projects wanting to use Hibernate ORM. + +Useful to either: + +1. Bootstrap a new user project +2. Bootstrap a test-case project diff --git a/tooling/project-template/project-template.gradle b/tooling/project-template/project-template.gradle new file mode 100644 index 0000000000..7addf7d7dd --- /dev/null +++ b/tooling/project-template/project-template.gradle @@ -0,0 +1,112 @@ +import org.apache.tools.ant.filters.ReplaceTokens + +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +plugins { + id 'base' + + // for publishing snapshots + id 'maven-publish' + id 'org.hibernate.build.maven-repo-auth' + + // publishing to BinTray + id "com.jfrog.bintray" + id "nu.studer.credentials" version "2.1" +} + +// NOTE : Had trouble using the `distribution` plugin, so manually using Jar/Tar tasks + +version = '0.1' + +ext { + processedTemplateDir = project.layout.buildDirectory.dir('resources/template') + archiveDir = project.layout.buildDirectory.dir('distributions') +} + +task processTemplateResources(type:Copy) { + inputs.files( 'src/template/resources' ) + outputs.dir( processedTemplateDir ) + + description = 'Copies the template sources into the build dir, performing some replacements' + + from( 'src/template/resources' ) { + filter( ReplaceTokens, tokens: [ 'ormVersion' : project.version.toString() ] ) + } + into processedTemplateDir.get().asFile +} + +task templateTgz(type:Tar) { + description = 'Bundles the template project into a TGZ archive' + + inputs.files( processedTemplateDir ) + outputs.dir( archiveDir ) + + dependsOn project.tasks.processTemplateResources + + compression = Compression.GZIP + + from processedTemplateDir.get().asFile + + destinationDirectory = archiveDir +} + +task templateZip(type:Zip) { + description = 'Bundles the template project into a Zip archive' + + inputs.files( processedTemplateDir ) + outputs.dir( archiveDir ) + + dependsOn project.tasks.processTemplateResources + + from processedTemplateDir.get().asFile + + destinationDirectory = archiveDir +} + +bintray { + user = credentials.'personal.bintray.user' + key = credentials.'personal.bintray.key' + + filesSpec { + from templateTgz + from templateZip + } + + pkg { + userOrg = 'hibernate' + repo = 'generic' + name = 'orm-project-template' + } +} + +task assembleDist( dependsOn: [tasks.templateTgz, tasks.templateZip] ) +task release( dependsOn: tasks.assembleDist ) + +tasks.publish { + dependsOn tasks.assembleDist +} + +tasks.bintrayUpload { + dependsOn tasks.assembleDist + doFirst { + if ( credentials.'personal.bintray.user' == null ) { + throw new GradleException( "BinTray user not known, cannot perform upload" ); + } + if ( credentials.'personal.bintray.key' == null ) { + throw new GradleException( "BinTray API key not known, cannot perform upload" ); + } + } +} + +if ( version.toString().endsWith( "-SNAPSHOT" ) ) { + tasks.bintrayUpload.enabled = false + tasks.release.dependsOn tasks.publish +} +else { + tasks.publish.enabled = false + tasks.release.dependsOn tasks.bintrayUpload +} \ No newline at end of file diff --git a/tooling/project-template/src/template/resources/README.adoc b/tooling/project-template/src/template/resources/README.adoc new file mode 100644 index 0000000000..d447ebe99e --- /dev/null +++ b/tooling/project-template/src/template/resources/README.adoc @@ -0,0 +1,5 @@ +A template project to help bootstrap a Gradle project with support for Hibernate +specific extensions. + +This might be useful for new users wanting to get started as well as a quick way to +bootstrap project for test cases (bug reports, etc) diff --git a/tooling/project-template/src/template/resources/build.gradle.kts b/tooling/project-template/src/template/resources/build.gradle.kts new file mode 100644 index 0000000000..9a1afd7d5a --- /dev/null +++ b/tooling/project-template/src/template/resources/build.gradle.kts @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +// ################################################################################### +// again, needed to be able to consume `org.hibernate.orm` plugin SNAPSHOTS +buildscript { + configurations { + classpath { + resolutionStrategy { + cacheChangingModulesFor(0, java.util.concurrent.TimeUnit.SECONDS ) + } + } + } +} +// ################################################################################### + + +plugins { + java + + // todo : find a way to inject this version + // - this is yet another example of where lazybones + // (or proper Gradle build-init feature) would be + // incredibly useful. Same with groupId, package-name, + // etc. + id( "org.hibernate.orm" ) version "@ormVersion@" +} + +group = "your.org" +version = "the-version" + +repositories { + mavenCentral() +} + +dependencies { + val ormVersion = "@ormVersion@" + val junit5Version = "5.3.1" + val h2Version = "1.4.199" + + implementation( "org.hibernate.orm", "hibernate-core", ormVersion ) + + testImplementation( "org.hibernate.orm", "hibernate-testing", ormVersion ) + testImplementation( "org.junit.jupiter", "junit-jupiter-api", junit5Version ) + testImplementation( "org.junit.jupiter", "junit-jupiter-params", junit5Version ) + + testRuntimeOnly( "org.junit.jupiter", "junit-jupiter-engine", junit5Version ) + testRuntimeOnly( "com.h2database", "h2", h2Version ) + testRuntimeOnly( "org.jboss.logging", "jboss-logging", "3.3.2.Final" ) + testRuntimeOnly( "log4j", "log4j", "1.2.17" ) +} + +hibernate { + enhancement { + // all false by default + lazyInitialization = true + dirtyTracking = true + } +} + +tasks { + test { + useJUnitPlatform() + } +} + +task( "compile" ) { + dependsOn( tasks.compileJava ) + dependsOn( tasks.compileTestJava ) + dependsOn( tasks.processResources ) + dependsOn( tasks.processTestResources ) +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 +} diff --git a/tooling/project-template/src/template/resources/settings.gradle.kts b/tooling/project-template/src/template/resources/settings.gradle.kts new file mode 100644 index 0000000000..edbb06527f --- /dev/null +++ b/tooling/project-template/src/template/resources/settings.gradle.kts @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +rootProject.name = "my-hibernate-project" + +// ################################################################################### +// A lot of magic to be able to consumer SNAPSHOT versions of the Hibernate ORM plugin... +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = "jboss-snapshots-repository" + url = uri( "https://repository.jboss.org/nexus/content/repositories/snapshots" ) + } + } + + resolutionStrategy { + eachPlugin { + if ( requested.id.namespace == "org.hibernate" + && requested.id.name == "orm" + && requested.version.orEmpty().endsWith("-SNAPSHOT" ) ) { + val notation = "org.hibernate.orm:hibernate-gradle-plugin:${requested.version}" + logger.lifecycle( "Swapping SNAPSHOT version of plugin : {}", notation ) + useModule( notation ) + } + } + } +} +// ################################################################################### \ No newline at end of file diff --git a/tooling/project-template/src/template/resources/src/main/java/org/your/domain/SimpleEntity.java b/tooling/project-template/src/template/resources/src/main/java/org/your/domain/SimpleEntity.java new file mode 100644 index 0000000000..f97493db5c --- /dev/null +++ b/tooling/project-template/src/template/resources/src/main/java/org/your/domain/SimpleEntity.java @@ -0,0 +1,38 @@ +package org.your.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * A simple example entity + */ +@Entity +public class SimpleEntity { + @Id + private Integer id; + private String name; + + private SimpleEntity() { + } + + public SimpleEntity(Integer id) { + this.id = id; + } + + public SimpleEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/tooling/project-template/src/template/resources/src/test/java/org/your/domain/EntityTests.java b/tooling/project-template/src/template/resources/src/test/java/org/your/domain/EntityTests.java new file mode 100644 index 0000000000..5c038859be --- /dev/null +++ b/tooling/project-template/src/template/resources/src/test/java/org/your/domain/EntityTests.java @@ -0,0 +1,53 @@ +package org.your.domain; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.arjuna.ats.internal.jdbc.recovery.JDBCXARecovery.PASSWORD; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.cfg.AvailableSettings.URL; +import static org.hibernate.cfg.AvailableSettings.USER; + +/** + * Tests for SimpleEntity + */ +@ServiceRegistry( + settings = { + // can define settings here, or in `hibernate.properties` file + @ServiceRegistry.Setting( name = URL, value = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000" ), + @ServiceRegistry.Setting( name = USER, value = "sa" ), + @ServiceRegistry.Setting( name = PASSWORD, value = "" ) + } +) +@DomainModel( annotatedClasses = SimpleEntity.class ) +@SessionFactory() +public class EntityTests { + @Test + public void basicTest(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final SimpleEntity entity = session.createQuery( "from SimpleEntity", SimpleEntity.class ).uniqueResult(); + assertThat( entity, notNullValue() ); + } + ); + } + + @BeforeEach + public void createTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.persist( new SimpleEntity( 1, "the first" ) ) + ); + } + + @BeforeEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.createQuery( "delete SimpleEntity" ).executeUpdate() + ); + } +} diff --git a/tooling/project-template/src/template/resources/src/test/resources/log4j.properties b/tooling/project-template/src/template/resources/src/test/resources/log4j.properties new file mode 100644 index 0000000000..9863d25fbe --- /dev/null +++ b/tooling/project-template/src/template/resources/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n + +log4j.rootLogger=warn, stdout +log4j.logger.org.hibernate=info \ No newline at end of file