You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mv...@apache.org on 2011/09/17 14:48:28 UTC

svn commit: r1171970 [2/3] - in /lucene/dev/trunk: lucene/src/java/org/apache/lucene/search/ modules/grouping/src/java/org/apache/lucene/search/grouping/ solr/ solr/core/src/java/org/apache/solr/handler/component/ solr/core/src/java/org/apache/solr/sea...

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/QueryCommandResult.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/QueryCommandResult.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/QueryCommandResult.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/QueryCommandResult.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,42 @@
+package org.apache.solr.search.grouping.distributed.command;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.TopDocs;
+
+/**
+ * Encapsulates {@link TopDocs} and the number of matches.
+ */
+public class QueryCommandResult {
+
+  private final TopDocs topDocs;
+  private final int matches;
+
+  public QueryCommandResult(TopDocs topDocs, int matches) {
+    this.topDocs = topDocs;
+    this.matches = matches;
+  }
+
+  public TopDocs getTopDocs() {
+    return topDocs;
+  }
+
+  public int getMatches() {
+    return matches;
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/SearchGroupsFieldCommand.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,101 @@
+package org.apache.solr.search.grouping.distributed.command;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.grouping.SearchGroup;
+import org.apache.lucene.search.grouping.TermFirstPassGroupingCollector;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.grouping.Command;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ *
+ */
+public class SearchGroupsFieldCommand implements Command<Collection<SearchGroup<BytesRef>>> {
+
+  public static class Builder {
+
+    private SchemaField field;
+    private Sort groupSort;
+    private Integer topNGroups;
+
+    public Builder setField(SchemaField field) {
+      this.field = field;
+      return this;
+    }
+
+    public Builder setGroupSort(Sort groupSort) {
+      this.groupSort = groupSort;
+      return this;
+    }
+
+    public Builder setTopNGroups(int topNGroups) {
+      this.topNGroups = topNGroups;
+      return this;
+    }
+
+    public SearchGroupsFieldCommand build() {
+      if (field == null || groupSort == null || topNGroups == null) {
+        throw new IllegalStateException("All fields must be set");
+      }
+
+      return new SearchGroupsFieldCommand(field, groupSort, topNGroups);
+    }
+
+  }
+
+  private final SchemaField field;
+  private final Sort groupSort;
+  private final int topNGroups;
+
+  private TermFirstPassGroupingCollector collector;
+
+  private SearchGroupsFieldCommand(SchemaField field, Sort groupSort, int topNGroups) {
+    this.field = field;
+    this.groupSort = groupSort;
+    this.topNGroups = topNGroups;
+  }
+
+  public List<Collector> create() throws IOException {
+    collector = new TermFirstPassGroupingCollector(field.getName(), groupSort, topNGroups);
+    return Arrays.asList((Collector) collector);
+  }
+
+  public Collection<SearchGroup<BytesRef>> result() {
+    return collector.getTopGroups(0, true);
+  }
+
+  public Sort getSortWithinGroup() {
+    return null;
+  }
+
+  public Sort getGroupSort() {
+    return groupSort;
+  }
+
+  public String getKey() {
+    return field.getName();
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/command/TopGroupsFieldCommand.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,165 @@
+package org.apache.solr.search.grouping.distributed.command;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.grouping.SearchGroup;
+import org.apache.lucene.search.grouping.TermAllGroupsCollector;
+import org.apache.lucene.search.grouping.TermSecondPassGroupingCollector;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.grouping.Command;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ *
+ */
+public class TopGroupsFieldCommand implements Command<TopGroups<BytesRef>> {
+
+  public static class Builder {
+
+    private SchemaField field;
+    private Sort groupSort;
+    private Sort sortWithinGroup;
+    private Collection<SearchGroup<BytesRef>> firstPhaseGroups;
+    private Integer maxDocPerGroup;
+    private boolean needScores = false;
+    private boolean needMaxScore = false;
+    private boolean needGroupCount = false;
+
+    public Builder setField(SchemaField field) {
+      this.field = field;
+      return this;
+    }
+
+    public Builder setGroupSort(Sort groupSort) {
+      this.groupSort = groupSort;
+      return this;
+    }
+
+    public Builder setSortWithinGroup(Sort sortWithinGroup) {
+      this.sortWithinGroup = sortWithinGroup;
+      return this;
+    }
+
+    public Builder setFirstPhaseGroups(Collection<SearchGroup<BytesRef>> firstPhaseGroups) {
+      this.firstPhaseGroups = firstPhaseGroups;
+      return this;
+    }
+
+    public Builder setMaxDocPerGroup(int maxDocPerGroup) {
+      this.maxDocPerGroup = maxDocPerGroup;
+      return this;
+    }
+
+    public Builder setNeedScores(Boolean needScores) {
+      this.needScores = needScores;
+      return this;
+    }
+
+    public Builder setNeedMaxScore(Boolean needMaxScore) {
+      this.needMaxScore = needMaxScore;
+      return this;
+    }
+
+    public Builder setNeedGroupCount(Boolean needGroupCount) {
+      this.needGroupCount = needGroupCount;
+      return this;
+    }
+
+    public TopGroupsFieldCommand build() {
+      if (field == null || groupSort == null ||  sortWithinGroup == null || firstPhaseGroups == null ||
+          maxDocPerGroup == null) {
+        throw new IllegalStateException("All required fields must be set");
+      }
+
+      return new TopGroupsFieldCommand(field, groupSort, sortWithinGroup, firstPhaseGroups, maxDocPerGroup, needScores, needMaxScore, needGroupCount);
+    }
+
+  }
+
+  private final SchemaField field;
+  private final Sort groupSort;
+  private final Sort sortWithinGroup;
+  private final Collection<SearchGroup<BytesRef>> firstPhaseGroups;
+  private final int maxDocPerGroup;
+  private final boolean needScores;
+  private final boolean needMaxScore;
+  private final boolean needGroupCount;
+
+  private TermSecondPassGroupingCollector secondPassCollector;
+  private TermAllGroupsCollector allGroupsCollector;
+
+  private TopGroupsFieldCommand(SchemaField field,
+                                Sort groupSort,
+                                Sort sortWithinGroup,
+                                Collection<SearchGroup<BytesRef>> firstPhaseGroups,
+                                int maxDocPerGroup,
+                                boolean needScores,
+                                boolean needMaxScore,
+                                boolean needGroupCount) {
+    this.field = field;
+    this.groupSort = groupSort;
+    this.sortWithinGroup = sortWithinGroup;
+    this.firstPhaseGroups = firstPhaseGroups;
+    this.maxDocPerGroup = maxDocPerGroup;
+    this.needScores = needScores;
+    this.needMaxScore = needMaxScore;
+    this.needGroupCount = needGroupCount;
+  }
+
+  public List<Collector> create() throws IOException {
+    List<Collector> collectors = new ArrayList<Collector>();
+    secondPassCollector = new TermSecondPassGroupingCollector(
+          field.getName(), firstPhaseGroups, groupSort, sortWithinGroup, maxDocPerGroup, needScores, needMaxScore, true
+    );
+    collectors.add(secondPassCollector);
+    if (!needGroupCount) {
+      return collectors;
+    }
+    allGroupsCollector = new TermAllGroupsCollector(field.getName());
+    collectors.add(allGroupsCollector);
+    return collectors;
+  }
+
+  public TopGroups<BytesRef> result() {
+    TopGroups<BytesRef> result = secondPassCollector.getTopGroups(0);
+    if (allGroupsCollector != null) {
+      result = new TopGroups<BytesRef>(result, allGroupsCollector.getGroupCount());
+    }
+    return result;
+  }
+
+  public String getKey() {
+    return field.getName();
+  }
+
+  public Sort getGroupSort() {
+    return groupSort;
+  }
+
+  public Sort getSortWithinGroup() {
+    return sortWithinGroup;
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/SearchGroupsRequestFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/SearchGroupsRequestFactory.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/SearchGroupsRequestFactory.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/SearchGroupsRequestFactory.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,84 @@
+package org.apache.solr.search.grouping.distributed.requestfactory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.search.grouping.distributed.ShardRequestFactory;
+
+/**
+ * Concrete implementation of {@link ShardRequestFactory} that creates {@link ShardRequest} instances for getting the
+ * search groups from all shards.
+ */
+public class SearchGroupsRequestFactory implements ShardRequestFactory {
+
+  /**
+   * {@inheritDoc}
+   */
+  public ShardRequest[] constructRequest(ResponseBuilder rb) {
+    ShardRequest sreq = new ShardRequest();
+    GroupingSpecification groupingSpecification = rb.getGroupingSpec();
+    if (groupingSpecification.getFields().length == 0) {
+      return new ShardRequest[0];
+    }
+
+    sreq.purpose = ShardRequest.PURPOSE_GET_TOP_GROUPS;
+
+    sreq.params = new ModifiableSolrParams(rb.req.getParams());
+    // TODO: base on current params or original params?
+
+    // don't pass through any shards param
+    sreq.params.remove(ShardParams.SHARDS);
+
+    // set the start (offset) to 0 for each shard request so we can properly merge
+    // results from the start.
+    if(rb.shards_start > -1) {
+      // if the client set shards.start set this explicitly
+      sreq.params.set(CommonParams.START,rb.shards_start);
+    } else {
+      sreq.params.set(CommonParams.START, "0");
+    }
+    // TODO: should we even use the SortSpec?  That's obtained from the QParser, and
+    // perhaps we shouldn't attempt to parse the query at this level?
+    // Alternate Idea: instead of specifying all these things at the upper level,
+    // we could just specify that this is a shard request.
+    if(rb.shards_rows > -1) {
+      // if the client set shards.rows set this explicity
+      sreq.params.set(CommonParams.ROWS,rb.shards_rows);
+    } else {
+      sreq.params.set(CommonParams.ROWS, rb.getSortSpec().getOffset() + rb.getSortSpec().getCount());
+    }
+
+    // in this first phase, request only the unique key field
+    // and any fields needed for merging.
+    sreq.params.set("group.distibuted.first","true");
+
+    if ( (rb.getFieldFlags() & SolrIndexSearcher.GET_SCORES)!=0 || rb.getSortSpec().includesScore()) {
+      sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName() + ",score");
+    } else {
+      sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName());
+    }
+    return new ShardRequest[] {sreq};
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/StoredFieldsShardRequestFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/StoredFieldsShardRequestFactory.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/StoredFieldsShardRequestFactory.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/StoredFieldsShardRequestFactory.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,100 @@
+package org.apache.solr.search.grouping.distributed.requestfactory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.GroupParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardDoc;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.grouping.distributed.ShardRequestFactory;
+import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
+
+import java.util.*;
+
+/**
+ *
+ */
+public class StoredFieldsShardRequestFactory implements ShardRequestFactory {
+
+  @Override
+  public ShardRequest[] constructRequest(ResponseBuilder rb) {
+    HashMap<String, Set<ShardDoc>> shardMap = new HashMap<String,Set<ShardDoc>>();
+    for (TopGroups<BytesRef> topGroups : rb.mergedTopGroups.values()) {
+      for (GroupDocs<BytesRef> group : topGroups.groups) {
+        mapShardToDocs(shardMap, group.scoreDocs);
+      }
+    }
+
+    for (QueryCommandResult queryCommandResult : rb.mergedQueryCommandResults.values()) {
+      mapShardToDocs(shardMap, queryCommandResult.getTopDocs().scoreDocs);
+    }
+
+    ShardRequest[] shardRequests = new ShardRequest[shardMap.size()];
+    SchemaField uniqueField = rb.req.getSchema().getUniqueKeyField();
+    int i = 0;
+    for (Collection<ShardDoc> shardDocs : shardMap.values()) {
+      ShardRequest sreq = new ShardRequest();
+      sreq.purpose = ShardRequest.PURPOSE_GET_FIELDS;
+      sreq.shards = new String[] {shardDocs.iterator().next().shard};
+      sreq.params = new ModifiableSolrParams();
+      sreq.params.add( rb.req.getParams());
+      sreq.params.remove(GroupParams.GROUP);
+      sreq.params.remove(CommonParams.SORT);
+      sreq.params.remove(ResponseBuilder.FIELD_SORT_VALUES);
+      String fl = sreq.params.get(CommonParams.FL);
+      if (fl != null) {
+         fl = fl.trim();
+        // currently, "score" is synonymous with "*,score" so
+        // don't add "id" if the fl is empty or "score" or it would change the meaning.
+         if (fl.length()!=0 && !"score".equals(fl) && !"*".equals(fl)) {
+           sreq.params.set(CommonParams.FL, fl+','+uniqueField.getName());
+         }
+      }
+
+      List<String> ids = new ArrayList<String>(shardDocs.size());
+      for (ShardDoc shardDoc : shardDocs) {
+        ids.add(shardDoc.id.toString());
+      }
+      sreq.params.add(ShardParams.IDS, StrUtils.join(ids, ','));
+      shardRequests[i++] = sreq;
+    }
+
+    return shardRequests;
+  }
+
+  private void mapShardToDocs(HashMap<String, Set<ShardDoc>> shardMap, ScoreDoc[] scoreDocs) {
+    for (ScoreDoc scoreDoc : scoreDocs) {
+      ShardDoc solrDoc = (ShardDoc) scoreDoc;
+      Set<ShardDoc> shardDocs = shardMap.get(solrDoc.shard);
+      if (shardDocs == null) {
+        shardMap.put(solrDoc.shard, shardDocs = new HashSet<ShardDoc>());
+      }
+      shardDocs.add(solrDoc);
+    }
+  }
+
+}
\ No newline at end of file

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/TopGroupsShardRequestFactory.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/TopGroupsShardRequestFactory.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/TopGroupsShardRequestFactory.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/requestfactory/TopGroupsShardRequestFactory.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,182 @@
+package org.apache.solr.search.grouping.distributed.requestfactory;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.analysis.reverse.ReverseStringFilter;
+import org.apache.lucene.search.grouping.SearchGroup;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.GroupParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.search.Grouping;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.search.grouping.distributed.ShardRequestFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Concrete implementation of {@link ShardRequestFactory} that creates {@link ShardRequest} instances for getting the
+ * top groups from all shards.
+ */
+public class TopGroupsShardRequestFactory implements ShardRequestFactory {
+
+  /**
+   * Represents a string value for
+   */
+  public static final String GROUP_NULL_VALUE = "" + ReverseStringFilter.START_OF_HEADING_MARKER;
+
+  /**
+   * {@inheritDoc}
+   */
+  public ShardRequest[] constructRequest(ResponseBuilder rb) {
+    // If we have a group.query we need to query all shards... Or we move this to the group first phase queries
+    boolean containsGroupByQuery = rb.getGroupingSpec().getQueries().length > 0;
+    // TODO: If groups.truncate=true we only have to query the specific shards even faceting and statistics are enabled
+    if ((rb.getQueryCommand().getFlags() & SolrIndexSearcher.GET_DOCSET) != 0 || containsGroupByQuery) {
+      // In case we need more results such as faceting and statistics we have to query all shards
+      return createRequestForAllShards(rb);
+    } else {
+      // In case we only need top groups we only have to query the shards that contain these groups.
+      return createRequestForSpecificShards(rb);
+    }
+  }
+
+  private ShardRequest[] createRequestForSpecificShards(ResponseBuilder rb) {
+    // Determine all unique shards to query for TopGroups
+    Set<String> shards = new HashSet<String>();
+    for (String command : rb.searchGroupToShard.keySet()) {
+      Map<SearchGroup<BytesRef>, String> groupsToShard = rb.searchGroupToShard.get(command);
+      shards.addAll(groupsToShard.values());
+    }
+
+    ShardRequest[] sreqs = new ShardRequest[shards.size()];
+    int i = 0;
+    for (String shard : shards) {
+      ShardRequest sreq = new ShardRequest();
+      sreq.purpose = ShardRequest.PURPOSE_GET_TOP_IDS;
+      sreq.actualShards = new String[] {shard};
+      sreq.params = new ModifiableSolrParams(rb.req.getParams());
+
+      // If group.format=simple group.offset doesn't make sense
+      Grouping.Format responseFormat = rb.getGroupingSpec().getResponseFormat();
+      if (responseFormat == Grouping.Format.simple || rb.getGroupingSpec().isMain()) {
+        sreq.params.remove(GroupParams.GROUP_OFFSET);
+      }
+
+      sreq.params.remove(ShardParams.SHARDS);
+
+      // set the start (offset) to 0 for each shard request so we can properly merge
+      // results from the start.
+      if(rb.shards_start > -1) {
+        // if the client set shards.start set this explicitly
+        sreq.params.set(CommonParams.START,rb.shards_start);
+      } else {
+        sreq.params.set(CommonParams.START, "0");
+      }
+      if(rb.shards_rows > -1) {
+        // if the client set shards.rows set this explicity
+        sreq.params.set(CommonParams.ROWS,rb.shards_rows);
+      } else {
+        sreq.params.set(CommonParams.ROWS, rb.getSortSpec().getOffset() + rb.getSortSpec().getCount());
+      }
+
+      sreq.params.set("group.distibuted.second","true");
+      for (Map.Entry<String, Collection<SearchGroup<BytesRef>>> entry : rb.mergedSearchGroups.entrySet()) {
+        for (SearchGroup<BytesRef> searchGroup : entry.getValue()) {
+          String groupValue;
+          if (searchGroup.groupValue != null) {
+            String rawGroupValue = searchGroup.groupValue.utf8ToString();
+            FieldType fieldType = rb.req.getSearcher().getSchema().getField(entry.getKey()).getType();
+            groupValue = fieldType.indexedToReadable(rawGroupValue);
+          } else {
+            groupValue = GROUP_NULL_VALUE;
+          }
+          sreq.params.add("group.topgroups." + entry.getKey(), groupValue);
+        }
+      }
+
+      if ((rb.getFieldFlags() & SolrIndexSearcher.GET_SCORES) != 0 || rb.getSortSpec().includesScore()) {
+        sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName() + ",score");
+      } else {
+        sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName());
+      }
+      sreqs[i++] = sreq;
+    }
+
+    return sreqs;
+  }
+
+  private ShardRequest[] createRequestForAllShards(ResponseBuilder rb) {
+    ShardRequest sreq = new ShardRequest();
+    sreq.purpose = ShardRequest.PURPOSE_GET_TOP_IDS;
+
+    sreq.params = new ModifiableSolrParams(rb.req.getParams());
+    // If group.format=simple group.offset doesn't make sense
+    Grouping.Format responseFormat = rb.getGroupingSpec().getResponseFormat();
+    if (responseFormat == Grouping.Format.simple || rb.getGroupingSpec().isMain()) {
+      sreq.params.remove(GroupParams.GROUP_OFFSET);
+    }
+    sreq.params.remove(ShardParams.SHARDS);
+
+    // set the start (offset) to 0 for each shard request so we can properly merge
+    // results from the start.
+    if(rb.shards_start > -1) {
+      // if the client set shards.start set this explicitly
+      sreq.params.set(CommonParams.START,rb.shards_start);
+    } else {
+      sreq.params.set(CommonParams.START, "0");
+    }
+    if(rb.shards_rows > -1) {
+      // if the client set shards.rows set this explicity
+      sreq.params.set(CommonParams.ROWS,rb.shards_rows);
+    } else {
+      sreq.params.set(CommonParams.ROWS, rb.getSortSpec().getOffset() + rb.getSortSpec().getCount());
+    }
+
+    sreq.params.set("group.distibuted.second","true");
+    for (Map.Entry<String, Collection<SearchGroup<BytesRef>>> entry : rb.mergedSearchGroups.entrySet()) {
+      for (SearchGroup<BytesRef> searchGroup : entry.getValue()) {
+        String groupValue;
+        if (searchGroup.groupValue != null) {
+         String rawGroupValue = searchGroup.groupValue.utf8ToString();
+          FieldType fieldType = rb.req.getSearcher().getSchema().getField(entry.getKey()).getType();
+          groupValue = fieldType.indexedToReadable(rawGroupValue);
+        } else {
+          groupValue = GROUP_NULL_VALUE;
+        }
+        sreq.params.add("group.topgroups." + entry.getKey(), groupValue);
+      }
+    }
+
+    if ( (rb.getFieldFlags() & SolrIndexSearcher.GET_SCORES)!=0 || rb.getSortSpec().includesScore()) {
+      sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName() + ",score");
+    } else {
+      sreq.params.set(CommonParams.FL, rb.req.getSchema().getUniqueKeyField().getName());
+    }
+
+    return new ShardRequest[] {sreq};
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/SearchGroupShardResponseProcessor.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,93 @@
+package org.apache.solr.search.grouping.distributed.responseprocessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.grouping.SearchGroup;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.search.SortSpec;
+import org.apache.solr.search.grouping.distributed.ShardResponseProcessor;
+import org.apache.solr.search.grouping.distributed.shardresultserializer.SearchGroupsResultTransformer;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Concrete implementation for merging {@link SearchGroup} instances from shard responses.
+ */
+public class SearchGroupShardResponseProcessor implements ShardResponseProcessor {
+
+  /**
+   * {@inheritDoc}
+   */
+  public void process(ResponseBuilder rb, ShardRequest shardRequest) {
+    SortSpec ss = rb.getSortSpec();
+    Sort groupSort = rb.getGroupingSpec().getGroupSort();
+    String[] fields = rb.getGroupingSpec().getFields();
+
+    Map<String, List<Collection<SearchGroup<BytesRef>>>> commandSearchGroups = new HashMap<String, List<Collection<SearchGroup<BytesRef>>>>();
+    Map<String, Map<SearchGroup<BytesRef>, String>> tempSearchGroupToShard = new HashMap<String, Map<SearchGroup<BytesRef>, String>>();
+    for (String field : fields) {
+      commandSearchGroups.put(field, new ArrayList<Collection<SearchGroup<BytesRef>>>(shardRequest.responses.size()));
+      tempSearchGroupToShard.put(field, new HashMap<SearchGroup<BytesRef>, String>());
+      if (!rb.searchGroupToShard.containsKey(field)) {
+        rb.searchGroupToShard.put(field, new HashMap<SearchGroup<BytesRef>, String>());
+      }
+    }
+
+    SearchGroupsResultTransformer serializer = new SearchGroupsResultTransformer(rb.req.getSearcher());
+    try {
+      for (ShardResponse srsp : shardRequest.responses) {
+        @SuppressWarnings("unchecked")
+        NamedList<NamedList> firstPhaseResult = (NamedList<NamedList>) srsp.getSolrResponse().getResponse().get("firstPhase");
+        Map<String, Collection<SearchGroup<BytesRef>>> result = serializer.transformToNative(firstPhaseResult, groupSort, null, srsp.getShard());
+        for (String field : commandSearchGroups.keySet()) {
+          Collection<SearchGroup<BytesRef>> searchGroups = result.get(field);
+          if (searchGroups == null) {
+            continue;
+          }
+
+          commandSearchGroups.get(field).add(searchGroups);
+          for (SearchGroup<BytesRef> searchGroup : searchGroups) {
+            tempSearchGroupToShard.get(field).put(searchGroup, srsp.getShard());
+          }
+        }
+      }
+      for (String groupField : commandSearchGroups.keySet()) {
+        List<Collection<SearchGroup<BytesRef>>> topGroups = commandSearchGroups.get(groupField);
+        Collection<SearchGroup<BytesRef>> mergedTopGroups = SearchGroup.merge(topGroups, ss.getOffset(), ss.getCount(), groupSort);
+        if (mergedTopGroups == null) {
+          continue;
+        }
+
+        rb.mergedSearchGroups.put(groupField, mergedTopGroups);
+        for (SearchGroup<BytesRef> mergedTopGroup : mergedTopGroups) {
+          rb.searchGroupToShard.get(groupField).put(mergedTopGroup, tempSearchGroupToShard.get(groupField).get(mergedTopGroup));
+        }
+      }
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+    }
+  }
+
+}
\ No newline at end of file

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/StoredFieldsShardResponseProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/StoredFieldsShardResponseProcessor.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/StoredFieldsShardResponseProcessor.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/StoredFieldsShardResponseProcessor.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,56 @@
+package org.apache.solr.search.grouping.distributed.responseprocessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.FieldDoc;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardDoc;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.search.grouping.distributed.ShardResponseProcessor;
+
+/**
+ * Concrete implementation for processing the stored field values from shard responses.
+ */
+public class StoredFieldsShardResponseProcessor implements ShardResponseProcessor {
+
+  /**
+   * {@inheritDoc}
+   */
+  public void process(ResponseBuilder rb, ShardRequest shardRequest) {
+    boolean returnScores = (rb.getFieldFlags() & SolrIndexSearcher.GET_SCORES) != 0;
+    ShardResponse srsp = shardRequest.responses.get(0);
+    SolrDocumentList docs = (SolrDocumentList)srsp.getSolrResponse().getResponse().get("response");
+    String uniqueIdFieldName = rb.req.getSchema().getUniqueKeyField().getName();
+
+    for (SolrDocument doc : docs) {
+      Object id = doc.getFieldValue(uniqueIdFieldName).toString();
+      ShardDoc shardDoc = rb.resultIds.get(id);
+      FieldDoc fieldDoc = (FieldDoc) shardDoc;
+      if (shardDoc != null) {
+        if (returnScores && !Float.isNaN(fieldDoc.score)) {
+            doc.setField("score", fieldDoc.score);
+        }
+        rb.retrievedDocuments.put(id, doc);
+      }
+    }
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/responseprocessor/TopGroupsShardResponseProcessor.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,141 @@
+package org.apache.solr.search.grouping.distributed.responseprocessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardDoc;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.search.Grouping;
+import org.apache.solr.search.grouping.distributed.ShardResponseProcessor;
+import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
+import org.apache.solr.search.grouping.distributed.shardresultserializer.TopGroupsResultTransformer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Concrete implementation for merging {@link TopGroups} instances from shard responses.
+ */
+public class TopGroupsShardResponseProcessor implements ShardResponseProcessor {
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  public void process(ResponseBuilder rb, ShardRequest shardRequest) {
+    Sort groupSort = rb.getGroupingSpec().getGroupSort();
+    String[] fields = rb.getGroupingSpec().getFields();
+    String[] queries = rb.getGroupingSpec().getQueries();
+    Sort sortWithinGroup = rb.getGroupingSpec().getSortWithinGroup();
+
+    // If group.format=simple group.offset doesn't make sense
+    int groupOffsetDefault;
+    if (rb.getGroupingSpec().getResponseFormat() == Grouping.Format.simple || rb.getGroupingSpec().isMain()) {
+      groupOffsetDefault = 0;
+    } else {
+      groupOffsetDefault = rb.getGroupingSpec().getGroupOffset();
+    }
+    int docsPerGroupDefault = rb.getGroupingSpec().getGroupLimit();
+
+    Map<String, List<TopGroups<BytesRef>>> commandTopGroups = new HashMap<String, List<TopGroups<BytesRef>>>();
+    for (String field : fields) {
+      commandTopGroups.put(field, new ArrayList<TopGroups<BytesRef>>());
+    }
+
+    Map<String, List<QueryCommandResult>> commandTopDocs = new HashMap<String, List<QueryCommandResult>>();
+    for (String query : queries) {
+      commandTopDocs.put(query, new ArrayList<QueryCommandResult>());
+    }
+
+    TopGroupsResultTransformer serializer = new TopGroupsResultTransformer(rb);
+    for (ShardResponse srsp : shardRequest.responses) {
+      NamedList<NamedList> secondPhaseResult = (NamedList<NamedList>) srsp.getSolrResponse().getResponse().get("secondPhase");
+      Map<String, ?> result = serializer.transformToNative(secondPhaseResult, groupSort, sortWithinGroup, srsp.getShard());
+      for (String field : commandTopGroups.keySet()) {
+        TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) result.get(field);
+        if (topGroups == null) {
+          continue;
+        }
+        commandTopGroups.get(field).add(topGroups);
+      }
+      for (String query : queries) {
+        commandTopDocs.get(query).add((QueryCommandResult) result.get(query));
+      }
+    }
+    try {
+      for (String groupField : commandTopGroups.keySet()) {
+        List<TopGroups<BytesRef>> topGroups = commandTopGroups.get(groupField);
+        if (topGroups.isEmpty()) {
+          continue;
+        }
+
+        TopGroups<BytesRef>[] topGroupsArr = new TopGroups[topGroups.size()];
+        rb.mergedTopGroups.put(groupField, TopGroups.merge(topGroups.toArray(topGroupsArr), groupSort, sortWithinGroup, groupOffsetDefault, docsPerGroupDefault));
+      }
+
+      for (String query : commandTopDocs.keySet()) {
+        List<QueryCommandResult> queryCommandResults = commandTopDocs.get(query);
+        List<TopDocs> topDocs = new ArrayList<TopDocs>(queryCommandResults.size());
+        int mergedMatches = 0;
+        for (QueryCommandResult queryCommandResult : queryCommandResults) {
+          topDocs.add(queryCommandResult.getTopDocs());
+          mergedMatches += queryCommandResult.getMatches();
+        }
+
+        int topN = rb.getGroupingSpec().getOffset() + rb.getGroupingSpec().getLimit();
+        TopDocs mergedTopDocs = TopDocs.merge(sortWithinGroup, topN, topDocs.toArray(new TopDocs[topDocs.size()]));
+        rb.mergedQueryCommandResults.put(query, new QueryCommandResult(mergedTopDocs, mergedMatches));
+      }
+
+      Map<Object, ShardDoc> resultIds = new HashMap<Object, ShardDoc>();
+      int i = 0;
+      for (TopGroups<BytesRef> topGroups : rb.mergedTopGroups.values()) {
+        for (GroupDocs<BytesRef> group : topGroups.groups) {
+          for (ScoreDoc scoreDoc : group.scoreDocs) {
+            ShardDoc solrDoc = (ShardDoc) scoreDoc;
+            solrDoc.positionInResponse = i++;
+            resultIds.put(solrDoc.id, solrDoc);
+          }
+        }
+      }
+      for (QueryCommandResult queryCommandResult : rb.mergedQueryCommandResults.values()) {
+        for (ScoreDoc scoreDoc : queryCommandResult.getTopDocs().scoreDocs) {
+          ShardDoc solrDoc = (ShardDoc) scoreDoc;
+          solrDoc.positionInResponse = i++;
+          resultIds.put(solrDoc.id, solrDoc);
+        }
+      }
+
+      rb.resultIds = resultIds;
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+    }
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/SearchGroupsResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/SearchGroupsResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/SearchGroupsResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/SearchGroupsResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,117 @@
+package org.apache.solr.search.grouping.distributed.shardresultserializer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.grouping.SearchGroup;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.CharsRef;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.search.grouping.Command;
+import org.apache.solr.search.grouping.distributed.command.SearchGroupsFieldCommand;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Implementation for transforming {@link SearchGroup} into a {@link NamedList} structure and visa versa.
+ */
+public class SearchGroupsResultTransformer implements ShardResultTransformer<List<Command>, Map<String, Collection<SearchGroup<BytesRef>>>> {
+
+  private final SolrIndexSearcher searcher;
+
+  public SearchGroupsResultTransformer(SolrIndexSearcher searcher) {
+    this.searcher = searcher;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public NamedList transform(List<Command> data) throws IOException {
+    NamedList<NamedList> result = new NamedList<NamedList>();
+    for (Command command : data) {
+      NamedList commandResult;
+      if (SearchGroupsFieldCommand.class.isInstance(command)) {
+        SearchGroupsFieldCommand fieldCommand = (SearchGroupsFieldCommand) command;
+        Collection<SearchGroup<BytesRef>> searchGroups = fieldCommand.result();
+        if (searchGroups == null) {
+          continue;
+        }
+
+        commandResult = serializeSearchGroup(searchGroups, fieldCommand.getGroupSort());
+      } else {
+        commandResult = null;
+      }
+
+      result.add(command.getKey(), commandResult);
+    }
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, Collection<SearchGroup<BytesRef>>> transformToNative(NamedList<NamedList> shardResponse, Sort groupSort, Sort sortWithinGroup, String shard) throws IOException {
+    Map<String, Collection<SearchGroup<BytesRef>>> result = new HashMap<String, Collection<SearchGroup<BytesRef>>>();
+    for (Map.Entry<String, NamedList> command : shardResponse) {
+      List<SearchGroup<BytesRef>> searchGroups = new ArrayList<SearchGroup<BytesRef>>();
+      @SuppressWarnings("unchecked")
+      NamedList<List<Comparable>> rawSearchGroups = command.getValue();
+      for (Map.Entry<String, List<Comparable>> rawSearchGroup : rawSearchGroups){
+        SearchGroup<BytesRef> searchGroup = new SearchGroup<BytesRef>();
+        searchGroup.groupValue = rawSearchGroup.getKey() != null ? new BytesRef(rawSearchGroup.getKey()) : null;
+        searchGroup.sortValues = rawSearchGroup.getValue().toArray(new Comparable[rawSearchGroup.getValue().size()]);
+        searchGroups.add(searchGroup);
+      }
+
+      result.put(command.getKey(), searchGroups);
+    }
+    return result;
+  }
+
+  private NamedList serializeSearchGroup(Collection<SearchGroup<BytesRef>> data, Sort groupSort) {
+    NamedList<Comparable[]> result = new NamedList<Comparable[]>();
+    CharsRef spare = new CharsRef();
+
+    for (SearchGroup<BytesRef> searchGroup : data) {
+      Comparable[] convertedSortValues = new Comparable[searchGroup.sortValues.length];
+      for (int i = 0; i < searchGroup.sortValues.length; i++) {
+        Comparable sortValue = (Comparable) searchGroup.sortValues[i];
+        SchemaField field = groupSort.getSort()[i].getField() != null ? searcher.getSchema().getFieldOrNull(groupSort.getSort()[i].getField()) : null;
+        if (field != null) {
+          FieldType fieldType = field.getType();
+          if (sortValue instanceof BytesRef) {
+            String indexedValue = ((BytesRef) sortValue).utf8ToChars(spare).toString();
+            sortValue = (Comparable) fieldType.toObject(field.createField(fieldType.indexedToReadable(indexedValue), 0.0f));
+          } else if (sortValue instanceof String) {
+            sortValue = (Comparable) fieldType.toObject(field.createField(fieldType.indexedToReadable((String) sortValue), 0.0f));
+          }
+        }
+        convertedSortValues[i] = sortValue;
+      }
+      String groupValue = searchGroup.groupValue != null ? searchGroup.groupValue.utf8ToString() : null;
+      result.add(groupValue, convertedSortValues);
+    }
+
+    return result;
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/ShardResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/ShardResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/ShardResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/ShardResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,53 @@
+package org.apache.solr.search.grouping.distributed.shardresultserializer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.Sort;
+import org.apache.solr.common.util.NamedList;
+
+import java.io.IOException;
+
+/**
+ * A <code>ShardResultTransformer</code> is responsible for transforming a grouped shard result into group related
+ * structures (such as {@link org.apache.lucene.search.grouping.TopGroups} and {@link org.apache.lucene.search.grouping.SearchGroup})
+ * and visa versa.
+ */
+public interface ShardResultTransformer<T, R> {
+
+  /**
+   * Transforms data to a {@link NamedList} structure for serialization purposes.
+   *
+   * @param data The data to be transformed
+   * @return {@link NamedList} structure
+   * @throws IOException If I/O related errors occur during transforming
+   */
+  NamedList transform(T data) throws IOException;
+
+  /**
+   * Transforms the specified shard response into native structures.
+   *
+   * @param shardResponse The shard response containing data in a {@link NamedList} structure
+   * @param groupSort The group sort
+   * @param sortWithinGroup The sort inside a group
+   * @param shard The shard address where the response originated from
+   * @return native structure of the data
+   * @throws IOException If I/O related errors occur during transforming
+   */
+  R transformToNative(NamedList<NamedList> shardResponse, Sort groupSort, Sort sortWithinGroup, String shard) throws IOException;
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/TopGroupsResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/TopGroupsResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/TopGroupsResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/distributed/shardresultserializer/TopGroupsResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,285 @@
+package org.apache.solr.search.grouping.distributed.shardresultserializer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.document.FieldSelectorResult;
+import org.apache.lucene.document.FieldSelectorVisitor;
+import org.apache.lucene.search.FieldDoc;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.CharsRef;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.ShardDoc;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.grouping.Command;
+import org.apache.solr.search.grouping.distributed.command.QueryCommand;
+import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
+import org.apache.solr.search.grouping.distributed.command.TopGroupsFieldCommand;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation for transforming {@link TopGroups} and {@link TopDocs} into a {@link NamedList} structure and
+ * visa versa.
+ */
+public class TopGroupsResultTransformer implements ShardResultTransformer<List<Command>, Map<String, ?>> {
+
+  private final ResponseBuilder rb;
+
+  public TopGroupsResultTransformer(ResponseBuilder rb) {
+    this.rb = rb;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public NamedList transform(List<Command> data) throws IOException {
+    NamedList<NamedList> result = new NamedList<NamedList>();
+    for (Command command : data) {
+      NamedList commandResult;
+      if (TopGroupsFieldCommand.class.isInstance(command)) {
+        TopGroupsFieldCommand fieldCommand = (TopGroupsFieldCommand) command;
+        SchemaField groupField = rb.req.getSearcher().getSchema().getField(fieldCommand.getKey());
+        commandResult = serializeTopGroups(fieldCommand.result(), groupField);
+      } else if (QueryCommand.class.isInstance(command)) {
+        QueryCommand queryCommand = (QueryCommand) command;
+        commandResult = serializeTopDocs(queryCommand.result());
+      } else {
+        commandResult = null;
+      }
+
+      result.add(command.getKey(), commandResult);
+    }
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public Map<String, ?> transformToNative(NamedList<NamedList> shardResponse, Sort groupSort, Sort sortWithinGroup, String shard) {
+    Map<String, Object> result = new HashMap<String, Object>();
+
+    for (Map.Entry<String, NamedList> entry : shardResponse) {
+      String key = entry.getKey();
+      NamedList commandResult = entry.getValue();
+      Integer totalGroupedHitCount = (Integer) commandResult.get("totalGroupedHitCount");
+      Integer totalHits = (Integer) commandResult.get("totalHits");
+      if (totalHits != null) {
+        Integer matches = (Integer) commandResult.get("matches");
+        Float maxScore = (Float) commandResult.get("maxScore");
+        if (maxScore == null) {
+          maxScore = Float.NaN;
+        }
+
+        @SuppressWarnings("unchecked")
+        List<NamedList<Object>> documents = (List<NamedList<Object>>) commandResult.get("documents");
+        ScoreDoc[] scoreDocs = new ScoreDoc[documents.size()];
+        int j = 0;
+        for (NamedList<Object> document : documents) {
+          Object uniqueId = document.get("id").toString();
+          Float score = (Float) document.get("score");
+          if (score == null) {
+            score = Float.NaN;
+          }
+          Object[] sortValues = ((List) document.get("sortValues")).toArray();
+          scoreDocs[j++] = new ShardDoc(score, sortValues, uniqueId, shard);
+        }
+        result.put(key, new QueryCommandResult(new TopDocs(totalHits, scoreDocs, maxScore), matches));
+        continue;
+      }
+
+      Integer totalHitCount = (Integer) commandResult.get("totalHitCount");
+      Integer totalGroupCount = (Integer) commandResult.get("totalGroupCount");
+
+      List<GroupDocs<BytesRef>> groupDocs = new ArrayList<GroupDocs<BytesRef>>();
+      for (int i = totalGroupCount == null ? 2 : 3; i < commandResult.size(); i++) {
+        String groupValue = commandResult.getName(i);
+        @SuppressWarnings("unchecked")
+        NamedList<Object> groupResult = (NamedList<Object>) commandResult.getVal(i);
+        Integer totalGroupHits = (Integer) groupResult.get("totalHits");
+        Float maxScore = (Float) groupResult.get("maxScore");
+        if (maxScore == null) {
+          maxScore = Float.NaN;
+        }
+
+        @SuppressWarnings("unchecked")
+        List<NamedList<Object>> documents = (List<NamedList<Object>>) groupResult.get("documents");
+        ScoreDoc[] scoreDocs = new ScoreDoc[documents.size()];
+        int j = 0;
+        for (NamedList<Object> document : documents) {
+          Object uniqueId = document.get("id").toString();
+          Float score = (Float) document.get("score");
+          if (score == null) {
+            score = Float.NaN;
+          }
+          Object[] sortValues = ((List) document.get("sortValues")).toArray();
+          scoreDocs[j++] = new ShardDoc(score, sortValues, uniqueId, shard);
+        }
+
+        BytesRef groupValueRef = groupValue != null ? new BytesRef(groupValue) : null;
+        groupDocs.add(new GroupDocs<BytesRef>(maxScore, totalGroupHits, scoreDocs, groupValueRef, null));
+      }
+
+      @SuppressWarnings("unchecked")
+      GroupDocs<BytesRef>[] groupDocsArr = groupDocs.toArray(new GroupDocs[groupDocs.size()]);
+      TopGroups<BytesRef> topGroups = new TopGroups<BytesRef>(
+        groupSort.getSort(), sortWithinGroup.getSort(), totalHitCount, totalGroupedHitCount, groupDocsArr
+      );
+      if (totalGroupCount != null) {
+        topGroups = new TopGroups<BytesRef>(topGroups, totalGroupCount);
+      }
+
+      result.put(key, topGroups);
+    }
+
+    return result;
+  }
+
+  protected NamedList serializeTopGroups(TopGroups<BytesRef> data, SchemaField groupField) throws IOException {
+    NamedList<Object> result = new NamedList<Object>();
+    result.add("totalGroupedHitCount", data.totalGroupedHitCount);
+    result.add("totalHitCount", data.totalHitCount);
+    if (data.totalGroupCount != null) {
+      result.add("totalGroupCount", data.totalGroupCount);
+    }
+    CharsRef spare = new CharsRef();
+
+    SchemaField uniqueField = rb.req.getSearcher().getSchema().getUniqueKeyField();
+    for (GroupDocs<BytesRef> searchGroup : data.groups) {
+      NamedList<Object> groupResult = new NamedList<Object>();
+      groupResult.add("totalHits", searchGroup.totalHits);
+      if (!Float.isNaN(searchGroup.maxScore)) {
+        groupResult.add("maxScore", searchGroup.maxScore);
+      }
+
+      List<NamedList<Object>> documents = new ArrayList<NamedList<Object>>();
+      for (int i = 0; i < searchGroup.scoreDocs.length; i++) {
+        NamedList<Object> document = new NamedList<Object>();
+        documents.add(document);
+
+        Document doc = retrieveDocument(uniqueField, searchGroup.scoreDocs[i].doc);
+        document.add("id", uniqueField.getType().toObject(doc.getField(uniqueField.getName())));
+        if (!Float.isNaN(searchGroup.scoreDocs[i].score))  {
+          document.add("score", searchGroup.scoreDocs[i].score);
+        }
+        if (!(searchGroup.scoreDocs[i] instanceof FieldDoc)) {
+          continue;
+        }
+
+        FieldDoc fieldDoc = (FieldDoc) searchGroup.scoreDocs[i];
+        Object[] convertedSortValues  = new Object[fieldDoc.fields.length];
+        for (int j = 0; j < fieldDoc.fields.length; j++) {
+          Object sortValue  = fieldDoc.fields[j];
+          Sort sortWithinGroup = rb.getGroupingSpec().getSortWithinGroup();
+          SchemaField field = sortWithinGroup.getSort()[j].getField() != null ? rb.req.getSearcher().getSchema().getFieldOrNull(sortWithinGroup.getSort()[j].getField()) : null;
+          if (field != null) {
+            FieldType fieldType = field.getType();
+            if (sortValue instanceof BytesRef) {
+              String indexedValue = ((BytesRef) sortValue).utf8ToChars(spare).toString();
+              sortValue = fieldType.toObject(field.createField(fieldType.indexedToReadable(indexedValue), 0.0f));
+            } else if (sortValue instanceof String) {
+              sortValue = fieldType.toObject(field.createField(fieldType.indexedToReadable((String) sortValue), 0.0f));
+            }
+          }
+          convertedSortValues[j] = sortValue;
+        }
+        document.add("sortValues", convertedSortValues);
+      }
+      groupResult.add("documents", documents);
+      String groupValue = searchGroup.groupValue != null ? groupField.getType().indexedToReadable(searchGroup.groupValue.utf8ToString()): null;
+      result.add(groupValue, groupResult);
+    }
+
+    return result;
+  }
+
+  protected NamedList serializeTopDocs(QueryCommandResult result) throws IOException {
+    NamedList<Object> queryResult = new NamedList<Object>();
+    queryResult.add("matches", result.getMatches());
+    queryResult.add("totalHits", result.getTopDocs().totalHits);
+    if (rb.getGroupingSpec().isNeedScore()) {
+      queryResult.add("maxScore", result.getTopDocs().getMaxScore());
+    }
+    List<NamedList> documents = new ArrayList<NamedList>();
+    queryResult.add("documents", documents);
+
+    SchemaField uniqueField = rb.req.getSearcher().getSchema().getUniqueKeyField();
+    CharsRef spare = new CharsRef();
+    for (ScoreDoc scoreDoc : result.getTopDocs().scoreDocs) {
+      NamedList<Object> document = new NamedList<Object>();
+      documents.add(document);
+
+      Document doc = retrieveDocument(uniqueField, scoreDoc.doc);
+      document.add("id", uniqueField.getType().toObject(doc.getField(uniqueField.getName())));
+      if (rb.getGroupingSpec().isNeedScore())  {
+        document.add("score", scoreDoc.score);
+      }
+      if (!FieldDoc.class.isInstance(scoreDoc)) {
+        continue;
+      }
+
+      FieldDoc fieldDoc = (FieldDoc) scoreDoc;
+      Object[] convertedSortValues  = new Object[fieldDoc.fields.length];
+      for (int j = 0; j < fieldDoc.fields.length; j++) {
+        Object sortValue  = fieldDoc.fields[j];
+        Sort groupSort = rb.getGroupingSpec().getGroupSort();
+        SchemaField field = groupSort.getSort()[j].getField() != null ? rb.req.getSearcher().getSchema().getFieldOrNull(groupSort.getSort()[j].getField()) : null;
+        if (field != null) {
+          FieldType fieldType = field.getType();
+          if (sortValue instanceof BytesRef) {
+            String indexedValue = ((BytesRef) sortValue).utf8ToChars(spare).toString();
+            sortValue = fieldType.toObject(field.createField(fieldType.indexedToReadable(indexedValue), 0.0f));
+          } else if (sortValue instanceof String) {
+            sortValue = fieldType.toObject(field.createField(fieldType.indexedToReadable((String) sortValue), 0.0f));
+          }
+        }
+        convertedSortValues[j] = sortValue;
+      }
+      document.add("sortValues", convertedSortValues);
+    }
+
+    return queryResult;
+  }
+
+  private Document retrieveDocument(final SchemaField uniqueField, int doc) throws IOException {
+    FieldSelectorVisitor fieldSelectorVisitor = new FieldSelectorVisitor(new FieldSelector() {
+
+      public FieldSelectorResult accept(String fieldName) {
+        if (uniqueField.getName().equals(fieldName)) {
+          return FieldSelectorResult.LOAD_AND_BREAK;
+        }
+        return FieldSelectorResult.NO_LOAD;
+      }
+    });
+    rb.req.getSearcher().doc(doc, fieldSelectorVisitor);
+    return fieldSelectorVisitor.getDocument();
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/EndResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,52 @@
+package org.apache.solr.search.grouping.endresulttransformer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.grouping.GroupingSpecification;
+
+import java.util.Map;
+
+/**
+ * Responsible for transforming the grouped result into the final format for displaying purposes.
+ */
+public interface EndResultTransformer {
+
+  /**
+   * Transforms the specified result into its final form and puts it into the specified response.
+   *
+   * @param result The map containing the grouping result (for grouping by field and query)
+   * @param response The response that will be rendered to the client
+   * @param groupingSpecification The grouping specification
+   * @param solrDocumentSource The source of {@link SolrDocument} instances
+   */
+  void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource);
+
+  /**
+   * Abstracts the source for {@link SolrDocument} instances.
+   * The source of documents is different for a distributed search than local search
+   */
+  public interface SolrDocumentSource {
+
+    SolrDocument retrieve(ScoreDoc doc);
+
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/GroupedEndResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,108 @@
+package org.apache.solr.search.grouping.endresulttransformer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.schema.FieldType;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.search.grouping.GroupingSpecification;
+import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ */
+public class GroupedEndResultTransformer implements EndResultTransformer {
+
+  private final SolrIndexSearcher searcher;
+
+  public GroupedEndResultTransformer(SolrIndexSearcher searcher) {
+    this.searcher = searcher;
+  }
+
+  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
+    NamedList<Object> commands = new NamedList<Object>();
+    for (Map.Entry<String, ?> entry : result.entrySet()) {
+      Object value = entry.getValue();
+      if (TopGroups.class.isInstance(value)) {
+        @SuppressWarnings("unchecked")
+        TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) value;
+        NamedList<Object> command = new SimpleOrderedMap<Object>();
+        command.add("matches", topGroups.totalHitCount);
+        if (topGroups.totalGroupCount != null) {
+          command.add("ngroups", topGroups.totalGroupCount);
+        }
+
+        List<NamedList> groups = new ArrayList<NamedList>();
+        SchemaField groupField = searcher.getSchema().getField(entry.getKey());
+        FieldType groupFieldType = groupField.getType();
+        for (GroupDocs<BytesRef> group : topGroups.groups) {
+          SimpleOrderedMap<Object> groupResult = new SimpleOrderedMap<Object>();
+          if (group.groupValue != null) {
+            groupResult.add(
+                "groupValue", groupFieldType.toObject(groupField.createField(group.groupValue.utf8ToString(), 0.0f))
+            );
+          } else {
+            groupResult.add("groupValue", null);
+          }
+          SolrDocumentList docList = new SolrDocumentList();
+          docList.setNumFound(group.totalHits);
+          if (!Float.isNaN(group.maxScore)) {
+            docList.setMaxScore(group.maxScore);
+          }
+          docList.setStart(groupingSpecification.getGroupOffset());
+          for (ScoreDoc scoreDoc : group.scoreDocs) {
+            docList.add(solrDocumentSource.retrieve(scoreDoc));
+          }
+          groupResult.add("doclist", docList);
+          groups.add(groupResult);
+        }
+        command.add("groups", groups);
+        commands.add(entry.getKey(), command);
+      } else if (QueryCommandResult.class.isInstance(value)) {
+        QueryCommandResult queryCommandResult = (QueryCommandResult) value;
+        NamedList<Object> command = new SimpleOrderedMap<Object>();
+        command.add("matches", queryCommandResult.getMatches());
+        SolrDocumentList docList = new SolrDocumentList();
+        docList.setNumFound(queryCommandResult.getTopDocs().totalHits);
+        if (!Float.isNaN(queryCommandResult.getTopDocs().getMaxScore())) {
+          docList.setMaxScore(queryCommandResult.getTopDocs().getMaxScore());
+        }
+        docList.setStart(groupingSpecification.getGroupOffset());
+        for (ScoreDoc scoreDoc :queryCommandResult.getTopDocs().scoreDocs){
+          docList.add(solrDocumentSource.retrieve(scoreDoc));
+        }
+        command.add("doclist", docList);
+        commands.add(entry.getKey(), command);
+      }
+    }
+    response.add("grouped", commands);
+  }
+
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/MainEndResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,59 @@
+package org.apache.solr.search.grouping.endresulttransformer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.grouping.GroupingSpecification;
+
+import java.util.Map;
+
+/**
+ *
+ */
+public class MainEndResultTransformer implements EndResultTransformer {
+
+  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
+    Object value = result.get(groupingSpecification.getFields()[0]);
+    if (TopGroups.class.isInstance(value)) {
+      @SuppressWarnings("unchecked")
+      TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) value;
+      SolrDocumentList docList = new SolrDocumentList();
+      docList.setStart(groupingSpecification.getOffset());
+      docList.setNumFound(topGroups.totalHitCount);
+
+      Float maxScore = Float.NEGATIVE_INFINITY;
+      for (GroupDocs<BytesRef> group : topGroups.groups) {
+        for (ScoreDoc scoreDoc : group.scoreDocs) {
+          if (maxScore < scoreDoc.score) {
+            maxScore = scoreDoc.score;
+          }
+          docList.add(solrDocumentSource.retrieve(scoreDoc));
+        }
+      }
+      if (maxScore != Float.NEGATIVE_INFINITY) {
+        docList.setMaxScore(maxScore);
+      }
+      response.add("response", docList);
+    }
+  }
+}

Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java?rev=1171970&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java (added)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/grouping/endresulttransformer/SimpleEndResultTransformer.java Sat Sep 17 12:48:27 2011
@@ -0,0 +1,73 @@
+package org.apache.solr.search.grouping.endresulttransformer;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.grouping.GroupDocs;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.grouping.GroupingSpecification;
+
+import java.util.Map;
+
+/**
+ *
+ */
+public class SimpleEndResultTransformer implements EndResultTransformer {
+
+  @Override
+  public void transform(Map<String, ?> result, SolrQueryResponse response, GroupingSpecification groupingSpecification, SolrDocumentSource solrDocumentSource) {
+    NamedList<Object> commands = new SimpleOrderedMap<Object>();
+    for (Map.Entry<String, ?> entry : result.entrySet()) {
+      Object value = entry.getValue();
+      if (TopGroups.class.isInstance(value)) {
+        @SuppressWarnings("unchecked")
+        TopGroups<BytesRef> topGroups = (TopGroups<BytesRef>) value;
+        NamedList<Object> command = new SimpleOrderedMap<Object>();
+        command.add("matches", topGroups.totalHitCount);
+        if (topGroups.totalGroupCount != null) {
+          command.add("ngroups", topGroups.totalGroupCount);
+        }
+        SolrDocumentList docList = new SolrDocumentList();
+        docList.setStart(groupingSpecification.getOffset());
+        docList.setNumFound(topGroups.totalHitCount);
+
+        Float maxScore = Float.NEGATIVE_INFINITY;
+        for (GroupDocs<BytesRef> group : topGroups.groups) {
+          for (ScoreDoc scoreDoc : group.scoreDocs) {
+            if (maxScore < scoreDoc.score) {
+              maxScore = scoreDoc.score;
+            }
+            docList.add(solrDocumentSource.retrieve(scoreDoc));
+          }
+        }
+        if (maxScore != Float.NEGATIVE_INFINITY) {
+          docList.setMaxScore(maxScore);
+        }
+        command.add("doclist", docList);
+        commands.add(entry.getKey(), command);
+      }
+    }
+
+    response.add("grouped", commands);
+  }
+}