com.isotrol.impe3.nr.core.LuceneTranslator.java Source code

Java tutorial

Introduction

Here is the source code for com.isotrol.impe3.nr.core.LuceneTranslator.java

Source

/**
 * This file is part of Port@l
 * Port@l 3.0 - Portal Engine and Management System
 * Copyright (C) 2010  Isotrol, SA.  http://www.isotrol.com
 *
 * Port@l is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Port@l 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Port@l.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.isotrol.impe3.nr.core;

import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayListWithCapacity;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanFilter;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilterClause;
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.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeFilter;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.TermsFilter;
import org.apache.lucene.search.WildcardQuery;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.isotrol.impe3.lucene.EmptyFilter;
import com.isotrol.impe3.lucene.PortalStandardAnalyzer;
import com.isotrol.impe3.lucene.PrefixAnalyzedQueryParser;
import com.isotrol.impe3.nr.api.FilterType;
import com.isotrol.impe3.nr.api.NRBooleanClause;
import com.isotrol.impe3.nr.api.NRBooleanQuery;
import com.isotrol.impe3.nr.api.NRMatchAllDocsQuery;
import com.isotrol.impe3.nr.api.NRRangeQuery;
import com.isotrol.impe3.nr.api.NRSortField;
import com.isotrol.impe3.nr.api.NRStringQuery;
import com.isotrol.impe3.nr.api.NRTerm;
import com.isotrol.impe3.nr.api.NRTermQuery;
import com.isotrol.impe3.nr.api.NRWildcardQuery;
import com.isotrol.impe3.nr.api.NodeFilter;
import com.isotrol.impe3.nr.api.NodeKey;
import com.isotrol.impe3.nr.api.NodeQuery;
import com.isotrol.impe3.nr.api.NodeSort;
import com.isotrol.impe3.nr.api.Schema;

/**
 * Converts Node Repository queries to Lucene queries.
 * @author Emilio Escobar Reyero.
 * @author Andres Rodriguez
 */
public final class LuceneTranslator {
    static final Map<FilterType, Occur> TYPE_MAP = ImmutableMap.of(FilterType.REQUIRED, Occur.MUST,
            FilterType.OPTIONAL, Occur.SHOULD, FilterType.FORBIDDEN, Occur.MUST_NOT);
    private final ImmutableMap<Class<?>, Object> functions;

    private final Analyzer analyzer;

    private static Term allLocalesTerm() {
        return new Term(Schema.LOCALE, Schema.ALL_LOCALES);
    }

    /** Term query shorcut. */
    private static TermQuery tq(String field, String value) {
        return new TermQuery(new Term(field, value));
    }

    /** Filter criteria map to query. */
    private static <K> BooleanQuery map2query(Function<? super K, Query> f, Map<K, FilterType> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        BooleanQuery bq = new BooleanQuery();
        for (Entry<K, FilterType> e : map.entrySet()) {
            bq.add(new BooleanClause(f.apply(e.getKey()), TYPE_MAP.get(e.getValue())));
        }
        return bq;
    }

    public LuceneTranslator() {
        this(new PortalStandardAnalyzer());
    }

    public LuceneTranslator(Analyzer analyzer) {
        this.analyzer = analyzer;
        this.functions = createFunctionsMap();
    }

    /**
     * Translate a node query to lucene query
     * @param query original query
     * @return lucene query
     */
    public Query query(NodeQuery query) {
        Preconditions.checkNotNull(query);
        Preconditions.checkArgument(functions.containsKey(query.getClass()));
        @SuppressWarnings("unchecked")
        final Function<Object, Object> f = (Function<Object, Object>) functions.get(query.getClass());
        final Query q = (Query) f.apply(query);
        q.setBoost(query.getBoost());
        return q;
    }

    /**
     * Translate a node sort to lucene sort
     * @param lsort original sort
     * @return lucene sort
     */
    public Sort sort(final NodeSort lsort) {

        if (lsort == null) {
            return null;
        }

        NRSortField[] lfields = lsort.getFields();

        SortField[] fields = new SortField[lfields.length];

        for (int i = 0; i < lfields.length; i++) {
            final SortField field = new SortField(lfields[i].getField(), lfields[i].getType(),
                    lfields[i].isReverse());
            fields[i] = field;
        }

        Sort sort = new Sort(fields);

        return sort;
    }

    private BooleanQuery boolQuery(BooleanClause.Occur occur, Iterable<Query> queries) {
        if (queries == null) {
            return null;
        }
        boolean empty = true;
        final BooleanQuery bq = new BooleanQuery();
        for (Query q : filter(queries, notNull())) {
            empty = false;
            final BooleanClause clause = new BooleanClause(q, occur);
            bq.add(clause);
        }
        return empty ? null : bq;
    }

    private BooleanQuery and(Iterable<Query> queries) {
        return boolQuery(BooleanClause.Occur.MUST, queries);
    }

    private Query locales(Map<Locale, FilterType> locales) {
        if (locales == null) {
            return new TermQuery(allLocalesTerm());
        }
        return map2query(LocaleQuery.INSTANCE, locales);
    }

