ome.services.search.FullText.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.search.FullText.java

Source

/*
 *   $Id$
 *
 *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services.search;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ome.conditions.ApiUsageException;
import ome.model.IAnnotated;
import ome.model.IObject;
import ome.system.ServiceFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.ProjectionConstants;
import org.hibernate.search.Search;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * Search based on Lucene's {@link Query} class. Takes a Google-like search
 * string and returns fully formed objects via Hibernate Search.
 * 
 * @author Josh Moore, josh at glencoesoftware.com
 * @since 3.0-Beta3
 */
public class FullText extends SearchAction {

    private static final Log log = LogFactory.getLog(FullText.class);

    private static final long serialVersionUID = 1L;

    private final String queryStr;

    private final org.apache.lucene.search.Query q;

    private final Class<? extends Analyzer> analyzer;

    public FullText(SearchValues values, String query, Class<? extends Analyzer> analyzer) {
        super(values);
        Assert.notNull(analyzer, "Analyzer required");
        this.analyzer = analyzer;

        if (values.onlyTypes == null || values.onlyTypes.size() != 1) {
            throw new ApiUsageException("Searches by full text are currently limited to a single type.\n"
                    + "Plese use Search.onlyType()");
        }

        if (query == null || query.length() < 1) {
            throw new IllegalArgumentException("Query string must be non-empty");
        }

        if ((query.startsWith("*") || query.startsWith("?")) && !values.leadingWildcard) {
            throw new ApiUsageException(
                    "Searches starting with a leading " + "wildcard (*,?) can be slow.\nPlease use "
                            + "setAllowLeadingWildcard() to permit this usage.");
        }

        if (query.equals("*")) {
            throw new ApiUsageException("Wildcard searches (*) must contain more than a single wildcard. ");
        }

        this.queryStr = query;
        try {
            final Analyzer a = analyzer.newInstance();
            final QueryParser parser = new /*Analyzing*/QueryParser("combined_fields", a);
            parser.setAllowLeadingWildcard(values.leadingWildcard);
            q = parser.parse(queryStr);
        } catch (ParseException pe) {
            final String msg = queryStr + " caused a parse exception: " + pe.getMessage();
            // No longer logging these, since it's a simple user error
            ApiUsageException aue = new ApiUsageException(msg);
            throw aue;
        } catch (InstantiationException e) {
            ApiUsageException aue = new ApiUsageException(analyzer.getName() + " cannot be instantiated.");
            throw aue;
        } catch (IllegalAccessException e) {
            ApiUsageException aue = new ApiUsageException(analyzer.getName() + " cannot be instantiated.");
            throw aue;
        }
    }

    @Transactional(readOnly = true)
    public Object doWork(Session s, ServiceFactory sf) {

        final Class<?> cls = values.onlyTypes.get(0);

        FullTextSession session = Search.getFullTextSession(s);
        Criteria criteria = session.createCriteria(cls);
        AnnotationCriteria ann = new AnnotationCriteria(criteria, values.fetchAnnotations);

        ids(criteria);
        ownerOrGroup(cls, criteria);
        createdOrModified(cls, criteria);
        annotatedBy(ann);
        annotatedBetween(ann);

        // annotatedWith
        if (values.onlyAnnotatedWith != null) {
            if (values.onlyAnnotatedWith.size() > 1) {
                throw new ApiUsageException("HHH-879: " + "At the moment Hibernate cannot fulfill this request.\n"
                        + "Please use only a single onlyAnnotatedWith "
                        + "parameter when performing full text searches.");
            } else if (values.onlyAnnotatedWith.size() > 0) {
                if (!IAnnotated.class.isAssignableFrom(cls)) {
                    // A non-IAnnotated object cannot have any
                    // Annotations, and so our results are null
                    return null; // EARLY EXIT !
                } else {
                    for (Class<?> annCls : values.onlyAnnotatedWith) {
                        SimpleExpression ofType = new TypeEqualityExpression("class", annCls);
                        ann.getChild().add(ofType);
                    }
                }
            } else {
                criteria.add(Restrictions.isEmpty("annotationLinks"));
            }
        }

        // orderBy
        if (values.orderBy.size() > 0) {
            for (int i = 0; i < values.orderBy.size(); i++) {
                String orderBy = values.orderBy.get(i);
                String orderWithoutMode = orderByPath(orderBy);
                boolean ascending = orderByAscending(orderBy);
                if (ascending) {
                    criteria.addOrder(Order.asc(orderWithoutMode));
                } else {
                    criteria.addOrder(Order.desc(orderWithoutMode));
                }
            }
        }

        final String ticket975 = "ticket:975 - Wrong return type: %s instead of %s\n"
                + "Under some circumstances, byFullText and related methods \n"
                + "like bySomeMustNone can return instances of the wrong \n"
                + "types. One known case is the use of onlyAnnotatedWith(). \n"
                + "If you are recieving this error, please try using the \n"
                + "intersection/union methods to achieve the same results.";

        // Main query
        FullTextQuery ftQuery = session.createFullTextQuery(this.q, cls);
        ftQuery.setProjection(ProjectionConstants.SCORE, ProjectionConstants.ID);
        List<?> result = ftQuery.list();
        int totalSize = ftQuery.getResultSize();

        if (result.size() == 0) {
            // EARLY EXIT 
            return result; // of wrong type but with generics it doesn't matter
        }

        final Map<Long, Integer> order = new HashMap<Long, Integer>();
        final Map<Long, Float> scores = new HashMap<Long, Float>();
        for (int i = 0; i < result.size(); i++) {
            Object[] parts = (Object[]) result.get(i);
            scores.put((Long) parts[1], (Float) parts[0]);
            order.put((Long) parts[1], i);
        }

        // TODO Could add a performance optimization here on returnUnloaded

        criteria.add(Restrictions.in("id", scores.keySet()));
        final List<IObject> check975 = criteria.list();
        for (IObject object : check975) {
            // TODO This is now all but impossible. Remove
            if (!cls.isAssignableFrom(object.getClass())) {
                throw new ApiUsageException(String.format(ticket975, object.getClass(), cls));
            } else {
                object.putAt("TOTAL_SIZE", totalSize);
                object.putAt(ProjectionConstants.SCORE, scores.get(object.getId()));
            }
        }

        // Order return value based on the original ordering

        final Comparator cmp = new Comparator() {
            public int compare(Object obj1, Object obj2) {
                IObject o1 = (IObject) obj1;
                IObject o2 = (IObject) obj2;
                Long id1 = o1.getId();
                Long id2 = o2.getId();
                Integer idx1 = order.get(id1);
                Integer idx2 = order.get(id2);
                return idx1.compareTo(idx2);
            }
        };
        Collections.sort(check975, cmp);
        return check975;
    }
}