Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public void processCreateUpdateDelete() {
// that's because we don't trust events to tell us whether the document already exists in the index.
// We don't trust the events for this because they can arrive in the wrong order
// (and that's the case here).
backendMock.expectWorks( OutboxPollingAutomaticIndexingLifecycleIT.IndexedEntity.NAME )
backendMock.expectWorks( IndexedEntity.INDEX )
.delete( "1" );

// The events were hidden until now, to ensure they were not processed in separate batches.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Hibernate Search, full-text search for your domain model
*
* 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.search.integrationtest.mapper.orm.coordination.outboxpolling.automaticindexing;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.Id;

import org.hibernate.SessionFactory;
import org.hibernate.boot.MappingException;
import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.cluster.impl.OutboxPollingAgentAdditionalJaxbMappingProducer;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.event.impl.OutboxPollingOutboxEventAdditionalJaxbMappingProducer;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.util.impl.integrationtest.common.rule.BackendMock;
import org.hibernate.search.util.impl.integrationtest.mapper.orm.CoordinationStrategyExpectations;
import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper;
import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils;

import org.junit.Rule;
import org.junit.Test;

public class OutboxPollingCustomEntityMappingIT {

private static final String ORIGINAL_OUTBOX_EVENT_TABLE_NAME = OutboxPollingOutboxEventAdditionalJaxbMappingProducer.TABLE_NAME;
private static final String CUSTOM_OUTBOX_EVENT_TABLE_NAME = "CUSTOM_OUTBOX_EVENT";
private static final String ORIGINAL_OUTBOX_EVENT_GENERATOR_NAME = ORIGINAL_OUTBOX_EVENT_TABLE_NAME + "_GENERATOR";
private static final String CUSTOM_OUTBOX_EVENT_GENERATOR_NAME = CUSTOM_OUTBOX_EVENT_TABLE_NAME + "_GENERATOR";

private static final String ORIGINAL_AGENT_TABLE_NAME = OutboxPollingAgentAdditionalJaxbMappingProducer.TABLE_NAME;
private static final String CUSTOM_AGENT_TABLE_NAME = "CUSTOM_AGENT";
private static final String ORIGINAL_AGENT_GENERATOR_NAME = ORIGINAL_AGENT_TABLE_NAME + "_GENERATOR";
private static final String CUSTOM_AGENT_GENERATOR_NAME = CUSTOM_AGENT_TABLE_NAME + "_GENERATOR";

private static final String VALID_OUTBOX_EVENT_MAPPING;
private static final String VALID_AGENT_EVENT_MAPPING;

private static final String[] SQL_KEYS;

static {
VALID_OUTBOX_EVENT_MAPPING = OutboxPollingOutboxEventAdditionalJaxbMappingProducer.ENTITY_DEFINITION
.replace( ORIGINAL_OUTBOX_EVENT_TABLE_NAME, CUSTOM_OUTBOX_EVENT_TABLE_NAME )
.replace( ORIGINAL_OUTBOX_EVENT_GENERATOR_NAME, CUSTOM_OUTBOX_EVENT_GENERATOR_NAME );

VALID_AGENT_EVENT_MAPPING = OutboxPollingAgentAdditionalJaxbMappingProducer.ENTITY_DEFINITION
.replace( ORIGINAL_AGENT_TABLE_NAME, CUSTOM_AGENT_TABLE_NAME )
.replace( ORIGINAL_AGENT_GENERATOR_NAME, CUSTOM_AGENT_GENERATOR_NAME );

SQL_KEYS = new String[] {
ORIGINAL_OUTBOX_EVENT_TABLE_NAME, CUSTOM_OUTBOX_EVENT_TABLE_NAME,
ORIGINAL_OUTBOX_EVENT_GENERATOR_NAME, CUSTOM_OUTBOX_EVENT_GENERATOR_NAME,
ORIGINAL_AGENT_TABLE_NAME, CUSTOM_AGENT_TABLE_NAME,
ORIGINAL_AGENT_GENERATOR_NAME, CUSTOM_AGENT_GENERATOR_NAME
};
}

@Rule
public BackendMock backendMock = new BackendMock();

@Rule
public OrmSetupHelper ormSetupHelper = OrmSetupHelper.withBackendMock( backendMock )
.coordinationStrategy( CoordinationStrategyExpectations.outboxPolling() );

private SessionFactory sessionFactory;

@Test
public void wrongOutboxEventMapping() {
assertThatThrownBy( () -> ormSetupHelper.start()
.withProperty( "hibernate.search.coordination.outboxevent.entity.mapping", "<ciao></ciao>" )
.setup( IndexedEntity.class )
)
.isInstanceOf( MappingException.class )
.hasMessageContainingAll( "Unable to perform unmarshalling", "unexpected element" );
}

@Test
public void wrongAgentMapping() {
assertThatThrownBy( () -> ormSetupHelper.start()
.withProperty( "hibernate.search.coordination.agent.entity.mapping", "<ciao></ciao>" )
.setup( IndexedEntity.class )
)
.isInstanceOf( MappingException.class )
.hasMessageContainingAll( "Unable to perform unmarshalling", "unexpected element" );
}

@Test
public void validOutboxEventMapping() {
MostRecentStatementInspector statementInspector = new MostRecentStatementInspector();

backendMock.expectAnySchema( IndexedEntity.INDEX );
sessionFactory = ormSetupHelper.start()
.withProperty( "hibernate.search.coordination.outboxevent.entity.mapping", VALID_OUTBOX_EVENT_MAPPING )
.withProperty( "hibernate.session_factory.statement_inspector", statementInspector )
.setup( IndexedEntity.class );
backendMock.verifyExpectationsMet();

backendMock.expectWorks( IndexedEntity.INDEX )
.add( "1", f -> f.field( "indexedField", "value for the field" ) );

int id = 1;
OrmUtils.withinTransaction( sessionFactory, session -> {
IndexedEntity entity = new IndexedEntity();
entity.setId( id );
entity.setIndexedField( "value for the field" );
session.persist( entity );
} );

backendMock.verifyExpectationsMet();

assertThat( statementInspector.countByKey( ORIGINAL_OUTBOX_EVENT_TABLE_NAME ) ).isZero();
assertThat( statementInspector.countByKey( CUSTOM_OUTBOX_EVENT_TABLE_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( ORIGINAL_OUTBOX_EVENT_GENERATOR_NAME ) ).isZero();
assertThat( statementInspector.countByKey( CUSTOM_OUTBOX_EVENT_GENERATOR_NAME ) ).isPositive();

assertThat( statementInspector.countByKey( ORIGINAL_AGENT_TABLE_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( CUSTOM_AGENT_TABLE_NAME ) ).isZero();
assertThat( statementInspector.countByKey( ORIGINAL_AGENT_GENERATOR_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( CUSTOM_AGENT_GENERATOR_NAME ) ).isZero();
}

@Test
public void validAgentMapping() {
MostRecentStatementInspector statementInspector = new MostRecentStatementInspector();

backendMock.expectAnySchema( IndexedEntity.INDEX );
sessionFactory = ormSetupHelper.start()
.withProperty( "hibernate.search.coordination.agent.entity.mapping", VALID_AGENT_EVENT_MAPPING )
.withProperty( "hibernate.session_factory.statement_inspector", statementInspector )
.setup( IndexedEntity.class );
backendMock.verifyExpectationsMet();

backendMock.expectWorks( IndexedEntity.INDEX )
.add( "1", f -> f.field( "indexedField", "value for the field" ) );

int id = 1;
OrmUtils.withinTransaction( sessionFactory, session -> {
IndexedEntity entity = new IndexedEntity();
entity.setId( id );
entity.setIndexedField( "value for the field" );
session.persist( entity );
} );

backendMock.verifyExpectationsMet();

assertThat( statementInspector.countByKey( ORIGINAL_OUTBOX_EVENT_TABLE_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( CUSTOM_OUTBOX_EVENT_TABLE_NAME ) ).isZero();
assertThat( statementInspector.countByKey( ORIGINAL_OUTBOX_EVENT_GENERATOR_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( CUSTOM_OUTBOX_EVENT_GENERATOR_NAME ) ).isZero();

assertThat( statementInspector.countByKey( ORIGINAL_AGENT_TABLE_NAME ) ).isZero();
assertThat( statementInspector.countByKey( CUSTOM_AGENT_TABLE_NAME ) ).isPositive();
assertThat( statementInspector.countByKey( ORIGINAL_AGENT_GENERATOR_NAME ) ).isZero();
assertThat( statementInspector.countByKey( CUSTOM_AGENT_GENERATOR_NAME ) ).isPositive();
}

@Entity(name = IndexedEntity.INDEX)
@Indexed(index = IndexedEntity.INDEX)
public static class IndexedEntity {
static final String INDEX = "IndexedEntity";

@Id
private Integer id;

@Basic
@GenericField
private String indexedField;

public IndexedEntity() {
}

public IndexedEntity(Integer id, String indexedField) {
this.id = id;
this.indexedField = indexedField;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getIndexedField() {
return indexedField;
}

public void setIndexedField(String indexedField) {
this.indexedField = indexedField;
}
}

public static class MostRecentStatementInspector implements StatementInspector {

private Map<String, List<String>> sqlByKey = new HashMap<>();

public MostRecentStatementInspector() {
for ( String key : SQL_KEYS ) {
sqlByKey.put( key, new ArrayList<>() );
}
}

@Override
public String inspect(String sql) {
for ( String key : SQL_KEYS ) {
if ( sql.contains( key ) ) {
sqlByKey.get( key ).add( sql );
}
}
return sql;
}

public int countByKey(String key) {
return sqlByKey.get( key ).size();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Hibernate Search, full-text search for your domain model
*
* 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.search.mapper.orm.coordination.outboxpolling.cfg.spi;

import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.cfg.HibernateOrmMapperOutboxPollingSettings;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.cluster.impl.Agent;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.cluster.impl.OutboxPollingAgentAdditionalJaxbMappingProducer;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.event.impl.OutboxEvent;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.event.impl.OutboxPollingOutboxEventAdditionalJaxbMappingProducer;
import org.hibernate.search.util.common.annotation.Incubating;

/**
* SPI-related settings.
*/
@Incubating
public final class HibernateOrmMapperOutboxPollingSpiSettings {

private HibernateOrmMapperOutboxPollingSpiSettings() {
}

/**
* The prefix expected for the key of every Hibernate Search configuration property
* when using the Hibernate ORM mapper.
*/
public static final String PREFIX = HibernateOrmMapperSettings.PREFIX;

/**
* Allows the user to define a specific Hibernate mapping for the {@link OutboxEvent} entity.
* <p>
* Only available when {@link HibernateOrmMapperSettings#COORDINATION_STRATEGY} is
* {@value HibernateOrmMapperOutboxPollingSettings#COORDINATION_STRATEGY_NAME}.
* <p>
* Expects a String value containing the xml expressing the Hibernate mapping for the entity.
* <p>
* The default for this value is {@link OutboxPollingOutboxEventAdditionalJaxbMappingProducer#ENTITY_DEFINITION}
*/
public static final String OUTBOXEVENT_ENTITY_MAPPING = PREFIX + Radicals.OUTBOXEVENT_ENTITY_MAPPING;

/**
* Allows the user to define a specific Hibernate mapping for the {@link Agent} entity.
* <p>
* Only available when {@link HibernateOrmMapperSettings#COORDINATION_STRATEGY} is
* {@value HibernateOrmMapperOutboxPollingSettings#COORDINATION_STRATEGY_NAME}.
* <p>
* Expects a String value containing the xml expressing the Hibernate mapping for the entity.
* <p>
* The default for this value is {@link OutboxPollingAgentAdditionalJaxbMappingProducer#ENTITY_DEFINITION}
*/
public static final String AGENT_ENTITY_MAPPING = PREFIX + Radicals.AGENT_ENTITY_MAPPING;

/**
* Configuration property keys without the {@link #PREFIX prefix}.
*/
public static final class Radicals {

private Radicals() {
}

public static final String COORDINATION_PREFIX = HibernateOrmMapperSettings.Radicals.COORDINATION_PREFIX;
public static final String OUTBOXEVENT_ENTITY_MAPPING = COORDINATION_PREFIX + CoordinationRadicals.OUTBOXEVENT_ENTITY_MAPPING;
public static final String AGENT_ENTITY_MAPPING = COORDINATION_PREFIX + CoordinationRadicals.AGENT_ENTITY_MAPPING;
}

public static final class CoordinationRadicals {

private CoordinationRadicals() {
}

public static final String OUTBOXEVENT_ENTITY_MAPPING = "outboxevent.entity.mapping";
public static final String AGENT_ENTITY_MAPPING = "agent.entity.mapping";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.boot.model.source.internal.hbm.MappingDocument;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.search.engine.cfg.ConfigurationPropertySource;
import org.hibernate.search.engine.cfg.spi.ConfigurationProperty;
import org.hibernate.search.mapper.orm.bootstrap.spi.HibernateSearchOrmMappingProducer;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.cfg.spi.HibernateOrmMapperOutboxPollingSpiSettings;
import org.hibernate.search.mapper.orm.coordination.outboxpolling.logging.impl.Log;
import org.hibernate.search.util.common.annotation.impl.SuppressForbiddenApis;
import org.hibernate.search.util.common.logging.impl.LoggerFactory;

import org.jboss.jandex.IndexView;

@SuppressWarnings("deprecation")
public class OutboxPollingAgentAdditionalJaxbMappingProducer
implements org.hibernate.boot.spi.AdditionalJaxbMappingProducer {
implements HibernateSearchOrmMappingProducer {

private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );

Expand All @@ -37,7 +37,7 @@ public class OutboxPollingAgentAdditionalJaxbMappingProducer
public static final String HSEARCH_PREFIX = "HSEARCH_";

// Must not be longer than 20 characters, so that the generator does not exceed the 30 characters for Oracle11g
private static final String TABLE_NAME = HSEARCH_PREFIX + "AGENT";
public static final String TABLE_NAME = HSEARCH_PREFIX + "AGENT";

private static final String CLASS_NAME = Agent.class.getName();

Expand All @@ -48,7 +48,7 @@ public class OutboxPollingAgentAdditionalJaxbMappingProducer
// because our override actually matches the default for the native entity name.
public static final String ENTITY_NAME = CLASS_NAME;

private static final String ENTITY_DEFINITION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
public static final String ENTITY_DEFINITION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<hibernate-mapping>\n" +
" <class name=\"" + CLASS_NAME + "\" entity-name=\"" + ENTITY_NAME + "\" table=\"" + TABLE_NAME + "\">\n" +
" <id name=\"id\">\n" +
Expand All @@ -70,15 +70,23 @@ public class OutboxPollingAgentAdditionalJaxbMappingProducer
" </class>\n" +
"</hibernate-mapping>\n";

private static final ConfigurationProperty<String> AGENT_ENTITY_MAPPING =
ConfigurationProperty.forKey( HibernateOrmMapperOutboxPollingSpiSettings.CoordinationRadicals.AGENT_ENTITY_MAPPING )
.asString()
.withDefault( ENTITY_DEFINITION )
.build();

@Override
@SuppressForbiddenApis(reason = "Strangely, this SPI involves the internal MappingBinder class,"
+ " and there's nothing we can do about it")
public Collection<MappingDocument> produceAdditionalMappings(final MetadataImplementor metadata,
IndexView jandexIndex, final MappingBinder mappingBinder, final MetadataBuildingContext buildingContext) {
log.applicationNodeGeneratedEntityMapping( ENTITY_DEFINITION );
public Collection<MappingDocument> produceMappings(ConfigurationPropertySource propertySource,
MappingBinder mappingBinder, MetadataBuildingContext buildingContext) {
String entityDefinition = AGENT_ENTITY_MAPPING.get( propertySource );

log.agentGeneratedEntityMapping( entityDefinition );
Origin origin = new Origin( SourceType.OTHER, "search" );

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( ENTITY_DEFINITION.getBytes() );
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( entityDefinition.getBytes() );
BufferedInputStream bufferedInputStream = new BufferedInputStream( byteArrayInputStream );
Binding<?> binding = mappingBinder.bind( bufferedInputStream, origin );

Expand Down
Loading