HHH-13130 - Provide Gradle-based bytecode enhancement as a task separate from the compileJava task
This commit is contained in:
parent
1bdbc0e764
commit
5c9cf4d0d8
|
@ -0,0 +1,23 @@
|
||||||
|
[[enhancement]]
|
||||||
|
== Enhancement
|
||||||
|
:sourcedir: ../../../../../test/java/org/hibernate/userguide/bytecode
|
||||||
|
:extrasdir: extras
|
||||||
|
|
||||||
|
Hibernate offers a number of services that can be added into an application's domain model classes
|
||||||
|
through bytecode enhancement...
|
||||||
|
|
||||||
|
|
||||||
|
[[enhancement-laziness]]
|
||||||
|
=== Laziness
|
||||||
|
|
||||||
|
|
||||||
|
[[enhancement-bidir]]
|
||||||
|
=== Bi-directionality
|
||||||
|
|
||||||
|
|
||||||
|
[[enhancement-dirty]]
|
||||||
|
=== In-line dirty checking
|
||||||
|
|
||||||
|
|
||||||
|
[[enhancement-extended]]
|
||||||
|
=== Extended enhancement
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.SourceSet
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.util.ConfigureUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
class EnhanceTask extends DefaultTask {
|
||||||
|
EnhanceExtension options
|
||||||
|
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 )
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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<URL> 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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,12 +55,9 @@ public class HibernatePlugin implements Plugin<Project> {
|
||||||
project.getExtensions().add( "hibernate", hibernateExtension );
|
project.getExtensions().add( "hibernate", hibernateExtension );
|
||||||
|
|
||||||
project.afterEvaluate(
|
project.afterEvaluate(
|
||||||
new Action<Project>() {
|
project1 -> {
|
||||||
@Override
|
if ( hibernateExtension.enhance != null ) {
|
||||||
public void execute(Project project) {
|
applyEnhancement( project1, hibernateExtension );
|
||||||
if ( hibernateExtension.enhance != null ) {
|
|
||||||
applyEnhancement( project, hibernateExtension );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -76,150 +73,11 @@ public class HibernatePlugin implements Plugin<Project> {
|
||||||
project.getLogger().debug( "Applying Hibernate enhancement action to SourceSet.{}", sourceSet.getName() );
|
project.getLogger().debug( "Applying Hibernate enhancement action to SourceSet.{}", sourceSet.getName() );
|
||||||
|
|
||||||
final Task compileTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() );
|
final Task compileTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() );
|
||||||
|
assert compileTask != null;
|
||||||
compileTask.doLast(
|
compileTask.doLast(
|
||||||
new Action<Task>() {
|
task -> EnhancementHelper.enhance( sourceSet, hibernateExtension.enhance, project )
|
||||||
@Override
|
|
||||||
public void execute(Task task) {
|
|
||||||
project.getLogger().debug( "Starting Hibernate enhancement on SourceSet.{}", sourceSet.getName() );
|
|
||||||
|
|
||||||
final ClassLoader classLoader = toClassLoader( sourceSet.getRuntimeClasspath() );
|
|
||||||
|
|
||||||
EnhancementContext enhancementContext = new DefaultEnhancementContext() {
|
|
||||||
@Override
|
|
||||||
public ClassLoader getLoadingClassLoader() {
|
|
||||||
return classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
|
||||||
return hibernateExtension.enhance.getEnableAssociationManagement();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
|
||||||
return hibernateExtension.enhance.getEnableDirtyTracking();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
|
||||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLazyLoadable(UnloadedField field) {
|
|
||||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
|
||||||
return hibernateExtension.enhance.getEnableExtendedEnhancement();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( hibernateExtension.enhance.getEnableExtendedEnhancement() ) {
|
|
||||||
logger.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 );
|
|
||||||
logger.info( "Successfully enhanced class [" + file + "]" );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.info( "Skipping class [" + file.getAbsolutePath() + "], not an entity nor embeddable" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassLoader toClassLoader(FileCollection runtimeClasspath) {
|
|
||||||
List<URL> urls = new ArrayList<URL>();
|
|
||||||
for ( File file : runtimeClasspath ) {
|
|
||||||
try {
|
|
||||||
urls.add( file.toURI().toURL() );
|
|
||||||
logger.debug( "Adding classpath entry for " + file.getAbsolutePath() );
|
|
||||||
}
|
|
||||||
catch (MalformedURLException e) {
|
|
||||||
throw new GradleException( "Unable to resolve classpath entry to URL : " + file.getAbsolutePath(), e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new URLClassLoader( urls.toArray( new URL[urls.size()] ), Enhancer.class.getClassLoader() );
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] doEnhancement(File root, File javaClassFile, Enhancer enhancer) {
|
|
||||||
try {
|
|
||||||
String className = javaClassFile.getAbsolutePath().substring(
|
|
||||||
root.getAbsolutePath().length() + 1,
|
|
||||||
javaClassFile.getAbsolutePath().length() - ".class".length()
|
|
||||||
).replace( File.separatorChar, '.' );
|
|
||||||
ByteArrayOutputStream originalBytes = new ByteArrayOutputStream();
|
|
||||||
FileInputStream fileInputStream = new FileInputStream( javaClassFile );
|
|
||||||
try {
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ( ( length = fileInputStream.read( buffer ) ) != -1 ) {
|
|
||||||
originalBytes.write( buffer, 0, length );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
fileInputStream.close();
|
|
||||||
}
|
|
||||||
return enhancer.enhance( className, originalBytes.toByteArray() );
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new GradleException( "Unable to enhance class : " + javaClassFile, e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, File file) {
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.orm.tooling.gradle
|
package org.hibernate.orm.tooling.gradle
|
||||||
import org.gradle.api.Project
|
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.gradle.testfixtures.ProjectBuilder
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -38,4 +42,46 @@ class HibernatePluginTest {
|
||||||
enableExtendedEnhancement = 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestTask.execute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue