You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mk...@apache.org on 2016/08/19 11:55:10 UTC

lucene-solr:master: SOLR-8643: made BlockJoinFacetComponent just a shortcut for BlockJoinDocSetFacetComponent, which supports pure disjunctions

Repository: lucene-solr
Updated Branches:
  refs/heads/master 250a867de -> 915e6e33b


SOLR-8643: made BlockJoinFacetComponent just a shortcut for BlockJoinDocSetFacetComponent, which supports pure disjunctions


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/915e6e33
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/915e6e33
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/915e6e33

Branch: refs/heads/master
Commit: 915e6e33b21cdceccd0c7dccdd907ab5c8708fc2
Parents: 250a867
Author: Mikhail Khludnev <mk...@apache.org>
Authored: Fri Aug 19 14:36:04 2016 +0300
Committer: Mikhail Khludnev <mk...@apache.org>
Committed: Fri Aug 19 14:36:25 2016 +0300

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   1 +
 .../join/BlockJoinDocSetFacetComponent.java     |  34 +---
 .../search/join/BlockJoinFacetAccsHolder.java   |  97 +++++++++++
 .../search/join/BlockJoinFacetCollector.java    | 131 ---------------
 .../search/join/BlockJoinFacetComponent.java    | 165 +------------------
 .../join/BlockJoinFacetComponentSupport.java    | 156 ++++++++++++++++++
 6 files changed, 262 insertions(+), 322 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 91347a5..2625d42 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -552,6 +552,7 @@ Other Changes
 
 * SOLR-2199: DataImportHandler (DIH) JdbcDataSource supports multiple resultsets per query (Kristine Jetzke, Mark Waddle via Mikhail Khludnev)
 
+* SOLR-8643: BlockJoinFacetComponent is substituted by BlockJoinFacetDocSetComponent. No need to change solrconfig.xml (Mikhail Khludnev)
 ==================  6.0.1 ==================
 
 Upgrade Notes

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/core/src/java/org/apache/solr/search/join/BlockJoinDocSetFacetComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinDocSetFacetComponent.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinDocSetFacetComponent.java
index ae33485..b8f3034 100644
--- a/solr/core/src/java/org/apache/solr/search/join/BlockJoinDocSetFacetComponent.java
+++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinDocSetFacetComponent.java
@@ -22,16 +22,13 @@ import java.util.List;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Collector;
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
-import org.apache.lucene.search.LeafCollector;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.ToParentBlockJoinQuery;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.handler.component.ResponseBuilder;
-import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.BitDocSet;
 import org.apache.solr.search.DocSet;
 import org.apache.solr.search.Filter;
@@ -40,11 +37,11 @@ import org.apache.solr.search.facet.BlockJoin;
 import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.AggregatableDocIter;
 
 /**
- * It does the same as BlockJoinFacetComponent, but operates on docsets, 
- * it should be faster for static mostly indexes. This component doesn't impact
- * query result caching, but hits filter cache to retrieve docsets. 
+ * Calculates facets on children documents and aggregates hits by parent documents.
+ * Enables when child.facet.field parameter specifies a field name for faceting. 
+ * So far it supports string fields only. It requires to search by {@link ToParentBlockJoinQuery}.
  * */
-public class BlockJoinDocSetFacetComponent extends BlockJoinFacetComponent {
+public class BlockJoinDocSetFacetComponent extends BlockJoinFacetComponentSupport {
   
   private final String bjqKey = this.getClass().getSimpleName()+".bjq";
   
@@ -115,27 +112,6 @@ public class BlockJoinDocSetFacetComponent extends BlockJoinFacetComponent {
     }
   }
 
-  private static final class NoDelegateFacetCollector extends BlockJoinFacetCollector {
-    {
-      setDelegate(new Collector() {
-        
-        @Override
-        public boolean needsScores() {
-          return false;
-        }
-        
-        @Override
-        public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
-          return null;
-        }
-      });
-    }
-    
-    private NoDelegateFacetCollector(SolrQueryRequest req) throws IOException {
-      super(req);
-    }
-  }
-
   public BlockJoinDocSetFacetComponent() {}
   
   @Override