    /**
     * Translate a node filter to lucene query
     * @param filter original filter
     * @return A lucene query representing the filter or {@code null} if the filter is a null filter.
     */
    public Query query(NodeFilter filter) {
        if (NodeFilter.isNull(filter)) {
            return new TermQuery(allLocalesTerm());
        }
        final List<Query> queries = Lists.newArrayListWithCapacity(4);
        queries.add(map2query(SetQuery.INSTANCE, filter.sets()));
        queries.add(map2query(CategoryQuery.INSTANCE, filter.categories()));
        queries.add(map2query(ContentTypeQuery.INSTANCE, filter.contentTypes()));
        queries.add(locales(filter.locales()));
        queries.add(map2query(TagQuery.INSTANCE, filter.tags()));
        queries.add(map2query(KeyQuery.INSTANCE, filter.keys()));
        return and(queries);
    }

    /**
     * Translate a node query and a node filter into lucene query
     * @param query Node query.
     * @param filter Node filter.
     * @return A lucene query representing the query and the filter.
     */
    public Query query(NodeQuery query, NodeFilter filter) {
        Query q = query(query);
        Query f = query(filter);
        if (f == null) {
            return q;
        }
        BooleanQuery both = new BooleanQuery();
        both.add(new BooleanClause(q, BooleanClause.Occur.MUST));
        both.add(new BooleanClause(f, BooleanClause.Occur.MUST));
        return both;
    }

    private Term get(NRTerm term) {
        return new Term(term.getField(), term.getText());
    }

