HHH-9813 - Improve Hibernate Enhance Maven Plugin

This commit is contained in:
barreiro 2015-05-22 07:23:04 +01:00 committed by Steve Ebersole
parent 5514aea2b4
commit ebb2ca19cd
6 changed files with 303 additions and 282 deletions

View File

@ -80,7 +80,7 @@ processResources.doLast {
project {
groupId project.group
packaging 'maven-plugin'
name 'Enhance Plugin of the Hibernate project for use with Maven build system.'
name 'Hibernate Enhance Maven Plugin'
description 'Enhance Plugin of the Hibernate project for use with Maven build system.'
properties {
'project.build.sourceEncoding' 'UTF-8'

View File

@ -1,257 +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.bytecode.enhance.plugins;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
/**
* This plugin will enhance Entity objects.
*
* @author Jeremy Whiting
* @phase "compile"
*/
@Mojo(name = "enhance", defaultPhase = LifecyclePhase.COMPILE)
@Execute(goal = "enhance", phase = LifecyclePhase.COMPILE)
public class MavenEnhancePlugin extends AbstractMojo implements EnhancementContext {
/**
* The contexts to use during enhancement.
*/
private List<File> classes = new ArrayList<File>();
private ClassPool pool = new ClassPool( false );
private final Enhancer enhancer = new Enhancer( this );
private static final String CLASS_EXTENSION = ".class";
@Parameter(property = "dir", defaultValue = "${project.build.outputDirectory}")
private String dir = null;
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info( "Started enhance plugin....." );
/** Perform a depth first search for files. */
File root = new File( this.dir );
walkDir( root );
if ( 0 < classes.size() ) {
for ( File file : classes ) {
processClassFile( file );
}
}
getLog().info( "Enhance plugin completed." );
}
/**
* Expects a directory.
*/
private void walkDir(File dir) {
walkDir(
dir,
new FileFilter() {
@Override
public boolean accept(File pathname) {
return ( pathname.isFile() && pathname.getName().endsWith( CLASS_EXTENSION ) );
}
},
new FileFilter() {
@Override
public boolean accept(File pathname) {
return ( pathname.isDirectory() );
}
}
);
}
private void walkDir(File dir, FileFilter classesFilter, FileFilter dirFilter) {
File[] dirs = dir.listFiles( dirFilter );
for ( File dir1 : dirs ) {
walkDir( dir1, classesFilter, dirFilter );
}
File[] files = dir.listFiles( classesFilter );
Collections.addAll( this.classes, files );
}
/**
* Atm only process files annotated with either @Entity or @Embeddable
*/
private void processClassFile(File javaClassFile) throws MojoExecutionException {
try {
final CtClass ctClass = getClassPool().makeClass( new FileInputStream( javaClassFile ) );
if ( this.isEntityClass( ctClass ) ) {
processEntityClassFile( javaClassFile, ctClass );
}
else if ( this.isCompositeClass( ctClass ) ) {
processCompositeClassFile( javaClassFile, ctClass );
}
}
catch (IOException e) {
throw new MojoExecutionException(
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e
);
}
}
private void processEntityClassFile(File javaClassFile, CtClass ctClass) {
try {
getLog().info( String.format( "Processing Entity class file [%1$s].", ctClass.getName() ) );
byte[] result = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
if ( result != null ) {
writeEnhancedClass( javaClassFile, result );
}
}
catch (Exception e) {
getLog().error( "Unable to enhance class [" + ctClass.getName() + "]", e );
}
}
private void processCompositeClassFile(File javaClassFile, CtClass ctClass) {
try {
getLog().info( String.format( "Processing Composite class file [%1$s].", ctClass.getName() ) );
byte[] result = enhancer.enhanceComposite( ctClass.getName(), ctClass.toBytecode() );
if ( result != null ) {
writeEnhancedClass( javaClassFile, result );
}
}
catch (Exception e) {
getLog().error( "Unable to enhance class [" + ctClass.getName() + "]", e );
}
}
private void writeEnhancedClass(File javaClassFile, byte[] result) throws MojoExecutionException {
try {
if ( javaClassFile.delete() ) {
if ( !javaClassFile.createNewFile() ) {
getLog().error( "Unable to recreate class file [" + javaClassFile.getName() + "]" );
}
}
else {
getLog().error( "Unable to delete class file [" + javaClassFile.getName() + "]" );
}
FileOutputStream outputStream = new FileOutputStream( javaClassFile, false );
try {
outputStream.write( result );
outputStream.flush();
}
finally {
try {
outputStream.close();
}
catch (IOException ignore) {
}
}
}
catch (FileNotFoundException ignore) {
// should not ever happen because of explicit checks
}
catch (IOException e) {
throw new MojoExecutionException(
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e
);
}
}
private ClassPool getClassPool() {
return this.pool;
}
private boolean shouldInclude(CtClass ctClass) {
// we currently only handle entity enhancement
return ctClass.hasAnnotation( Entity.class );
}
@Override
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return classDescriptor.hasAnnotation( Entity.class );
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return classDescriptor.hasAnnotation( Embeddable.class );
}
@Override
public boolean doBiDirectionalAssociationManagement(CtField field) {
return false;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return true;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
@Override
public boolean isPersistentField(CtField ctField) {
// current check is to look for @Transient
return !ctField.hasAnnotation( Transient.class );
}
@Override
public boolean isMappedCollection(CtField field) {
try {
return ( field.getAnnotation( OneToMany.class ) != null ||
field.getAnnotation( ManyToMany.class ) != null ||
field.getAnnotation( ElementCollection.class ) != null );
}
catch (ClassNotFoundException e) {
return false;
}
}
@Override
public CtField[] order(CtField[] persistentFields) {
// for now...
return persistentFields;
// eventually needs to consult the Hibernate metamodel for proper ordering
}
}

View File

@ -0,0 +1,242 @@
/*
* 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.maven;
import java.io.File;
import java.io.FileFilter;
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.Collections;
import java.util.List;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
/**
* This plugin will enhance Entity objects.
*
* @author Jeremy Whiting
* @author Luis Barreiro
*/
@Mojo(name = "enhance", defaultPhase = LifecyclePhase.COMPILE)
@Execute(goal = "enhance", phase = LifecyclePhase.COMPILE)
public class MavenEnhancePlugin extends AbstractMojo {
/**
* The contexts to use during enhancement.
*/
private List<File> sourceSet = new ArrayList<File>();
@Parameter(property = "dir", defaultValue = "${project.build.outputDirectory}")
private String dir = null;
@Parameter(property = "enableLazyInitialization", defaultValue = "true")
private boolean enableLazyInitialization = true;
@Parameter(property = "enableDirtyTracking", defaultValue = "true")
private boolean enableDirtyTracking = true;
@Parameter(property = "enableAssociationManagement", defaultValue = "true")
private boolean enableAssociationManagement = true;
private boolean shouldApply() {
return enableLazyInitialization || enableDirtyTracking || enableAssociationManagement;
}
public void execute() throws MojoExecutionException, MojoFailureException {
if ( !shouldApply() ) {
return;
}
getLog().info( "Starting Hibernate enhancement for class sourceSet on " + dir );
/** Perform a depth first search for sourceSet. */
File root = new File( this.dir );
walkDir( root );
final ClassLoader classLoader = toClassLoader( sourceSet );
EnhancementContext enhancementContext = new DefaultEnhancementContext() {
@Override
public ClassLoader getLoadingClassLoader() {
return classLoader;
}
@Override
public boolean doBiDirectionalAssociationManagement(CtField field) {
return enableAssociationManagement;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return enableDirtyTracking;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return enableLazyInitialization;
}
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
};
final Enhancer enhancer = new Enhancer( enhancementContext );
final ClassPool classPool = new ClassPool( false );
for ( File file : sourceSet ) {
final CtClass ctClass = toCtClass( file, classPool );
if ( !ctClass.hasAnnotation( Entity.class ) && !ctClass.hasAnnotation( Embedded.class ) ) {
getLog().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(List<File> runtimeClasspath) throws MojoExecutionException {
List<URL> urls = new ArrayList<URL>();
for ( File file : runtimeClasspath ) {
try {
urls.add( file.toURI().toURL() );
}
catch (MalformedURLException e) {
throw new MojoExecutionException( "Unable to resolve classpath entry to URL : " + file.getAbsolutePath(), e );
}
}
return new URLClassLoader( urls.toArray( new URL[urls.size()] ), Enhancer.class.getClassLoader() );
}
private CtClass toCtClass(File file, ClassPool classPool) throws MojoExecutionException {
try {
final InputStream is = new FileInputStream( file.getAbsolutePath() );
try {
return classPool.makeClass( is );
}
catch (IOException e) {
throw new MojoExecutionException( "Javassist unable to load class in preparation for enhancing : " + file.getAbsolutePath(), e );
}
finally {
try {
is.close();
}
catch (IOException e) {
getLog().info( "Was unable to close InputStream : " + file.getAbsolutePath(), e );
}
}
}
catch (FileNotFoundException e) {
// should never happen, but...
throw new MojoExecutionException( "Unable to locate class file for InputStream: " + file.getAbsolutePath(), e );
}
}
private byte[] doEnhancement(CtClass ctClass, Enhancer enhancer) throws MojoExecutionException {
try {
return enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
}
catch (Exception e) {
throw new MojoExecutionException( "Unable to enhance class : " + ctClass.getName(), e );
}
}
/**
* Expects a directory.
*/
private void walkDir(File dir) {
walkDir(
dir,
new FileFilter() {
@Override
public boolean accept(File pathname) {
return ( pathname.isFile() && pathname.getName().endsWith( ".class" ) );
}
},
new FileFilter() {
@Override
public boolean accept(File pathname) {
return ( pathname.isDirectory() );
}
}
);
}
private void walkDir(File dir, FileFilter classesFilter, FileFilter dirFilter) {
File[] dirs = dir.listFiles( dirFilter );
for ( File dir1 : dirs ) {
walkDir( dir1, classesFilter, dirFilter );
}
File[] files = dir.listFiles( classesFilter );
Collections.addAll( this.sourceSet, files );
}
private void writeOutEnhancedClass(byte[] enhancedBytecode, CtClass ctClass, File file) throws MojoExecutionException{
try {
if ( file.delete() ) {
if ( !file.createNewFile() ) {
getLog().error( "Unable to recreate class file [" + ctClass.getName() + "]" );
}
}
else {
getLog().error( "Unable to delete class file [" + ctClass.getName() + "]" );
}
}
catch (IOException e) {
getLog().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 MojoExecutionException( "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 MojoExecutionException( "Error opening class file for writing : " + file.getAbsolutePath(), e );
}
}
}

View File

@ -1,17 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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>.
-->
<!-- Generated by maven-plugin-tools 3.2 on 2013-12-19 -->
<plugin>
<name>Enhance Plugin of the Hibernate project for use with Maven build system.</name>
<description></description>
<name>Hibernate Enhance Maven Plugin</name>
<description>Enhance Plugin of the Hibernate project for use with Maven build system.</description>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>@version@</version>
@ -29,7 +25,7 @@
<phase>compile</phase>
<executePhase>compile</executePhase>
<executeGoal>enhance</executeGoal>
<implementation>org.hibernate.bytecode.enhance.plugins.MavenEnhancePlugin</implementation>
<implementation>org.hibernate.orm.tooling.maven.MavenEnhancePlugin</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
@ -40,11 +36,35 @@
<type>java.lang.String</type>
<required>false</required>
<editable>true</editable>
<description></description>
<description>Base directory where to search for .class files</description>
</parameter>
<parameter>
<name>enableLazyInitialization</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for lazy loading of attributes</description>
</parameter>
<parameter>
<name>enableDirtyTracking</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for tracking of dirty attributes</description>
</parameter>
<parameter>
<name>enableAssociationManagement</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for management of bi-direction associations</description>
</parameter>
</parameters>
<configuration>
<dir implementation="java.lang.String" default-value="${project.build.outputDirectory}">${dir}</dir>
<dir>${project.build.outputDirectory}</dir>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
</mojo>
</mojos>

View File

@ -1,17 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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>.
-->
<!-- Generated by maven-plugin-tools 3.1 on 2013-12-19 -->
<plugin>
<name>Enhance Plugin of the Hibernate project for use with Maven build system.</name>
<description></description>
<name>Hibernate Enhance Maven Plugin</name>
<description>Enhance Plugin of the Hibernate project for use with Maven build system.</description>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>@version@</version>

View File

@ -1,17 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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>.
-->
<!-- Generated by maven-plugin-tools 3.2 on 2013-12-19 -->
<plugin>
<name>Enhance Plugin of the Hibernate project for use with Maven build system.</name>
<description></description>
<name>Hibernate Enhance Maven Plugin</name>
<description>Enhance Plugin of the Hibernate project for use with Maven build system.</description>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>@version@</version>
@ -31,7 +27,7 @@
<phase>compile</phase>
<executePhase>compile</executePhase>
<executeGoal>enhance</executeGoal>
<implementation>org.hibernate.bytecode.enhance.plugins.MavenEnhancePlugin</implementation>
<implementation>org.hibernate.orm.tooling.maven.MavenEnhancePlugin</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
@ -42,11 +38,35 @@
<type>java.lang.String</type>
<required>false</required>
<editable>true</editable>
<description></description>
<description>Base directory where to search for .class files</description>
</parameter>
<parameter>
<name>enableLazyInitialization</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for lazy loading of attributes</description>
</parameter>
<parameter>
<name>enableDirtyTracking</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for tracking of dirty attributes</description>
</parameter>
<parameter>
<name>enableAssociationManagement</name>
<type>java.lang.Boolean</type>
<required>false</required>
<editable>true</editable>
<description>Enable enhancement for management of bi-direction associations</description>
</parameter>
</parameters>
<configuration>
<dir implementation="java.lang.String" default-value="${project.build.outputDirectory}">${dir}</dir>
<dir>${project.build.outputDirectory}</dir>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
</mojo>
</mojos>