org.hibernate.search.test.query.SortTest.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.search.test.query.SortTest.java

Source

/*
 * 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.query;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.FieldComparatorSource;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;

import org.hibernate.Transaction;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.test.SearchTestCase;
import org.hibernate.search.test.TestConstants;

/**
 * @author Hardy Ferentschik
 */
public class SortTest extends SearchTestCase {

    private static FullTextSession fullTextSession;
    private static QueryParser queryParser;

    public void setUp() throws Exception {
        super.setUp();
        fullTextSession = Search.getFullTextSession(openSession());
        queryParser = new QueryParser(TestConstants.getTargetLuceneVersion(), "title", TestConstants.stopAnalyzer);

        createTestBooks();
        createTestNumbers();
    }

    public void tearDown() throws Exception {
        // check for ongoing transaction which is an indicator that something went wrong
        // don't call the cleanup methods in this case. Otherwise the original error get swallowed
        if (!fullTextSession.getTransaction().isActive()) {
            deleteTestBooks();
            deleteTestNumbers();
            fullTextSession.close();
        }
        super.tearDown();
    }

    @SuppressWarnings("unchecked")
    public void testResultOrderedById() throws Exception {
        Transaction tx = fullTextSession.beginTransaction();

        Query query = queryParser.parse("summary:lucene");
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Book.class);
        Sort sort = new Sort(new SortField("id", SortField.STRING, false));
        hibQuery.setSort(sort);
        List<Book> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 3, result.size());
        int id = 1;
        for (Book b : result) {
            assertEquals("Expected another id", Integer.valueOf(id), b.getId());
            id++;
        }

        tx.commit();
    }

    @SuppressWarnings("unchecked")
    public void testResultOrderedBySummaryStringAscending() throws Exception {
        Transaction tx = fullTextSession.beginTransaction();

        // order by summary
        Query query = queryParser.parse("summary:lucene OR summary:action");
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Book.class);
        Sort sort = new Sort(new SortField("summary_forSort", SortField.STRING)); //ASC
        hibQuery.setSort(sort);
        List<Book> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 4, result.size());
        assertEquals("Groovy in Action", result.get(0).getSummary());

        tx.commit();
    }

    @SuppressWarnings("unchecked")
    public void testResultOrderedBySummaryStringDescending() throws Exception {
        Transaction tx = fullTextSession.beginTransaction();

        // order by summary backwards
        Query query = queryParser.parse("summary:lucene OR summary:action");
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Book.class);
        Sort sort = new Sort(new SortField("summary_forSort", SortField.STRING, true)); //DESC
        hibQuery.setSort(sort);
        List<Book> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 4, result.size());
        assertEquals("Hibernate & Lucene", result.get(0).getSummary());

        tx.commit();
    }

    @SuppressWarnings("unchecked")
    public void testResultOrderedByDateDescending() throws Exception {
        Transaction tx = fullTextSession.beginTransaction();

        // order by date backwards
        Query query = queryParser.parse("summary:lucene OR summary:action");
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Book.class);
        Sort sort = new Sort(new SortField("publicationDate", SortField.STRING, true)); //DESC
        hibQuery.setSort(sort);
        List<Book> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 4, result.size());
        for (Book book : result) {
            System.out.println(book.getSummary() + " : " + book.getPublicationDate());
        }
        assertEquals("Groovy in Action", result.get(0).getSummary());

        tx.commit();
    }

    @SuppressWarnings("unchecked")
    public void testCustomFieldComparatorAscendingSort() {
        Transaction tx = fullTextSession.beginTransaction();

        Query query = new MatchAllDocsQuery();
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, NumberHolder.class);
        Sort sort = new Sort(new SortField("sum", new SumFieldComparatorSource()));
        hibQuery.setSort(sort);
        List<NumberHolder> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 4, result.size());

        int previousSum = 0;
        for (NumberHolder n : result) {
            assertTrue("Documents should be ordered by increasing sum", previousSum < n.getSum());
            previousSum = n.getSum();
        }

        tx.commit();
    }

    @SuppressWarnings("unchecked")
    public void testCustomFieldComparatorDescendingSort() {
        Transaction tx = fullTextSession.beginTransaction();

        Query query = new MatchAllDocsQuery();
        FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, NumberHolder.class);
        Sort sort = new Sort(new SortField("sum", new SumFieldComparatorSource(), true));
        hibQuery.setSort(sort);
        List<NumberHolder> result = hibQuery.list();
        assertNotNull(result);
        assertEquals("Wrong number of test results.", 4, result.size());

        int previousSum = 100;
        for (NumberHolder n : result) {
            assertTrue("Documents should be ordered by decreasing sum", previousSum > n.getSum());
            previousSum = n.getSum();
        }

        tx.commit();
    }

    /**
     * Helper method creating three books with the same title and summary.
     * When searching for these books the results should be returned in the order
     * they got added to the index.
     */
    private void createTestBooks() {
        Transaction tx = fullTextSession.beginTransaction();
        Calendar cal = Calendar.getInstance();
        cal.set(2007, Calendar.JULY, 25, 11, 20, 30);
        Book book = new Book(1, "Hibernate & Lucene", "This is a test book.");
        book.setPublicationDate(cal.getTime());
        fullTextSession.save(book);
        cal.add(Calendar.SECOND, 1);
        book = new Book(2, "Hibernate & Lucene", "This is a test book.");
        book.setPublicationDate(cal.getTime());
        fullTextSession.save(book);
        cal.add(Calendar.SECOND, 1);
        book = new Book(3, "Hibernate & Lucene", "This is a test book.");
        book.setPublicationDate(cal.getTime());
        fullTextSession.save(book);
        cal.add(Calendar.SECOND, 1);
        book = new Book(4, "Groovy in Action", "The bible of Groovy");
        book.setPublicationDate(cal.getTime());
        fullTextSession.save(book);
        tx.commit();
        fullTextSession.clear();
    }

    /**
     * Helper method creating test data for number holder.
     */
    private void createTestNumbers() {
        Transaction tx = fullTextSession.beginTransaction();
        NumberHolder holder = new NumberHolder(1, 1);
        fullTextSession.save(holder);
        holder = new NumberHolder(1, 10);
        fullTextSession.save(holder);
        holder = new NumberHolder(1, 5);
        fullTextSession.save(holder);
        holder = new NumberHolder(3, 2);
        fullTextSession.save(holder);
        tx.commit();
        fullTextSession.clear();
    }

    private void deleteTestBooks() {
        Transaction tx = fullTextSession.beginTransaction();
        fullTextSession.createQuery("delete " + Book.class.getName()).executeUpdate();
        tx.commit();
        fullTextSession.clear();
    }

    private void deleteTestNumbers() {
        Transaction tx = fullTextSession.beginTransaction();
        fullTextSession.createQuery("delete " + NumberHolder.class.getName()).executeUpdate();
        tx.commit();
        fullTextSession.clear();
    }

    protected Class<?>[] getAnnotatedClasses() {
        return new Class[] { Book.class, Author.class, NumberHolder.class };
    }

    @Entity
    @Indexed
    public static class NumberHolder {
        @Id
        @GeneratedValue
        int id;

        @Field(analyze = Analyze.NO)
        int num1;

        @Field(analyze = Analyze.NO)
        int num2;

        public NumberHolder(int num1, int num2) {
            this.num1 = num1;
            this.num2 = num2;
        }

        public int getSum() {
            return num1 + num2;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("NumbersHolder");
            sb.append("{id=").append(id);
            sb.append(", num1=").append(num1);
            sb.append(", num2=").append(num2);
            sb.append('}');
            return sb.toString();
        }

        @SuppressWarnings("unused")
        private NumberHolder() {
        }
    }

    public static class SumFieldComparatorSource extends FieldComparatorSource {
        @Override
        public FieldComparator<?> newComparator(String fieldName, int numHits, int sortPos, boolean reversed)
                throws IOException {
            return new SumFieldComparator(numHits, "num1", "num2");
        }
    }

    public static class SumFieldComparator extends FieldComparator<Integer> {
        private final String field1;
        private final String field2;
        private final int[] field1Values;
        private final int[] field2Values;

        private int[] currentReaderValuesField1;
        private int[] currentReaderValuesField2;
        private int bottom;

        public SumFieldComparator(int numHits, String field1, String field2) {
            this.field1 = field1;
            this.field2 = field2;
            this.field1Values = new int[numHits];
            this.field2Values = new int[numHits];
        }

        @Override
        public int compare(int slot1, int slot2) {
            final int v1 = field1Values[slot1] + field2Values[slot1];
            final int v2 = field1Values[slot2] + field2Values[slot2];

            return compareValues(v1, v2);
        }

        private int compareValues(int v1, int v2) {
            if (v1 > v2) {
                return 1;
            } else if (v1 < v2) {
                return -1;
            } else {
                return 0;
            }
        }

        @Override
        public int compareBottom(int doc) {
            int v = currentReaderValuesField1[doc] + currentReaderValuesField2[doc];
            return compareValues(bottom, v);
        }

        @Override
        public void copy(int slot, int doc) {
            int v1 = currentReaderValuesField1[doc];
            field1Values[slot] = v1;

            int v2 = currentReaderValuesField2[doc];
            field2Values[slot] = v2;
        }

        @Override
        public void setNextReader(IndexReader reader, int docBase) throws IOException {
            currentReaderValuesField1 = FieldCache.DEFAULT.getInts(reader, field1, FieldCache.DEFAULT_INT_PARSER,
                    false);
            currentReaderValuesField2 = FieldCache.DEFAULT.getInts(reader, field2, FieldCache.DEFAULT_INT_PARSER,
                    false);
        }

        @Override
        public void setBottom(final int bottom) {
            this.bottom = field1Values[bottom] + field2Values[bottom];
        }

        @Override
        public Integer value(int slot) {
            return field1Values[slot] + field2Values[slot];
        }
    }
}