@@ -196,7 +172,7 @@ public class BlockJoinDocSetFacetComponent extends BlockJoinFacetComponent {
       
       Filter filter = selectedChildren.getTopFilter();
 
-      final BlockJoinFacetCollector facetCounter = new NoDelegateFacetCollector(rb.req);
+      final BlockJoinFacetAccsHolder facetCounter = new BlockJoinFacetAccsHolder(rb.req);
       
       for (int subIdx = 0; subIdx < leaves.size(); subIdx++) {
         LeafReaderContext subCtx = leaves.get(subIdx);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
new file mode 100644
index 0000000..bf7f7b2
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetAccsHolder.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+package org.apache.solr.search.join;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.join.ToParentBlockJoinQuery.ChildrenMatchesScorer;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.AggregatableDocIter;
+import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.SortedIntsAggDocIterator;
+
+/**
+ * For each collected parent document creates matched block, which is a docSet with matched children and parent doc
+ * itself. Then updates each BlockJoinFieldFacetAccumulator with the created matched block.
+ */
+class BlockJoinFacetAccsHolder {
+  private BlockJoinFieldFacetAccumulator[] blockJoinFieldFacetAccumulators;
+  private boolean firstSegment = true;
+  private ChildrenMatchesScorer blockJoinScorer;
+  private int[] childDocs = new int[0];
+  
+  BlockJoinFacetAccsHolder(SolrQueryRequest req) throws IOException {
+    String[] facetFieldNames = BlockJoinFacetComponentSupport.getChildFacetFields(req);
+    assert facetFieldNames != null;
+    blockJoinFieldFacetAccumulators = new BlockJoinFieldFacetAccumulator[facetFieldNames.length];
+    for (int i = 0; i < facetFieldNames.length; i++) {
+      blockJoinFieldFacetAccumulators[i] = new BlockJoinFieldFacetAccumulator(facetFieldNames[i], req.getSearcher());
+    }
+  }
+  
+
+  protected void doSetNextReader(LeafReaderContext context) throws IOException {
+    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
+      if(!firstSegment){
+        blockJoinFieldFacetAccumulator.migrateGlobal();
+      }
+      blockJoinFieldFacetAccumulator.setNextReader(context);
+    }
+    firstSegment = false;
+  }
+  
+  public void finish() throws IOException {
+    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
+        blockJoinFieldFacetAccumulator.migrateGlobal();
+    }
+  }
+
+  protected void incrementFacets(int parent) throws IOException {
+    final int[] docNums = blockJoinScorer.swapChildDocs(childDocs);
+    // now we don't
+    //includeParentDoc(parent);
+    //final int childCountPlusParent = childTracking.getChildCount()+1;
+    final int childCountNoParent = blockJoinScorer.getChildCount();
+    final SortedIntsAggDocIterator iter = new SortedIntsAggDocIterator(docNums, childCountNoParent, parent);
+    countFacets(iter);
+  }
+
+  /** is not used 
+  protected int[] includeParentDoc(int parent) {
+    final int[] docNums = ArrayUtil.grow(childTracking.getChildDocs(), childTracking.getChildCount()+1);
+    childTracking.setChildDocs(docNums); // we include parent into block, I'm not sure whether it makes sense
+    docNums[childTracking.getChildCount()]=parent;
+    return docNums;
+  }*/
+
+  protected void countFacets(final AggregatableDocIter iter) throws IOException {
+    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
+      blockJoinFieldFacetAccumulator.updateCountsWithMatchedBlock( iter);
+    }
+  }
+  
+  NamedList getFacets() {
+    NamedList<NamedList<Integer>> facets = new NamedList<>(blockJoinFieldFacetAccumulators.length);
+    for (BlockJoinFieldFacetAccumulator state : blockJoinFieldFacetAccumulators) {
+      facets.add(state.getFieldName(), state.getFacetValue());
+    }
+    return facets;
+  }
+  
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetCollector.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetCollector.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetCollector.java
deleted file mode 100644
index da84d9f..0000000
--- a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetCollector.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.search.join;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.Queue;
-
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.Scorer;
-import org.apache.lucene.search.join.ToParentBlockJoinQuery.ChildrenMatchesScorer;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.search.DelegatingCollector;
-import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.AggregatableDocIter;
-import org.apache.solr.search.join.BlockJoinFieldFacetAccumulator.SortedIntsAggDocIterator;
-
-/**
- * For each collected parent document creates matched block, which is a docSet with matched children and parent doc
- * itself. Then updates each BlockJoinFieldFacetAccumulator with the created matched block.
- */
-class BlockJoinFacetCollector extends DelegatingCollector {
-  private BlockJoinFieldFacetAccumulator[] blockJoinFieldFacetAccumulators;
-  private boolean firstSegment = true;
-  private ChildrenMatchesScorer blockJoinScorer;
-  private int[] childDocs = new int[0];
-  
-  BlockJoinFacetCollector(SolrQueryRequest req) throws IOException {
-    String[] facetFieldNames = BlockJoinFacetComponent.getChildFacetFields(req);
-    assert facetFieldNames != null;
-    blockJoinFieldFacetAccumulators = new BlockJoinFieldFacetAccumulator[facetFieldNames.length];
-    for (int i = 0; i < facetFieldNames.length; i++) {
-      blockJoinFieldFacetAccumulators[i] = new BlockJoinFieldFacetAccumulator(facetFieldNames[i], req.getSearcher());
-    }
-  }
-  
-  @Override
-  public void setScorer(Scorer scorer) throws IOException {
-    super.setScorer(scorer);
-    blockJoinScorer = getToParentScorer(scorer, new LinkedList<Scorer>());
-    if (blockJoinScorer != null) {
-      // instruct scorer to keep track of the child docIds for retrieval purposes.
-      blockJoinScorer.trackPendingChildHits();
-    }
-  }
-  
-  private ChildrenMatchesScorer getToParentScorer(Scorer scorer, Queue<Scorer> queue) {
-    if (scorer == null || scorer instanceof ChildrenMatchesScorer) {
-      return (ChildrenMatchesScorer) scorer;
-    } else {
-      for (Scorer.ChildScorer child : scorer.getChildren()) {
-        queue.add(child.child);
-      }
-      return getToParentScorer(queue.poll(), queue);
-    }
-  }
-  
-  @Override
-  protected void doSetNextReader(LeafReaderContext context) throws IOException {
-    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
-      if(!firstSegment){
-        blockJoinFieldFacetAccumulator.migrateGlobal();
-      }
-      blockJoinFieldFacetAccumulator.setNextReader(context);
-    }
-    firstSegment = false;
-    super.doSetNextReader(context);
-  }
-  
-  @Override
-  public void collect(int doc) throws IOException {
-    incrementFacets(doc);
-    super.collect(doc);
-  }
-  
-  @Override
-  public void finish() throws IOException {
-    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
-        blockJoinFieldFacetAccumulator.migrateGlobal();
-    }
-    super.finish();
-  }
-
-  protected void incrementFacets(int parent) throws IOException {
-    final int[] docNums = blockJoinScorer.swapChildDocs(childDocs);
-    // now we don't
-    //includeParentDoc(parent);
-    //final int childCountPlusParent = childTracking.getChildCount()+1;
-    final int childCountNoParent = blockJoinScorer.getChildCount();
-    final SortedIntsAggDocIterator iter = new SortedIntsAggDocIterator(docNums, childCountNoParent, parent);
-    countFacets(iter);
-  }
-
-  /** is not used 
-  protected int[] includeParentDoc(int parent) {
-    final int[] docNums = ArrayUtil.grow(childTracking.getChildDocs(), childTracking.getChildCount()+1);
-    childTracking.setChildDocs(docNums); // we include parent into block, I'm not sure whether it makes sense
-    docNums[childTracking.getChildCount()]=parent;
-    return docNums;
-  }*/
-
-  protected void countFacets(final AggregatableDocIter iter) throws IOException {
-    for (BlockJoinFieldFacetAccumulator blockJoinFieldFacetAccumulator : blockJoinFieldFacetAccumulators) {
-      blockJoinFieldFacetAccumulator.updateCountsWithMatchedBlock( iter);
-    }
-  }
-  
-  NamedList getFacets() {
-    NamedList<NamedList<Integer>> facets = new NamedList<>(blockJoinFieldFacetAccumulators.length);
-    for (BlockJoinFieldFacetAccumulator state : blockJoinFieldFacetAccumulators) {
-      facets.add(state.getFieldName(), state.getFacetValue());
-    }
-    return facets;
-  }
-  
-  
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponent.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponent.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponent.java
index 03a33d1..16f84cc 100644
--- a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponent.java
+++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponent.java
@@ -16,167 +16,8 @@
  */
 package org.apache.solr.search.join;
 
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+/** this is just a stub refers to {@link BlockJoinDocSetFacetComponent} to avoid
+ * changes in configs */
+public class BlockJoinFacetComponent extends BlockJoinDocSetFacetComponent {
 
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.join.ToParentBlockJoinQuery;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.ShardParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.handler.component.ResponseBuilder;
-import org.apache.solr.handler.component.SearchComponent;
-import org.apache.solr.handler.component.ShardRequest;
-import org.apache.solr.handler.component.ShardResponse;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.search.DelegatingCollector;
-import org.apache.solr.search.SolrIndexSearcher;
-
-
-/**
- * Calculates facets on children documents and aggregates hits by parent documents.
- * Enables when child.facet.field parameter specifies a field name for faceting. 
- * So far it supports string fields only. It requires to search by {@link ToParentBlockJoinQuery}.
- * It disables query result cache but only when it's ebaled for request by child.facet.field parameter 
- * */
-public class BlockJoinFacetComponent extends SearchComponent {
-  public static final String CHILD_FACET_FIELD_PARAMETER = "child.facet.field";
-  public static final String NO_TO_PARENT_BJQ_MESSAGE = "Block join faceting is allowed with ToParentBlockJoinQuery only";
-  public static final String COLLECTOR_CONTEXT_PARAM = "blockJoinFacetCollector";
-
-  @Override
-  public void prepare(ResponseBuilder rb) throws IOException {
-
-    if (getChildFacetFields(rb.req) != null) {
-      validateQuery(rb.getQuery());
-      // we count facets only when searching
-      rb.setFieldFlags(rb.getFieldFlags() | SolrIndexSearcher.NO_CHECK_QCACHE);
-      if (rb.getFilters() == null) {
-        rb.setFilters(new LinkedList<Query>());
-      }
-      DelegatingCollector blockJoinFacetCollector = new BlockJoinFacetCollector(rb.req);
-      rb.req.getContext().put(COLLECTOR_CONTEXT_PARAM, blockJoinFacetCollector);
-      rb.getFilters().add(new BlockJoinFacetFilter(blockJoinFacetCollector));
-    }
-  }
-
-  protected void validateQuery(Query query) {
-    if (!(query instanceof ToParentBlockJoinQuery)) {
-      if (query instanceof BooleanQuery) {
-        List<BooleanClause> clauses = ((BooleanQuery) query).clauses();
-        for (BooleanClause clause : clauses) {
-          if (clause.getQuery() instanceof ToParentBlockJoinQuery) {
-            return;
-          }
-        }
-      }
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, NO_TO_PARENT_BJQ_MESSAGE);
-    }
-  }
-
-  static String[] getChildFacetFields(SolrQueryRequest req) {
-    return req.getParams().getParams(CHILD_FACET_FIELD_PARAMETER);
-  }
-
-  @Override
-  public void process(ResponseBuilder rb) throws IOException {
-    if (getChildFacetFields(rb.req) != null) {
-      BlockJoinFacetCollector blockJoinFacetCollector = (BlockJoinFacetCollector) rb.req.getContext().get(COLLECTOR_CONTEXT_PARAM);
-      assert blockJoinFacetCollector != null;
-      NamedList output;
-      if (isShard(rb)) {
-        // distributed search, put results into own cell in order not to clash with facet component
-        output = getChildFacetFields(rb.rsp.getValues(), true);
-      } else {
-        // normal process, put results into standard response
-        output = getFacetFieldsList(rb);
-      }
-      mergeFacets(output, blockJoinFacetCollector.getFacets());
-    }
-  }
-
-  private boolean isShard(ResponseBuilder rb) {
-    return "true".equals(rb.req.getParams().get(ShardParams.IS_SHARD));
-  }
-
-  private NamedList getChildFacetFields(NamedList responseValues, boolean createIfAbsent) {
-    return getNamedListFromList(responseValues, "child_facet_fields", createIfAbsent);
-  }
-
-  private void mergeFacets(NamedList childFacetFields, NamedList shardFacets) {
-    if (shardFacets != null) {
-      for (Map.Entry<String, NamedList<Integer>> nextShardFacet : (Iterable<Map.Entry<String, NamedList<Integer>>>) shardFacets) {
-        String fieldName = nextShardFacet.getKey();
-        NamedList<Integer> collectedFacet = (NamedList<Integer>) childFacetFields.get(fieldName);
-        NamedList<Integer> shardFacet = nextShardFacet.getValue();
-        if (collectedFacet == null) {
-          childFacetFields.add(fieldName, shardFacet);
-        } else {
-          mergeFacetValues(collectedFacet, shardFacet);
-        }
-      }
-    }
-  }
-
-  private void mergeFacetValues(NamedList<Integer> collectedFacetValue, NamedList<Integer> shardFacetValue) {
-    for (Map.Entry<String, Integer> nextShardValue : shardFacetValue) {
-      String facetValue = nextShardValue.getKey();
-      Integer shardCount = nextShardValue.getValue();
-      int indexOfCollectedValue = collectedFacetValue.indexOf(facetValue, 0);
-      if (indexOfCollectedValue == -1) {
-        collectedFacetValue.add(facetValue, shardCount);
-      } else {
-        int newCount = collectedFacetValue.getVal(indexOfCollectedValue) + shardCount;
-        collectedFacetValue.setVal(indexOfCollectedValue, newCount);
-      }
-    }
-  }
-
-  private NamedList getNamedListFromList(NamedList parentList, String name, boolean createIfAbsent) {
-    NamedList result = null;
-    if (parentList != null) {
-      result = (NamedList) parentList.get(name);
-      if (result == null && createIfAbsent) {
-        result = new NamedList();
-        parentList.add(name, result);
-      }
-    }
-    return result;
-  }
-
-  @Override
-  public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
-    NamedList collectedChildFacetFields = getChildFacetFields(rb.rsp.getValues(), true);
-    List<ShardResponse> responses = sreq.responses;
-    for (ShardResponse shardResponse : responses) {
-      NamedList shardChildFacetFields = getChildFacetFields(shardResponse.getSolrResponse().getResponse(), false);
-      mergeFacets(collectedChildFacetFields, shardChildFacetFields);
-    }
-  }
-
-  @Override
-  public void finishStage(ResponseBuilder rb) {
-    if (rb.stage != ResponseBuilder.STAGE_GET_FIELDS) return;
-    NamedList childFacetFields = getChildFacetFields(rb.rsp.getValues(), true);
-    NamedList facetFields = getFacetFieldsList(rb);
-    for (Map.Entry<String, NamedList> childFacetField : (Iterable<Map.Entry<String, NamedList>>) childFacetFields) {
-     facetFields.add(childFacetField.getKey(), childFacetField.getValue());
-    }
-    rb.rsp.getValues().remove("child_facet_fields");
-  }
-
-  private NamedList getFacetFieldsList(ResponseBuilder rb) {
-    NamedList facetCounts = getNamedListFromList(rb.rsp.getValues(), "facet_counts", true);
-    return getNamedListFromList(facetCounts, "facet_fields", true);
-  }
-
-
-  @Override
-  public String getDescription() {
-    return "BlockJoin facet component";
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/915e6e33/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponentSupport.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponentSupport.java b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponentSupport.java
new file mode 100644
index 0000000..85aa799
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/join/BlockJoinFacetComponentSupport.java
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+package org.apache.solr.search.join;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.join.ToParentBlockJoinQuery;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ShardParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.SearchComponent;
+import org.apache.solr.handler.component.ShardRequest;
+import org.apache.solr.handler.component.ShardResponse;
+import org.apache.solr.request.SolrQueryRequest;
+
+abstract class BlockJoinFacetComponentSupport extends SearchComponent {
+  public static final String CHILD_FACET_FIELD_PARAMETER = "child.facet.field";
+  public static final String NO_TO_PARENT_BJQ_MESSAGE = "Block join faceting is allowed with ToParentBlockJoinQuery only";
+  public static final String COLLECTOR_CONTEXT_PARAM = "blockJoinFacetCollector";
+
+  protected void validateQuery(Query query) {
+    if (!(query instanceof ToParentBlockJoinQuery)) {
+      if (query instanceof BooleanQuery) {
+        List<BooleanClause> clauses = ((BooleanQuery) query).clauses();
+        for (BooleanClause clause : clauses) {
+          if (clause.getQuery() instanceof ToParentBlockJoinQuery) {
+            return;
+          }
+        }
+      }
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, NO_TO_PARENT_BJQ_MESSAGE);
+    }
+  }
+
+  static String[] getChildFacetFields(SolrQueryRequest req) {
+    return req.getParams().getParams(CHILD_FACET_FIELD_PARAMETER);
+  }
+
+  @Override
+  public void process(ResponseBuilder rb) throws IOException {
+    if (getChildFacetFields(rb.req) != null) {
+      BlockJoinFacetAccsHolder blockJoinFacetCollector = (BlockJoinFacetAccsHolder) rb.req.getContext().get(COLLECTOR_CONTEXT_PARAM);
+      assert blockJoinFacetCollector != null;
+      NamedList output;
+      if (isShard(rb)) {
+        // distributed search, put results into own cell in order not to clash with facet component
+        output = getChildFacetFields(rb.rsp.getValues(), true);
+      } else {
+        // normal process, put results into standard response
+        output = getFacetFieldsList(rb);
+      }
+      mergeFacets(output, blockJoinFacetCollector.getFacets());
+    }
+  }
+
+  private boolean isShard(ResponseBuilder rb) {
+    return "true".equals(rb.req.getParams().get(ShardParams.IS_SHARD));
+  }
+
+  private NamedList getChildFacetFields(NamedList responseValues, boolean createIfAbsent) {
+    return getNamedListFromList(responseValues, "child_facet_fields", createIfAbsent);
+  }
+
+  private void mergeFacets(NamedList childFacetFields, NamedList shardFacets) {
+    if (shardFacets != null) {
+      for (Map.Entry<String, NamedList<Integer>> nextShardFacet : (Iterable<Map.Entry<String, NamedList<Integer>>>) shardFacets) {
+        String fieldName = nextShardFacet.getKey();
+        NamedList<Integer> collectedFacet = (NamedList<Integer>) childFacetFields.get(fieldName);
+        NamedList<Integer> shardFacet = nextShardFacet.getValue();
+        if (collectedFacet == null) {
+          childFacetFields.add(fieldName, shardFacet);
+        } else {
+          mergeFacetValues(collectedFacet, shardFacet);
+        }
+      }
+    }
+  }
+
+  private void mergeFacetValues(NamedList<Integer> collectedFacetValue, NamedList<Integer> shardFacetValue) {
+    for (Map.Entry<String, Integer> nextShardValue : shardFacetValue) {
+      String facetValue = nextShardValue.getKey();
+      Integer shardCount = nextShardValue.getValue();
+      int indexOfCollectedValue = collectedFacetValue.indexOf(facetValue, 0);
+      if (indexOfCollectedValue == -1) {
+        collectedFacetValue.add(facetValue, shardCount);
+      } else {
+        int newCount = collectedFacetValue.getVal(indexOfCollectedValue) + shardCount;
+        collectedFacetValue.setVal(indexOfCollectedValue, newCount);
+      }
+    }
+  }
+
+  private NamedList getNamedListFromList(NamedList parentList, String name, boolean createIfAbsent) {
+    NamedList result = null;
+    if (parentList != null) {
+      result = (NamedList) parentList.get(name);
+      if (result == null && createIfAbsent) {
+        result = new NamedList();
+        parentList.add(name, result);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
+    NamedList collectedChildFacetFields = getChildFacetFields(rb.rsp.getValues(), true);
+    List<ShardResponse> responses = sreq.responses;
+    for (ShardResponse shardResponse : responses) {
+      NamedList shardChildFacetFields = getChildFacetFields(shardResponse.getSolrResponse().getResponse(), false);
+      mergeFacets(collectedChildFacetFields, shardChildFacetFields);
+    }
+  }
+
+  @Override
+  public void finishStage(ResponseBuilder rb) {
+    if (rb.stage != ResponseBuilder.STAGE_GET_FIELDS) return;
+    NamedList childFacetFields = getChildFacetFields(rb.rsp.getValues(), true);
+    NamedList facetFields = getFacetFieldsList(rb);
+    for (Map.Entry<String, NamedList> childFacetField : (Iterable<Map.Entry<String, NamedList>>) childFacetFields) {
+     facetFields.add(childFacetField.getKey(), childFacetField.getValue());
+    }
+    rb.rsp.getValues().remove("child_facet_fields");
+  }
+
+  private NamedList getFacetFieldsList(ResponseBuilder rb) {
+    NamedList facetCounts = getNamedListFromList(rb.rsp.getValues(), "facet_counts", true);
+    return getNamedListFromList(facetCounts, "facet_fields", true);
+  }
+
+
+  @Override
+  public String getDescription() {
+    return "BlockJoin facet component";
+  }
+}