    private ImmutableMap<Class<?>, Object> createFunctionsMap() {
        ImmutableMap.Builder<Class<?>, Object> builder = ImmutableMap.builder();

        builder.put(NRTermQuery.class, new Function<NRTermQuery, TermQuery>() {
            public TermQuery apply(NRTermQuery input) {
                return new TermQuery(get(input.getTerm()));
            }
        });

        builder.put(NRWildcardQuery.class, new Function<NRWildcardQuery, WildcardQuery>() {
            public WildcardQuery apply(NRWildcardQuery input) {
                return new WildcardQuery(get(input.getTerm()));
            }
        });

        builder.put(NRRangeQuery.class, new Function<NRRangeQuery, Query>() {
            public Query apply(NRRangeQuery input) {
                return new TermRangeQuery(input.getFieldName(), input.getLowerVal(), input.getUpperVal(),
                        input.isIncludeLower(), input.isIncludeUpper());
            }
        });

        builder.put(NRMatchAllDocsQuery.class, new Function<NRMatchAllDocsQuery, Query>() {
            public Query apply(NRMatchAllDocsQuery input) {
                return new MatchAllDocsQuery();
            }
        });

        builder.put(NRBooleanQuery.class, new Function<NRBooleanQuery, Query>() {
            public Query apply(NRBooleanQuery input) {
                final BooleanQuery query = new BooleanQuery();
                for (BooleanClause clause : Iterables.transform(input.getClauses(),
                        new Function<NRBooleanClause, BooleanClause>() {

                            public BooleanClause apply(NRBooleanClause input) {
                                final Query q = query(input.getQuery());
                                final BooleanClause.Occur occur;
                                switch (input.getOccur()) {
                                case MUST:
                                    occur = BooleanClause.Occur.MUST;
                                    break;
                                case SHOULD:
                                    occur = BooleanClause.Occur.SHOULD;
                                    break;
                                case MUST_NOT:
                                    occur = BooleanClause.Occur.MUST_NOT;
                                    break;
                                default:
                                    throw new IllegalStateException();
                                }
                                return new BooleanClause(q, occur);
                            };
                        })) {

                    if (clause != null && clause.getQuery() != null && clause.getQuery().toString().length() > 0) {
                        query.add(clause);
                    }
                }
                return query;
            }
        });

        builder.put(NRStringQuery.class, new Function<NRStringQuery, Query>() {
            public Query apply(NRStringQuery input) {
                final NRTerm term = input.getTerm();
                final QueryParser parser = new PrefixAnalyzedQueryParser(term.getField(), analyzer);

                try {
                    return parser.parse(term.getText());
                } catch (ParseException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        });

        return builder.build();
    }

    /** Set to query. */
    private enum SetQuery implements Function<String, Query> {
        INSTANCE;

        public Query apply(String input) {
            return tq(Schema.NODESET, input);
        }
    }

    /** Category to query. */
    private enum CategoryQuery implements Function<Optional<UUID>, Query> {
        INSTANCE;

        public Query apply(Optional<UUID> input) {
            if (input.isPresent()) {
                return tq(Schema.CATEGORY, input.get().toString());
            }
            return tq(Schema.CATEGORY, Schema.NULL_UUID);
        }
    }

    /** Content type to query. */
    private enum ContentTypeQuery implements Function<UUID, Query> {
        INSTANCE;

        public Query apply(UUID input) {
            return tq(Schema.TYPE, input.toString());
        }
    }

    /** Locale to query. */
    private enum LocaleQuery implements Function<Locale, Query> {
        INSTANCE;

        public Query apply(Locale input) {
            final String v = input.toString();
            final BooleanQuery inner = new BooleanQuery();
            inner.add(new BooleanClause(tq(Schema.LOCALE, Schema.ALL_LOCALES), BooleanClause.Occur.MUST));
            inner.add(new BooleanClause(tq(Schema.OTHER_LOCALE, v), BooleanClause.Occur.MUST_NOT));
            final BooleanQuery outer = new BooleanQuery();
            outer.add(new BooleanClause(tq(Schema.LOCALE, v), BooleanClause.Occur.SHOULD));
            outer.add(new BooleanClause(inner, BooleanClause.Occur.SHOULD));
            return outer;
        }
    }

    /** Tag to query. */
    private enum TagQuery implements Function<String, Query> {
        INSTANCE;

        public Query apply(String input) {
            return tq(Schema.TAG, input);
        }
    }

    /** Node key to query. */
    private enum KeyQuery implements Function<NodeKey, Query> {
        INSTANCE;

        public Query apply(NodeKey input) {
            return tq(Schema.NODEKEY, input.toString());
        }
    }

    // Filters

    /** Filter criteria map to lucene filter. */
    private static <K> void map2filter(List<Filter> filters, Function<? super K, Filter> f,
            Map<K, FilterType> map) {
        if (map != null && !map.isEmpty()) {
            BooleanFilter bf = new BooleanFilter();
            for (Entry<K, FilterType> e : map.entrySet()) {
                bf.add(new FilterClause(f.apply(e.getKey()), TYPE_MAP.get(e.getValue())));
            }
            filters.add(bf);
        }
    }

    private static void dueFilter(List<Filter> filters) {
        final String now = Schema.dateToString(new Date());
        filters.add(new TermRangeFilter(Schema.RELEASEDATE, Schema.getMinDateString(), now, true, true));
        filters.add(new TermRangeFilter(Schema.EXPIRATIONDATE, now, Schema.getMaxDateString(), true, true));
    }

    private static TermsFilter tf(String field, String value) {
        TermsFilter tf = new TermsFilter();
        add(tf, field, value);
        return tf;
    }

    private static void add(TermsFilter tf, String field, String value) {
        tf.addTerm(new Term(field, value));
    }

    private static Filter allLocalesFilter() {
        final TermsFilter tf = new TermsFilter();
        tf.addTerm(allLocalesTerm());
        return tf;
    }

    /**
     * Node filter to Lucene filter translator.
     * @param filter Filter to translate
     * @return The lucene filter.
     */
    public Filter getFilter(NodeFilter filter) {
        if (NodeFilter.isNull(filter)) {
            return allLocalesFilter();
        }
        if (NodeFilter.isEmpty(filter)) {
            return EmptyFilter.create();
        }
        final List<Filter> filters = newArrayListWithCapacity(7);
        // Due filter.
        if (filter.isDue()) {
            dueFilter(filters);
        }
        // Collections
        map2filter(filters, SetFilter.INSTANCE, filter.sets());
        map2filter(filters, CategoryFilter.INSTANCE, filter.categories());
        map2filter(filters, ContentTypeFilter.INSTANCE, filter.contentTypes());
        if (filter.locales() == null) {
            filters.add(allLocalesFilter());
        } else {
            map2filter(filters, Locale2Filter.INSTANCE, filter.locales());
        }
        map2filter(filters, TagFilter.INSTANCE, filter.tags());
        map2filter(filters, KeyFilter.INSTANCE, filter.keys());
        // Done
        if (filters.isEmpty()) {
            return null;
        }
        if (filters.size() == 1) {
            return filters.get(0);
        }
        final BooleanFilter bf = new BooleanFilter();
        for (Filter f : filters) {
            bf.add(new FilterClause(f, Occur.MUST));
        }
        return bf;
    }

    /** Set to filter. */
    private enum SetFilter implements Function<String, Filter> {
        INSTANCE;

        public Filter apply(String input) {
            return tf(Schema.NODESET, input);
        }
    }

    /** Category to filter. */
    private enum CategoryFilter implements Function<Optional<UUID>, Filter> {
        INSTANCE;

        public Filter apply(Optional<UUID> input) {
            if (input.isPresent()) {
                return tf(Schema.CATEGORY, input.get().toString());
            }
            return tf(Schema.CATEGORY, Schema.NULL_UUID);
        }
    }

    /** Content type to filter. */
    private enum ContentTypeFilter implements Function<UUID, Filter> {
        INSTANCE;

        public Filter apply(UUID input) {
            return tf(Schema.TYPE, input.toString());
        }
    }

    /** Locale to filter. */
    private enum Locale2Filter implements Function<Locale, Filter> {
        INSTANCE;

        public Filter apply(Locale input) {
            return new LocaleFilter(input);
        }
    }

    /** Tag to filter. */
    private enum TagFilter implements Function<String, Filter> {
        INSTANCE;

        public Filter apply(String input) {
            return tf(Schema.TAG, input);
        }
    }

    /** Node key to filter. */
    private enum KeyFilter implements Function<NodeKey, Filter> {
        INSTANCE;

        public Filter apply(NodeKey input) {
            return tf(Schema.NODEKEY, input.toString());
        }
    }

}