HHH-9809 - Improve Hibernate Gradle plugin
This commit is contained in:
parent
acea523607
commit
1812bb2ef3
|
@ -0,0 +1,93 @@
|
|||
= Bytecode Enhancement
|
||||
:toc:
|
||||
|
||||
This guide covers Hibernate's ability to enhance an applications domain model, the ways to perform that
|
||||
enhancement and the capabilities introduced into the domain model by the enhancement.
|
||||
|
||||
|
||||
== The capabilities
|
||||
|
||||
Hibernate will enhance the classes in an application's domain model in order to add one or more of the
|
||||
following capabilities:
|
||||
|
||||
. Lazy state initialization
|
||||
. Dirtiness tracking
|
||||
. Automatic bi-directional association management
|
||||
. Performance optimizations
|
||||
|
||||
todo : explain each in detail
|
||||
|
||||
|
||||
== Performing enhancement
|
||||
|
||||
Ultimately all enhancement is handled by the `org.hibernate.bytecode.enhance.spi.Enhancer` class. Custom means to
|
||||
enhancement can certainly be crafted on top of Enhancer, but that is beyond the scope of this guide. Here we
|
||||
will focus on the means Hibernate already exposes for performing these enhancements.
|
||||
|
||||
=== Run-time enhancement
|
||||
|
||||
Currently run-time enhancement of the domain model is only supported in managed JPA environments following the
|
||||
JPA defined SPI for performing class transformations. Even then, this support is disabled by default. In this
|
||||
scenario, run-time enhancement can be enabled by specifying `hibernate.ejb.use_class_enhancer=true` as a
|
||||
persistent unit property.
|
||||
|
||||
|
||||
=== Build-time enhancement
|
||||
|
||||
Hibernate also offers the ability to integrate the enhancement of the domain model as part of the
|
||||
normal build cycle of that domain model. Gradle, Ant and Maven are all supported. One possible benefit
|
||||
of this approach is that the enhanced classes are what gets added to the jar and can then be used on both
|
||||
sides of serialization.
|
||||
|
||||
|
||||
=== Gradle Plugin
|
||||
|
||||
Hibernate provides a Gradle plugin that is capable of providing build-time enhancement of the domain model as they are
|
||||
compiled as part of a Gradle build. To use the plugin a project would first need to apply it:
|
||||
|
||||
[[gradle-plugin-apply-example]]
|
||||
.Apply the plugin
|
||||
====
|
||||
[source, GROOVY]
|
||||
----
|
||||
ext {
|
||||
hibernateVersion = 'hibernate-version-you-want'
|
||||
}
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion"
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
At the moment there is not much to configure with regard to the Enhancer, but what is configurable is exposed
|
||||
through a DSL extension registered. By default enhancement will not be done (in preparation for when this
|
||||
Gradle plugin offers additional capabilities). To enable it you must configure the following DSL extension:
|
||||
|
||||
[[gradle-plugin-apply-example]]
|
||||
.Apply the plugin
|
||||
====
|
||||
[source, GROOVY]
|
||||
----
|
||||
hibernate {
|
||||
enhance {
|
||||
// any configuration goes here
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Currently the "enhance" extension supports 3 properties:
|
||||
|
||||
* `enableLazyInitialization`
|
||||
* `enableDirtyTracking`
|
||||
* `enableAssociationManagement`
|
||||
|
||||
Once enhancement overall is enabled, the default for these 3 properties is `true`.
|
||||
|
||||
|
||||
=== Ant Task
|
||||
|
||||
|
||||
=== Maven Plugin
|
|
@ -19,6 +19,7 @@ NOTE: This is still very much a work in progress. <<helping,Help>> is definitely
|
|||
== Tooling
|
||||
|
||||
* See the <<metamodelgen/MetamodelGenerator.adoc#,Metamodel Generator Guide>> for details on generating a JPA "Static Metamodel"
|
||||
* see the <<bytecode/BytecodeEnhancement.adoc#,Bytecode Enhancement Guide>> for information on bytecode enhancement
|
||||
* Guide on the Gradle plugin coming soon
|
||||
* Guide on the Ant tasks coming soon
|
||||
* Guide on the Maven plugin coming soon
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Defines a Gradle plugin for introducing Hibernate specific tasks and capabilities into and end-user build.
|
||||
|
||||
Currently the only capability added is for bytecode enhancement of the user domain model, although other capabilities are
|
||||
planned.
|
||||
|
||||
todo : usage
|
|
@ -4,19 +4,21 @@
|
|||
* 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>.
|
||||
*/
|
||||
apply plugin: 'java-gradle-plugin'
|
||||
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
compile( project(':hibernate-core') )
|
||||
compile( libraries.jpa )
|
||||
compile( libraries.javassist )
|
||||
compile gradleApi()
|
||||
compile localGroovy()
|
||||
compile( project( ':hibernate-core' ) )
|
||||
compile( libraries.jpa )
|
||||
compile( libraries.javassist )
|
||||
compile gradleApi()
|
||||
compile localGroovy()
|
||||
}
|
||||
|
||||
mavenPom {
|
||||
name = 'Hibernate Gradle plugin'
|
||||
description = "Gradle plugin for integrating Hibernate functionality into your build"
|
||||
name = 'Hibernate Gradle plugin'
|
||||
description = "Gradle plugin for integrating Hibernate functionality into your build"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Gradle DSL extension for configuring various Hibernate bytecode enhancement. Registered
|
||||
* under "hibernate.enhance".
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
class EnhanceExtension {
|
||||
def boolean enableLazyInitialization = true
|
||||
def boolean enableDirtyTracking = true
|
||||
def boolean enableAssociationManagement = true
|
||||
|
||||
boolean shouldApply() {
|
||||
return enableLazyInitialization || enableDirtyTracking || enableAssociationManagement;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* 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.tooling.gradle
|
||||
package org.hibernate.orm.tooling.gradle
|
||||
|
||||
import javax.persistence.ElementCollection
|
||||
import javax.persistence.Entity
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.Project
|
||||
import org.gradle.api.plugins.JavaPluginConvention
|
||||
import org.gradle.api.tasks.SourceSet
|
||||
import org.gradle.util.ConfigureUtil
|
||||
|
||||
/**
|
||||
* Gradle DSL extension for configuring various Hibernate built-time tasks. Registered
|
||||
* under "hibernate".
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
class HibernateExtension {
|
||||
private final Project project
|
||||
|
||||
/**
|
||||
* The source sets that hold persistent model. Default is project.sourceSets.main
|
||||
*/
|
||||
def SourceSet[] sourceSets
|
||||
/**
|
||||
* Configuration for bytecode enhancement. Private; see instead {@link #enhance(groovy.lang.Closure)}
|
||||
*/
|
||||
protected EnhanceExtension enhance
|
||||
|
||||
HibernateExtension(Project project) {
|
||||
this.project = project
|
||||
this.sourceSet( project.getConvention().getPlugin( JavaPluginConvention ).sourceSets.main )
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single SourceSet.
|
||||
*
|
||||
* @param sourceSet The SourceSet to add
|
||||
*/
|
||||
void sourceSet(SourceSet sourceSet) {
|
||||
if ( sourceSets == null ) {
|
||||
sourceSets = []
|
||||
}
|
||||
sourceSets += sourceSets
|
||||
}
|
||||
|
||||
void enhance(Closure closure) {
|
||||
enhance = new EnhanceExtension()
|
||||
ConfigureUtil.configure( closure, enhance )
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.tooling.gradle;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.logging.Logger;
|
||||
import org.gradle.api.logging.Logging;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The Hibernate Gradle plugin. Adds Hibernate build-time capabilities into your Gradle-based build.
|
||||
*
|
||||
* @author Jeremy Whiting
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class HibernatePlugin implements Plugin<Project> {
|
||||
private final Logger logger = Logging.getLogger( HibernatePlugin.class );
|
||||
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply( "java" );
|
||||
|
||||
final HibernateExtension hibernateExtension = new HibernateExtension( project );
|
||||
|
||||
project.getLogger().debug( "Adding Hibernate extensions to the build [{}]", project.getName() );
|
||||
project.getExtensions().add( "hibernate", hibernateExtension );
|
||||
|
||||
project.afterEvaluate(
|
||||
new Action<Project>() {
|
||||
@Override
|
||||
public void execute(Project project) {
|
||||
if ( hibernateExtension.enhance != null ) {
|
||||
applyEnhancement( project, hibernateExtension );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void applyEnhancement(final Project project, final HibernateExtension hibernateExtension) {
|
||||
if ( !hibernateExtension.enhance.shouldApply() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( final SourceSet sourceSet : hibernateExtension.getSourceSets() ) {
|
||||
project.getLogger().debug( "Applying Hibernate enhancement action to SourceSet.{}", sourceSet.getName() );
|
||||
|
||||
final Task compileTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() );
|
||||
|
||||
final ClassLoader classLoader = toClassLoader( sourceSet.getRuntimeClasspath() );
|
||||
|
||||
compileTask.doLast(
|
||||
new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task task) {
|
||||
project.getLogger().debug( "Starting Hibernate enhancement on SourceSet.{}", sourceSet.getName() );
|
||||
|
||||
EnhancementContext enhancementContext = new DefaultEnhancementContext() {
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
return hibernateExtension.enhance.getEnableAssociationManagement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return hibernateExtension.enhance.getEnableDirtyTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
||||
}
|
||||
};
|
||||
|
||||
final Enhancer enhancer = new Enhancer( enhancementContext );
|
||||
final ClassPool classPool = new ClassPool( false );
|
||||
|
||||
|
||||
final FileTree fileTree = project.fileTree( sourceSet.getOutput().getClassesDir() );
|
||||
for ( File file : fileTree ) {
|
||||
if ( !file.getName().endsWith( ".class" ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CtClass ctClass = toCtClass( file, classPool );
|
||||
|
||||
if ( !ctClass.hasAnnotation( Entity.class )
|
||||
&& !ctClass.hasAnnotation( Embedded.class ) ) {
|
||||
logger.debug( "Skipping class file [" + file.getAbsolutePath() + "], not an entity nor embedded" );
|
||||
continue;
|
||||
}
|
||||
|
||||
final byte[] enhancedBytecode = doEnhancement( ctClass, enhancer );
|
||||
writeOutEnhancedClass( enhancedBytecode, ctClass, file );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassLoader toClassLoader(FileCollection runtimeClasspath) {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
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[urls.size()] ),
|
||||
ClassLoader.getSystemClassLoader().getParent()
|
||||
);
|
||||
}
|
||||
|
||||
private CtClass toCtClass(File file, ClassPool classPool) {
|
||||
try {
|
||||
final InputStream is = new FileInputStream( file.getAbsolutePath() );
|
||||
|
||||
try {
|
||||
return classPool.makeClass( is );
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GradleException( "Javassist unable to load class in preparation for enhancing : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.info( "Was unable to close InputStream : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// should never happen, but...
|
||||
throw new GradleException( "Unable to locate class file for InputStream: " + file.getAbsolutePath(), e );
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] doEnhancement(CtClass ctClass, Enhancer enhancer) {
|
||||
try {
|
||||
return enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new GradleException( "Unable to enhance class : " + ctClass.getName(), e );
|
||||
}
|
||||
}
|
||||
|
||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, CtClass ctClass, File file) {
|
||||
try {
|
||||
if ( file.delete() ) {
|
||||
if ( !file.createNewFile() ) {
|
||||
logger.error( "Unable to recreate class file [" + ctClass.getName() + "]" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error( "Unable to delete class file [" + ctClass.getName() + "]" );
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn( "Problem preparing class file for writing out enhancements [" + ctClass.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 [" + ctClass.getName() + "] to file [" + file.getAbsolutePath() + "]", e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
ctClass.detach();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new GradleException( "Error opening class file for writing : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.tooling.gradle;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.BasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The Hibernate Gradle plugin. Adds Hibernate build-time capabilities into your Gradle-based build.
|
||||
*
|
||||
* @author Jeremy Whiting
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class HibernatePlugin implements Plugin<Project> {
|
||||
|
||||
public static final String ENHANCE_TASK_NAME = "enhance";
|
||||
|
||||
public void apply(Project project) {
|
||||
applyEnhancement( project );
|
||||
}
|
||||
|
||||
private void applyEnhancement(Project project) {
|
||||
project.getLogger().debug( "Applying Hibernate enhancement to project." );
|
||||
|
||||
// few things...
|
||||
|
||||
// 1) would probably be best as a doLast Action attached to the compile task rather than
|
||||
// a task. Really ideally would be a task association for "always run after", but Gradle
|
||||
// does not yet have that (mustRunAfter is very different semantic, finalizedBy is closer but
|
||||
// will run even if the first task fails). The initial attempt here fell into the "maven" trap
|
||||
// of trying to run a dependent task by attaching it to a task know to run after the we want to run after;
|
||||
// which is a situation tailored made for Task.doLast
|
||||
|
||||
// 2) would be better to allow specifying which SourceSet to apply this to. For example, in the Hibernate
|
||||
// build itself, this would be best applied to the 'test' sourceSet; though generally speaking the
|
||||
// 'main' sourceSet is more appropriate
|
||||
|
||||
// for now, we'll just:
|
||||
// 1) use a EnhancerTask + finalizedBy
|
||||
// 2) apply to main sourceSet
|
||||
|
||||
EnhancerTask enhancerTask = project.getTasks().create( ENHANCE_TASK_NAME, EnhancerTask.class );
|
||||
enhancerTask.setGroup( BasePlugin.BUILD_GROUP );
|
||||
|
||||
// connect up the task in the task dependency graph
|
||||
Task classesTask = project.getTasks().getByName( JavaPlugin.CLASSES_TASK_NAME );
|
||||
enhancerTask.dependsOn( classesTask );
|
||||
classesTask.finalizedBy( enhancerTask );
|
||||
}
|
||||
}
|
|
@ -4,4 +4,4 @@
|
|||
# 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>.
|
||||
#
|
||||
implementation-class=org.hibernate.tooling.gradle.HibernatePlugin
|
||||
implementation-class=org.hibernate.orm.tooling.gradle.HibernatePlugin
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.Project
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertNotNull
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
/**
|
||||
* Test what we can. ProjectBuilder is better than nothing, but still quited limited in what
|
||||
* you can test (e.g. you cannot test task execution).
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
class HibernatePluginTest {
|
||||
@Test
|
||||
public void testHibernatePluginAddsExtension() {
|
||||
Project project = ProjectBuilder.builder().build()
|
||||
project.plugins.apply 'org.hibernate.orm'
|
||||
|
||||
assertNotNull( project.extensions.findByName( "hibernate" ) )
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHibernateExtensionConfig() {
|
||||
Project project = ProjectBuilder.builder().build()
|
||||
project.plugins.apply 'org.hibernate.orm'
|
||||
|
||||
project.extensions.findByType( HibernateExtension.class ).enhance {
|
||||
enableAssociationManagement = false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue