org.alfresco.repo.search.impl.solr.DbOrIndexSwitchingQueryLanguage.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.search.impl.solr.DbOrIndexSwitchingQueryLanguage.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.search.impl.solr;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.solr.SOLRDAO;
import org.alfresco.repo.search.impl.lucene.ADMLuceneSearcherImpl;
import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryLanguage;
import org.alfresco.repo.search.impl.lucene.LuceneQueryLanguageSPI;
import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet;
import org.alfresco.repo.search.impl.querymodel.QueryModelException;
import org.alfresco.repo.search.results.ChildAssocRefResultSet;
import org.alfresco.repo.solr.NodeParameters;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.QueryConsistency;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StopWatch;

/**
 * @author Andy
 * @author Matt Ward
 */
public class DbOrIndexSwitchingQueryLanguage extends AbstractLuceneQueryLanguage {
    protected static final Log logger = LogFactory.getLog(DbOrIndexSwitchingQueryLanguage.class);

    LuceneQueryLanguageSPI dbQueryLanguage;

    LuceneQueryLanguageSPI indexQueryLanguage;

    QueryConsistency queryConsistency = QueryConsistency.DEFAULT;

    private NodeService nodeService;

    private SOLRDAO solrDao;

    private boolean hybridEnabled;

    /**
     * @param dbQueryLanguage the dbQueryLanguage to set
     */
    public void setDbQueryLanguage(LuceneQueryLanguageSPI dbQueryLanguage) {
        this.dbQueryLanguage = dbQueryLanguage;
    }

    /**
     * @param indexQueryLanguage the indexQueryLanguage to set
     */
    public void setIndexQueryLanguage(LuceneQueryLanguageSPI indexQueryLanguage) {
        this.indexQueryLanguage = indexQueryLanguage;
    }

    /**
     * @param queryConsistency the queryConsistency to set
     */
    public void setQueryConsistency(QueryConsistency queryConsistency) {
        this.queryConsistency = queryConsistency;
    }

