/* Copyright (2007-2012) Schibsted ASA * This file is part of Possom. * * Possom 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. * * Possom 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 Possom. If not, see <http://www.gnu.org/licenses/>. */ package no.sesat.search.mode.command; import com.fastsearch.esp.search.query.BaseParameter; import com.fastsearch.esp.search.query.IQuery; import com.fastsearch.esp.search.result.EmptyValueException; import com.fastsearch.esp.search.result.IDocumentSummary; import com.fastsearch.esp.search.result.IDocumentSummaryField; import com.fastsearch.esp.search.result.IQueryResult; import com.fastsearch.esp.search.result.IllegalType; import no.sesat.search.datamodel.generic.StringDataObject; import no.sesat.search.mode.config.ClusteringEspFastCommandConfig; import no.sesat.search.result.FastSearchResult; import no.sesat.search.result.ResultItem; import no.sesat.search.result.ResultList; import org.apache.log4j.Logger; import java.io.IOException; /** Search command to cluster results from a NewsEspSearchCommand. * * * @version $Id$ */ public class NewsClusteringESPFastCommand extends NewsEspSearchCommand { private static final Logger LOG = Logger.getLogger(NewsClusteringESPFastCommand.class); private static final String ERR_CONVERT = "Could not convert result"; public NewsClusteringESPFastCommand(final Context cxt) { super(cxt); } /** * Modifies several queryParameters depending on situation. * * @param query the FAST IQuery to modify */ @Override protected void modifyQuery(final IQuery query) { super.modifyQuery(query); final ClusteringEspFastCommandConfig config = getSearchConfiguration(); final StringDataObject clusterId = datamodel.getParameters().getValue(config.getClusterIdParameter()); if (clusterId == null && ! config.isClusteringDisabled()) { LOG.debug("--- Modifying query. ---"); final int resultsPerCluster = config.getResultsPerCluster(); final int resultCount = config.getResultsToReturn() * resultsPerCluster; query.setParameter("collapseon", "batv" + config.getClusterField()); query.setParameter("collapsenum", resultsPerCluster); query.setParameter(BaseParameter.HITS, Math.max(resultCount, config.getCollapsingMaxFetch())); } } /** * Creates a clustered FastSearchResult. * * @param result the clustered searchResult * @return the FAST IQueryReslt to make the searchResult from * @throws IOException */ @Override protected FastSearchResult<ResultItem> createSearchResult(final IQueryResult result) throws IOException { try { final ClusteringEspFastCommandConfig config = getSearchConfiguration(); final StringDataObject clusterId = datamodel.getParameters() != null ? datamodel.getParameters().getValue(config.getClusterIdParameter()) : null; if (config.isClusteringDisabled()) { final FastSearchResult<ResultItem> searchResult = super.createSearchResult(result); final int offset = getOffset(); if (offset + config.getResultsToReturn() < result.getDocCount()) { addNextOffsetField(offset + config.getResultsToReturn(), searchResult); } return searchResult; } else if (clusterId == null) { return createClusteredSearchResult(config, getOffset(), result); } else { return createCollapsedResults(config, getOffset(), result); } } catch (IllegalType e) { LOG.error(ERR_CONVERT, e); } catch (EmptyValueException e) { LOG.error(ERR_CONVERT, e); } catch (RuntimeException e) { LOG.error(ERR_CONVERT, e); } // Falling back to super implementation, because this one does not work. return super.createSearchResult(result); } private FastSearchResult<ResultItem> createClusteredSearchResult( final ClusteringEspFastCommandConfig config, final int offset, final IQueryResult result) throws IllegalType, EmptyValueException { final String clusterField = config.getClusterField(); // final String nestedResultsField = config.getNestedResultsField(); // Following will throw either ClassCastException or NPE of navigators are used final FastSearchResult<ResultItem> searchResult = new FastSearchResult<ResultItem>(); final int maxClusterCount = config.getResultsToReturn(); IDocumentSummaryField currentClusterId; IDocumentSummaryField lastClusterId = null; int collectedClusters = 0; int collectedHits = 0; ResultList<ResultItem> clusterEntry = null; LOG.debug("HitCount=" + result.getDocCount() + ", clusterField=" + clusterField + ", offset=" + offset); final int firstHit = offset; for (int i = firstHit; i < result.getDocCount(); i++) { try { final IDocumentSummary document = result.getDocument(i + 1); currentClusterId = document.getSummaryField(clusterField); if (currentClusterId.isEmpty() || lastClusterId == null || lastClusterId.isEmpty() || currentClusterId.getStringValue().equals("0") || (!currentClusterId.getStringValue().equals(lastClusterId.getStringValue()))) { LOG.debug("Adding new cluster: " + currentClusterId + ", count is: " + collectedClusters); if (collectedClusters < maxClusterCount) { clusterEntry = addResult(config, searchResult, document); if (!currentClusterId.isEmpty()) { clusterEntry = clusterEntry.addField( config.getClusterIdParameter(), currentClusterId.getStringValue()); } final IDocumentSummaryField clusterHitCount = document.getSummaryField("fcocount"); if (!clusterHitCount.isEmpty()) { clusterEntry.setHitCount(Integer.parseInt(clusterHitCount.getStringValue())); } lastClusterId = currentClusterId; } else { break; } collectedClusters++; } else { LOG.debug("Adding subResult for: " + currentClusterId.getStringValue()); addResult(config, clusterEntry, document); } collectedHits++; } catch (NullPointerException e) { // The doc count is not 100% accurate. LOG.debug("Error finding document " + e); break; } } if (offset + collectedHits < result.getDocCount()) { addNextOffsetField(offset + collectedHits, searchResult); } searchResult.setHitCount(result.getDocCount()); return searchResult; } @Override public ClusteringEspFastCommandConfig getSearchConfiguration() { return (ClusteringEspFastCommandConfig) super.getSearchConfiguration(); } }