/*
* 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.test.jms.master;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.jms.MessageConsumer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.persistence.criteria.CriteriaDelete;
import org.apache.activemq.broker.BrokerService;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.Field;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.Query;
import org.hibernate.Session;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.backend.AddLuceneWork;
import org.hibernate.search.backend.LuceneWork;
import org.hibernate.search.backend.spi.DeleteByQueryLuceneWork;
import org.hibernate.search.backend.spi.SingularTermDeletionQuery;
import org.hibernate.search.cfg.Environment;
import org.hibernate.search.engine.ProjectionConstants;
import org.hibernate.search.query.engine.spi.HSQuery;
import org.hibernate.search.spi.IndexingMode;
import org.hibernate.search.test.SearchTestBase;
import org.hibernate.search.testsupport.TestConstants;
import org.hibernate.search.testsupport.concurrency.Poller;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Tests that the Master node in a JMS cluster can properly process messages placed onto the queue.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
* @author Sanne Grinovero
*/
public class JMSMasterTest extends SearchTestBase {
/**
* Name of the test queue as found in JNDI (see jndi.properties).
*/
private static final String QUEUE_NAME = "queue/searchtest";
/**
* Name of the connection factory as found in JNDI (see jndi.properties).
*/
private static final String CONNECTION_FACTORY_NAME = "java:/ConnectionFactory";
public static final Poller POLLER = Poller.milliseconds( 10_000, 100 );
private final QueryParser parser = new QueryParser( "id", TestConstants.stopAnalyzer );
/**
* ActiveMQ message broker.
*/
private BrokerService brokerService;
private QueueSession queueSession;
@Test
public void testMessageSending() throws Exception {
TShirt shirt = createObject();
List<LuceneWork> queue = createDocumentAndWorkQueue( shirt );
registerMessageListener();
assertEquals( 0, listByQuery( "logo:jboss" ).size() );
sendMessage( queue );
// need to sleep to give JMS processing and indexing time
POLLER.pollAssertion( () -> assertEquals( 1, listByQuery( "logo:jboss" ).size() ) );
FullTextSession ftSession = Search.getFullTextSession( openSession() );
ftSession.getTransaction().begin();
CriteriaDelete<TShirt> delete = ftSession.getCriteriaBuilder().createCriteriaDelete( TShirt.class );
delete.from( TShirt.class );
ftSession.createQuery( delete ).executeUpdate();
ftSession.purgeAll( TShirt.class );
ftSession.getTransaction().commit();
ftSession.close();
assertEquals( 0, listByQuery( "logo:jboss" ).size() );
{
shirt = createObject();
queue = createDocumentAndWorkQueue( shirt );
registerMessageListener();
sendMessage( queue );
POLLER.pollAssertion( () -> assertEquals( 1, listByQuery( "logo:jboss" ).size() ) );
{
DeleteByQueryLuceneWork work = new DeleteByQueryLuceneWork( TShirt.class, new SingularTermDeletionQuery( "logo", "jboss" ) );
List<LuceneWork> l = new ArrayList<>();
l.add( work );
this.registerMessageListener();
this.sendMessage( l );
}
POLLER.pollAssertion( () -> {
HSQuery hsQuery = this.getExtendedSearchIntegrator().createHSQuery(
this.getExtendedSearchIntegrator().buildQueryBuilder().forEntity( TShirt.class ).get().all().createQuery(),
TShirt.class
);
assertEquals( 0, hsQuery.queryResultSize() );
} );
}
}
@SuppressWarnings("unchecked")
private List<TShirt> listByQuery(String luceneQueryString) throws ParseException {
FullTextSession ftSess = Search.getFullTextSession( openSession() );
try {
ftSess.getTransaction().begin();
try {
Query luceneQuery = parser.parse( luceneQueryString );
FullTextQuery query = ftSess.createFullTextQuery( luceneQuery );
@SuppressWarnings({ "rawtypes" })
List result = query.list();
return result;
}
finally {
ftSess.getTransaction().commit();
}
}
finally {
ftSess.close();
}
}
private void registerMessageListener() throws Exception {
MessageConsumer consumer = getQueueSession().createConsumer( getMessageQueue() );
consumer.setMessageListener( new MDBSearchController( getExtendedSearchIntegrator() ) );
}
private void sendMessage(List<LuceneWork> queue) throws Exception {
ObjectMessage message = getQueueSession().createObjectMessage();
final String indexName = getIndexName();
message.setStringProperty(
Environment.INDEX_NAME_JMS_PROPERTY,
indexName );
byte[] data = getExtendedSearchIntegrator().getWorkSerializer().toSerializedModel( queue );
message.setObject( data );
QueueSender sender = getQueueSession().createSender( getMessageQueue() );
sender.send( message );
}
protected String getIndexName() {
return org.hibernate.search.test.jms.master.TShirt.class.getName();
}
private Queue getMessageQueue() throws Exception {
Context ctx = getJndiInitialContext();
return (Queue) ctx.lookup( QUEUE_NAME );
}
private QueueSession getQueueSession() throws Exception {
if ( queueSession == null ) {
Context ctx = getJndiInitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup( CONNECTION_FACTORY_NAME );
QueueConnection conn = factory.createQueueConnection();
conn.start();
queueSession = conn.createQueueSession( false, QueueSession.AUTO_ACKNOWLEDGE );
}
return queueSession;
}
private Context getJndiInitialContext() throws NamingException {
Properties props = new Properties();
props.setProperty(
Context.INITIAL_CONTEXT_FACTORY, "org.apache.activemq.jndi.ActiveMQInitialContextFactory"
);
props.setProperty( Context.PROVIDER_URL, "vm://localhost" );
props.setProperty( "connectionFactoryNames", "ConnectionFactory, java:/ConnectionFactory" );
props.setProperty( "queue.queue/searchtest", "searchQueue" );
return new javax.naming.InitialContext( props );
}
/**
* Manually create the work queue. This lists gets send by the Slaves to the Master for indexing.
*
* @param shirt The shirt to index
*
* @return A manually create <code>LuceneWork</code> list.
*/
private List<LuceneWork> createDocumentAndWorkQueue(TShirt shirt) {
Document doc = new Document();
Field field = new Field(
ProjectionConstants.OBJECT_CLASS, shirt.getClass().getName(), Field.Store.YES, Field.Index.NOT_ANALYZED
);
doc.add( field );
field = new Field( "id", String.valueOf( shirt.getId() ), Field.Store.YES, Field.Index.ANALYZED );
doc.add( field );
field = new Field( "logo", shirt.getLogo(), Field.Store.NO, Field.Index.ANALYZED );
doc.add( field );
DoubleField numField = new DoubleField( "length", shirt.getLength(), Field.Store.NO );
doc.add( numField );
LuceneWork luceneWork = new AddLuceneWork(
shirt.getId(), String.valueOf( shirt.getId() ), shirt.getClass(), doc
);
List<LuceneWork> queue = new ArrayList<LuceneWork>();
queue.add( luceneWork );
return queue;
}
/**
* Create a test object without triggering indexing,
* because Hibernate Search listeners are disabled.
*
* @return a <code>TShirt</code> test object.
*/
private TShirt createObject() {
Session s = openSession();
s.getTransaction().begin();
TShirt ts = new TShirt();
ts.setLogo( "JBoss balls" );
ts.setSize( "large" );
ts.setLength( 23.2d );
s.persist( ts );
s.getTransaction().commit();
s.close();
return ts;
}
@Override
@Before
public void setUp() throws Exception {
// create and start the brokerService
brokerService = createTestingBrokerService();
super.setUp();
}
/**
* @return A started JMS Broker
*/
public static BrokerService createTestingBrokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent( false );
// disabling the following greatly speedups the tests:
brokerService.setUseJmx( false );
brokerService.setUseShutdownHook( false );
brokerService.setEnableStatistics( false );
brokerService.start();
return brokerService;
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
if ( brokerService != null ) {
brokerService.stop();
}
}
@Override
public void configure(Map<String,Object> cfg) {
// See createObject()
cfg.put( Environment.INDEXING_STRATEGY, IndexingMode.MANUAL.toExternalRepresentation() );
// explicitly set the backend even though local is default.
cfg.put( "hibernate.search.default." + Environment.WORKER_BACKEND, "local" );
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class[] {
TShirt.class
};
}
}