Skip to content

Commit

Permalink
HSEARCH-4783 Clean up the database schema in tests involving ORM boot…
Browse files Browse the repository at this point in the history
…strap failures

The ORM bootstrap does not *always* clean up the schema on failure, in
particular if that failure happens in an observer.

Useful in particular in
org.hibernate.search.integrationtest.mapper.orm.bootstrap.BootstrapFailureIT,
which used to leave tables in the database schema,
which caused trouble when running tests against MariaDB/MySQL.
  • Loading branch information
yrodiere committed Jan 27, 2023
1 parent 1436147 commit 6e38423
Showing 1 changed file with 79 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
package org.hibernate.search.util.impl.integrationtest.mapper.orm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.hibernate.SessionFactory;
Expand All @@ -19,22 +21,30 @@
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.classloading.internal.TcclLookupPrecedence;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.search.util.common.annotation.impl.SuppressForbiddenApis;
import org.hibernate.search.util.common.impl.CollectionHelper;
import org.hibernate.search.util.common.impl.SuppressingCloser;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator;

import org.jboss.logging.Logger;

public final class SimpleSessionFactoryBuilder {
private static final Logger log = Logger.getLogger( SimpleSessionFactoryBuilder.class.getName() );

private boolean cleanUpSchemaOnBuildFailure = true;
private final List<Consumer<BootstrapServiceRegistryBuilder>> bootstrapServiceRegistryBuilderContributors = new ArrayList<>();
private final List<Consumer<StandardServiceRegistryBuilder>> serviceRegistryBuilderContributors = new ArrayList<>();
private final List<Consumer<MetadataSources>> metadataSourcesContributors = new ArrayList<>();
private final List<Consumer<MetadataImplementor>> metadataContributors = new ArrayList<>();
private final List<Consumer<SessionFactoryBuilder>> sessionFactoryBuilderContributors = new ArrayList<>();

public void setCleanUpSchemaOnBuildFailure(boolean cleanUpSchemaOnBuildFailure) {
this.cleanUpSchemaOnBuildFailure = cleanUpSchemaOnBuildFailure;
}

@SuppressForbiddenApis(reason = "Strangely, this API involves the internal TcclLookupPrecedence class,"
+ " and there's nothing we can do about it")
public SimpleSessionFactoryBuilder setTcclLookupPrecedenceBefore() {
Expand Down Expand Up @@ -97,37 +107,86 @@ public SimpleSessionFactoryBuilder onSessionFactoryBuilder(Consumer<SessionFacto
return this;
}

public SessionFactory build() {
BootstrapServiceRegistry bootstrapServiceRegistry = null;
ServiceRegistry serviceRegistry = null;
MetadataSources metadataSources;
SessionFactoryBuilder sessionFactoryBuilder;
private void doFirstPhase(BuildState state) {
BootstrapServiceRegistryBuilder bootstrapServiceRegistryBuilder = new BootstrapServiceRegistryBuilder();
bootstrapServiceRegistryBuilderContributors.forEach( c -> c.accept( bootstrapServiceRegistryBuilder ) );
state.bootstrapServiceRegistry = bootstrapServiceRegistryBuilder.build();

try {
BootstrapServiceRegistryBuilder bootstrapServiceRegistryBuilder = new BootstrapServiceRegistryBuilder();
bootstrapServiceRegistryBuilderContributors.forEach( c -> c.accept( bootstrapServiceRegistryBuilder ) );
bootstrapServiceRegistry = bootstrapServiceRegistryBuilder.build();
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder( state.bootstrapServiceRegistry );
serviceRegistryBuilderContributors.forEach( c -> c.accept( registryBuilder ) );
state.serviceRegistry = registryBuilder.build();

StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder( bootstrapServiceRegistry );
serviceRegistryBuilderContributors.forEach( c -> c.accept( registryBuilder ) );
serviceRegistry = registryBuilder.build();
MetadataSources metadataSources = new MetadataSources( state.serviceRegistry );
metadataSourcesContributors.forEach( c -> c.accept( metadataSources ) );
state.metadata = metadataSources.buildMetadata();

metadataSources = new MetadataSources( serviceRegistry );
metadataSourcesContributors.forEach( c -> c.accept( metadataSources ) );
Metadata metadata = metadataSources.buildMetadata();
metadataContributors.forEach( c -> c.accept( (MetadataImplementor) state.metadata ) );
}

metadataContributors.forEach( c -> c.accept( (MetadataImplementor) metadata ) );
public SessionFactory build() {
BuildState state = new BuildState();
try {
doFirstPhase( state );

sessionFactoryBuilder = metadata.getSessionFactoryBuilder();
SessionFactoryBuilder sessionFactoryBuilder = state.metadata.getSessionFactoryBuilder();
sessionFactoryBuilderContributors.forEach( c -> c.accept( sessionFactoryBuilder ) );

return sessionFactoryBuilder.build();
try {
return sessionFactoryBuilder.build();
}
catch (RuntimeException | AssertionError e) {
// Schema creation happens on session factory building,
// so we only need to do this if we fail during session factory building.
if ( cleanUpSchemaOnBuildFailure ) {
new SuppressingCloser( e )
.push( this::cleanUpSchema );
}
throw e;
}
}
catch (RuntimeException e) {
state.closeOnFailure( e );
throw e;
}
}

// A failure to build the session factory triggers closing the service registry,
// which pretty much prevents doing *anything*, including dropping the schema.
// So... we'll need to restart part of the build.
private void cleanUpSchema() {
BuildState state = new BuildState();
try {
doFirstPhase( state );

@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, Object> settingsSnapshot = new HashMap<>(
( state.serviceRegistry.getService( ConfigurationService.class ) ).getSettings() );
settingsSnapshot.put( AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, "drop" );
settingsSnapshot.put( AvailableSettings.JAKARTA_HBM2DDL_SCRIPTS_ACTION, "none" );
SchemaManagementToolCoordinator.process(
state.metadata,
state.serviceRegistry,
settingsSnapshot,
action -> {
throw new IllegalStateException( "No delayed action was expected" );
}
);
}
catch (RuntimeException e) {
new SuppressingCloser( e )
state.closeOnFailure( e );
throw e;
}
}

static class BuildState {
BootstrapServiceRegistry bootstrapServiceRegistry;
ServiceRegistry serviceRegistry;
Metadata metadata;

void closeOnFailure(Throwable t) {
new SuppressingCloser( t )
.push( bootstrapServiceRegistry )
.push( serviceRegistry );
throw e;
}
}

Expand Down

0 comments on commit 6e38423

Please sign in to comment.