/*
* 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.jackrabbit.core.query.lucene.join;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.jackrabbit.core.query.lucene.FieldNames;
import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
import org.apache.jackrabbit.core.query.lucene.NamespaceMappings;
import org.apache.jackrabbit.core.query.lucene.ScoreNode;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
/**
* <code>EquiJoin</code> implements an equi join condition.
*/
public class EquiJoin extends AbstractCondition {
/**
* The index reader.
*/
private final IndexReader reader;
private final Map<String, List<ScoreNode[]>> rowsByInnerNodeValue =
new HashMap<String, List<ScoreNode[]>>();
private final Map<Integer, Set<String>> valuesByOuterNodeDocument =
new HashMap<Integer, Set<String>>();
/**
* Creates a new equi join condition.
*
* @param inner the inner query hits.
* @param innerScoreNodeIndex the selector name for the inner query hits.
* @param nsMappings the namespace mappings
* @param reader the index reader.
* @param innerProperty the name of the property of the inner query
* hits.
* @param outerProperty the name of the property of the outer query
* hits.
* @throws IOException if an error occurs while reading from the index.
* @throws IllegalNameException
*/
public EquiJoin(
MultiColumnQueryHits inner, int innerScoreNodeIndex,
NamespaceMappings nsMappings, IndexReader reader,
Name innerProperty, Name outerProperty)
throws IOException, IllegalNameException {
super(inner);
this.reader = reader;
// create lookup map
Map<Integer, List<ScoreNode[]>> rowsByInnerDocument =
new HashMap<Integer, List<ScoreNode[]>>();
ScoreNode[] row = inner.nextScoreNodes();
while (row != null) {
int document = row[innerScoreNodeIndex].getDoc(reader);
List<ScoreNode[]> rows = rowsByInnerDocument.get(document);
if (rows == null) {
rows = new ArrayList<ScoreNode[]>();
rowsByInnerDocument.put(document, rows);
}
rows.add(row);
row = inner.nextScoreNodes();
}
// Build the rowsByInnerNodeValue map for efficient lookup in
// the getMatchingScoreNodes() method
String innerName = nsMappings.translateName(innerProperty);
for (Map.Entry<Term, String> entry : getPropertyTerms(innerName)) {
String value = entry.getValue();
TermDocs docs = reader.termDocs(entry.getKey());
while (docs.next()) {
List<ScoreNode[]> match = rowsByInnerDocument.get(docs.doc());
if (match != null) {
List<ScoreNode[]> rows = rowsByInnerNodeValue.get(value);
if (rows == null) {
rows = new ArrayList<ScoreNode[]>();
rowsByInnerNodeValue.put(value, rows);
}
rows.addAll(match);
}
}
}
// Build the valuesByOuterNodeDocument map for efficient lookup in
// the getMatchingScoreNodes() method
String outerName = nsMappings.translateName(outerProperty);
for (Map.Entry<Term, String> entry : getPropertyTerms(outerName)) {
String value = entry.getValue();
TermDocs docs = reader.termDocs(entry.getKey());
while (docs.next()) {
Set<String> values = valuesByOuterNodeDocument.get(docs.doc());
if (values == null) {
values = new HashSet<String>();
valuesByOuterNodeDocument.put(docs.doc(), values);
}
values.add(value);
}
}
}
/**
* {@inheritDoc}
*/
public ScoreNode[][] getMatchingScoreNodes(ScoreNode outer)
throws IOException {
List<ScoreNode[]> list = new ArrayList<ScoreNode[]>();
Set<String> values = valuesByOuterNodeDocument.get(outer.getDoc(reader));
if (values != null) {
for (String value : values) {
List<ScoreNode[]> rows = rowsByInnerNodeValue.get(value);
if (rows != null) {
list.addAll(rows);
}
}
}
return list.toArray(new ScoreNode[list.size()][]);
}
private Set<Map.Entry<Term, String>> getPropertyTerms(String property)
throws IOException {
Map<Term, String> map = new HashMap<Term, String>();
Term prefix = new Term(
FieldNames.PROPERTIES,
FieldNames.createNamedValue(property, ""));
TermEnum terms = reader.terms(prefix);
do {
Term term = terms.term();
if (term == null
|| !term.field().equals(prefix.field())
|| !term.text().startsWith(prefix.text())) {
break;
}
map.put(term, term.text().substring(prefix.text().length()));
} while (terms.next());
return map.entrySet();
}
}