/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.test;
import java.io.File;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.SQLException;
import junit.framework.TestCase;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.store.Directory;
import org.junit.After;
import org.junit.Before;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.common.util.StringHelper;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jdbc.Work;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchException;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.engine.spi.SearchFactoryImplementor;
import org.hibernate.search.indexes.impl.DirectoryBasedIndexManager;
import org.hibernate.search.indexes.spi.IndexManager;
import org.hibernate.search.test.fwk.SkipLog;
import org.hibernate.search.util.impl.ContextHelper;
import org.hibernate.search.util.impl.FileHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.tool.hbm2ddl.SchemaExport;
/**
* Base class for Hibernate Search unit tests.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public abstract class SearchTestCase extends TestCase {
private static final Log log = LoggerFactory.make();
private SessionFactory sessions;
protected Session session;
private SearchFactoryImplementor searchFactory;
protected Configuration cfg;
private static Class<?> lastTestClass;
@Before
public void setUp() throws Exception {
if ( cfg == null || lastTestClass != getClass() ) {
buildConfiguration();
}
lastTestClass = getClass();
openSessionFactory();
}
protected void openSessionFactory() {
if ( sessions == null ) {
if ( cfg == null ) {
throw new IllegalStateException( "configuration was not built" );
}
setSessions( cfg.buildSessionFactory( /*new TestInterceptor()*/ ) );
}
else {
throw new IllegalStateException( "there should be no SessionFactory initialized at this point" );
}
}
protected void closeSessionFactory() {
if ( sessions == null ) {
throw new IllegalStateException( "there is no SessionFactory to close" );
}
else {
sessions.close();
sessions = null;
}
}
protected void handleUnclosedResources() {
if ( session != null && session.isOpen() ) {
if ( session.isConnected() ) {
session.doWork( new RollbackWork() );
}
session.close();
session = null;
log.debug("Closing open session. Make sure to close sessions explicitly in your tests!");
}
else {
session = null;
}
}
protected void closeResources() {
}
protected String[] getXmlFiles() {
return new String[] { };
}
protected void setCfg(Configuration cfg) {
this.cfg = cfg;
}
protected Configuration getCfg() {
return cfg;
}
public Session openSession() throws HibernateException {
session = getSessions().openSession();
return session;
}
protected void setSessions(SessionFactory sessions) {
this.sessions = sessions;
}
protected SessionFactory getSessions() {
if ( cfg == null ) {
throw new IllegalStateException( "Configuration should be already defined at this point" );
}
if ( sessions == null ) {
throw new IllegalStateException( "SessionFactory should be already defined at this point" );
}
return sessions;
}
protected void configure(Configuration cfg) {
cfg.setProperty( "hibernate.search.lucene_version", TestConstants.getTargetLuceneVersion().name() );
cfg.setProperty( "hibernate.search.default.directory_provider", "ram" );
cfg.setProperty( "hibernate.search.default.indexBase", getBaseIndexDir().getAbsolutePath() );
cfg.setProperty( org.hibernate.search.Environment.ANALYZER_CLASS, StopAnalyzer.class.getName() );
cfg.setProperty( "hibernate.search.default.indexwriter.merge_factor", "100" );
cfg.setProperty( "hibernate.search.default.indexwriter.max_buffered_docs", "1000" );
}
protected Directory getDirectory(Class<?> clazz) {
SearchFactoryImplementor searchFactoryBySFI = ContextHelper.getSearchFactoryBySFI( ( SessionFactoryImplementor ) sessions );
IndexManager[] indexManagers = searchFactoryBySFI.getIndexBindingForEntity( clazz ).getIndexManagers();
DirectoryBasedIndexManager indexManager = (DirectoryBasedIndexManager) indexManagers[0];
return indexManager.getDirectoryProvider().getDirectory();
}
@After
public void tearDown() throws Exception {
//runSchemaDrop();
handleUnclosedResources();
closeResources();
closeSessionFactory();
ensureIndexesAreEmpty();
}
protected abstract Class<?>[] getAnnotatedClasses();
protected boolean recreateSchema() {
return true;
}
protected void runSchemaGeneration() {
SchemaExport export = new SchemaExport( cfg );
export.create( true, true );
}
protected void runSchemaDrop() {
SchemaExport export = new SchemaExport( cfg );
export.drop( true, true );
}
protected void ensureIndexesAreEmpty() {
if ( "jms".equals( getCfg().getProperty( "hibernate.search.worker.backend" ) ) ) {
log.debug( "JMS based test. Skipping index emptying" );
return;
}
FileHelper.delete( getBaseIndexDir() );
}
protected SearchFactory getSearchFactory() {
if ( searchFactory == null ) {
Session session = openSession();
FullTextSession fullTextSession = Search.getFullTextSession( session );
searchFactory = ( SearchFactoryImplementor ) fullTextSession.getSearchFactory();
fullTextSession.close();
}
return searchFactory;
}
protected File getBaseIndexDir() {
// Make sure no directory is ever reused across the testsuite as Windows might not be able
// to delete the files after usage. See also
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154
String shortTestName = this.getClass().getSimpleName() + "." + this.getName();
// the constructor File(File, String) is broken too, see :
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5066567
// So make sure to use File(String, String) in this case as TestConstants works with absolute paths!
File indexPath = new File( TestConstants.getIndexDirectory(), shortTestName );
return indexPath;
}
protected void buildConfiguration() {
if ( cfg != null ) {
throw new IllegalStateException( "Configuration was already built" );
}
try {
setCfg( new Configuration() );
configure( cfg );
if ( recreateSchema() ) {
cfg.setProperty( org.hibernate.cfg.Environment.HBM2DDL_AUTO, "create-drop" );
}
for ( String aPackage : getAnnotatedPackages() ) {
getCfg().addPackage( aPackage );
}
for ( Class<?> aClass : getAnnotatedClasses() ) {
getCfg().addAnnotatedClass( aClass );
}
for ( String xmlFile : getXmlFiles() ) {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( xmlFile );
getCfg().addInputStream( is );
}
}
catch ( HibernateException e ) {
e.printStackTrace();
throw e;
}
catch ( SearchException e ) {
e.printStackTrace();
throw e;
}
catch ( Exception e ) {
e.printStackTrace();
throw new RuntimeException( e );
}
}
protected String[] getAnnotatedPackages() {
return new String[] { };
}
protected SearchFactoryImplementor getSearchFactoryImpl() {
FullTextSession s = Search.getFullTextSession( openSession() );
s.close();
SearchFactory searchFactory = s.getSearchFactory();
return (SearchFactoryImplementor) searchFactory;
}
private static class RollbackWork implements Work {
public void execute(Connection connection) throws SQLException {
connection.rollback();
}
}
private void reportSkip(Skip skip) {
reportSkip( skip.reason, skip.testDescription );
}
protected void reportSkip(String reason, String testDescription) {
StringBuilder builder = new StringBuilder();
builder.append( "*** skipping test [" );
builder.append( fullTestName() );
builder.append( "] - " );
builder.append( testDescription );
builder.append( " : " );
builder.append( reason );
SkipLog.LOG.warn( builder.toString() );
}
protected Dialect getDialect() {
return Dialect.getDialect();
}
protected Skip buildSkip(Dialect dialect, String comment, String jiraKey) {
StringBuilder buffer = new StringBuilder();
buffer.append( "skipping database-specific test [" );
buffer.append( fullTestName() );
buffer.append( "] for dialect [" );
buffer.append( dialect.getClass().getName() );
buffer.append( ']' );
if ( StringHelper.isNotEmpty( comment ) ) {
buffer.append( "; " ).append( comment );
}
if ( StringHelper.isNotEmpty( jiraKey ) ) {
buffer.append( " (" ).append( jiraKey ).append( ')' );
}
return new Skip( buffer.toString(), null );
}
protected <T extends Annotation> T locateAnnotation(Class<T> annotationClass, Method runMethod) {
T annotation = runMethod.getAnnotation( annotationClass );
if ( annotation == null ) {
annotation = getClass().getAnnotation( annotationClass );
}
if ( annotation == null ) {
annotation = runMethod.getDeclaringClass().getAnnotation( annotationClass );
}
return annotation;
}
protected final Skip determineSkipByDialect(Dialect dialect, Method runMethod) throws Exception {
// skips have precedence, so check them first
SkipForDialect skipForDialectAnn = locateAnnotation( SkipForDialect.class, runMethod );
if ( skipForDialectAnn != null ) {
for ( Class<? extends Dialect> dialectClass : skipForDialectAnn.value() ) {
if ( skipForDialectAnn.strictMatching() ) {
if ( dialectClass.equals( dialect.getClass() ) ) {
return buildSkip( dialect, skipForDialectAnn.comment(), skipForDialectAnn.jiraKey() );
}
}
else {
if ( dialectClass.isInstance( dialect ) ) {
return buildSkip( dialect, skipForDialectAnn.comment(), skipForDialectAnn.jiraKey() );
}
}
}
}
return null;
}
protected static class Skip {
private final String reason;
private final String testDescription;
public Skip(String reason, String testDescription) {
this.reason = reason;
this.testDescription = testDescription;
}
}
@Override
protected void runTest() throws Throwable {
Method runMethod = findTestMethod();
FailureExpected failureExpected = locateAnnotation( FailureExpected.class, runMethod );
try {
super.runTest();
if ( failureExpected != null ) {
throw new FailureExpectedTestPassedException();
}
}
catch ( FailureExpectedTestPassedException t ) {
closeResources();
throw t;
}
catch ( Throwable t ) {
if ( t instanceof InvocationTargetException ) {
t = ( ( InvocationTargetException ) t ).getTargetException();
}
if ( t instanceof IllegalAccessException ) {
t.fillInStackTrace();
}
closeResources();
if ( failureExpected != null ) {
StringBuilder builder = new StringBuilder();
if ( StringHelper.isNotEmpty( failureExpected.message() ) ) {
builder.append( failureExpected.message() );
}
else {
builder.append( "ignoring @FailureExpected test" );
}
builder.append( " (" )
.append( failureExpected.jiraKey() )
.append( ")" );
SkipLog.LOG.warn( builder.toString(), t );
}
else {
throw t;
}
}
}
@Override
public void runBare() throws Throwable {
Method runMethod = findTestMethod();
final Skip skip = determineSkipByDialect( Dialect.getDialect(), runMethod );
if ( skip != null ) {
reportSkip( skip );
return;
}
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
public String fullTestName() {
return this.getClass().getName() + "#" + this.getName();
}
private Method findTestMethod() {
String fName = getName();
assertNotNull( fName );
Method runMethod = null;
try {
runMethod = getClass().getMethod( fName );
}
catch ( NoSuchMethodException e ) {
fail( "Method \"" + fName + "\" not found" );
}
if ( !Modifier.isPublic( runMethod.getModifiers() ) ) {
fail( "Method \"" + fName + "\" should be public" );
}
return runMethod;
}
private static class FailureExpectedTestPassedException extends Exception {
public FailureExpectedTestPassedException() {
super( "Test marked as @FailureExpected, but did not fail!" );
}
}
}