diff --git a/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle b/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle index 8cf198508b36..3f09b9a3c36f 100644 --- a/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle +++ b/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle @@ -12,14 +12,34 @@ plugins { description = 'Maven plugin to integrate aspects of Hibernate into your build.' +sourceSets { + intTest { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } +} + +configurations { + intTestImplementation.extendsFrom implementation + intTestRuntimeOnly.extendsFrom runtimeOnly +} + dependencies { implementation project( ":hibernate-core" ) - implementation "org.apache.maven:maven-plugin-api:3.6.3" + implementation "org.apache.maven:maven-plugin-api:3.9.11" implementation "org.apache.maven:maven-project:2.2.1" implementation "org.apache.maven.shared:file-management:3.1.0" - compileOnly "org.apache.maven.plugin-tools:maven-plugin-annotations:3.6.0" + compileOnly "org.apache.maven.plugin-tools:maven-plugin-tools-annotations:3.15.1" + + intTestImplementation 'org.junit.jupiter:junit-jupiter:5.13.4' + intTestImplementation 'org.apache.maven:maven-embedder:3.9.11' + intTestRuntimeOnly 'org.junit.platform:junit-platform-launcher' + intTestRuntimeOnly 'ch.qos.logback:logback-classic:1.5.18' + intTestRuntimeOnly 'org.apache.maven:maven-compat:3.9.11' + intTestRuntimeOnly 'org.apache.maven.resolver:maven-resolver-transport-http:1.9.24' + intTestRuntimeOnly 'org.apache.maven.resolver:maven-resolver-connector-basic:1.9.24' } def releasePrepareTask = tasks.register("releasePrepare") { @@ -37,6 +57,22 @@ tasks.register("preVerifyRelease") { dependsOn releasePrepareTask } +tasks.register('integrationTest', Test) { + description = 'Runs integration tests.' + group = 'verification' + + testClassesDirs = sourceSets.intTest.output.classesDirs + classpath = sourceSets.intTest.runtimeClasspath + shouldRunAfter test + + useJUnitPlatform() + +} + +tasks.forbiddenApisIntTest { + enabled = false +} + var publishingExtension = project.getExtensions().getByType(PublishingExtension) as PublishingExtension publishingExtension.publications.named("publishedArtifacts") { from components.java @@ -64,3 +100,10 @@ publishingExtension.publications.named("publishedArtifacts") { } } +integrationTest { + environment "hibernateVersion", project.version +} + +integrationTest.dependsOn rootProject.childProjects.'hibernate-core'.tasks.publishToMavenLocal +integrationTest.dependsOn publishToMavenLocal +check.dependsOn integrationTest diff --git a/tooling/hibernate-maven-plugin/src/intTest/java/org/hibernate/orm/tooling/maven/EnhancerMojoTestIT.java b/tooling/hibernate-maven-plugin/src/intTest/java/org/hibernate/orm/tooling/maven/EnhancerMojoTestIT.java new file mode 100644 index 000000000000..14f557ff3bd7 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/intTest/java/org/hibernate/orm/tooling/maven/EnhancerMojoTestIT.java @@ -0,0 +1,321 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.tooling.maven; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.apache.maven.cli.MavenCli; +import org.hibernate.bytecode.enhance.spi.EnhancementInfo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.List; + +public class EnhancerMojoTestIT { + + public static final String MVN_HOME = "maven.multiModuleProjectDirectory"; + + @TempDir + File projectDir; + + private MavenCli mavenCli; + + @BeforeEach + public void beforeEach() throws Exception { + copyJavFiles(); + System.setProperty(MVN_HOME, projectDir.getAbsolutePath()); + mavenCli = new MavenCli(); + } + + @Test + public void testEnhancementDefault() throws Exception { + // The default configuration for the enhance goal are as follows: + // enableLazyInitialization = 'true' + // enableDirtyTracking = 'true' + // enableAssociationManagement = 'false' + // enableExtendedEnhancement = 'false' + // classesDirectory = 'target/classes' + String configurationElement = "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Both Bar and Baz should be enhanced + assertTrue(isEnhanced( "Bar" )); + assertTrue(isEnhanced( "Baz" )); + // Both Bar and Baz contain the method '$$_hibernate_getInterceptor' + // because of the default setting of 'enableLazyInitialization' + assertTrue( methodIsPresentInClass("$$_hibernate_getInterceptor", "Bar")); + assertTrue( methodIsPresentInClass("$$_hibernate_getInterceptor", "Baz")); + // Both Bar and Baz contain the method '$$_hibernate_hasDirtyAttributes' + // because of the default setting of 'enableDirtyTracking' + assertTrue( methodIsPresentInClass("$$_hibernate_hasDirtyAttributes", "Bar")); + assertTrue( methodIsPresentInClass("$$_hibernate_hasDirtyAttributes", "Baz")); + // Foo is not an entity and extended enhancement is not enabled so the class is not enhanced + assertFalse(isEnhanced("Foo")); + // Association management should not be present + assertFalse(isAssociationManagementPresent()); + } + + @Test + public void testEnhancementFileSet() throws Exception { + // Use the defaults settings for enhancement (see #testEnhancementDefault) + // The files are read from the specified 'fileset' element: + // - the folder specified by 'dir' + // - the 'Baz.class' file is excluded + String configurationElement = + "\n" + + " \n"+ + " \n" + + " " + projectDir.getAbsolutePath() + "/target" + "\n" + + " \n" + + " **/Baz.class\n" + + " \n" + + " \n" + + " \n" + + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Bar is enhanced + assertTrue(isEnhanced( "Bar" )); + // Baz is not enhanced because it was excluded from the file set + assertFalse(isEnhanced( "Baz" )); + // Foo is not enhanced because it is not an entity and extended enhancement was not enabled + assertFalse( isEnhanced( "Foo" ) ); + // Association management should not be present + assertFalse(isAssociationManagementPresent()); + } + + @Test + public void testEnhancementNoLazyInitialization() throws Exception { + // Change the default setting for 'enableLazyInitialization' to 'false' + // Otherwise use the settings of #testEnhancementDefault + String configurationElement = + "\n" + + " false\n"+ + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Both Bar and Baz are enhanced, Foo is not + assertTrue( isEnhanced( "Bar" )); + assertTrue( isEnhanced( "Baz" )); + // Foo is not enhanced because it is not an entity and extended enhancement was not enabled + assertFalse( isEnhanced( "Foo" ) ); + // but $$_hibernate_getInterceptor is not present in the enhanced classes + // because of the 'false' value of 'enableLazyInitialization' + assertFalse( methodIsPresentInClass("$$_hibernate_getInterceptor", "Bar")); + assertFalse( methodIsPresentInClass("$$_hibernate_getInterceptor", "Baz")); + // Association management should not be present + assertFalse(isAssociationManagementPresent()); + } + + @Test + public void testEnhancementNoDirtyTracking() throws Exception { + // Change the default setting for 'enableDirtyTracking' to 'false' + // Otherwise use the settings of #testEnhancementDefault + String configurationElement = + "\n" + + " false\n"+ + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Both Bar and Baz should be enhanced + assertTrue( isEnhanced( "Bar" )); + assertTrue( isEnhanced( "Baz" )); + // Foo is not enhanced because it is not an entity and extended enhancement was not enabled + assertFalse( isEnhanced( "Foo" ) ); + // $$_hibernate_hasDirtyAttributes is not present in the enhanced classes + // because of the 'false' value of 'enableLazyInitialization' + assertFalse( methodIsPresentInClass("$$_hibernate_hasDirtyAttributes", "Bar")); + assertFalse( methodIsPresentInClass("$$_hibernate_hasDirtyAttributes", "Baz")); + // Association management should not be present + assertFalse(isAssociationManagementPresent()); + } + + @Test + public void testEnhancementEnableAssociationManagement() throws Exception { + // Change the default setting for 'enableAssociationManagement' to 'true' + // Otherwise use the settings of #testEnhancementDefault + String configurationElement = + "\n" + + " true\n"+ + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Both Bar and Baz are enhanced, Foo is not + assertTrue( isEnhanced( "Bar" )); + assertTrue( isEnhanced( "Baz" )); + assertFalse( isEnhanced( "Foo" ) ); + // Now verify that the association management is in place; + assertTrue(isAssociationManagementPresent()); + } + + @Test + public void testEnhancementEnableExtendedEnhancement() throws Exception { + // Change the default setting for 'enableExtendedEnhancement' to 'true' + // Otherwise use the settings of #testEnhancementDefault + String configurationElement = + "\n" + + " true\n"+ + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // Both Bar and Baz are enhanced because they are entities + assertTrue( isEnhanced( "Bar" )); + assertTrue( isEnhanced( "Baz" )); + // Though Foo is not an entity, it is enhanced because of the setting of 'enableExtendedEnhancement' + assertTrue( isEnhanced( "Foo" ) ); + // No association management is in place; + assertFalse(isAssociationManagementPresent()); + } + + + @Test + public void testNoEnhancement() throws Exception { + // Setting the values of all the settings to 'false' has the effect + // of not executing the enhancement at all. + // The setting of 'enableAssociationManagement' and 'enableExtendedEnhancement' to + // false is not really needed in this case as that's what their default is + String configurationElement = + "\n" + + " false\n"+ + " false\n"+ + " false\n"+ + " false\n"+ + "\n"; + preparePomXml(configurationElement); + executeCompileGoal(); + executeEnhanceGoal(); + // None of the classes should be enhanced + assertFalse( isEnhanced( "Bar" )); + assertFalse( isEnhanced( "Baz" )); + assertFalse( isEnhanced( "Foo" ) ); + // No association management is in place; + assertFalse(isAssociationManagementPresent()); + } + + private void executeCompileGoal() { + // The class files should not exist + assertFalse(fileExists("target/classes/Bar.class")); + assertFalse(fileExists("target/classes/Baz.class")); + assertFalse(fileExists("target/classes/Foo.class")); + // Execute the 'compile' target + new MavenCli().doMain( + new String[]{"compile"}, + projectDir.getAbsolutePath(), + null, + null); + // The class files should exist now + assertTrue( fileExists( "target/classes/Bar.class" ) ); + assertTrue( fileExists( "target/classes/Baz.class" ) ); + assertTrue( fileExists( "target/classes/Foo.class" ) ); + } + + private void executeEnhanceGoal() throws Exception { + // The class files should not be enhanced at this point + assertFalse( isEnhanced( "Bar" )); + assertFalse( isEnhanced( "Baz" )); + assertFalse( isEnhanced( "Foo" )); + // Execute the 'enhance' target + mavenCli.doMain( + new String[]{"process-classes"}, + projectDir.getAbsolutePath(), + null, + null); + // The results are verified in the respective tests + } + + private void preparePomXml(String configurationElement) throws Exception { + URL url = getClass().getClassLoader().getResource("pom.xm_"); + File source = new File(url.toURI()); + assertFalse( fileExists( "pom.xml" )); + String pomXmlContents = new String(Files.readAllBytes( source.toPath() )); + pomXmlContents = pomXmlContents.replace( "@hibernate-version@", System.getenv("hibernateVersion")); + pomXmlContents = pomXmlContents.replace( "@configuration@", configurationElement); + File destination = new File(projectDir, "pom.xml"); + Files.writeString(destination.toPath(), pomXmlContents); + assertTrue( fileExists( "pom.xml" ) ); + } + + private void copyJavFiles() throws Exception { + File srcDir = new File(projectDir, "src/main/java"); + srcDir.mkdirs(); + String[] javFileNames = {"Bar.jav_", "Baz.jav_", "Foo.jav_"}; + for (String javFileName : javFileNames) { + copyJavFile( javFileName, srcDir ); + } + } + + private void copyJavFile(String javFileName, File toFolder) throws Exception { + URL url = getClass().getClassLoader().getResource( javFileName ); + assert url != null; + File source = new File(url.toURI()); + File destination = new File(toFolder, javFileName.replace( '_', 'a' )); + assertTrue(source.exists()); + assertTrue(source.isFile()); + Files.copy(source.toPath(), destination.toPath()); + assertTrue(destination.exists()); + assertTrue(destination.isFile()); + } + + private ClassLoader getTestClassLoader() throws Exception { + return new URLClassLoader( new URL[] { new File(projectDir, "target/classes").toURI().toURL() } ); + } + + private boolean isEnhanced(String className) throws Exception { + return getTestClassLoader().loadClass( className ).isAnnotationPresent( EnhancementInfo.class ); + } + + private boolean methodIsPresentInClass(String methodName, String className) throws Exception { + Class classToCheck = getTestClassLoader().loadClass( className ); + try { + Object m = classToCheck.getMethod( methodName, new Class[] {} ); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + private boolean isAssociationManagementPresent() throws Exception { + // Some dynamic programming + ClassLoader loader = getTestClassLoader(); + // Obtain the class objects for 'Baz' and 'Bar' + Class bazClass = loader.loadClass( "Baz" ); + Class barClass = loader.loadClass( "Bar" ); + // Create an instance of both 'Baz' and 'Bar' + Object bazObject = bazClass.getDeclaredConstructor().newInstance(); + Object barObject = barClass.getDeclaredConstructor().newInstance(); + // Lookup the 'bars' field of class 'Baz' (an ArrayList of 'Bar' objects) + Field bazBarsField = bazClass.getDeclaredField( "bars" ); + bazBarsField.setAccessible( true ); + // Obtain the 'bars' list of the 'Baz' object; it should be empty + List bazBarsList = (List) bazBarsField.get( bazObject ); // baz.bars + assertTrue(bazBarsList.isEmpty()); + // Lookup the 'setBaz' method of class 'Bar' and invoke it on the 'Bar' object + Method barSetBazMethod = barClass.getDeclaredMethod( "setBaz", new Class[] { bazClass } ); + barSetBazMethod.invoke( barObject, bazObject ); // bar.setBaz(baz) + // Reobtain the 'bars' list of the 'Baz' object + bazBarsList = (List) bazBarsField.get( bazObject ); + // If there is association management, the 'bars' list should contain the 'Bar' object + return bazBarsList.contains( barObject ); // baz.bars.contains(bar) + } + + private boolean fileExists(String relativePath) { + return new File( projectDir, relativePath ).exists(); + } + +} diff --git a/tooling/hibernate-maven-plugin/src/intTest/resources/Bar.jav_ b/tooling/hibernate-maven-plugin/src/intTest/resources/Bar.jav_ new file mode 100644 index 000000000000..a5b7325842f7 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/intTest/resources/Bar.jav_ @@ -0,0 +1,24 @@ +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; + +@Entity +public class Bar { + + private String foo; + + @ManyToOne + private Baz baz; + + public String getFoo() { + return foo; + } + + public void setFoo(String f) { + foo = f; + } + + public Baz getBaz() { return baz; } + + public void setBaz(Baz baz) { this.baz = baz; } + +} diff --git a/tooling/hibernate-maven-plugin/src/intTest/resources/Baz.jav_ b/tooling/hibernate-maven-plugin/src/intTest/resources/Baz.jav_ new file mode 100644 index 000000000000..5e33d11767bb --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/intTest/resources/Baz.jav_ @@ -0,0 +1,27 @@ +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; + +import java.util.List; +import java.util.ArrayList; + +@Entity +public class Baz { + + private String foo; + + @OneToMany(mappedBy = "baz") + private List bars = new ArrayList(); + + String getFoo() { + return foo; + } + + public void setFoo(String f) { + foo = f; + } + + public void addBar(Bar bar) { + bars.add( bar ); + } + +} diff --git a/tooling/hibernate-maven-plugin/src/intTest/resources/Foo.jav_ b/tooling/hibernate-maven-plugin/src/intTest/resources/Foo.jav_ new file mode 100644 index 000000000000..4c3df431118c --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/intTest/resources/Foo.jav_ @@ -0,0 +1,13 @@ +public class Foo { + + private Bar bar; + + Bar getBar() { + return bar; + } + + public void setBar(Bar b) { + bar = b; + } + +} diff --git a/tooling/hibernate-maven-plugin/src/intTest/resources/pom.xm_ b/tooling/hibernate-maven-plugin/src/intTest/resources/pom.xm_ new file mode 100644 index 000000000000..6f48cc3aaa3a --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/intTest/resources/pom.xm_ @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.hibernate.orm.tooling.maven + enhance-test + 0.0.1-SNAPSHOT + + + @hibernate-version@ + + + + + org.hibernate.orm + hibernate-core + ${hibernate.version} + + + + + + + org.hibernate.orm + hibernate-maven-plugin + ${hibernate.version} + + + @configuration@ + + enhance + + + + + + + + \ No newline at end of file diff --git a/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java index 061af2c34be0..aa924ab9e186 100644 --- a/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java +++ b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java @@ -33,29 +33,57 @@ public class HibernateEnhancerMojo extends AbstractMojo { final private List sourceSet = new ArrayList(); private Enhancer enhancer; + /** + * A list of FileSets in which to look for classes to enhance. + * This parameter is optional but if it is specified, the 'classesDirectory' parameter is ignored. + */ @Parameter private FileSet[] fileSets; + /** + * The folder in which to look for classes to enhance. + * This parameter is required but if the 'fileSets' parameter is specified, it will be ignored. + */ @Parameter( defaultValue = "${project.build.directory}/classes", required = true) private File classesDirectory; + /** + * A boolean that indicates whether or not to add association management to automatically + * synchronize a bidirectional association when only one side is changed + */ @Parameter( defaultValue = "false", required = true) private boolean enableAssociationManagement; + /** + * A boolean that indicates whether or not to add dirty tracking + * @deprecated See HHH-15641 + */ + @Deprecated( + forRemoval = true) @Parameter( - defaultValue = "false", + defaultValue = "true", required = true) private boolean enableDirtyTracking; + /** + * A boolean that indicates whether or not to add lazy initialization + * @deprecated See HHH-15641 + */ + @Deprecated( + forRemoval = true) @Parameter( - defaultValue = "false", + defaultValue = "true", required = true) private boolean enableLazyInitialization; + /** + * A boolean that indicates whether or not to add extended enhancement. + * This setting will provide bytecode enhancement, even for non-entity classes + */ @Parameter( defaultValue = "false", required = true) @@ -64,13 +92,23 @@ public class HibernateEnhancerMojo extends AbstractMojo { public void execute() { getLog().debug(STARTING_EXECUTION_OF_ENHANCE_MOJO); processParameters(); - assembleSourceSet(); - createEnhancer(); - discoverTypes(); - performEnhancement(); + if (enhancementIsNeeded()) { + assembleSourceSet(); + createEnhancer(); + discoverTypes(); + performEnhancement(); + } getLog().debug(ENDING_EXECUTION_OF_ENHANCE_MOJO); } + private boolean enhancementIsNeeded() { + // enhancement is not needed when all the parameters are false + return enableAssociationManagement || + enableDirtyTracking || + enableLazyInitialization || + enableExtendedEnhancement; + } + private void processParameters() { if (!enableLazyInitialization) { getLog().warn(ENABLE_LAZY_INITIALIZATION_DEPRECATED); diff --git a/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojoTest.java b/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojoTest.java index 93853350184d..243812245525 100644 --- a/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojoTest.java +++ b/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojoTest.java @@ -519,6 +519,9 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl @Test void testExecute() throws Exception { + Field enableDirtyTrackingField = HibernateEnhancerMojo.class.getDeclaredField( "enableDirtyTracking" ); + enableDirtyTrackingField.setAccessible( true ); + enableDirtyTrackingField.set( enhanceMojo, true ); Method executeMethod = HibernateEnhancerMojo.class.getDeclaredMethod("execute", new Class[] {}); executeMethod.setAccessible(true); final String barSource =