    /**
     * @param nodeService the nodeService to set
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setSolrDao(SOLRDAO solrDao) {
        this.solrDao = solrDao;
    }

    public void setHybridEnabled(boolean hybridEnabled) {
        this.hybridEnabled = hybridEnabled;
    }

    public ResultSet executeQuery(SearchParameters searchParameters, ADMLuceneSearcherImpl admLuceneSearcher) {
        QueryConsistency consistency = searchParameters.getQueryConsistency();
        if (consistency == QueryConsistency.DEFAULT) {
            consistency = queryConsistency;
        }

        switch (consistency) {
        case EVENTUAL:
            if (indexQueryLanguage != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Using SOLR query: " + dbQueryLanguage.getName() + " for " + searchParameters);
                }
                StopWatch stopWatch = new StopWatch("index only");
                stopWatch.start();
                ResultSet results = indexQueryLanguage.executeQuery(searchParameters, admLuceneSearcher);
                stopWatch.stop();
                if (logger.isDebugEnabled()) {
                    logger.debug("SOLR returned " + results.length() + " results in "
                            + stopWatch.getLastTaskTimeMillis() + "ms");
                }
                return results;
            } else {
                throw new QueryModelException("No query language available");
            }
        case TRANSACTIONAL:
            if (dbQueryLanguage != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Trying db query for " + dbQueryLanguage.getName() + " for " + searchParameters);
                }
                StopWatch stopWatch = new StopWatch("database only");
                stopWatch.start();
                ResultSet results = dbQueryLanguage.executeQuery(flattenDBQuery(searchParameters),
                        admLuceneSearcher);
                stopWatch.stop();
                if (logger.isDebugEnabled()) {
                    logger.debug("DB returned " + results.length() + " results in "
                            + stopWatch.getLastTaskTimeMillis() + "ms");
                }
                return results;
            } else {
                throw new QueryModelException("No query language available");
            }
        case HYBRID:
            if (!hybridEnabled) {
                throw new DisabledFeatureException("Hybrid query is disabled.");
            }
            return executeHybridQuery(searchParameters, admLuceneSearcher);
        case DEFAULT:
        case TRANSACTIONAL_IF_POSSIBLE:
        default:
            StopWatch stopWatch = new StopWatch("DB if possible");
            if (dbQueryLanguage != null) {
                try {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "Trying db query for " + dbQueryLanguage.getName() + " for " + searchParameters);
                    }
                    stopWatch.start();
                    ResultSet results = dbQueryLanguage.executeQuery(flattenDBQuery(searchParameters),
                            admLuceneSearcher);
                    stopWatch.stop();
                    if (logger.isDebugEnabled()) {
                        logger.debug("DB returned " + results.length() + " results in "
                                + stopWatch.getLastTaskTimeMillis() + "ms");
                    }
                    return results;
                } catch (QueryModelException qme) {
                    if (stopWatch.isRunning()) {
                        stopWatch.stop();
                    }
                    // MNT-10323: Logging configuration on JBoss leads to clogging of the log with a lot of these errors because of INFO level when WQS module is installed
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "DB query failed for " + dbQueryLanguage.getName() + " for " + searchParameters,
                                qme);
                    }

                    if (indexQueryLanguage != null) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(
                                    "Using SOLR query: " + dbQueryLanguage.getName() + " for " + searchParameters);
                        }
                        stopWatch.start();
                        ResultSet results = indexQueryLanguage.executeQuery(searchParameters, admLuceneSearcher);
                        stopWatch.stop();
                        if (logger.isDebugEnabled()) {
                            logger.debug("SOLR returned " + results.length() + " results in "
                                    + stopWatch.getLastTaskTimeMillis() + "ms");
                        }
                        return results;
                    }
                }
            } else {
                if (indexQueryLanguage != null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("(No DB QL) Using SOLR query: " + "dbQueryLanguage==null" + " for "
                                + searchParameters);
                    }
                    stopWatch.start();
                    ResultSet results = indexQueryLanguage.executeQuery(searchParameters, admLuceneSearcher);
                    stopWatch.stop();
                    if (logger.isDebugEnabled()) {
                        logger.debug("SOLR returned " + results.length() + " results in "
                                + stopWatch.getLastTaskTimeMillis() + "ms");
                    }
                    return results;
                }
            }
            throw new QueryModelException("No query language available");
        }

    }

    private SearchParameters flattenDBQuery(SearchParameters sp) {
        if (sp.getFilterQueries().size() == 0) {
            return sp;
        } else {
            SearchParameters flatten = sp.copy();
            StringBuilder queryBuilder = new StringBuilder();

            queryBuilder.append("( ").append(sp.getQuery()).append(" )");
            for (String filter : sp.getFilterQueries()) {
                queryBuilder.append("AND ( ").append(filter).append(" )");
            }
            flatten.setQuery(queryBuilder.toString());
            // the filter can be left and will be ignored by the DB query
            return flatten;
        }
    }

    private ResultSet executeHybridQuery(SearchParameters searchParameters,
            ADMLuceneSearcherImpl admLuceneSearcher) {
        if (indexQueryLanguage == null || dbQueryLanguage == null) {
            throw new QueryModelException("Both index and DB query language required for hybrid search [index="
                    + indexQueryLanguage + ", DB=" + dbQueryLanguage + "]");
        }

        StopWatch stopWatch = new StopWatch("hybrid search");
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Hybrid search, using SOLR query: " + dbQueryLanguage.getName() + " for " + searchParameters);
        }
        stopWatch.start("index query");
        ResultSet indexResults = indexQueryLanguage.executeQuery(searchParameters, admLuceneSearcher);
        stopWatch.stop();
        if (logger.isDebugEnabled()) {
            logger.debug("SOLR query returned " + indexResults.length() + " results in "
                    + stopWatch.getLastTaskTimeMillis() + "ms");
        }
        // TODO: if the results are up-to-date, then nothing more to do - return the results.

        if (!(indexResults instanceof SolrJSONResultSet)) {
            if (logger.isWarnEnabled()) {
                logger.warn("Hybrid search can only use database when SOLR is also in use. "
                        + "Skipping DB search, returning results from index.");
            }
            return indexResults;
        }

        long lastTxId = ((SolrJSONResultSet) indexResults).getLastIndexedTxId();
        searchParameters.setSinceTxId(lastTxId);
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Hybrid search, using DB query: " + dbQueryLanguage.getName() + " for " + searchParameters);
        }
        stopWatch.start("database query");
        ResultSet dbResults = dbQueryLanguage.executeQuery(searchParameters, admLuceneSearcher);
        stopWatch.stop();
        if (logger.isDebugEnabled()) {
            logger.debug("DB query returned " + dbResults.length() + " results in "
                    + stopWatch.getLastTaskTimeMillis() + "ms");
        }
        // Merge result sets
        List<ChildAssociationRef> childAssocs = new ArrayList<>();
        NodeParameters nodeParameters = new NodeParameters();
        nodeParameters.setFromTxnId(lastTxId + 1);
        // TODO: setToTxnId(null) when SolrDAO behaviour is fixed.
        nodeParameters.setToTxnId(Long.MAX_VALUE);
        stopWatch.start("get changed nodes");
        List<Node> changedNodeList = solrDao.getNodes(nodeParameters, null);
        stopWatch.stop();
        if (logger.isDebugEnabled()) {
            logger.debug("Nodes changed since last indexed transaction (ID " + lastTxId + ") = "
                    + changedNodeList.size() + " (took " + stopWatch.getLastTaskTimeMillis() + "ms)");
        }
        stopWatch.start("merge result sets");
        Set<NodeRef> nodeRefs = new HashSet<>(changedNodeList.size());
        for (Node n : changedNodeList) {
            nodeRefs.add(n.getNodeRef());
        }
        // Only use the SOLR results for nodes that haven't changed since indexing.
        for (ChildAssociationRef car : indexResults.getChildAssocRefs()) {
            if (!nodeRefs.contains(car.getChildRef())) {
                childAssocs.add(car);
            }
        }
        // Merge in all the database results.
        childAssocs.addAll(dbResults.getChildAssocRefs());

        ResultSet results = new ChildAssocRefResultSet(nodeService, childAssocs);
        stopWatch.stop(); // merge result sets
        if (logger.isDebugEnabled()) {
            String stats = String.format("SOLR=%d, DB=%d, total=%d", indexResults.length(), dbResults.length(),
                    results.length());
            logger.debug("Hybrid search returning combined results with counts: " + stats);
            logger.debug(stopWatch.prettyPrint());
        }
        return results;
    }
}