diff --git a/local-build-plugins/build.gradle b/local-build-plugins/build.gradle index 87e451545a..b06a32cbe9 100644 --- a/local-build-plugins/build.gradle +++ b/local-build-plugins/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation "org.apache.maven:maven-compat:3.9.9" implementation "org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18" implementation "org.apache.maven.resolver:maven-resolver-transport-http:1.9.18" + implementation "org.slf4j:slf4j-simple:1.7.36" } tasks.compileJava { diff --git a/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderPlugin.java b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderPlugin.java index 9aa4867c1a..865a909e31 100644 --- a/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderPlugin.java +++ b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderPlugin.java @@ -9,6 +9,10 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + /** * Plugin for integrating Maven Embedder into the Gradle build to execute * some Maven tasks/goals/mojos. @@ -29,16 +33,34 @@ public class MavenEmbedderPlugin implements Plugin { MavenEmbedderConfig.class ); + final Provider workingDirectory = project.getLayout().getBuildDirectory().dir("maven-embedder/workspace"); + // add the MavenEmbedderService shared-build-service final Provider embedderServiceProvider = sharedServices.registerIfAbsent( "maven-embedder", MavenEmbedderService.class, (spec) -> { spec.getParameters().getProjectVersion().set( project.getVersion().toString() ); - spec.getParameters().getWorkingDirectory().set( project.getLayout().getProjectDirectory() ); + spec.getParameters().getWorkingDirectory().set( workingDirectory ); spec.getParameters().getMavenLocalDirectory().set( dsl.getLocalRepositoryDirectory() ); } ); + project.getTasks().register( + "installHibernateCore", + RunMavenTask.class, + (task) -> { + configureInstallHibernateCoreTask( + configureInstallTask( configureRunMavenTask( task, embedderServiceProvider ) ) ); + }); + + project.getTasks().register( + "installHibernateScanJandex", + RunMavenTask.class, + (task) -> { + configureInstallHibernateScanJandexTask( + configureInstallTask( configureRunMavenTask( task, embedderServiceProvider ) )); + } ); + // Via the plugin's POM, we tell Maven to generate the descriptors into // `target/generated/sources/plugin-descriptors/META-INF/maven`. // `META-INF/maven` is the relative path we need inside the jar, so we @@ -46,6 +68,8 @@ public class MavenEmbedderPlugin implements Plugin { // `target/generated/sources/plugin-descriptors` part. final Provider descriptorsDir = project.getLayout().getBuildDirectory().dir( "generated/sources/plugin-descriptors" ); + + // create the "mirror" task which calls the appropriate Maven tasks/goals/mojos behind the scenes using the embedder service final TaskProvider generatePluginDescriptorTask = project.getTasks().register( "generatePluginDescriptor", MavenPluginDescriptorTask.class, (task) -> { task.setGroup( "maven embedder" ); @@ -61,11 +85,160 @@ public class MavenEmbedderPlugin implements Plugin { final SourceSet mainSourceSet = sourceSets.getByName( "main" ); mainSourceSet.getResources().srcDir( task.getDescriptorDirectory() ); + // the hibernate-core jar needs to be present in the local repository // we need compilation to happen before we generate the descriptors - task.dependsOn( "compileJava" ); + task.dependsOn( "prepareWorkspace", "installHibernateCore", "installHibernateScanJandex"); } ); + project.getTasks().register( + "createMavenWrapper", + RunMavenTask.class, + (task) -> { + configuraCreateMavenWrapperTask( + configureRunMavenTask( task, embedderServiceProvider )); + } ); + + project.getTasks().register( + "installHibernateMavenPlugin", + RunMavenTask.class, + (task) -> { + configureInstallHibernateMavenPluginTask( + configureInstallTask( + configureRunMavenTask( task, embedderServiceProvider ))); + } ); + + project.getTasks().register( + "integrationTest", + RunMavenTask.class, + (task) -> { + configureIntegrationTestTask( + configureRunMavenTask( task, embedderServiceProvider )); + } ); + // we need the descriptor generation to happen before we jar project.getTasks().named( "jar", (jarTask) -> jarTask.dependsOn( generatePluginDescriptorTask ) ); + project.getTasks().named( "check" , (checkTask) -> checkTask.dependsOn( "integrationTest", generatePluginDescriptorTask ) ); } + + private void configureInstallHibernateMavenPluginTask(RunMavenTask task) { + List arguments = new ArrayList(task.getArguments().get()); + arguments.add("-Dfile=" + getHibernateMavenPluginArtifactFilePath( task.getProject() )); + arguments.add("-DartifactId=hibernate-maven-plugin"); + arguments.add( "-DpomFile=" + getHibernateMavenPluginPomFilePath( task.getProject() ) ); + task.getArguments().set( arguments ); + task.dependsOn("jar", "generatePluginDescriptor"); + } + + private void configureInstallHibernateCoreTask(RunMavenTask task) { + List arguments = new ArrayList(task.getArguments().get()); + arguments.add("-Dfile=" + getHibernateCoreArtifactFilePath( task.getProject() )); + arguments.add("-DartifactId=hibernate-core"); + arguments.add( "-DpomFile=" + getHibernateCorePomFilePath( task.getProject() ) ); + task.getArguments().set( arguments ); + task.dependsOn(":hibernate-core:generatePomFileForPublishedArtifactsPublication", ":hibernate-core:jar"); + } + + private void configureInstallHibernateScanJandexTask(RunMavenTask task) { + List arguments = new ArrayList(task.getArguments().get()); + arguments.add("-Dfile=" + getHibernateScanJandexArtifactFilePath( task.getProject() )); + arguments.add("-DartifactId=hibernate-scan-jandex"); + task.getArguments().set( arguments ); + task.dependsOn(":hibernate-scan-jandex:jar"); + } + + private void configureIntegrationTestTask(RunMavenTask task) { + task.getGoals().set( "invoker:run" ); + task.dependsOn("createMavenWrapper", "installHibernateMavenPlugin"); + } + + private void configuraCreateMavenWrapperTask(RunMavenTask task) { + task.getGoals().set("wrapper:wrapper"); + task.getArguments().set( List.of("-f" + getIntegrationTestFolderPath( task.getProject() ) )); + task.dependsOn( "prepareWorkspace" ); + } + + private String getHibernateMavenPluginPomFilePath(Project project) { + return project + .getLayout() + .getBuildDirectory() + .file( "publications/publishedArtifacts/pom-default.xml") + .get() + .getAsFile() + .getAbsolutePath(); + } + + private String getHibernateMavenPluginArtifactFilePath(Project project) { + final String artifactName = "hibernate-maven-plugin-" + project.getVersion() + ".jar"; + final File libsFolder = project.getLayout().getBuildDirectory().dir("libs" ).get().getAsFile(); + return new File(libsFolder, artifactName).getAbsolutePath(); + } + + private RunMavenTask configureRunMavenTask( + RunMavenTask task, + Provider embedderServiceProvider) { + task.setGroup( "maven embedder" ); + task.getMavenEmbedderService().set( embedderServiceProvider ); + task.usesService( embedderServiceProvider ); + return task; + } + + private Directory getWorkingDirectory(Project project) { + return project.getLayout().getBuildDirectory().dir("maven-embedder/workspace").get(); + } + + private String getIntegrationTestFolderPath(Project project) { + return getWorkingDirectory( project).dir( "src/it/enhance" ).getAsFile().getAbsolutePath(); + } + + private RunMavenTask configureInstallTask(RunMavenTask task) { + task.getGoals().set( "install:install-file" ); + ArrayList arguments = new ArrayList(); + arguments.add("-DgroupId=" + task.getProject().getGroup().toString()); + arguments.add("-Dversion=" + task.getProject().getVersion()); + arguments.add("-Dpackaging=jar"); + task.getArguments().set( arguments ); + return task; + } + + private String getHibernateCoreArtifactFilePath(Project project) { + final String artifactName = "hibernate-core-" + project.getVersion() + ".jar"; + final File hibernateCoreLibsFolder = getHibernateCoreBuildDirectory( project ) + .dir( "libs" ) + .getAsFile(); + return new File(hibernateCoreLibsFolder, artifactName).getAbsolutePath(); + } + + private String getHibernateCorePomFilePath(Project project) { + return getHibernateCoreBuildDirectory( project ) + .file( "publications/publishedArtifacts/pom-default.xml" ) + .getAsFile() + .getAbsolutePath(); + } + + private String getHibernateScanJandexArtifactFilePath(Project project) { + final String artifactName = "hibernate-scan-jandex-" + project.getVersion() + ".jar"; + final File hibernateScanJandexLibsFolder = getHibernateScanJandexBuildDirectory( project ) + .dir( "libs" ) + .getAsFile(); + return new File(hibernateScanJandexLibsFolder, artifactName).getAbsolutePath(); + } + + private Directory getHibernateCoreBuildDirectory(Project project) { + return project + .getRootProject() + .project( "hibernate-core" ) + .getLayout() + .getBuildDirectory() + .get(); + } + + private Directory getHibernateScanJandexBuildDirectory(Project project) { + return project + .getRootProject() + .project( "hibernate-scan-jandex" ) + .getLayout() + .getBuildDirectory() + .get(); + } + } diff --git a/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderService.java b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderService.java index 6309c07909..b1ffbd8ceb 100644 --- a/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderService.java +++ b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/MavenEmbedderService.java @@ -1,6 +1,7 @@ package org.hibernate.build.maven.embedder; import org.apache.maven.cli.MavenCli; +import org.gradle.api.GradleException; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; @@ -37,7 +38,7 @@ public abstract class MavenEmbedderService implements BuildService getMavenEmbedderService(); + + @Input + abstract Property getGoals(); + + @Input + abstract ListProperty getArguments(); + + @TaskAction + public void run() { + getMavenEmbedderService().get().execute( constructTaskAndArgs() ); + } + + private String[] constructTaskAndArgs() { + List args = new ArrayList(); + args.add( getGoals().get() ); + args.addAll( getArguments().get() ); + return args.toArray(new String[0]); + } + +} diff --git a/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/logging/Slf4jConfiguration.java b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/logging/Slf4jConfiguration.java new file mode 100644 index 0000000000..a105531f6d --- /dev/null +++ b/local-build-plugins/src/main/java/org/hibernate/build/maven/embedder/logging/Slf4jConfiguration.java @@ -0,0 +1,30 @@ +package org.hibernate.build.maven.embedder.logging; + +import org.apache.maven.cli.logging.impl.Slf4jSimpleConfiguration; +import org.slf4j.LoggerFactory; +import org.slf4j.impl.MavenSlf4jSimpleFriend; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class Slf4jConfiguration extends Slf4jSimpleConfiguration { + @Override + public void activate() { + resetLoggerFactory(); + initMavenSlf4jSimpleFriend(); + } + + private void resetLoggerFactory() { + try { + Method m = LoggerFactory.class.getDeclaredMethod("reset", new Class[]{}); + m.setAccessible(true); + m.invoke(null); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private void initMavenSlf4jSimpleFriend() { + MavenSlf4jSimpleFriend.init(); + } +} diff --git a/local-build-plugins/src/main/resources/META-INF/maven/slf4j-configuration.properties b/local-build-plugins/src/main/resources/META-INF/maven/slf4j-configuration.properties new file mode 100644 index 0000000000..0885e0b310 --- /dev/null +++ b/local-build-plugins/src/main/resources/META-INF/maven/slf4j-configuration.properties @@ -0,0 +1 @@ +org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext org.hibernate.build.maven.embedder.logging.Slf4jConfiguration diff --git a/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle b/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle index 0f71188098..9a21390ec9 100644 --- a/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle +++ b/tooling/hibernate-maven-plugin/hibernate-maven-plugin.gradle @@ -1,6 +1,6 @@ description = 'Maven plugin to integrate aspects of Hibernate into your build.' -//apply from: rootProject.file( 'gradle/published-java-module.gradle' ) +apply from: rootProject.file( 'gradle/publishing-pom.gradle' ) apply from: rootProject.file( 'gradle/java-module.gradle' ) apply plugin: 'org.hibernate.build.maven-embedder' @@ -18,4 +18,100 @@ dependencies { implementation "org.apache.maven:maven-plugin-api:3.6.3" implementation "org.apache.maven.plugin-tools:maven-plugin-annotations:3.6.0" implementation "org.apache.maven:maven-project:2.2.1" -} \ No newline at end of file + implementation "org.apache.maven.shared:file-management:3.1.0" +} + +publishing { + publications { + publishedArtifacts(MavenPublication) { + from components.java + pom.withXml { + asNode() + .version + .plus { + packaging('maven-plugin') + } + asNode() + .dependencies + .dependency + .findAll { dependency -> + dependency.groupId.text().startsWith('org.apache.maven') + } + .each { dependency -> + if (dependency.groupId.text().startsWith('org.apache.maven.shared')) { + dependency.scope*.value = 'compile' + } else { + dependency.scope*.value = 'provided' + } + } + asNode() + .dependencies + .dependency + .findAll { dependency -> + dependency.groupId.text().startsWith('org.hibernate.orm') + } + .each { dependency -> + dependency.scope*.value = 'compile' + } + asNode() + .dependencies + .plus { + def plugins = build().appendNode('plugins') + def pluginPlugin = plugins.appendNode('plugin') + pluginPlugin.appendNode('groupId', 'org.apache.maven.plugins') + pluginPlugin.appendNode('artifactId', 'maven-plugin-plugin') + pluginPlugin.appendNode('version', '3.15.0') + def pluginConfiguration = pluginPlugin.appendNode('configuration') + pluginConfiguration.appendNode('goalPrefix', 'plugin') + pluginConfiguration.appendNode('outputDirectory', layout.buildDirectory.dir('generated/sources/plugin-descriptors/META-INF/maven').get().getAsFile().getAbsolutePath() ) + def invokerPlugin = plugins.appendNode('plugin'); + invokerPlugin.appendNode('groupId', 'org.apache.maven.plugins') + invokerPlugin.appendNode('artifactId', 'maven-invoker-plugin') + invokerPlugin.appendNode('version', '3.8.0') + def invokerConfiguration = invokerPlugin.appendNode('configuration'); + invokerConfiguration.appendNode('debug', 'true'); + invokerConfiguration.appendNode('mavenExecutable', 'mvnw'); + def scriptVariables = invokerConfiguration.appendNode('scriptVariables'); + scriptVariables.appendNode('hibernateCoreJarPath', layout.buildDirectory.file('maven-embedder/maven-local/org/hibernate/orm/hibernate-core/' + project.version + '/hibernate-core-' + project.version + '.jar').get().getAsFile().getAbsolutePath()) + } + } + } + } + +} + +// Following tasks need to be performed: +// 1. Compile the Java classes +// 2. Copy the source tree to the working directory +// 3. Copy the compiled Java classes to the working directory +// 4. Install the 'hibernate-core' dependency in the local Maven repo +// 5. Install the 'hibernate-scan-jandex' dependency in the local Maven repo +// 6. Generate the pom.xml file for the Maven plugin +// 7. Generate the Maven plugin descriptor +// 8. Create the jar for the Maven plugin +// 9. Install the Maven plugin descriptor in the local Maven repo +// 10. Run the integration tests + +// Prepare the working directory +tasks.register('prepareWorkspace', Copy) { + into('target/maven-embedder/workspace') + // copy the plugin pom + with( copySpec { + from('target/publications/publishedArtifacts/pom-default.xml') + rename('pom-default.xml', 'pom.xml') + dependsOn('generatePomFileForPublishedArtifactsPublication') + }) + // copy the compiled java classes + into('target/classes') { + with( copySpec { + from('target/classes/java/main') + dependsOn('compileJava') + }) + } + // copy the integration tests + into('src/it') { + with( copySpec { + from('src/it') + }) + } +} diff --git a/tooling/hibernate-maven-plugin/src/it/enhance/pom.xml b/tooling/hibernate-maven-plugin/src/it/enhance/pom.xml new file mode 100644 index 0000000000..373439ef9e --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/it/enhance/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + org.hibernate.orm.tooling.maven + enhance-test + 0.0.1-SNAPSHOT + + + + org.hibernate.orm + hibernate-core + 7.0.0.Beta1 + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + + + + org.hibernate.orm + hibernate-maven-plugin + @project.version@ + + + enhance + process-classes + + + + ${project.build.directory}/classes + + **/Baz.class + + + + true + + + enhance + + + + + + + + \ No newline at end of file diff --git a/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Bar.java b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Bar.java new file mode 100644 index 0000000000..304efc24db --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Bar.java @@ -0,0 +1,18 @@ +package org.foo; + +import jakarta.persistence.Entity; + +@Entity +public class Bar { + + private String foo; + + String getFoo() { + return foo; + } + + public void setFoo(String f) { + foo = f; + } + +} diff --git a/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Baz.java b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Baz.java new file mode 100644 index 0000000000..169826b58d --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Baz.java @@ -0,0 +1,18 @@ +package org.foo; + +import jakarta.persistence.Entity; + +@Entity +public class Baz { + + private String foo; + + String getFoo() { + return foo; + } + + public void setFoo(String f) { + foo = f; + } + +} diff --git a/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Foo.java b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Foo.java new file mode 100644 index 0000000000..8427ed7439 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/it/enhance/src/main/java/org/foo/Foo.java @@ -0,0 +1,15 @@ +package org.foo; + +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/it/enhance/verify.bsh b/tooling/hibernate-maven-plugin/src/it/enhance/verify.bsh new file mode 100644 index 0000000000..f165dfcae1 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/it/enhance/verify.bsh @@ -0,0 +1,51 @@ +File classesFolder = new File(basedir, "target/classes"); +if (!classesFolder.exists()) { + throw new FileNotFoundException("Folder should exist: " + classesFolder); +} + +File hibCoreJar = new File(localRepositoryPath, hibernateCoreJarPath); +if (!hibCoreJar.exists()) { + throw new FileNotFoundException("Hibernate Core jar should exist: " + hibCoreJar); +} + +URL[] urls = new URL[2]; +urls[0] = classesFolder.toURI().toURL(); +urls[1] = hibCoreJar.toURI().toURL(); + +ClassLoader loader = new URLClassLoader(urls); + +Class barClass = loader.loadClass("org.foo.Bar"); +if (barClass == null) { + throw new ClassNotFoundException("Class 'org.foo.Bar' should be loaded"); +} +Class fooClass = loader.loadClass("org.foo.Foo"); +if (fooClass == null) { + throw new FileNotFoundException("Class 'org.foo.Foo' should be loaded"); +} +Class bazClass = loader.loadClass("org.foo.Baz"); +if (bazClass == null) { + throw new FileNotFoundException("Class 'org.foo.Baz' should be loaded"); +} + +Object m = barClass.getMethod("$$_hibernate_getEntityInstance", new Class[]{}); +if (m == null) { + throw new NoSuchMethodException("Method 'org.foo.Baz#$$_hibernate_getEntityInstance' does not exist"); +} else { + System.out.println("Class '" + barClass.getName() + "' was enhanced succesfully!"); +} + +try { + m = fooClass.getMethod("$$_hibernate_getEntityInstance", new Class[]{}); + // we need to fail here because the class 'org.foo.Foo' should not be enhanced + throw new RuntimeException("Class '" + fooClass + "' should not be enhanced!"); +} catch (NoSuchMethodException e) { + System.out.println("Class '" + fooClass.getName() + "' was correctly left unchanged."); +} + +try { + m = bazClass.getMethod("$$_hibernate_getEntityInstance", new Class[]{}); + // we need to fail here because the class 'org.foo.Baz' should not be enhanced + throw new RuntimeException("Class '" + bazClass + "' should not be enhanced!"); +} catch (NoSuchMethodException e) { + System.out.println("Class '" + bazClass.getName() + "' was correctly left unchanged."); +} diff --git a/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/EnhancementContext.java b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/EnhancementContext.java new file mode 100644 index 0000000000..e755d25308 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/EnhancementContext.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.tooling.maven; + +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.UnloadedClass; +import org.hibernate.bytecode.enhance.spi.UnloadedField; + +public class EnhancementContext extends DefaultEnhancementContext { + + private ClassLoader classLoader = null; + private boolean enableAssociationManagement = false; + private boolean enableDirtyTracking = false; + private boolean enableLazyInitialization = false; + private boolean enableExtendedEnhancement = false; + + public EnhancementContext( + ClassLoader classLoader, + boolean enableAssociationManagement, + boolean enableDirtyTracking, + boolean enableLazyInitialization, + boolean enableExtendedEnhancement) { + this.classLoader = classLoader; + this.enableAssociationManagement = enableAssociationManagement; + this.enableDirtyTracking = enableDirtyTracking; + this.enableLazyInitialization = enableLazyInitialization; + this.enableExtendedEnhancement = enableExtendedEnhancement; + } + + @Override + public ClassLoader getLoadingClassLoader() { + return classLoader; + } + + @Override + public boolean doBiDirectionalAssociationManagement(UnloadedField field) { + return enableAssociationManagement; + } + + @Override + public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) { + return enableDirtyTracking; + } + + @Override + public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) { + return enableLazyInitialization; + } + + @Override + public boolean isLazyLoadable(UnloadedField field) { + return enableLazyInitialization; + } + + @Override + public boolean doExtendedEnhancement(UnloadedClass classDescriptor) { + return enableExtendedEnhancement; + } + +} 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 new file mode 100644 index 0000000000..071218521c --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/main/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojo.java @@ -0,0 +1,303 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.tooling.maven; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.shared.model.fileset.FileSet; +import org.apache.maven.shared.model.fileset.util.FileSetManager; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.internal.BytecodeProviderInitiator; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +/** + * Maven mojo for performing build-time enhancement of entity objects. + */ +@Mojo(name = "enhance", defaultPhase = LifecyclePhase.PROCESS_CLASSES) +public class HibernateEnhancerMojo extends AbstractMojo { + + final private List sourceSet = new ArrayList(); + private Enhancer enhancer; + + @Parameter + private FileSet[] fileSets; + + @Parameter( + defaultValue = "${project.build.directory}/classes", + readonly = true, + required = true) + private File classesDirectory; + + @Parameter( + defaultValue = "false", + readonly = true, + required = true) + private boolean enableAssociationManagement; + + @Parameter( + defaultValue = "false", + readonly = true, + required = true) + private boolean enableDirtyTracking; + + @Parameter( + defaultValue = "false", + readonly = true, + required = true) + private boolean enableLazyInitialization; + + @Parameter( + defaultValue = "false", + readonly = true, + required = true) + private boolean enableExtendedEnhancement; + + public void execute() { + getLog().debug(STARTING_EXECUTION_OF_ENHANCE_MOJO); + processParameters(); + assembleSourceSet(); + createEnhancer(); + discoverTypes(); + performEnhancement(); + getLog().debug(ENDING_EXECUTION_OF_ENHANCE_MOJO); + } + + private void processParameters() { + if (!enableLazyInitialization) { + getLog().warn(ENABLE_LAZY_INITIALIZATION_DEPRECATED); + } + if (!enableDirtyTracking) { + getLog().warn(ENABLE_DIRTY_TRACKING_DEPRECATED); + } + if (fileSets == null) { + fileSets = new FileSet[1]; + fileSets[0] = new FileSet(); + fileSets[0].setDirectory(classesDirectory.getAbsolutePath()); + getLog().debug(ADDED_DEFAULT_FILESET_WITH_BASE_DIRECTORY.formatted(fileSets[0].getDirectory())); + } + } + + private void assembleSourceSet() { + getLog().debug(STARTING_ASSEMBLY_OF_SOURCESET); + for (FileSet fileSet : fileSets) { + addFileSetToSourceSet(fileSet); + } + getLog().debug(ENDING_ASSEMBLY_OF_SOURCESET); + } + + private void addFileSetToSourceSet(FileSet fileSet) { + getLog().debug(PROCESSING_FILE_SET); + String directory = fileSet.getDirectory(); + FileSetManager fileSetManager = new FileSetManager(); + File baseDir = classesDirectory; + if (directory != null && classesDirectory != null) { + baseDir = new File(directory); + } + getLog().debug(USING_BASE_DIRECTORY.formatted(baseDir)); + for (String fileName : fileSetManager.getIncludedFiles(fileSet)) { + File candidateFile = new File(baseDir, fileName); + if (fileName.endsWith(".class")) { + sourceSet.add(candidateFile); + getLog().info(ADDED_FILE_TO_SOURCE_SET.formatted(candidateFile)); + } + else { + getLog().debug(SKIPPING_NON_CLASS_FILE.formatted(candidateFile)); + } + } + getLog().debug(FILESET_PROCESSED_SUCCESFULLY); + } + + private ClassLoader createClassLoader() { + getLog().debug(CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory)) ; + List urls = new ArrayList<>(); + try { + urls.add(classesDirectory.toURI().toURL()); + } + catch (MalformedURLException e) { + getLog().error(UNEXPECTED_ERROR_WHILE_CONSTRUCTING_CLASSLOADER, e); + } + return new URLClassLoader( + urls.toArray(new URL[0]), + Enhancer.class.getClassLoader()); + } + + private EnhancementContext createEnhancementContext() { + getLog().debug(CREATE_ENHANCEMENT_CONTEXT) ; + return new EnhancementContext( + createClassLoader(), + enableAssociationManagement, + enableDirtyTracking, + enableLazyInitialization, + enableExtendedEnhancement); + } + + private void createEnhancer() { + getLog().debug(CREATE_BYTECODE_ENHANCER) ; + enhancer = BytecodeProviderInitiator + .buildDefaultBytecodeProvider() + .getEnhancer(createEnhancementContext()); + } + + private void discoverTypes() { + getLog().debug(STARTING_TYPE_DISCOVERY) ; + for (File classFile : sourceSet) { + discoverTypesForClass(classFile); + } + getLog().debug(ENDING_TYPE_DISCOVERY) ; + } + + private void discoverTypesForClass(File classFile) { + getLog().debug(TRYING_TO_DISCOVER_TYPES_FOR_CLASS_FILE.formatted(classFile)); + try { + enhancer.discoverTypes( + determineClassName(classFile), + Files.readAllBytes( classFile.toPath())); + getLog().info(SUCCESFULLY_DISCOVERED_TYPES_FOR_CLASS_FILE.formatted(classFile)); + } + catch (IOException e) { + getLog().error(UNABLE_TO_DISCOVER_TYPES_FOR_CLASS_FILE.formatted(classFile), e); + } + } + + private String determineClassName(File classFile) { + getLog().debug(DETERMINE_CLASS_NAME_FOR_FILE.formatted(classFile)); + String classFilePath = classFile.getAbsolutePath(); + String classesDirectoryPath = classesDirectory.getAbsolutePath(); + return classFilePath.substring( + classesDirectoryPath.length() + 1, + classFilePath.length() - ".class".length()) + .replace(File.separatorChar, '.'); + } + + private void performEnhancement() { + getLog().debug(STARTING_CLASS_ENHANCEMENT) ; + for (File classFile : sourceSet) { + long lastModified = classFile.lastModified(); + enhanceClass(classFile); + final boolean timestampReset = classFile.setLastModified( lastModified ); + if ( !timestampReset ) { + getLog().debug(SETTING_LASTMODIFIED_FAILED_FOR_CLASS_FILE.formatted(classFile)); + } + } + getLog().debug(ENDING_CLASS_ENHANCEMENT) ; + } + + private void enhanceClass(File classFile) { + getLog().debug(TRYING_TO_ENHANCE_CLASS_FILE.formatted(classFile)); + try { + byte[] newBytes = enhancer.enhance( + determineClassName(classFile), + Files.readAllBytes(classFile.toPath())); + if (newBytes != null) { + writeByteCodeToFile(newBytes, classFile); + getLog().info(SUCCESFULLY_ENHANCED_CLASS_FILE.formatted(classFile)); + } + else { + getLog().info(SKIPPING_FILE.formatted(classFile)); + } + } + catch (EnhancementException | IOException e) { + getLog().error(ERROR_WHILE_ENHANCING_CLASS_FILE.formatted(classFile), e);; + } + } + + private void writeByteCodeToFile(byte[] bytes, File file) { + getLog().debug(WRITING_BYTE_CODE_TO_FILE.formatted(file)); + if (clearFile(file)) { + try { + Files.write( file.toPath(), bytes); + getLog().debug(AMOUNT_BYTES_WRITTEN_TO_FILE.formatted(bytes.length, file)); + } + catch (FileNotFoundException e) { + getLog().error(ERROR_OPENING_FILE_FOR_WRITING.formatted(file), e ); + } + catch (IOException e) { + getLog().error(ERROR_WRITING_BYTES_TO_FILE.formatted(file), e ); + } + } + } + + private boolean clearFile(File file) { + getLog().debug(TRYING_TO_CLEAR_FILE.formatted(file)); + boolean success = false; + if ( file.delete() ) { + try { + if ( !file.createNewFile() ) { + getLog().error(UNABLE_TO_CREATE_FILE.formatted(file)); + } + else { + getLog().info(SUCCESFULLY_CLEARED_FILE.formatted(file)); + success = true; + } + } + catch (IOException e) { + getLog().warn(PROBLEM_CLEARING_FILE.formatted(file), e); + } + } + else { + getLog().error(UNABLE_TO_DELETE_FILE.formatted(file)); + } + return success; + } + + // info messages + static final String SUCCESFULLY_CLEARED_FILE = "Succesfully cleared the contents of file: %s"; + static final String SUCCESFULLY_ENHANCED_CLASS_FILE = "Succesfully enhanced class file: %s"; + static final String SKIPPING_FILE = "Skipping file: %s"; + static final String SUCCESFULLY_DISCOVERED_TYPES_FOR_CLASS_FILE = "Succesfully discovered types for classes in file: %s"; + static final String ADDED_FILE_TO_SOURCE_SET = "Added file to source set: %s"; + + // warning messages + static final String PROBLEM_CLEARING_FILE = "Problem clearing file for writing out enhancements [ %s ]"; + static final String ENABLE_LAZY_INITIALIZATION_DEPRECATED = "The 'enableLazyInitialization' configuration is deprecated and will be removed. Set the value to 'true' to get rid of this warning"; + static final String ENABLE_DIRTY_TRACKING_DEPRECATED = "The 'enableDirtyTracking' configuration is deprecated and will be removed. Set the value to 'true' to get rid of this warning"; + + // error messages + static final String UNABLE_TO_CREATE_FILE = "Unable to create file: %s"; + static final String UNABLE_TO_DELETE_FILE = "Unable to delete file: %s"; + static final String ERROR_WRITING_BYTES_TO_FILE = "Error writing bytes to file : %s"; + static final String ERROR_OPENING_FILE_FOR_WRITING = "Error opening file for writing : %s"; + static final String ERROR_WHILE_ENHANCING_CLASS_FILE = "An exception occurred while trying to class file: %s"; + static final String UNABLE_TO_DISCOVER_TYPES_FOR_CLASS_FILE = "Unable to discover types for classes in file: %s"; + static final String UNEXPECTED_ERROR_WHILE_CONSTRUCTING_CLASSLOADER = "An unexpected error occurred while constructing the classloader"; + + // debug messages + static final String TRYING_TO_CLEAR_FILE = "Trying to clear the contents of file: %s"; + static final String AMOUNT_BYTES_WRITTEN_TO_FILE = "%s bytes were succesfully written to file: %s"; + static final String WRITING_BYTE_CODE_TO_FILE = "Writing byte code to file: %s"; + static final String DETERMINE_CLASS_NAME_FOR_FILE = "Determining class name for file: %s"; + static final String TRYING_TO_ENHANCE_CLASS_FILE = "Trying to enhance class file: %s"; + static final String STARTING_CLASS_ENHANCEMENT = "Starting class enhancement"; + static final String SETTING_LASTMODIFIED_FAILED_FOR_CLASS_FILE = "Setting lastModified failed for class file: %s"; + static final String ENDING_CLASS_ENHANCEMENT = "Ending class enhancement"; + static final String TRYING_TO_DISCOVER_TYPES_FOR_CLASS_FILE = "Trying to discover types for classes in file: %s"; + static final String STARTING_TYPE_DISCOVERY = "Starting type discovery"; + static final String ENDING_TYPE_DISCOVERY = "Ending type discovery"; + static final String CREATE_BYTECODE_ENHANCER = "Creating bytecode enhancer"; + static final String CREATE_ENHANCEMENT_CONTEXT = "Creating enhancement context"; + static final String CREATE_URL_CLASSLOADER_FOR_FOLDER = "Creating URL ClassLoader for folder: %s"; + static final String PROCESSING_FILE_SET = "Processing FileSet"; + static final String USING_BASE_DIRECTORY = "Using base directory: %s"; + static final String SKIPPING_NON_CLASS_FILE = "Skipping non '.class' file: %s"; + static final String FILESET_PROCESSED_SUCCESFULLY = "FileSet was processed succesfully"; + static final String STARTING_ASSEMBLY_OF_SOURCESET = "Starting assembly of the source set"; + static final String ENDING_ASSEMBLY_OF_SOURCESET = "Ending the assembly of the source set"; + static final String ADDED_DEFAULT_FILESET_WITH_BASE_DIRECTORY = "Addded a default FileSet with base directory: %s"; + static final String STARTING_EXECUTION_OF_ENHANCE_MOJO = "Starting execution of enhance mojo"; + static final String ENDING_EXECUTION_OF_ENHANCE_MOJO = "Ending execution of enhance mojo"; + +} diff --git a/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/EnhancementContextTest.java b/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/EnhancementContextTest.java new file mode 100644 index 0000000000..22d769a430 --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/EnhancementContextTest.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.tooling.maven; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.URL; +import java.net.URLClassLoader; + +import org.junit.jupiter.api.Test; + +public class EnhancementContextTest { + + @Test + void testGetClassLoader() { + ClassLoader testLoader = new URLClassLoader(new URL[0]); + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertNull(context.getLoadingClassLoader()); + context = new EnhancementContext(testLoader, false, false, false, false); + assertSame(testLoader, context.getLoadingClassLoader()); + } + + @Test + void testDoBiDirectionalAssociationManagement() { + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertFalse(context.doBiDirectionalAssociationManagement(null)); + context = new EnhancementContext(null, true, false, false, false); + assertTrue(context.doBiDirectionalAssociationManagement(null)); + } + + @Test + void testDoDirtyCheckingInline() { + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertFalse(context.doDirtyCheckingInline(null)); + context = new EnhancementContext(null, false, true, false, false); + assertTrue(context.doDirtyCheckingInline(null)); + } + + @Test + void testHasLazyLoadableAttributes() { + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertFalse(context.hasLazyLoadableAttributes(null)); + context = new EnhancementContext(null, false, false, true, false); + assertTrue(context.hasLazyLoadableAttributes(null)); + } + + @Test + void testIsLazyLoadable() { + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertFalse(context.isLazyLoadable(null)); + context = new EnhancementContext(null, false, false, true, false); + assertTrue(context.isLazyLoadable(null)); + } + + @Test + void testDoExtendedEnhancement() { + EnhancementContext context = new EnhancementContext(null, false, false, false, false); + assertFalse(context.doExtendedEnhancement(null)); + context = new EnhancementContext(null, false, false, false, true); + assertTrue(context.doExtendedEnhancement(null)); + } + +} 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 new file mode 100644 index 0000000000..5f9aa3e52e --- /dev/null +++ b/tooling/hibernate-maven-plugin/src/test/java/org/hibernate/orm/tooling/maven/HibernateEnhancerMojoTest.java @@ -0,0 +1,644 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.tooling.maven; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; + +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.shared.model.fileset.FileSet; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import jakarta.persistence.Entity; + +public class HibernateEnhancerMojoTest { + + @TempDir + File tempDir; + + private List logMessages = new ArrayList(); + + private Field classesDirectoryField; + private Field fileSetsField; + private Field sourceSetField; + private Field enhancerField; + + private File classesDirectory; // folder '${tempDir}/classes' + private File fooFolder; // folder '${classesDirectory}/org/foo' + private File barFolder; // folder '${classesDirectory}/bar' + private File barClassFile; // file '${fooFolder}/Bar.class' + private File fooTxtFile; // file '${barFolder}/Foo.txt' + + private HibernateEnhancerMojo enhanceMojo; + + @BeforeEach + void beforeEach() throws Exception { + classesDirectoryField = HibernateEnhancerMojo.class.getDeclaredField("classesDirectory"); + classesDirectoryField.setAccessible(true); + fileSetsField = HibernateEnhancerMojo.class.getDeclaredField("fileSets"); + fileSetsField.setAccessible(true); + sourceSetField = HibernateEnhancerMojo.class.getDeclaredField("sourceSet"); + sourceSetField.setAccessible(true); + enhancerField = HibernateEnhancerMojo.class.getDeclaredField("enhancer"); + enhancerField.setAccessible(true); + enhanceMojo = new HibernateEnhancerMojo(); + enhanceMojo.setLog(createLog()); + classesDirectory = new File(tempDir, "classes"); + classesDirectory.mkdirs(); + classesDirectoryField.set(enhanceMojo, classesDirectory); + fooFolder = new File(classesDirectory, "org/foo"); + fooFolder.mkdirs(); + barFolder = new File(classesDirectory, "bar"); + barFolder.mkdirs(); + barClassFile = new File(fooFolder, "Bar.class"); + barClassFile.createNewFile(); + fooTxtFile = new File (barFolder, "Foo.txt"); + fooTxtFile.createNewFile(); + } + + @Test + void testAssembleSourceSet() throws Exception { + Method assembleSourceSetMethod = HibernateEnhancerMojo.class.getDeclaredMethod("assembleSourceSet"); + assembleSourceSetMethod.setAccessible(true); + FileSet[] fileSets = new FileSet[1]; + fileSets[0] = new FileSet(); + fileSets[0].setDirectory(classesDirectory.getAbsolutePath()); + fileSetsField.set(enhanceMojo, fileSets); + List sourceSet = (List)sourceSetField.get(enhanceMojo); + assertTrue(sourceSet.isEmpty()); + assembleSourceSetMethod.invoke(enhanceMojo); + assertFalse(sourceSet.isEmpty()); + assertTrue(sourceSet.contains(barClassFile)); + assertFalse(sourceSet.contains(fooTxtFile)); + assertEquals(1, sourceSet.size()); + // verify the log messages + assertEquals(7, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_ASSEMBLY_OF_SOURCESET)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.PROCESSING_FILE_SET)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.USING_BASE_DIRECTORY.formatted(classesDirectory))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.ADDED_FILE_TO_SOURCE_SET.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.SKIPPING_NON_CLASS_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.FILESET_PROCESSED_SUCCESFULLY)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ENDING_ASSEMBLY_OF_SOURCESET)); + } + + @Test + void testAddFileSetToSourceSet() throws Exception { + Method addFileSetToSourceSetMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "addFileSetToSourceSet", + new Class[] { FileSet.class}); + addFileSetToSourceSetMethod.setAccessible(true); + File fooClassFile = new File(fooFolder, "Foo.class"); + fooClassFile.createNewFile(); + File bazFolder = new File(classesDirectory, "org/baz"); + bazFolder.mkdirs(); + File bazClassFile = new File(bazFolder, "Baz.class"); + bazClassFile.createNewFile(); + FileSet fileSet = new FileSet(); + fileSet.setDirectory(classesDirectory.getAbsolutePath()); + fileSet.addInclude("**/Foo*"); + fileSet.addInclude("**/*.class"); + fileSet.addExclude("**/baz/**"); + addFileSetToSourceSetMethod.invoke(enhanceMojo, fileSet); + // verify log messages + assertEquals(6, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.PROCESSING_FILE_SET)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.USING_BASE_DIRECTORY.formatted(classesDirectory))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.ADDED_FILE_TO_SOURCE_SET.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.ADDED_FILE_TO_SOURCE_SET.formatted(fooClassFile))); + assertFalse(logMessages.contains(INFO + HibernateEnhancerMojo.ADDED_FILE_TO_SOURCE_SET.formatted(bazClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.SKIPPING_NON_CLASS_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.FILESET_PROCESSED_SUCCESFULLY)); + } + + @Test + void testCreateClassLoader() throws Exception { + Method createClassLoaderMethod = HibernateEnhancerMojo.class.getDeclaredMethod("createClassLoader"); + createClassLoaderMethod.setAccessible(true); + ClassLoader classLoader = (ClassLoader)createClassLoaderMethod.invoke(enhanceMojo); + assertNotNull(classLoader); + URL fooResource = classLoader.getResource("bar/Foo.txt"); + assertNotNull(fooResource); + assertEquals(fooTxtFile.toURI().toURL(), fooResource); + // verify log messages + // verify log messages + assertEquals(1, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + } + + @Test + void testCreateEnhancementContext() throws Exception { + Method createEnhancementContextMethod = HibernateEnhancerMojo.class.getDeclaredMethod("createEnhancementContext"); + createEnhancementContextMethod.setAccessible(true); + EnhancementContext enhancementContext = (EnhancementContext)createEnhancementContextMethod.invoke(enhanceMojo); + URLClassLoader classLoader = (URLClassLoader)enhancementContext.getLoadingClassLoader(); + assertEquals(classesDirectory.toURI().toURL(), classLoader.getURLs()[0]); + assertFalse(enhancementContext.doBiDirectionalAssociationManagement(null)); + assertFalse(enhancementContext.doDirtyCheckingInline(null)); + assertFalse(enhancementContext.hasLazyLoadableAttributes(null)); + assertFalse(enhancementContext.isLazyLoadable(null)); + assertFalse(enhancementContext.doExtendedEnhancement(null)); + // verify log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + logMessages.clear(); + Field enableAssociationManagementField = HibernateEnhancerMojo.class.getDeclaredField("enableAssociationManagement"); + enableAssociationManagementField.setAccessible(true); + enableAssociationManagementField.set(enhanceMojo, Boolean.TRUE); + enhancementContext = (EnhancementContext)createEnhancementContextMethod.invoke(enhanceMojo); + assertEquals(classesDirectory.toURI().toURL(), classLoader.getURLs()[0]); + assertTrue(enhancementContext.doBiDirectionalAssociationManagement(null)); + assertFalse(enhancementContext.doDirtyCheckingInline(null)); + assertFalse(enhancementContext.hasLazyLoadableAttributes(null)); + assertFalse(enhancementContext.isLazyLoadable(null)); + assertFalse(enhancementContext.doExtendedEnhancement(null)); + // verify log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + logMessages.clear(); + Field enableDirtyTrackingField = HibernateEnhancerMojo.class.getDeclaredField("enableDirtyTracking"); + enableDirtyTrackingField.setAccessible(true); + enableDirtyTrackingField.set(enhanceMojo, Boolean.TRUE); + enhancementContext = (EnhancementContext)createEnhancementContextMethod.invoke(enhanceMojo); + assertEquals(classesDirectory.toURI().toURL(), classLoader.getURLs()[0]); + assertTrue(enhancementContext.doBiDirectionalAssociationManagement(null)); + assertTrue(enhancementContext.doDirtyCheckingInline(null)); + assertFalse(enhancementContext.hasLazyLoadableAttributes(null)); + assertFalse(enhancementContext.isLazyLoadable(null)); + assertFalse(enhancementContext.doExtendedEnhancement(null)); + // verify log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + logMessages.clear(); + Field enableLazyInitializationField = HibernateEnhancerMojo.class.getDeclaredField("enableLazyInitialization"); + enableLazyInitializationField.setAccessible(true); + enableLazyInitializationField.set(enhanceMojo, Boolean.TRUE); + enhancementContext = (EnhancementContext)createEnhancementContextMethod.invoke(enhanceMojo); + assertEquals(classesDirectory.toURI().toURL(), classLoader.getURLs()[0]); + assertTrue(enhancementContext.doBiDirectionalAssociationManagement(null)); + assertTrue(enhancementContext.doDirtyCheckingInline(null)); + assertTrue(enhancementContext.hasLazyLoadableAttributes(null)); + assertTrue(enhancementContext.isLazyLoadable(null)); + assertFalse(enhancementContext.doExtendedEnhancement(null)); + // verify log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + logMessages.clear(); + Field enableExtendedEnhancementField = HibernateEnhancerMojo.class.getDeclaredField("enableExtendedEnhancement"); + enableExtendedEnhancementField.setAccessible(true); + enableExtendedEnhancementField.set(enhanceMojo, Boolean.TRUE); + enhancementContext = (EnhancementContext)createEnhancementContextMethod.invoke(enhanceMojo); + assertEquals(classesDirectory.toURI().toURL(), classLoader.getURLs()[0]); + assertTrue(enhancementContext.doBiDirectionalAssociationManagement(null)); + assertTrue(enhancementContext.doDirtyCheckingInline(null)); + assertTrue(enhancementContext.hasLazyLoadableAttributes(null)); + assertTrue(enhancementContext.isLazyLoadable(null)); + assertTrue(enhancementContext.doExtendedEnhancement(null)); + // verify log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + logMessages.clear(); + } + + @Test + void testCreateEnhancer() throws Exception { + Method createEnhancerMethod = HibernateEnhancerMojo.class.getDeclaredMethod("createEnhancer"); + createEnhancerMethod.setAccessible(true); + Enhancer enhancer = (Enhancer)enhancerField.get(enhanceMojo); + assertNull(enhancer); + createEnhancerMethod.invoke(enhanceMojo); + enhancer = (Enhancer)enhancerField.get(enhanceMojo); + assertNotNull(enhancer); + Field byteByddyEnhancementContextField = EnhancerImpl.class.getDeclaredField("enhancementContext"); + byteByddyEnhancementContextField.setAccessible(true); + Object byteByddyEnhancementContext = byteByddyEnhancementContextField.get(enhancer); + assertNotNull(byteByddyEnhancementContext); + Field enhancementContextField = byteByddyEnhancementContext.getClass().getDeclaredField("enhancementContext"); + enhancementContextField.setAccessible(true); + EnhancementContext enhancementContext = (EnhancementContext)enhancementContextField.get(byteByddyEnhancementContext); + assertNotNull(enhancementContext); + ClassLoader classLoader = enhancementContext.getLoadingClassLoader(); + assertNotNull(classLoader); + assertNotNull(classLoader); + URL fooResource = classLoader.getResource("bar/Foo.txt"); + assertNotNull(fooResource); + assertEquals(fooTxtFile.toURI().toURL(), fooResource); + // verify log messages + assertEquals(3, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_BYTECODE_ENHANCER)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_ENHANCEMENT_CONTEXT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_URL_CLASSLOADER_FOR_FOLDER.formatted(classesDirectory))); + } + + @Test + void testDetermineClassName() throws Exception { + Method determineClassNameMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "determineClassName", + new Class[] { File.class }); + determineClassNameMethod.setAccessible(true); + assertEquals("org.foo.Bar", determineClassNameMethod.invoke(enhanceMojo, barClassFile)); + // check log messages + assertEquals(1, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + } + + @Test + void testDiscoverTypesForClass() throws Exception { + final List hasRun = new ArrayList(); + Method discoverTypesForClassMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "discoverTypesForClass", + new Class[] { File.class }); + discoverTypesForClassMethod.setAccessible(true); + Enhancer enhancer = (Enhancer)Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Enhancer.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("discoverTypes")) { + assertEquals("org.foo.Bar", args[0]); + hasRun.add(0, true); + } + return null; + } + }); + enhancerField.set(enhanceMojo, enhancer); + assertFalse(hasRun.contains(true)); + discoverTypesForClassMethod.invoke(enhanceMojo, barClassFile); + assertTrue(hasRun.contains(true)); + // verify log messages + assertEquals(3, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_DISCOVER_TYPES_FOR_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_DISCOVERED_TYPES_FOR_CLASS_FILE.formatted(barClassFile))); + } + + @Test + void testDiscoverTypes() throws Exception { + final List hasRun = new ArrayList(); + Method discoverTypesMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "discoverTypes", + new Class[] { }); + discoverTypesMethod.setAccessible(true); + Enhancer enhancer = (Enhancer)Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Enhancer.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("discoverTypes")) { + assertEquals("org.foo.Bar", args[0]); + hasRun.add(0, true); + } + return null; + } + }); + enhancerField.set(enhanceMojo, enhancer); + assertFalse(hasRun.contains(true)); + discoverTypesMethod.invoke(enhanceMojo); + assertFalse(hasRun.contains(true)); + // verify the log messages + assertEquals(2, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_TYPE_DISCOVERY)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ENDING_TYPE_DISCOVERY)); + logMessages.clear(); + List sourceSet = new ArrayList(); + sourceSet.add(barClassFile); + sourceSetField.set(enhanceMojo, sourceSet); + discoverTypesMethod.invoke(enhanceMojo); + assertTrue(hasRun.contains(true)); + // verify the log messages + assertEquals(5, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_TYPE_DISCOVERY)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_DISCOVER_TYPES_FOR_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_DISCOVERED_TYPES_FOR_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ENDING_TYPE_DISCOVERY)); + } + + @Test + void testClearFile() throws Exception { + Method clearFileMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "clearFile", + new Class[] { File.class }); + clearFileMethod.setAccessible(true); + Files.writeString(fooTxtFile.toPath(), "foobar"); + fooTxtFile.setLastModified(0); + assertEquals("foobar", new String(Files.readAllBytes(fooTxtFile.toPath()))); + boolean result = (boolean)clearFileMethod.invoke(enhanceMojo, new File("foobar")); + assertFalse(result); + result = (boolean)clearFileMethod.invoke(enhanceMojo, fooTxtFile); + long modified = fooTxtFile.lastModified(); + assertTrue(result); + // File should be empty + assertTrue(Files.readAllBytes(fooTxtFile.toPath()).length == 0); + // last modification 'after' should be after 'before' + assertNotEquals(0, modified); + assertTrue(modified > 0); + // check log messages + assertEquals(4, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_CLEAR_FILE.formatted("foobar"))); + assertTrue(logMessages.contains(ERROR + HibernateEnhancerMojo.UNABLE_TO_DELETE_FILE.formatted("foobar"))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_CLEAR_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_CLEARED_FILE.formatted(fooTxtFile))); + } + + @Test + void testWriteByteCodeToFile() throws Exception { + Method writeByteCodeToFileMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "writeByteCodeToFile", + new Class[] { byte[].class, File.class}); + writeByteCodeToFileMethod.setAccessible(true); + fooTxtFile.setLastModified(0); + // File fooTxtFile is empty + assertTrue(Files.readAllBytes(fooTxtFile.toPath()).length == 0); + writeByteCodeToFileMethod.invoke(enhanceMojo, "foobar".getBytes(), fooTxtFile); + long modified = fooTxtFile.lastModified(); + // last modification 'after' should be after 'before' + assertNotEquals(0, modified); + assertTrue(modified > 0); + // File should be contain 'foobar' + assertEquals(new String(Files.readAllBytes(fooTxtFile.toPath())), "foobar"); + // check log messages + assertEquals(4, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.WRITING_BYTE_CODE_TO_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_CLEAR_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_CLEARED_FILE.formatted(fooTxtFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.AMOUNT_BYTES_WRITTEN_TO_FILE.formatted(6, fooTxtFile))); + } + + @Test + void testEnhanceClass() throws Exception { + final List calls = new ArrayList(); + calls.add(0, 0); + Method enhanceClassMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "enhanceClass", + new Class[] { File.class }); + enhanceClassMethod.setAccessible(true); + Enhancer enhancer = (Enhancer)Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Enhancer.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + calls.set(0, calls.get(0) + 1); + if (method.getName().equals("enhance")) { + assertEquals("org.foo.Bar", args[0]); + } + if (calls.get(0) == 1) { + return "foobar".getBytes(); + } else if (calls.get(0) == 2) { + return null; + } else { + throw new EnhancementException("foobar"); + } + } + }); + long beforeRuns = barClassFile.lastModified(); + // First Run -> file is modified + enhancerField.set(enhanceMojo, enhancer); + assertEquals(0, calls.get(0)); + enhanceClassMethod.invoke(enhanceMojo, barClassFile); + long afterFirstRun = barClassFile.lastModified(); + assertEquals(1, calls.get(0)); + assertTrue(afterFirstRun >= beforeRuns); + assertEquals("foobar", new String(Files.readAllBytes(barClassFile.toPath()))); + // verify log messages + assertEquals(7, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_ENHANCE_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.WRITING_BYTE_CODE_TO_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_CLEAR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_CLEARED_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.AMOUNT_BYTES_WRITTEN_TO_FILE.formatted("foobar".length(), barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_ENHANCED_CLASS_FILE.formatted(barClassFile))); + // Second Run -> file is not modified + logMessages.clear(); + enhanceClassMethod.invoke(enhanceMojo, barClassFile); + long afterSecondRun = barClassFile.lastModified(); + assertEquals(2, calls.get(0)); + assertEquals(afterSecondRun, afterFirstRun); + assertEquals("foobar", new String(Files.readAllBytes(barClassFile.toPath()))); + // verify log messages + assertEquals(3, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_ENHANCE_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SKIPPING_FILE.formatted(barClassFile))); + // Third Run -> exception! + logMessages.clear(); + try { + enhanceClassMethod.invoke(enhanceMojo, barClassFile); + fail(); + } catch (Throwable e) { + long afterThirdRun = barClassFile.lastModified(); + assertEquals(3, calls.get(0)); + assertEquals(afterThirdRun, afterFirstRun); + assertEquals("foobar", new String(Files.readAllBytes(barClassFile.toPath()))); + // verify log messages + assertEquals(3, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_ENHANCE_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(ERROR + HibernateEnhancerMojo.ERROR_WHILE_ENHANCING_CLASS_FILE.formatted(barClassFile))); + } + } + + @Test + void testPerformEnhancement() throws Exception { + final List hasRun = new ArrayList(); + Method performEnhancementMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "performEnhancement", + new Class[] { }); + performEnhancementMethod.setAccessible(true); + Enhancer enhancer = (Enhancer)Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Enhancer.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("enhance")) { + assertEquals("org.foo.Bar", args[0]); + hasRun.add(0, true); + } + return "foobar".getBytes(); + } + }); + enhancerField.set(enhanceMojo, enhancer); + List sourceSet = new ArrayList(); + sourceSet.add(barClassFile); + sourceSetField.set(enhanceMojo, sourceSet); + long lastModified = barClassFile.lastModified(); + assertFalse(hasRun.contains(true)); + assertNotEquals("foobar", new String(Files.readAllBytes(barClassFile.toPath()))); + performEnhancementMethod.invoke(enhanceMojo); + assertTrue(hasRun.contains(true)); + assertEquals("foobar", new String(Files.readAllBytes(barClassFile.toPath()))); + assertEquals(lastModified, barClassFile.lastModified()); + // verify the log messages + assertEquals(9, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_CLASS_ENHANCEMENT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_ENHANCE_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.DETERMINE_CLASS_NAME_FOR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.WRITING_BYTE_CODE_TO_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.TRYING_TO_CLEAR_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_CLEARED_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.AMOUNT_BYTES_WRITTEN_TO_FILE.formatted("foobar".length(), barClassFile))); + assertTrue(logMessages.contains(INFO + HibernateEnhancerMojo.SUCCESFULLY_ENHANCED_CLASS_FILE.formatted(barClassFile))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ENDING_CLASS_ENHANCEMENT)); + } + + @Test + void testExecute() throws Exception { + Method executeMethod = HibernateEnhancerMojo.class.getDeclaredMethod("execute", new Class[] {}); + executeMethod.setAccessible(true); + final String barSource = + "package org.foo;" + + "import jakarta.persistence.Entity;" + + "@Entity public class Bar { "+ + " private String foo; " + + " String getFoo() { return foo; } " + + " public void setFoo(String f) { foo = f; } " + + "}"; + File barJavaFile = new File(fooFolder, "Bar.java"); + Files.writeString(barJavaFile.toPath(), barSource); + final String fooSource = + "package org.foo;" + + "public class Foo { "+ + " private Bar bar; " + + " Bar getBar() { return bar; } " + + " public void setBar(Bar b) { bar = b; } " + + "}"; + File fooJavaFile = new File(fooFolder, "Foo.java"); + Files.writeString(fooJavaFile.toPath(), fooSource); + File fooClassFile = new File(fooFolder, "Foo.class"); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + URL url = Entity.class.getProtectionDomain().getCodeSource().getLocation(); + String classpath = new File(url.toURI()).getAbsolutePath(); + String[] options = List.of( + "-cp", + classpath, + barJavaFile.getAbsolutePath(), + fooJavaFile.getAbsolutePath()).toArray(new String[] {}); + compiler.run(null, null, null, options); + String barBytesString = new String(Files.readAllBytes(barClassFile.toPath())); + String fooBytesString = new String(Files.readAllBytes(fooClassFile.toPath())); + List sourceSet = new ArrayList(); + sourceSet.add(barClassFile); + sourceSet.add(fooClassFile); + sourceSetField.set(enhanceMojo, sourceSet); + assertTrue(logMessages.isEmpty()); + executeMethod.invoke(enhanceMojo); + assertNotEquals(barBytesString, new String(Files.readAllBytes(barClassFile.toPath()))); + assertEquals(fooBytesString, new String(Files.readAllBytes(fooClassFile.toPath()))); + URLClassLoader classLoader = new URLClassLoader( + new URL[] {classesDirectory.toURI().toURL()}, + getClass().getClassLoader()); + Class barClass = classLoader.loadClass("org.foo.Bar"); + assertNotNull(barClass); + Method m = barClass.getMethod("$$_hibernate_getEntityInstance", new Class[]{}); + assertNotNull(m); + Class fooClass = classLoader.loadClass("org.foo.Foo"); + try { + m = fooClass.getMethod("$$_hibernate_getEntityInstance", new Class[]{}); + fail(); + } catch (NoSuchMethodException e) { + assertEquals("org.foo.Foo.$$_hibernate_getEntityInstance()", e.getMessage()); + } + classLoader.close(); + // verify in the log messages at least if all the needed methods have been invoked + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_EXECUTION_OF_ENHANCE_MOJO)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ADDED_DEFAULT_FILESET_WITH_BASE_DIRECTORY.formatted(classesDirectory))); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_ASSEMBLY_OF_SOURCESET)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.CREATE_BYTECODE_ENHANCER)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_TYPE_DISCOVERY)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.STARTING_CLASS_ENHANCEMENT)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ENDING_EXECUTION_OF_ENHANCE_MOJO)); + } + + @Test + void testProcessParameters() throws Exception { + Method processParametersMethod = HibernateEnhancerMojo.class.getDeclaredMethod( + "processParameters", + new Class[] {}); + processParametersMethod.setAccessible(true); + Field enableLazyInitializationField = HibernateEnhancerMojo.class.getDeclaredField("enableLazyInitialization"); + enableLazyInitializationField.setAccessible(true); + Field enableDirtyTrackingField = HibernateEnhancerMojo.class.getDeclaredField("enableDirtyTracking"); + enableDirtyTrackingField.setAccessible(true); + assertTrue(logMessages.isEmpty()); + assertNull(fileSetsField.get(enhanceMojo)); + processParametersMethod.invoke(enhanceMojo); + assertEquals(3, logMessages.size()); + assertTrue(logMessages.contains(WARNING + HibernateEnhancerMojo.ENABLE_LAZY_INITIALIZATION_DEPRECATED)); + assertTrue(logMessages.contains(WARNING + HibernateEnhancerMojo.ENABLE_DIRTY_TRACKING_DEPRECATED)); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ADDED_DEFAULT_FILESET_WITH_BASE_DIRECTORY.formatted(classesDirectory))); + FileSet[] fileSets = (FileSet[])fileSetsField.get(enhanceMojo); + assertNotNull(fileSets); + assertEquals(1, fileSets.length); + assertEquals(classesDirectory.getAbsolutePath(), fileSets[0].getDirectory()); + fileSetsField.set(enhanceMojo, null); + logMessages.clear(); + enableLazyInitializationField.set(enhanceMojo, Boolean.TRUE); + enableDirtyTrackingField.set(enhanceMojo, Boolean.TRUE); + processParametersMethod.invoke(enhanceMojo); + assertEquals(1, logMessages.size()); + assertTrue(logMessages.contains(DEBUG + HibernateEnhancerMojo.ADDED_DEFAULT_FILESET_WITH_BASE_DIRECTORY.formatted(classesDirectory))); + } + + private Log createLog() { + return (Log)Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { Log.class}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("info".equals(method.getName())) { + logMessages.add(INFO + args[0]); + } else if ("warn".equals(method.getName())) { + logMessages.add(WARNING + args[0]); + } else if ("error".equals(method.getName())) { + logMessages.add(ERROR + args[0]); + } else if ("debug".equals(method.getName())) { + logMessages.add(DEBUG + args[0]); + } + return null; + } + }); + } + + static final String DEBUG = "[DEBUG] "; + static final String ERROR = "[ERROR] "; + static final String WARNING = "[WARNING] "; + static final String INFO = "[INFO] "; + +}