HHH-16529 - Verify the version used for enhancement against the version being used
https://hibernate.atlassian.net/browse/HHH-16529
This commit is contained in:
parent
ab4ac5a64e
commit
c5ccae1b90
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
||||
/**
|
||||
* Indicates that the version of Hibernate used to enhance
|
||||
* a class is different from the version being used at runtime.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class VersionMismatchException extends EnhancementException {
|
||||
private final String typeName;
|
||||
private final String enhancementVersion;
|
||||
private final String runtimeVersion;
|
||||
|
||||
public VersionMismatchException(
|
||||
TypeDescription typeDescription,
|
||||
String enhancementVersion,
|
||||
String runtimeVersion) {
|
||||
super(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Mismatch between Hibernate version used for bytecode enhancement (%s) and runtime (%s) for `%s`",
|
||||
enhancementVersion,
|
||||
runtimeVersion,
|
||||
typeDescription.getName()
|
||||
)
|
||||
);
|
||||
|
||||
this.typeName = typeDescription.getName();
|
||||
this.enhancementVersion = enhancementVersion;
|
||||
this.runtimeVersion = runtimeVersion;
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public String getEnhancementVersion() {
|
||||
return enhancementVersion;
|
||||
}
|
||||
|
||||
public String getRuntimeVersion() {
|
||||
return runtimeVersion;
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.bytebuddy;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -18,11 +16,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Transient;
|
||||
|
||||
import org.hibernate.Version;
|
||||
import org.hibernate.bytecode.enhance.VersionMismatchException;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
|
@ -48,6 +43,9 @@ import org.hibernate.engine.spi.SelfDirtinessTracker;
|
|||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Transient;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.annotation.AnnotationList;
|
||||
|
@ -67,6 +65,8 @@ import net.bytebuddy.implementation.Implementation;
|
|||
import net.bytebuddy.implementation.StubMethod;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
|
||||
|
||||
public class EnhancerImpl implements Enhancer {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
|
||||
|
@ -153,6 +153,9 @@ public class EnhancerImpl implements Enhancer {
|
|||
typeDescription
|
||||
) );
|
||||
}
|
||||
catch (EnhancementException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw new EnhancementException( "Failed to enhance class " + className, e );
|
||||
}
|
||||
|
@ -168,13 +171,17 @@ public class EnhancerImpl implements Enhancer {
|
|||
log.debugf( "Skipping enhancement of [%s]: it's an interface", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
|
||||
// can't effectively enhance records
|
||||
if ( managedCtClass.isRecord() ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: it's a record", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
// skip already enhanced classes
|
||||
|
||||
// handle already enhanced classes
|
||||
if ( alreadyEnhanced( managedCtClass ) ) {
|
||||
verifyVersions( managedCtClass, enhancementContext );
|
||||
|
||||
log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
|
@ -380,6 +387,31 @@ public class EnhancerImpl implements Enhancer {
|
|||
}
|
||||
}
|
||||
|
||||
private static void verifyVersions(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext) {
|
||||
final AnnotationDescription.Loadable<EnhancementInfo> existingInfo = managedCtClass
|
||||
.getDeclaredAnnotations()
|
||||
.ofType( EnhancementInfo.class );
|
||||
if ( existingInfo == null ) {
|
||||
// There is an edge case here where a user manually adds `implement Managed` to
|
||||
// their domain class, in which case there will most likely not be a
|
||||
// `EnhancementInfo` annotation. Such cases should simply not do version checking.
|
||||
//
|
||||
// However, there is also ambiguity in this case with classes that were enhanced
|
||||
// with old versions of Hibernate which did not add that annotation as part of
|
||||
// enhancement. But overall we consider this condition to be acceptable
|
||||
return;
|
||||
}
|
||||
|
||||
final String enhancementVersion = extractVersion( existingInfo );
|
||||
if ( !Version.getVersionString().equals( enhancementVersion ) ) {
|
||||
throw new VersionMismatchException( managedCtClass, enhancementVersion, Version.getVersionString() );
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractVersion(AnnotationDescription.Loadable<EnhancementInfo> annotation) {
|
||||
return annotation.load().version();
|
||||
}
|
||||
|
||||
private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) {
|
||||
return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool );
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ public final class ByteBuddyState {
|
|||
private final TypeCache<TypeCache.SimpleKey> proxyCache;
|
||||
private final TypeCache<TypeCache.SimpleKey> basicProxyCache;
|
||||
|
||||
ByteBuddyState() {
|
||||
public ByteBuddyState() {
|
||||
this( ClassFileVersion.ofThisVm( ClassFileVersion.JAVA_V11 ) );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.test.bytecode.enhance.version;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementInfo;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity(name = "SimpleEntity")
|
||||
@Table(name = "SimpleEntity")
|
||||
@EnhancementInfo(version = "5.3.0.Final")
|
||||
public class SimpleEntity implements ManagedEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public Object $$_hibernate_getEntityInstance() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityEntry $$_hibernate_getEntityEntry() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedEntity $$_hibernate_getNextManagedEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.test.bytecode.enhance.version;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.hibernate.bytecode.enhance.VersionMismatchException;
|
||||
import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl;
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState;
|
||||
import org.hibernate.bytecode.spi.ByteCodeHelper;
|
||||
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Jira( "https://hibernate.atlassian.net/browse/HHH-16529" )
|
||||
public class VersionMismatchTests {
|
||||
@Test
|
||||
void testVersionMismatch() throws IOException {
|
||||
final DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext();
|
||||
final ByteBuddyState byteBuddyState = new ByteBuddyState();
|
||||
final Enhancer enhancer = new EnhancerImpl( enhancementContext, byteBuddyState );
|
||||
|
||||
final String classFile = SimpleEntity.class.getName().replace( '.', '/' ) + ".class";
|
||||
try (InputStream classFileStream = SimpleEntity.class.getClassLoader().getResourceAsStream( classFile )) {
|
||||
final byte[] originalBytes = ByteCodeHelper.readByteCode( classFileStream );
|
||||
enhancer.enhance( SimpleEntity.class.getName(), originalBytes );
|
||||
fail( "Expecting failure" );
|
||||
}
|
||||
catch (VersionMismatchException expected) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue