/*
* 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.integration.osgi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.debugConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.batchindexing.MassIndexerProgressMonitor;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.karaf.options.LogLevelOption;
import org.ops4j.pax.exam.options.MavenArtifactUrlReference;
import org.ops4j.pax.exam.options.MavenUrlReference;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.wiring.BundleRevision;
/**
* A basic integration test that executes Hibernate Search in Apache Karaf using
* PaxExam (see <a href="https://ops4j1.jira.com/wiki/display/PAXEXAM3/Pax+Exam">online docs</a>).
*
* If there is a problem with the actual feature configuration, debugging this tests won't help,
* since the Karaf container won't even start. In this case it is easiest to start a local Karaf installation:
* <pre>
* {@code
* > ./karaf
* }
* </pre>
*
* Then in the Karaf console type:
* <pre>
* {@code
* feature:repo-add mvn:org.hibernate/hibernate-search-integrationtest-osgi-features/<version>/xml/features
* feature:install hibernate-search
* }
* </pre>
*
* You can then verify the bundle existence with command:
* <pre>
* {@code
* feature:list
* }
* </pre>
*
* If/when investigating strange failures due to RMI registries, remember to check that
* your local hostname resolves to 127.0.0.1.
* In Fedora this isn't the case by default, and causes problems with Karaf.
*
* @author Hardy Ferentschik
*/
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class HibernateSearchWithKarafIT {
/**
* Switch to simplify debugging of this test
* when it fails
*/
private static final boolean DEBUG = false;
@Inject
private BundleContext bundleContext;
private ServiceReference serviceReference;
@Configuration
public Option[] config() {
MavenArtifactUrlReference karafUrl = maven()
.groupId( "org.apache.karaf" )
.artifactId( "apache-karaf" )
.type( "tar.gz" )
.versionAsInProject();
MavenUrlReference karafStandardRepo = maven()
.groupId( "org.apache.karaf.features" )
.artifactId( "standard" )
.classifier( "features" )
.type( "xml" )
.versionAsInProject();
MavenUrlReference hibernateSearchFeature = maven()
.groupId( "org.hibernate" )
.artifactId( "hibernate-search-integrationtest-osgi-features" )
.classifier( "features" )
.type( "xml" )
.versionAsInProject();
File examDir = new File( "target/exam" );
File ariesLogDir = new File( examDir, "/aries/log" );
return new Option[] {
DEBUG ? debugConfiguration( "5005", true ) : null,
logLevel( LogLevelOption.LogLevel.WARN ),
karafDistributionConfiguration()
.frameworkUrl( karafUrl )
.unpackDirectory( examDir )
.useDeployFolder( false ),
DEBUG ? keepRuntimeFolder() : null,
configureConsole()
.ignoreRemoteShell()
.ignoreLocalConsole() ,
features( karafStandardRepo, "scr" ),
features( hibernateSearchFeature, "hibernate-search" ),
// configure Aries transaction manager
editConfigurationFilePut(
"etc/org.apache.aries.transaction.cfg",
"aries.transaction.howl.logFileDir", ariesLogDir.getAbsolutePath()
),
editConfigurationFilePut(
"etc/org.apache.aries.transaction.cfg",
"aries.transaction.recoverable", "true"
),
editConfigurationFilePut(
"etc/org.apache.aries.transaction.cfg",
"aries.transaction.timeout", "600"
),
editConfigurationFilePut(
"etc/org.apache.karaf.management.cfg",
"rmiServerHost", "127.0.0.1"
),
editConfigurationFilePut(
"etc/org.apache.karaf.management.cfg",
"rmiRegistryHost", "127.0.0.1"
),
// set the log level for the in container logging to INFO
// also just logging to file (out), check data/log/karaf.log w/i the Karaf installation for the test execution log
editConfigurationFilePut(
"etc/org.ops4j.pax.logging.cfg",
"log4j.rootLogger", "INFO, out"
)
};
}
@Before
public void setUp() {
serviceReference = bundleContext.getServiceReference( SessionFactory.class.getName() );
assertNotNull( "The service reference should not be null", serviceReference );
}
@After
public void tearDown() {
bundleContext.ungetService( serviceReference );
}
/**
* Starts all bundles to make sure there are no "uses constraint violations" caused by packages exported from the
* bundles contributed by the HSEARCH Karaf feature.
*/
@Test
@TestForIssue(jiraKey = "HSEARCH-1774")
public void testStartingAllBundles() throws Exception {
for ( Bundle bundle : bundleContext.getBundles() ) {
if ( !isFragmentBundle( bundle ) ) {
bundle.start();
}
}
}
@Test
public void testAccessSessionFactory() throws Exception {
SessionFactory sessionFactory = (SessionFactory) bundleContext.getService( serviceReference );
assertNotNull( "The session factory should not be null", sessionFactory );
}
@Test
public void testAccessSearchFactory() throws Exception {
SessionFactory sessionFactory = (SessionFactory) bundleContext.getService( serviceReference );
FullTextSession fullTextSession = Search.getFullTextSession( sessionFactory.openSession() );
assertNotNull( "Unable to create fulltext session from ORM Session", fullTextSession );
SearchFactory searchFactory = fullTextSession.getSearchFactory();
assertNotNull( "Unable to access SearchFactory", searchFactory );
assertEquals( "There should only be one indexed type", 1, searchFactory.getIndexedTypes().size() );
assertEquals( "Wrong indexed type", Muppet.class, searchFactory.getIndexedTypes().iterator().next() );
}
@Test
public void testIndexAndSearch() throws Exception {
SessionFactory sessionFactory = (SessionFactory) bundleContext.getService( serviceReference );
FullTextSession fullTextSession = Search.getFullTextSession( sessionFactory.openSession() );
assertElmoIndexed( fullTextSession, false );
persistElmo( fullTextSession );
assertElmoIndexed( fullTextSession, true );
}
@Test
public void testBatchIndexing() throws Exception {
SessionFactory sessionFactory = (SessionFactory) bundleContext.getService( serviceReference );
FullTextSession fullTextSession = Search.getFullTextSession( sessionFactory.openSession() );
AssertingMassIndexerProgressMonitor progressMonitor = new AssertingMassIndexerProgressMonitor( 0 );
fullTextSession.createIndexer( Muppet.class ).progressMonitor( progressMonitor ).startAndWait();
progressMonitor.assertExpectedProgressMade();
persistElmo( fullTextSession );
progressMonitor = new AssertingMassIndexerProgressMonitor( 1 );
fullTextSession.createIndexer( Muppet.class ).progressMonitor( progressMonitor ).startAndWait();
progressMonitor.assertExpectedProgressMade();
}
@SuppressWarnings("unchecked")
@Test
public void testSimpleQueryStringDSL() throws Exception {
SessionFactory sessionFactory = (SessionFactory) bundleContext.getService( serviceReference );
FullTextSession fullTextSession = Search.getFullTextSession( sessionFactory.openSession() );
persistElmo( fullTextSession );
QueryBuilder qb = fullTextSession.getSearchFactory()
.buildQueryBuilder()
.forEntity( Muppet.class )
.get();
FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery( qb.simpleQueryString().onField( "name" ).matching( "Elmo" ).createQuery(), Muppet.class );
List<Muppet> results = fullTextQuery.getResultList();
assertEquals( "Elmo should be there", 1, results.size() );
Muppet muppet = results.get( 0 );
assertEquals( "Index muppet is not Elmo", "Elmo", muppet.getName() );
}
private boolean isFragmentBundle(Bundle bundle) {
return ( bundle.adapt( BundleRevision.class ).getTypes() & BundleRevision.TYPE_FRAGMENT ) != 0;
}
private void persistElmo(FullTextSession fullTextSession) {
Transaction transaction = fullTextSession.beginTransaction();
Muppet elmo = new Muppet( "Elmo" );
fullTextSession.persist( elmo );
transaction.commit();
fullTextSession.clear();
}
private void assertElmoIndexed(FullTextSession fullTextSession, boolean indexed) {
Query matchAllQuery = new MatchAllDocsQuery();
FullTextQuery query = fullTextSession.createFullTextQuery( matchAllQuery, Muppet.class );
if ( indexed ) {
assertEquals( "Elmo should be there", 1, query.list().size() );
Muppet muppet = (Muppet) query.list().get( 0 );
assertEquals( "Index muppet is not Elmo", "Elmo", muppet.getName() );
}
else {
assertEquals( "Elmo should not be there", 0, query.list().size() );
}
}
public class AssertingMassIndexerProgressMonitor implements MassIndexerProgressMonitor {
private final AtomicLong totalCount = new AtomicLong();
private final AtomicLong finishedCount = new AtomicLong();
private final int expectedTotalCount;
public AssertingMassIndexerProgressMonitor(int expectedTotalCount) {
this.expectedTotalCount = expectedTotalCount;
}
@Override
public void documentsAdded(long increment) {
}
@Override
public void documentsBuilt(int number) {
}
@Override
public void entitiesLoaded(int size) {
}
@Override
public void addToTotalCount(long count) {
totalCount.addAndGet( count );
}
@Override
public void indexingCompleted() {
finishedCount.incrementAndGet();
}
public void assertExpectedProgressMade() {
assertEquals( "Unexpected total count", expectedTotalCount, totalCount.get() );
assertEquals( "Finished called more than once", 1, finishedCount.get() );
}
}
}