Skip to content

Commit

Permalink
Add build-time configuration property quarkus.hibernate-envers.active
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere authored and miador committed Sep 6, 2022
1 parent 8b7d549 commit 28d3225
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package io.quarkus.hibernate.envers.deployment;

import java.util.List;
import java.util.Set;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.hibernate.envers.HibernateEnversBuildTimeConfig;
import io.quarkus.hibernate.envers.HibernateEnversRecorder;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationStaticConfiguredBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;

@BuildSteps(onlyIfNot = HibernateEnversEnabled.class)
public final class HibernateEnversDisabledProcessor {

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
public void disableHibernateEnvers(HibernateEnversRecorder recorder,
public void disableHibernateEnversStaticInit(HibernateEnversRecorder recorder,
HibernateEnversBuildTimeConfig buildTimeConfig,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems,
BuildProducer<HibernateOrmIntegrationStaticConfiguredBuildItem> integrationProducer) {
checkNoExplicitActiveTrue(buildTimeConfig);
for (PersistenceUnitDescriptorBuildItem puDescriptor : persistenceUnitDescriptorBuildItems) {
integrationProducer.produce(
new HibernateOrmIntegrationStaticConfiguredBuildItem(HibernateEnversProcessor.HIBERNATE_ENVERS,
Expand All @@ -28,4 +33,28 @@ public void disableHibernateEnvers(HibernateEnversRecorder recorder,
.setXmlMappingRequired(false));
}
}

// TODO move this to runtime init once we implement in Hibernate ORM a way
// to remove entity types from the metamodel on runtime init
public void checkNoExplicitActiveTrue(HibernateEnversBuildTimeConfig buildTimeConfig) {
for (var entry : buildTimeConfig.getAllPersistenceUnitConfigsAsMap().entrySet()) {
var config = entry.getValue();
if (config.active.isPresent() && config.active.get()) {
var puName = entry.getKey();
String enabledPropertyKey = HibernateEnversBuildTimeConfig.extensionPropertyKey("enabled");
String activePropertyKey = HibernateEnversBuildTimeConfig.persistenceUnitPropertyKey(puName, "active");
throw new ConfigurationException(
"Hibernate Envers activated explicitly for persistence unit '" + puName
+ "', but the Hibernate Envers extension was disabled at build time."
+ " If you want Hibernate Envers to be active for this persistence unit, you must set '"
+ enabledPropertyKey
+ "' to 'true' at build time."
+ " If you don't want Hibernate Envers to be active for this persistence unit, you must leave '"
+ activePropertyKey
+ "' unset or set it to 'false'.",
Set.of(enabledPropertyKey, activePropertyKey));
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ public void registerEnversReflections(BuildProducer<ReflectiveClassBuildItem> re

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
public void applyConfig(HibernateEnversRecorder recorder, HibernateEnversBuildTimeConfig buildTimeConfig,
public void applyStaticConfig(HibernateEnversRecorder recorder, HibernateEnversBuildTimeConfig buildTimeConfig,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems,
BuildProducer<HibernateOrmIntegrationStaticConfiguredBuildItem> integrationProducer) {
for (PersistenceUnitDescriptorBuildItem puDescriptor : persistenceUnitDescriptorBuildItems) {
String puName = puDescriptor.getPersistenceUnitName();
integrationProducer.produce(
new HibernateOrmIntegrationStaticConfiguredBuildItem(HIBERNATE_ENVERS,
puDescriptor.getPersistenceUnitName())
.setInitListener(recorder.createStaticInitListener(buildTimeConfig))
new HibernateOrmIntegrationStaticConfiguredBuildItem(HIBERNATE_ENVERS, puName)
.setInitListener(recorder.createStaticInitListener(buildTimeConfig, puName))
.setXmlMappingRequired(true));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.hibernate.orm.envers.config;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import javax.inject.Inject;
import javax.persistence.metamodel.Bindable;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.envers.AuditReaderFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.envers.MyAuditedEntity;
import io.quarkus.test.QuarkusUnitTest;

public class ConfigActiveFalseAndAuditedEntityTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClass(MyAuditedEntity.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.hibernate-envers.active", "false");

@Inject
SessionFactory sessionFactory;

@Test
public void test() {
assertThat(sessionFactory.getMetamodel().getEntities())
.extracting(Bindable::getBindableJavaType)
// In particular this should not contain the revision entity
.containsExactlyInAnyOrder((Class) MyAuditedEntity.class);

try (Session session = sessionFactory.openSession()) {
assertThatThrownBy(() -> AuditReaderFactory.get(session).isEntityClassAudited(MyAuditedEntity.class))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Service is not yet initialized");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.hibernate.orm.envers.config;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import javax.inject.Inject;
import javax.persistence.metamodel.Bindable;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.envers.AuditReaderFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.hibernate.orm.envers.MyAuditedEntity;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;

public class ConfigEnabledFalseAndActiveTrueTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClass(MyAuditedEntity.class))
.withConfigurationResource("application.properties")
.overrideConfigKey("quarkus.hibernate-envers.enabled", "false")
.overrideConfigKey("quarkus.hibernate-envers.active", "true")
.assertException(throwable -> assertThat(throwable)
.isInstanceOf(ConfigurationException.class)
.hasMessageContaining(
"Hibernate Envers activated explicitly for persistence unit '<default>', but the Hibernate Envers extension was disabled at build time",
"If you want Hibernate Envers to be active for this persistence unit, you must set 'quarkus.hibernate-envers.enabled' to 'true' at build time",
"If you don't want Hibernate Envers to be active for this persistence unit, you must leave 'quarkus.hibernate-envers.active' unset or set it to 'false'"));

@Inject
SessionFactory sessionFactory;

@Test
public void test() {
assertThat(sessionFactory.getMetamodel().getEntities())
.extracting(Bindable::getBindableJavaType)
// In particular this should not contain the revision entity
.containsExactlyInAnyOrder((Class) MyAuditedEntity.class);

try (Session session = sessionFactory.openSession()) {
assertThatThrownBy(() -> AuditReaderFactory.get(session).isEntityClassAudited(MyAuditedEntity.class))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Service is not yet initialized");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void test() {
assertThat(sessionFactory.getMetamodel().getEntities())
.extracting(Bindable::getBindableJavaType)
// In particular this should not contain the revision entity
.containsExactly((Class) MyAuditedEntity.class);
.containsExactlyInAnyOrder((Class) MyAuditedEntity.class);

try (Session session = sessionFactory.openSession()) {
assertThatThrownBy(() -> AuditReaderFactory.get(session).isEntityClassAudited(MyAuditedEntity.class))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.quarkus.hibernate.envers;

import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
Expand All @@ -14,13 +19,37 @@ public class HibernateEnversBuildTimeConfig {
* If Hibernate Envers is disabled during the build, all processing related to Hibernate Envers will be skipped,
* and the audit entities will not be added to the Hibernate ORM metamodel
* nor to the database schema that Hibernate ORM generates,
* but it will not be possible to use Hibernate Envers at runtime.
* but it will not be possible to use Hibernate Envers at runtime:
* `quarkus.hibernate-envers.active` will default to `false` and setting it to `true` will lead to an error.
*
* @asciidoclet
*/
@ConfigItem(defaultValue = "true")
public boolean enabled;

/**
* Configuration for the default persistence unit.
*/
@ConfigItem(name = ConfigItem.PARENT)
public HibernateEnversBuildTimeConfigPersistenceUnit defaultPersistenceUnit;

/**
* Configuration for additional named persistence units.
*/
@ConfigDocSection
@ConfigDocMapKey("persistence-unit-name")
@ConfigItem(name = ConfigItem.PARENT)
public Map<String, HibernateEnversBuildTimeConfigPersistenceUnit> persistenceUnits;

public Map<String, HibernateEnversBuildTimeConfigPersistenceUnit> getAllPersistenceUnitConfigsAsMap() {
Map<String, HibernateEnversBuildTimeConfigPersistenceUnit> map = new TreeMap<>();
if (defaultPersistenceUnit != null) {
map.put(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME, defaultPersistenceUnit);
}
map.putAll(persistenceUnits);
return map;
}

/**
* Enable store_data_at_delete feature.
* Maps to {@link org.hibernate.envers.configuration.EnversSettings#STORE_DATA_AT_DELETE}.
Expand Down Expand Up @@ -177,4 +206,17 @@ public class HibernateEnversBuildTimeConfig {
*/
@ConfigItem(defaultValue = "org.hibernate.envers.boot.internal.LegacyModifiedColumnNamingStrategy")
public Optional<String> modifiedColumnNamingStrategy;

public static String extensionPropertyKey(String radical) {
return "quarkus.hibernate-envers." + radical;
}

public static String persistenceUnitPropertyKey(String persistenceUnitName, String radical) {
StringBuilder keyBuilder = new StringBuilder("quarkus.hibernate-envers.");
if (!PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) {
keyBuilder.append("\"").append(persistenceUnitName).append("\".");
}
keyBuilder.append(radical);
return keyBuilder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.hibernate.envers;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class HibernateEnversBuildTimeConfigPersistenceUnit {

/**
* Whether Hibernate Envers should be active for this persistence unit at runtime.
*
* If Hibernate Envers is not active, the audit entities will *still* be added to the Hibernate ORM metamodel
* and to the database schema that Hibernate ORM generates:
* you would need to disable Hibernate Envers at build time (i.e. set `quarkus.hibernate-envers.enabled` to `false`)
* in order to avoid that.
* However, when Hibernate Envers is not active, it will not process entity change events
* nor create new versions of entities.
* and accessing the AuditReader through AuditReaderFactory will not be possible.
*
* Note that if Hibernate Envers is disabled (i.e. `quarkus.hibernate-envers.enabled` is set to `false`),
* it won't be active for any persistence unit, and setting this property to `true` will fail.
*
* @asciidoclet
*/
@ConfigItem(defaultValueDocumentation = "`true` if Hibernate ORM is enabled; `false` otherwise")
public Optional<Boolean> active = Optional.empty();

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,30 @@
@Recorder
public class HibernateEnversRecorder {

public HibernateOrmIntegrationStaticInitListener createStaticInitListener(HibernateEnversBuildTimeConfig buildTimeConfig) {
return new HibernateEnversIntegrationStaticInitListener(buildTimeConfig);
public HibernateOrmIntegrationStaticInitListener createStaticInitListener(HibernateEnversBuildTimeConfig buildTimeConfig,
String puName) {
return new HibernateEnversIntegrationStaticInitListener(buildTimeConfig, puName);
}

private static final class HibernateEnversIntegrationStaticInitListener
implements HibernateOrmIntegrationStaticInitListener {
private HibernateEnversBuildTimeConfig buildTimeConfig;
private final HibernateEnversBuildTimeConfig buildTimeConfig;
private final String puName;

private HibernateEnversIntegrationStaticInitListener(HibernateEnversBuildTimeConfig buildTimeConfig) {
private HibernateEnversIntegrationStaticInitListener(HibernateEnversBuildTimeConfig buildTimeConfig, String puName) {
this.buildTimeConfig = buildTimeConfig;
this.puName = puName;
}

@Override
public void contributeBootProperties(BiConsumer<String, Object> propertyCollector) {
var puConfig = buildTimeConfig.getAllPersistenceUnitConfigsAsMap().get(puName);
if (puConfig != null && puConfig.active.isPresent() && !puConfig.active.get()) {
propertyCollector.accept(EnversService.INTEGRATION_ENABLED, "false");
// Do not process other properties: Hibernate Envers is inactive anyway.
return;
}

addConfig(propertyCollector, EnversSettings.STORE_DATA_AT_DELETE, buildTimeConfig.storeDataAtDelete);
addConfig(propertyCollector, EnversSettings.AUDIT_TABLE_SUFFIX, buildTimeConfig.auditTableSuffix);
addConfig(propertyCollector, EnversSettings.AUDIT_TABLE_PREFIX, buildTimeConfig.auditTablePrefix);
Expand Down

0 comments on commit 28d3225

Please sign in to comment.