From c2ca2193c936500f7bea808854c47711c7da4d03 Mon Sep 17 00:00:00 2001 From: Jeremy Whiting Date: Wed, 26 Jun 2013 08:45:14 +0100 Subject: [PATCH] HHH-8306 Added Gradle plugin to provide build time support for enhancing entity objects. --- hibernate-gradle-plugin/build.gradle | 47 ++++++++ .../enhance/plugins/EnhancePlugin.java | 66 +++++++++++ .../plugins/EnhancePluginConvention.groovy | 46 ++++++++ .../enhance/plugins/EnhanceTask.groovy | 110 ++++++++++++++++++ .../contexts/AssociationReference.java | 57 +++++++++ .../enhance/contexts/BaseContext.java | 78 +++++++++++++ .../gradle-plugins/enhance.properties | 1 + 7 files changed, 405 insertions(+) create mode 100644 hibernate-gradle-plugin/build.gradle create mode 100644 hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePlugin.java create mode 100644 hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePluginConvention.groovy create mode 100644 hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhanceTask.groovy create mode 100644 hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/AssociationReference.java create mode 100644 hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/BaseContext.java create mode 100644 hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/enhance.properties diff --git a/hibernate-gradle-plugin/build.gradle b/hibernate-gradle-plugin/build.gradle new file mode 100644 index 000000000000..cb180bd5a34e --- /dev/null +++ b/hibernate-gradle-plugin/build.gradle @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +apply plugin: 'groovy' +apply plugin: 'maven' +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + compile group: 'org.hibernate', name: 'hibernate-core', version: '4.2.2.Final' + compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: '1.0.0.Draft-16' + compile group: 'org.javassist', name: 'javassist', version: '3.15.0-GA' + compile gradleApi() + compile localGroovy() +} + +/* Available for testing locally. */ +install { + repositories.mavenInstaller { + pom.groupId = 'org.hibernate' + pom.artifactId = 'hibernate-gradle-plugin' + pom.version = '0.1' + } +} diff --git a/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePlugin.java b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePlugin.java new file mode 100644 index 000000000000..1bbd0724e654 --- /dev/null +++ b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePlugin.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.plugins; + +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.JavaBasePlugin; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.hibernate.bytecode.enhance.plugins.EnhanceTask; +import org.hibernate.bytecode.enhance.plugins.EnhancePluginConvention; + +/** + * This plugin will add Entity enhancement behaviour to the build lifecycle. + * + * @author Jeremy Whiting + */ +public class EnhancePlugin implements Plugin { + + public static final String ENHANCE_TASK_NAME = "enhance"; + public static final String PLUGIN_CONVENTION_NAME = "enhance"; + public static final String HAPPENS_BEFORE_ENHANCE_TASK_NAME = JavaPlugin.CLASSES_TASK_NAME; + public static final String HAPPENS_AFTER_ENHANCE_TASK_NAME = JavaPlugin.JAR_TASK_NAME; + + public void apply(Project project) { + project.getLogger().debug( "Applying enhance plugin to project." ); + project.getConvention().getPlugins().put( PLUGIN_CONVENTION_NAME, new EnhancePluginConvention() ); + configureTask( project ); + project.getLogger().debug( String.format( "DAG has been configured with enhance task dependent on [%s].", HAPPENS_BEFORE_ENHANCE_TASK_NAME ) ); + } + + private void configureTask(Project project) { + EnhanceTask enhanceTask = project.getTasks().create( ENHANCE_TASK_NAME, EnhanceTask.class ); + // connect up the task in the task dependency graph + final Configuration config = enhanceTask.getProject().getConfigurations().getByName( JavaPlugin.COMPILE_CONFIGURATION_NAME ); + Task classesTask = project.getTasks().getByName( JavaPlugin.CLASSES_TASK_NAME ); + enhanceTask.dependsOn( classesTask ); + enhanceTask.mustRunAfter( HAPPENS_BEFORE_ENHANCE_TASK_NAME ); + + Task jarTask = project.getTasks().getByName( HAPPENS_AFTER_ENHANCE_TASK_NAME ); + jarTask.dependsOn( enhanceTask ); + } +} diff --git a/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePluginConvention.groovy b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePluginConvention.groovy new file mode 100644 index 000000000000..7c29ede44a8b --- /dev/null +++ b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhancePluginConvention.groovy @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.plugins + +/** +* Convention object for plugin. +*/ +class EnhancePluginConvention { + + def contexts = [] + + public EnhancePluginConvention() { + contexts.add('org.hibernate.bytecode.enhance.contexts.AssociationReference') + } + + def add(final String fullyQualifiedClassName) { + contexts.add(fullyQualifiedClassName); + } + List getContexts() { + return contexts + } + def clear() { + contexts.clear(); + } +} diff --git a/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhanceTask.groovy b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhanceTask.groovy new file mode 100644 index 000000000000..5ba698fa4349 --- /dev/null +++ b/hibernate-gradle-plugin/src/main/groovy/org/hibernate/bytecode/enhance/plugins/EnhanceTask.groovy @@ -0,0 +1,110 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.plugins + +import org.hibernate.bytecode.enhance.spi.Enhancer +import javassist.ClassPool +import javassist.CtClass +import javassist.CtField +import javax.persistence.Transient +import javax.persistence.Entity +import java.io.FileInputStream +import java.io.FileOutputStream +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.Convention +import org.gradle.api.tasks.TaskAction +import org.gradle.api.file.FileTree + +/** +* Plugin to enhance Entities using the context(s) to determine +* which to apply. +* @author Jeremy Whiting +*/ +public class EnhanceTask extends DefaultTask { + + public EnhanceTask() { + super() + setDescription('Enhances Entity classes for efficient association referencing.') + } + + @TaskAction + def enhance () { + logger.info( 'enhance task started') + ext.pool = new ClassPool(false) + EnhancePluginConvention convention = (EnhancePluginConvention)project.getConvention().getPlugins().get( EnhancePlugin.PLUGIN_CONVENTION_NAME) + convention.contexts.each( + { String context -> + ext.enhancer = new Enhancer(EnhanceTask.class.getClassLoader().loadClass(context).newInstance()) + FileTree tree = project.fileTree(dir: project.sourceSets.main.output.classesDir) + tree.include '**/*.class' + tree.each( + { File file -> + final byte[] enhancedBytecode; + InputStream is = null; + CtClass clas = null; + try { + is = new FileInputStream(file.toString()) + clas =ext.pool.makeClass(is) + if (!clas.hasAnnotation(Entity.class)){ + logger.debug("Class $file not an annotated Entity class. skipping...") + } else { + enhancedBytecode = ext.enhancer.enhance( clas.getName(), clas.toBytecode() ); + } + } + catch (Exception e) { + logger.error( "Unable to enhance class [${file.toString()}]", e ) + return + } finally { + try { + if (null != is) is.close(); + } finally{} + } + if (null != enhancedBytecode){ + if ( file.delete() ) { + if ( ! file.createNewFile() ) { + logger.error( "Unable to recreate class file [" + clas.getName() + "]") + } + } + else { + logger.error( "Unable to delete class file [" + clas.getName() + "]") + } + FileOutputStream outputStream = new FileOutputStream( file, false ) + try { + outputStream.write( enhancedBytecode ) + outputStream.flush() + } + finally { + try { + if (outputStream != null) outputStream.close() + clas.detach()//release memory + } + catch ( IOException ignore) { + } + } + } + }) + }) + logger.info( 'enhance task finished') + } +} diff --git a/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/AssociationReference.java b/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/AssociationReference.java new file mode 100644 index 000000000000..2668145c303e --- /dev/null +++ b/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/AssociationReference.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.contexts; + +import javassist.CtClass; +import javassist.CtField; + +public class AssociationReference extends BaseContext { + + private ClassLoader overridden; + + public ClassLoader getLoadingClassLoader() { + if ( null == this.overridden ) { + return getClass().getClassLoader(); + } + else { + return this.overridden; + } + } + + public void setClassLoader(ClassLoader loader) { + this.overridden = loader; + } + + public boolean isEntityClass(CtClass classDescriptor) { + return true; + } + + public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { + return true; + } + + public boolean isLazyLoadable(CtField field) { + return true; + } +} diff --git a/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/BaseContext.java b/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/BaseContext.java new file mode 100644 index 000000000000..8252fb8af889 --- /dev/null +++ b/hibernate-gradle-plugin/src/main/java/org/hibernate/bytecode/enhance/contexts/BaseContext.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.bytecode.enhance.contexts; + +import java.beans.Transient; + +import javassist.CtClass; +import javassist.CtField; + +import org.hibernate.bytecode.enhance.spi.EnhancementContext; + +abstract public class BaseContext implements EnhancementContext { + + private ClassLoader overridden; + + public ClassLoader getLoadingClassLoader() { + if ( null == this.overridden ) { + return getClass().getClassLoader(); + } + else { + return this.overridden; + } + } + + public void setClassLoader(ClassLoader loader) { + this.overridden = loader; + } + + public boolean isEntityClass(CtClass classDescriptor) { + return false; + } + + public boolean isCompositeClass(CtClass classDescriptor) { + return false; + } + + public boolean doDirtyCheckingInline(CtClass classDescriptor) { + return false; + } + + public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { + return false; + } + + public boolean isLazyLoadable(CtField field) { + return false; + } + + public CtField[] order(CtField[] fields) { + // TODO: load ordering from configuration. + return fields; + } + + public boolean isPersistentField(CtField ctField) { + return !ctField.hasAnnotation( Transient.class ); + } +} diff --git a/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/enhance.properties b/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/enhance.properties new file mode 100644 index 000000000000..33985e39dab8 --- /dev/null +++ b/hibernate-gradle-plugin/src/main/resources/META-INF/gradle-plugins/enhance.properties @@ -0,0 +1 @@ +implementation-class=org.hibernate.bytecode.enhance.plugins.EnhancePlugin