You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2009/04/27 14:43:31 UTC

svn commit: r768954 [3/4] - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jsr283/query/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query...

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/QueryConstraint.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/QueryConstraint.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/QueryConstraint.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/QueryConstraint.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,133 @@
+/*
+ * 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.constraint;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.commons.query.qom.SelectorImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.QueryHits;
+import org.apache.jackrabbit.core.query.lucene.Util;
+import org.apache.jackrabbit.core.query.lucene.LuceneQueryFactory;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>QueryConstraint</code> implements a constraint that is based on a
+ * lucene query.
+ */
+public abstract class QueryConstraint extends SelectorBasedConstraint {
+
+    /**
+     * The constraint query.
+     */
+    private final Query constraint;
+
+    /**
+     * The lucene query factory.
+     */
+    private final LuceneQueryFactory factory;
+
+    /**
+     * The bitset with the matching document numbers.
+     */
+    private BitSet matches;
+
+    /**
+     * Creates a new query constraint using the given lucene query.
+     *
+     * @param constraint the lucene query constraint.
+     * @param selector   the selector for this constraint.
+     * @param factory    the lucene query factory.
+     */
+    public QueryConstraint(Query constraint,
+                           SelectorImpl selector,
+                           LuceneQueryFactory factory) {
+        super(selector);
+        this.constraint = constraint;
+        this.factory = factory;
+    }
+
+    //----------------------------< Constraint >--------------------------------
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean evaluate(ScoreNode[] row,
+                            Name[] selectorNames,
+                            EvaluationContext context)
+            throws IOException {
+        ScoreNode sn = row[getSelectorIndex(selectorNames)];
+        return sn != null && evaluate(sn, context);
+    }
+
+    //--------------------------------< internal >------------------------------
+
+    /**
+     * Evaluates this constraint for the given score node <code>sn</code>.
+     *
+     * @param sn      the current score node.
+     * @param context the evaluation context.
+     * @return <code>true</code> if this constraint is satisfied for the given
+     *         score node <code>sn</code>; <code>false</code> otherwise.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    private boolean evaluate(ScoreNode sn, EvaluationContext context)
+            throws IOException {
+        initMatches(context);
+        return matches.get(sn.getDoc(context.getIndexReader()));
+    }
+
+    /**
+     * Initializes the matches for the constraint query. If the matches are
+     * already initialized then this method returns immediately.
+     *
+     * @param context the evaluation context.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    private void initMatches(EvaluationContext context) throws IOException {
+        if (matches == null) {
+            Query selectorQuery;
+            BooleanQuery and = new BooleanQuery();
+            try {
+                selectorQuery = factory.create(getSelector());
+                and.add(selectorQuery, BooleanClause.Occur.MUST);
+                and.add(constraint, BooleanClause.Occur.MUST);
+            } catch (RepositoryException e) {
+                throw Util.createIOException(e);
+            }
+
+            IndexReader reader = context.getIndexReader();
+            QueryHits hits = context.evaluate(and);
+            try {
+                matches = new BitSet();
+                ScoreNode sn;
+                while ((sn = hits.nextScoreNode()) != null) {
+                    matches.set(sn.getDoc(reader));
+                }
+            } finally {
+                hits.close();
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/QueryConstraint.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SameNodeConstraint.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SameNodeConstraint.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SameNodeConstraint.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SameNodeConstraint.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,55 @@
+/*
+ * 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.constraint;
+
+import java.io.IOException;
+
+import org.apache.jackrabbit.spi.commons.query.qom.SameNodeImpl;
+import org.apache.jackrabbit.spi.commons.query.qom.SelectorImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+
+/**
+ * <code>SameNodeConstraint</code> implements a same node constraint.
+ */
+public class SameNodeConstraint extends HierarchyConstraint {
+
+    /**
+     * Creates a same node constraint.
+     *
+     * @param constraint the QOM constraint.
+     * @param selector   the selector for this constraint.
+     */
+    public SameNodeConstraint(SameNodeImpl constraint, SelectorImpl selector) {
+        super(constraint.getPath(), selector);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean evaluate(ScoreNode[] row,
+                            Name[] selectorNames,
+                            EvaluationContext context)
+            throws IOException {
+        ScoreNode sn = row[getSelectorIndex(selectorNames)];
+        if (sn == null) {
+            return false;
+        } else {
+            return sn.getNodeId().equals(getBaseNodeId(context));
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SameNodeConstraint.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SelectorBasedConstraint.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SelectorBasedConstraint.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SelectorBasedConstraint.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SelectorBasedConstraint.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,69 @@
+/*
+ * 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.constraint;
+
+import java.util.Arrays;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.query.qom.SelectorImpl;
+
+/**
+ * <code>SelectorBasedConstraint</code> implements a constraint that is based
+ * on a named selector.
+ */
+public abstract class SelectorBasedConstraint implements Constraint {
+
+    /**
+     * The selector this constrained is based on.
+     */
+    private final SelectorImpl selector;
+
+    /**
+     * Cached selector index. Initially set to <code>-1</code>.
+     */
+    private int selectorIndex = -1;
+
+    /**
+     * Creates a new constraint based on the given <code>selector</code>.
+     *
+     * @param selector the selector this constraint is based on.
+     */
+    public SelectorBasedConstraint(SelectorImpl selector) {
+        this.selector = selector;
+    }
+
+    /**
+     * Returns the selector index of this constraint.
+     *
+     * @param names the selector names.
+     * @return the selector index.
+     */
+    protected int getSelectorIndex(Name[] names) {
+        if (selectorIndex == -1) {
+            selectorIndex = Arrays.asList(names).indexOf(
+                    selector.getSelectorQName());
+        }
+        return selectorIndex;
+    }
+
+    /**
+     * @return the selector of this constraint.
+     */
+    protected SelectorImpl getSelector() {
+        return selector;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/SelectorBasedConstraint.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/UpperCaseOperand.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/UpperCaseOperand.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/UpperCaseOperand.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/UpperCaseOperand.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,64 @@
+/*
+ * 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.constraint;
+
+import java.io.IOException;
+
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.Util;
+
+/**
+ * <code>UpperCaseOperand</code> implements an upper case operand.
+ */
+public class UpperCaseOperand extends DynamicOperand {
+
+    /**
+     * The dynamic operand for which to lower case the value.
+     */
+    private final DynamicOperand operand;
+
+    /**
+     * Creates a new upper case operand.
+     *
+     * @param operand the operand to upper case the value.
+     */
+    public UpperCaseOperand(DynamicOperand operand) {
+        super();
+        this.operand = operand;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Value[] getValues(ScoreNode sn, EvaluationContext context)
+            throws IOException {
+        try {
+            ValueFactory vf = context.getSession().getValueFactory();
+            Value[] values = operand.getValues(sn, context);
+            for (int i = 0; i < values.length; i++) {
+                values[i] = vf.createValue(values[i].getString().toUpperCase());
+            }
+            return values;
+        } catch (RepositoryException e) {
+            throw Util.createIOException(e);
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/constraint/UpperCaseOperand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractCondition.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractCondition.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractCondition.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractCondition.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,75 @@
+/*
+ * 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.util.Arrays;
+import java.io.IOException;
+
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * <code>AbstractCondition</code> is a base class for join conditions.
+ */
+public abstract class AbstractCondition implements Condition {
+
+    /**
+     * The inner query hits.
+     */
+    protected final MultiColumnQueryHits inner;
+
+    /**
+     * Creates a new join condition with the given <code>inner</code> query
+     * hits.
+     *
+     * @param inner the inner query hits.
+     */
+    public AbstractCondition(MultiColumnQueryHits inner) {
+        this.inner = inner;
+    }
+
+    /**
+     * @return selector names of the inner query hits.
+     */
+    public Name[] getInnerSelectorNames() {
+        return inner.getSelectorNames();
+    }
+
+    /**
+     * Closes this join condition and frees resources. Namely closes the inner
+     * query hits.
+     *
+     * @throws IOException if an error occurs while closing the inner query
+     *                     hits.
+     */
+    public void close() throws IOException {
+        inner.close();
+    }
+
+    /**
+     * Returns the index of the selector with the given <code>selectorName</code>
+     * within the given <code>source</code>.
+     *
+     * @param source       a source.
+     * @param selectorName a selector name.
+     * @return the index within the source or <code>-1</code> if the name does
+     *         not exist in <code>source</code>.
+     */
+    protected static int getIndex(MultiColumnQueryHits source, Name selectorName) {
+        return Arrays.asList(source.getSelectorNames()).indexOf(selectorName);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractCondition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,122 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.HierarchyResolver;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>AncestorNodeJoin</code> implements an ancestor node join condition.
+ */
+public class AncestorNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits. The
+     * inner score nodes are indexed by the document numbers of their ancestor
+     * nodes.
+     */
+    private final ScoreNodeMap contextIndex = new ScoreNodeMap();
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * The hierarchy resolver.
+     */
+    private final HierarchyResolver resolver;
+
+    /**
+     * Reusable array of document numbers.
+     */
+    private int[] docNums = new int[1];
+
+    /**
+     * Reusable list of ancestor document numbers.
+     */
+    private final List ancestors = new ArrayList();
+
+    /**
+     * Creates a new ancestor node join condition.
+     *
+     * @param context             the inner query hits.
+     * @param contextSelectorName the selector name for the inner query hits.
+     * @param reader              the index reader.
+     * @param resolver            the hierarchy resolver.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public AncestorNodeJoin(MultiColumnQueryHits context,
+                            Name contextSelectorName,
+                            IndexReader reader,
+                            HierarchyResolver resolver) throws IOException {
+        super(context);
+        this.reader = reader;
+        this.resolver = resolver;
+        int idx = getIndex(context, contextSelectorName);
+        ScoreNode[] nodes;
+        while ((nodes = context.nextScoreNodes()) != null) {
+            Integer docNum = new Integer(nodes[idx].getDoc(reader));
+            ancestors.clear();
+            collectAncestors(docNum.intValue());
+            for (int i = 0; i < ancestors.size(); i++) {
+                Integer doc = (Integer) ancestors.get(i);
+                contextIndex.addScoreNodes(doc, nodes);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * The outer query hits loop contains the ancestor score nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode ancestor)
+            throws IOException {
+        Integer doc = new Integer(ancestor.getDoc(reader));
+        return contextIndex.getScoreNodes(doc);
+    }
+
+    /**
+     * Collects the ancestors of the given <code>doc</code> number into
+     * {@link #ancestors}.
+     *
+     * @param doc the current document number.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    private void collectAncestors(int doc) throws IOException {
+        docNums = resolver.getParents(doc, docNums);
+        if (docNums.length == 1) {
+            ancestors.add(new Integer(docNums[0]));
+            collectAncestors(docNums[0]);
+        } else if (docNums.length > 1) {
+            // clone because recursion uses docNums again
+            int[] tmp = (int[]) docNums.clone();
+            for (int i = 0; i < tmp.length; i++) {
+                ancestors.add(new Integer(tmp[i]));
+                collectAncestors(tmp[i]);
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -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.jackrabbit.core.query.lucene.join;
+
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+
+/**
+ * <code>AncestorPathNodeJoin</code> implements an ancestor path node join
+ * condition.
+ */
+public class AncestorPathNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits, indexed
+     * by the path of the inner query hits.
+     */
+    private final ScoreNodeMap contextIndex = new ScoreNodeMap();
+
+    /**
+     * The hierarchy manager.
+     */
+    private final HierarchyManager hmgr;
+
+    /**
+     * The relative path from the outer to the inner query hits.
+     */
+    private final Path relPath;
+
+    /**
+     * Creates an ancestor path node join.
+     *
+     * @param context             the inner query hits.
+     * @param contextSelectorName the selector name for the inner query hits.
+     * @param relPath             the relative path of the join condition.
+     * @param hmgr                the hierarchy manager of the workspace.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public AncestorPathNodeJoin(MultiColumnQueryHits context,
+                                Name contextSelectorName,
+                                Path relPath,
+                                HierarchyManager hmgr) throws IOException {
+        super(context);
+        this.hmgr = hmgr;
+        this.relPath = relPath;
+        int idx = getIndex(context, contextSelectorName);
+        ScoreNode[] nodes;
+        while ((nodes = context.nextScoreNodes()) != null) {
+            try {
+                Path p = hmgr.getPath(nodes[idx].getNodeId());
+                contextIndex.addScoreNodes(p, nodes);
+            } catch (RepositoryException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * The outer query hits loop contains the ancestor nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode ancestor)
+            throws IOException {
+        try {
+            Path ancestorPath = hmgr.getPath(ancestor.getNodeId());
+            PathBuilder builder = new PathBuilder(ancestorPath);
+            builder.addAll(relPath.getElements());
+            return contextIndex.getScoreNodes(builder.getPath().getNormalizedPath());
+        } catch (RepositoryException e) {
+            // ignore, probably does not exist anymore
+            return null;
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,107 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.HierarchyResolver;
+import org.apache.jackrabbit.spi.commons.query.qom.ChildNodeJoinConditionImpl;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>ChildNodeJoin</code> implements a child node join condition.
+ */
+public class ChildNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits, indexed
+     * by the document number of the parent node.
+     */
+    private final ScoreNodeMap parentIndex = new ScoreNodeMap();
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * The hierarchy resolver.
+     */
+    private final HierarchyResolver resolver;
+
+    /**
+     * Reusable array of document numbers.
+     */
+    private int[] docNums = new int[1];
+
+    /**
+     * Reusable list of score nodes.
+     */
+    private List tmpScoreNodes = new ArrayList();
+
+    /**
+     * Creates a new child node join condition.
+     *
+     * @param parent    the inner query hits.
+     * @param reader    the index reader.
+     * @param resolver  the hierarchy resolver.
+     * @param condition the QOM child node join condition.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public ChildNodeJoin(MultiColumnQueryHits parent,
+                         IndexReader reader,
+                         HierarchyResolver resolver,
+                         ChildNodeJoinConditionImpl condition)
+            throws IOException {
+        super(parent);
+        this.reader = reader;
+        this.resolver = resolver;
+        int idx = getIndex(parent, condition.getParentSelectorQName());
+        ScoreNode[] nodes;
+        while ((nodes = parent.nextScoreNodes()) != null) {
+            Integer docNum = new Integer(nodes[idx].getDoc(reader));
+            parentIndex.addScoreNodes(docNum, nodes);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * The outer query hits loop contains the child nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode child) throws IOException {
+        docNums = resolver.getParents(child.getDoc(reader), docNums);
+        tmpScoreNodes.clear();
+        for (int i = 0; i < docNums.length; i++) {
+            ScoreNode[][] sn = parentIndex.getScoreNodes(new Integer(docNums[i]));
+            if (sn != null) {
+                for (int j = 0; j < sn.length; j++) {
+                    tmpScoreNodes.add(sn[j]);
+                }
+            }
+        }
+        if (tmpScoreNodes.isEmpty()) {
+            return null;
+        } else {
+            return (ScoreNode[][]) tmpScoreNodes.toArray(new ScoreNode[tmpScoreNodes.size()][]);
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Condition.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Condition.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Condition.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Condition.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,51 @@
+/*
+ * 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 org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * <code>Condition</code> defines an interface for a join condition.
+ */
+public interface Condition {
+
+    /**
+     * Returns the matching inner score nodes for the given outer score node
+     * <code>sn</code>.
+     *
+     * @param outer the current score nodes of the outer source.
+     * @return the matching score nodes in the inner source.
+     * @throws IOException if an error occurs while evaluating the condition.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode outer)
+            throws IOException;
+
+    /**
+     * @return the selector name of the inner hits.
+     */
+    public Name[] getInnerSelectorNames();
+
+    /**
+     * Closes this condition and frees resources.
+     *
+     * @throws IOException if an error occurs while closing this condition.
+     */
+    public void close() throws IOException;
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Condition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,136 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.HierarchyResolver;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>DescendantNodeJoin</code> implements a descendant node join condition.
+ */
+public class DescendantNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits, indexed
+     * by the document number.
+     */
+    private final ScoreNodeMap contextIndex = new ScoreNodeMap();
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * The hierarchy resolver.
+     */
+    private final HierarchyResolver resolver;
+
+    /**
+     * Reusable array of document numbers.
+     */
+    private int[] docNums = new int[1];
+
+    /**
+     * Reusable list of document number.
+     */
+    private final List ancestors = new ArrayList();
+
+    /**
+     * Reusable list of score nodes.
+     */
+    private final List scoreNodes = new ArrayList();
+
+    /**
+     * Creates a new descendant node join condition.
+     *
+     * @param context             the inner query hits.
+     * @param contextSelectorName the selector name for the inner query hits.
+     * @param reader              the index reader.
+     * @param resolver            the hierarchy resolver.
+     * @throws IOException if an error occurs while reading fromt the index.
+     */
+    public DescendantNodeJoin(MultiColumnQueryHits context,
+                              Name contextSelectorName,
+                              IndexReader reader,
+                              HierarchyResolver resolver) throws IOException {
+        super(context);
+        this.reader = reader;
+        this.resolver = resolver;
+        int idx = getIndex(context, contextSelectorName);
+        ScoreNode[] nodes;
+        while ((nodes = context.nextScoreNodes()) != null) {
+            Integer docNum = new Integer(nodes[idx].getDoc(reader));
+            contextIndex.addScoreNodes(docNum, nodes);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * The outer query hits loop contains the descendant nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode descendant)
+            throws IOException {
+        ancestors.clear();
+        collectAncestors(descendant.getDoc(reader));
+        scoreNodes.clear();
+        for (int i = 0; i < ancestors.size(); i++) {
+            Integer ancestor = (Integer) ancestors.get(i);
+            ScoreNode[][] sn = contextIndex.getScoreNodes(ancestor);
+            if (sn != null) {
+                for (int j = 0; j < sn.length; j++) {
+                    scoreNodes.add(sn[j]);
+                }
+            }
+        }
+        if (scoreNodes.isEmpty()) {
+            return null;
+        } else {
+            return (ScoreNode[][]) scoreNodes.toArray(new ScoreNode[scoreNodes.size()][]);
+        }
+    }
+
+    /**
+     * Collects the ancestors of the given <code>doc</code> number into
+     * {@link #ancestors}.
+     *
+     * @param doc the current document number.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    private void collectAncestors(int doc) throws IOException {
+        docNums = resolver.getParents(doc, docNums);
+        if (docNums.length == 1) {
+            ancestors.add(new Integer(docNums[0]));
+            collectAncestors(docNums[0]);
+        } else if (docNums.length > 1) {
+            // clone because recursion uses docNums again
+            int[] tmp = (int[]) docNums.clone();
+            for (int i = 0; i < tmp.length; i++) {
+                ancestors.add(new Integer(tmp[i]));
+                collectAncestors(tmp[i]);
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,93 @@
+/*
+ * 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 javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+
+/**
+ * <code>DescendantPathNodeJoin</code> implements a descendant path node join
+ * condition.
+ */
+public class DescendantPathNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits, indexed
+     * by the path of the of the inner query hits plus the relative path of this
+     * condition.
+     */
+    private final ScoreNodeMap contextIndex = new ScoreNodeMap();
+
+    /**
+     * The hierarchy manager.
+     */
+    private final HierarchyManager hmgr;
+
+    /**
+     * Creates a new descendant path node join condition.
+     *
+     * @param context             the inner query hits.
+     * @param contextSelectorName the selector name for the inner query hits.
+     * @param relPath             the relative path of the join condition.
+     * @param hmgr                the hierarchy manager.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public DescendantPathNodeJoin(MultiColumnQueryHits context,
+                                  Name contextSelectorName,
+                                  Path relPath,
+                                  HierarchyManager hmgr) throws IOException {
+        super(context);
+        this.hmgr = hmgr;
+        int idx = getIndex(context, contextSelectorName);
+        ScoreNode[] nodes;
+        while ((nodes = context.nextScoreNodes()) != null) {
+            try {
+                Path p = hmgr.getPath(nodes[idx].getNodeId());
+                PathBuilder builder = new PathBuilder(p);
+                builder.addAll(relPath.getElements());
+                p = builder.getPath().getNormalizedPath();
+                contextIndex.addScoreNodes(p, nodes);
+            } catch (RepositoryException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * The outer query hits loop contains the descendant nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode descendant)
+            throws IOException {
+        try {
+            Path p = hmgr.getPath(descendant.getNodeId());
+            return contextIndex.getScoreNodes(p);
+        } catch (RepositoryException e) {
+            // ignore, probably does not exist anymore
+        }
+        return null;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,98 @@
+/*
+ * 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 org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.SortComparatorSource;
+import org.apache.lucene.search.ScoreDocComparator;
+import org.apache.lucene.search.ScoreDoc;
+
+/**
+ * <code>EquiJoin</code> implements an equi join condition.
+ */
+public class EquiJoin extends AbstractCondition {
+
+    /**
+     * Reusable score doc for value lookups.
+     */
+    private final ScoreDoc sDoc = new ScoreDoc(-1, 1.0f);
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * Map of inner score nodes indexed by the value of their join property.
+     */
+    private final ScoreNodeMap innerScoreNodes = new ScoreNodeMap();
+
+    /**
+     * The score doc comparator for the outer query hits.
+     */
+    private final ScoreDocComparator outerLookup;
+
+    /**
+     * Creates a new equi join condition.
+     *
+     * @param inner               the inner query hits.
+     * @param innerScoreNodeIndex the selector name for the inner query hits.
+     * @param scs                 the sort comparator source.
+     * @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.
+     */
+    public EquiJoin(MultiColumnQueryHits inner,
+                    int innerScoreNodeIndex,
+                    SortComparatorSource scs,
+                    IndexReader reader,
+                    Name innerProperty,
+                    Name outerProperty) throws IOException {
+        super(inner);
+        this.reader = reader;
+        this.outerLookup = scs.newComparator(reader, outerProperty.toString());
+        ScoreDocComparator comparator = scs.newComparator(reader, innerProperty.toString());
+        ScoreNode[] nodes;
+        // create lookup map
+        while ((nodes = inner.nextScoreNodes()) != null) {
+            Integer doc = new Integer(nodes[innerScoreNodeIndex].getDoc(reader));
+            sDoc.doc = doc.intValue();
+            Comparable value = comparator.sortValue(sDoc);
+            if (value != null) {
+                innerScoreNodes.addScoreNodes(value, nodes);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode outer)
+            throws IOException {
+        sDoc.doc = outer.getDoc(reader);
+        Comparable value = outerLookup.sortValue(sDoc);
+        return innerScoreNodes.getScoreNodes(value);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Join.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Join.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Join.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Join.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,359 @@
+/*
+ * 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.List;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.HierarchyResolver;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.query.qom.JoinConditionImpl;
+import org.apache.jackrabbit.spi.commons.query.qom.DefaultQOMTreeVisitor;
+import org.apache.jackrabbit.spi.commons.query.qom.DescendantNodeJoinConditionImpl;
+import org.apache.jackrabbit.spi.commons.query.qom.EquiJoinConditionImpl;
+import org.apache.jackrabbit.spi.commons.query.qom.ChildNodeJoinConditionImpl;
+import org.apache.jackrabbit.spi.commons.query.qom.SameNodeJoinConditionImpl;
+import org.apache.jackrabbit.spi.commons.query.jsr283.qom.QueryObjectModelConstants;
+import org.apache.lucene.search.SortComparatorSource;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>Join</code> implements the result of a join.
+ */
+public class Join implements MultiColumnQueryHits, QueryObjectModelConstants {
+
+    /**
+     * The outer query hits.
+     */
+    protected final MultiColumnQueryHits outer;
+
+    /**
+     * The score node index of the outer query hits.
+     */
+    protected final int outerScoreNodeIndex;
+
+    /**
+     * Whether this is an inner join.
+     */
+    protected final boolean innerJoin;
+
+    /**
+     * The join condition.
+     */
+    protected final Condition condition;
+
+    /**
+     * The selector names.
+     */
+    protected final Name[] selectorNames;
+
+    /**
+     * An array of empty inner query hits.
+     */
+    protected final ScoreNode[] emptyInnerHits;
+
+    /**
+     * A buffer for joined score node rows.
+     */
+    protected final List buffer = new LinkedList();
+
+    /**
+     * Creates a new join.
+     *
+     * @param outer               the outer query hits.
+     * @param outerScoreNodeIndex the score node index of the outer query hits
+     *                            that is used for the join.
+     * @param innerJoin           whether this is an inner join.
+     * @param condition           the join condition.
+     */
+    private Join(MultiColumnQueryHits outer,
+                 int outerScoreNodeIndex,
+                 boolean innerJoin,
+                 Condition condition) {
+        this.outer = outer;
+        this.outerScoreNodeIndex = outerScoreNodeIndex;
+        this.innerJoin = innerJoin;
+        this.condition = condition;
+        this.emptyInnerHits = new ScoreNode[condition.getInnerSelectorNames().length];
+        // outer selector names go to the left, inner selector
+        // names go to the right.
+        // this needs to be in sync with ScoreNode[] aggregration/joining
+        // in nextScoreNodes() !
+        this.selectorNames = new Name[outer.getSelectorNames().length + emptyInnerHits.length];
+        System.arraycopy(outer.getSelectorNames(), 0, selectorNames, 0, outer.getSelectorNames().length);
+        System.arraycopy(condition.getInnerSelectorNames(), 0, selectorNames, outer.getSelectorNames().length, emptyInnerHits.length);
+    }
+
+    /**
+     * Creates a new join result.
+     *
+     * @param left      the left query hits.
+     * @param right     the right query hits.
+     * @param joinType  the join type.
+     * @param condition the QOM join condition.
+     * @param reader    the index reader.
+     * @param resolver  the hierarchy resolver.
+     * @param scs       the sort comparator source of the index.
+     * @param hmgr      the hierarchy manager of the workspace.
+     * @return the join result.
+     * @throws IOException if an error occurs while executing the join.
+     */
+    public static Join create(final MultiColumnQueryHits left,
+                              final MultiColumnQueryHits right,
+                              final int joinType,
+                              final JoinConditionImpl condition,
+                              final IndexReader reader,
+                              final HierarchyResolver resolver,
+                              final SortComparatorSource scs,
+                              final HierarchyManager hmgr)
+            throws IOException {
+        try {
+            return (Join) condition.accept(new DefaultQOMTreeVisitor() {
+
+                private boolean isInner = joinType == JOIN_TYPE_INNER;
+                private MultiColumnQueryHits outer;
+                private int outerIdx;
+
+                public Object visit(DescendantNodeJoinConditionImpl node, Object data)
+                        throws Exception {
+                    MultiColumnQueryHits ancestor = getSourceWithName(node.getAncestorSelectorQName(), left, right);
+                    MultiColumnQueryHits descendant = getSourceWithName(node.getDescendantSelectorQName(), left, right);
+                    Condition c;
+                    if (isInner || descendant == left && joinType == JOIN_TYPE_LEFT_OUTER
+                            || descendant == right && joinType == JOIN_TYPE_RIGHT_OUTER) {
+                        // also applies to inner join
+                        // assumption: DescendantNodeJoin is more
+                        // efficient than AncestorNodeJoin, TODO: verify
+                        outer = descendant;
+                        outerIdx = getIndex(outer, node.getDescendantSelectorQName());
+                        c = new DescendantNodeJoin(ancestor, node.getAncestorSelectorQName(), reader, resolver);
+                    } else {
+                        // left == ancestor
+                        outer = ancestor;
+                        outerIdx = getIndex(outer, node.getAncestorSelectorQName());
+                        c = new AncestorNodeJoin(descendant, node.getDescendantSelectorQName(), reader, resolver);
+                    }
+                    return new Join(outer, outerIdx, isInner, c);
+                }
+
+                public Object visit(EquiJoinConditionImpl node, Object data)
+                        throws Exception {
+                    MultiColumnQueryHits src1 = getSourceWithName(node.getSelector1QName(), left, right);
+                    MultiColumnQueryHits src2 = getSourceWithName(node.getSelector2QName(), left, right);
+                    MultiColumnQueryHits inner;
+                    Name innerName;
+                    Name innerPropName;
+                    Name outerPropName;
+                    if (isInner || src1 == left && joinType == JOIN_TYPE_LEFT_OUTER
+                            || src1 == right && joinType == JOIN_TYPE_RIGHT_OUTER) {
+                        outer = src1;
+                        outerIdx = getIndex(outer, node.getSelector1QName());
+                        inner = src2;
+                        innerName = node.getSelector2QName();
+                        innerPropName = node.getProperty2QName();
+                        outerPropName = node.getProperty1QName();
+                    } else {
+                        outer = src2;
+                        outerIdx = getIndex(outer, node.getSelector2QName());
+                        inner = src1;
+                        innerName = node.getSelector1QName();
+                        innerPropName = node.getProperty1QName();
+                        outerPropName = node.getProperty2QName();
+                    }
+
+                    Condition c = new EquiJoin(inner, getIndex(inner, innerName),
+                            scs, reader, innerPropName, outerPropName);
+                    return new Join(outer, outerIdx, isInner, c);
+                }
+
+                public Object visit(ChildNodeJoinConditionImpl node, Object data)
+                        throws Exception {
+                    MultiColumnQueryHits child = getSourceWithName(node.getChildSelectorQName(), left, right);
+                    MultiColumnQueryHits parent = getSourceWithName(node.getParentSelectorQName(), left, right);
+                    Condition c;
+                    if (child == left && joinType == JOIN_TYPE_LEFT_OUTER
+                            || child == right && joinType == JOIN_TYPE_RIGHT_OUTER) {
+                        outer = child;
+                        outerIdx = getIndex(outer, node.getChildSelectorQName());
+                        c = new ChildNodeJoin(parent, reader, resolver, node);
+                    } else {
+                        // also applies to inner joins
+                        // assumption: ParentNodeJoin is more efficient than
+                        // ChildNodeJoin, TODO: verify
+                        outer = parent;
+                        outerIdx = getIndex(outer, node.getParentSelectorQName());
+                        c = new ParentNodeJoin(child, reader, resolver, node);
+                    }
+                    return new Join(outer, outerIdx, isInner, c);
+                }
+
+                public Object visit(SameNodeJoinConditionImpl node, Object data)
+                        throws Exception {
+                    MultiColumnQueryHits src1 = getSourceWithName(node.getSelector1QName(), left, right);
+                    MultiColumnQueryHits src2 = getSourceWithName(node.getSelector2QName(), left, right);
+                    Condition c;
+                    if (isInner || src1 == left && joinType == JOIN_TYPE_LEFT_OUTER
+                            || src1 == right && joinType == JOIN_TYPE_RIGHT_OUTER) {
+                        outer = src1;
+                        outerIdx = getIndex(outer, node.getSelector1QName());
+                        if (node.getSelector2QPath() != null) {
+                            c = new DescendantPathNodeJoin(src2, node.getSelector2QName(),
+                                    node.getSelector2QPath(), hmgr);
+                        } else {
+                            c = new SameNodeJoin(src2, node.getSelector2QName(), reader);
+                        }
+                    } else {
+                        outer = src2;
+                        outerIdx = getIndex(outer, node.getSelector2QName());
+                        if (node.getSelector2QPath() != null) {
+                            c = new AncestorPathNodeJoin(src1, node.getSelector1QName(),
+                                    node.getSelector2QPath(), hmgr);
+                        } else {
+                            c = new SameNodeJoin(src1, node.getSelector1QName(), reader);
+                        }
+                    }
+                    return new Join(outer, outerIdx, isInner, c);
+                }
+            }, null);
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            IOException ex = new IOException(e.getMessage());
+            ex.initCause(e);
+            throw ex;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ScoreNode[] nextScoreNodes() throws IOException {
+        if (!buffer.isEmpty()) {
+            return (ScoreNode[]) buffer.remove(0);
+        }
+        do {
+            // refill buffer
+            ScoreNode[] sn = outer.nextScoreNodes();
+            if (sn == null) {
+                return null;
+            }
+            ScoreNode[][] nodes = condition.getMatchingScoreNodes(sn[outerScoreNodeIndex]);
+            if (nodes != null) {
+                for (int i = 0; i < nodes.length; i++) {
+                    ScoreNode[] node = nodes[i];
+                    // create array with both outer and inner
+                    ScoreNode[] tmp = new ScoreNode[sn.length + node.length];
+                    System.arraycopy(sn, 0, tmp, 0, sn.length);
+                    System.arraycopy(node, 0, tmp, sn.length, node.length);
+                    buffer.add(tmp);
+                }
+            } else if (!innerJoin) {
+                // create array with both inner and outer
+                ScoreNode[] tmp = new ScoreNode[sn.length + emptyInnerHits.length];
+                System.arraycopy(sn, 0, tmp, 0, sn.length);
+                System.arraycopy(emptyInnerHits, 0, tmp, sn.length, emptyInnerHits.length);
+                buffer.add(tmp);
+            }
+        } while (buffer.isEmpty());
+
+        return (ScoreNode[]) buffer.remove(0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Name[] getSelectorNames() {
+        return selectorNames;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Closes {@link #outer} source and the {@link #condition}.
+     */
+    public void close() throws IOException {
+        IOException ex = null;
+        try {
+            outer.close();
+        } catch (IOException e) {
+            ex = e;
+        }
+        try {
+            condition.close();
+        } catch (IOException e) {
+            if (ex == null) {
+                ex = e;
+            }
+        }
+        if (ex != null) {
+            throw ex;
+        }
+    }
+
+    /**
+     * This default implementation always returns <code>-1</code>.
+     *
+     * @return always <code>-1</code>.
+     */
+    public int getSize() {
+        return -1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Skips by calling {@link #nextScoreNodes()} <code>n</code> times. Sub
+     * classes may provide a more performance implementation.
+     */
+    public void skip(int n) throws IOException {
+        while (n-- > 0) {
+            if (nextScoreNodes() == null) {
+                return;
+            }
+        }
+    }
+
+    protected static MultiColumnQueryHits getSourceWithName(
+            Name selectorName,
+            MultiColumnQueryHits left,
+            MultiColumnQueryHits right) {
+        if (Arrays.asList(left.getSelectorNames()).contains(selectorName)) {
+            return left;
+        } else if (Arrays.asList(right.getSelectorNames()).contains(selectorName)) {
+            return right;
+        } else {
+            throw new IllegalArgumentException("unknown selector name: " + selectorName);
+        }
+    }
+
+    /**
+     * Returns the index of the selector with the given <code>selectorName</code>
+     * within the given <code>source</code>.
+     *
+     * @param source       a source.
+     * @param selectorName a selector name.
+     * @return the index within the source or <code>-1</code> if the name does
+     *         not exist in <code>source</code>.
+     */
+    protected static int getIndex(MultiColumnQueryHits source,
+                                  Name selectorName) {
+        return Arrays.asList(source.getSelectorNames()).indexOf(selectorName);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/Join.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ParentNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ParentNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ParentNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ParentNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,78 @@
+/*
+ * 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 org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.HierarchyResolver;
+import org.apache.jackrabbit.spi.commons.query.qom.ChildNodeJoinConditionImpl;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>ParentNodeJoin</code> implements a parent node join condition.
+ */
+public class ParentNodeJoin extends AbstractCondition {
+
+    /**
+     * The child score nodes indexed by their parent document number.
+     */
+    private final ScoreNodeMap childIndex = new ScoreNodeMap();
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * Creates a new parent node join condition.
+     *
+     * @param child     the inner query hits.
+     * @param reader    the index reader.
+     * @param resolver  the hierarchy resolver.
+     * @param condition the QOM child node join condition.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public ParentNodeJoin(MultiColumnQueryHits child,
+                          IndexReader reader,
+                          HierarchyResolver resolver,
+                          ChildNodeJoinConditionImpl condition)
+            throws IOException {
+        super(child);
+        this.reader = reader;
+        int idx = getIndex(child, condition.getChildSelectorQName());
+        ScoreNode[] nodes;
+        int[] docNums = new int[1];
+        while ((nodes = child.nextScoreNodes()) != null) {
+            docNums = resolver.getParents(nodes[idx].getDoc(reader), docNums);
+            for (int i = 0; i < docNums.length; i++) {
+                Integer parentId = new Integer(docNums[i]);
+                childIndex.addScoreNodes(parentId, nodes);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * The outer query hits loop contains the parent score nodes.
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode parent)
+            throws IOException {
+        return childIndex.getScoreNodes(new Integer(parent.getDoc(reader)));
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ParentNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoin.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoin.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoin.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoin.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,70 @@
+/*
+ * 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 org.apache.jackrabbit.core.query.lucene.MultiColumnQueryHits;
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * <code>SameNodeJoin</code> implements a same node join condition.
+ */
+public class SameNodeJoin extends AbstractCondition {
+
+    /**
+     * A score node map with the score nodes from the inner query hits, indexed
+     * by their document number.
+     */
+    private final ScoreNodeMap innerIndex = new ScoreNodeMap();
+
+    /**
+     * The index reader.
+     */
+    private final IndexReader reader;
+
+    /**
+     * Creates a new same node join.
+     *
+     * @param inner             the inner query hits.
+     * @param innerSelectorName the selector name for the inner query hits.
+     * @param reader            the index reader.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public SameNodeJoin(MultiColumnQueryHits inner,
+                        Name innerSelectorName,
+                        IndexReader reader) throws IOException {
+        super(inner);
+        this.reader = reader;
+        int idx = getIndex(inner, innerSelectorName);
+        ScoreNode[] nodes;
+        while ((nodes = inner.nextScoreNodes()) != null) {
+            Integer docNum = new Integer(nodes[idx].getDoc(reader));
+            innerIndex.addScoreNodes(docNum, nodes);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ScoreNode[][] getMatchingScoreNodes(ScoreNode outer)
+            throws IOException {
+        return innerIndex.getScoreNodes(new Integer(outer.getDoc(reader)));
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/SameNodeJoin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ScoreNodeMap.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ScoreNodeMap.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ScoreNodeMap.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ScoreNodeMap.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,93 @@
+/*
+ * 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.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.jackrabbit.core.query.lucene.ScoreNode;
+
+/**
+ * <code>ScoreNodeMap</code> implements a simple mapping of an arbitrary key
+ * to an array of <code>ScoreNode[]</code>.
+ */
+public final class ScoreNodeMap {
+
+    /**
+     * The internal map.
+     */
+    private final Map map = new HashMap();
+
+    /**
+     * Adds <code>scoreNodes</code> to this map under the given <code>key</code>.
+     * If there already exists a mapping with the given <code>key</code> the
+     * <code>scoreNodes</code> are added to the existingmapping. The add
+     * operation works as follows:
+     * <ul>
+     * <li>If the existing value for <code>key</code> is a <code>ScoreNode[]</code>,
+     * then the value is turned into a <code>List</code> and the exising value
+     * as well as the new value are added to the <code>List</code>. Finally
+     * the <code>List</code> is uses as the new value for the mapping.
+     * </li>
+     * <li>If the existing value for <code>key</code> is a <code>List</code> the
+     * <code>scoreNodes</code> are simply added to the <code>List</code>.
+     * </li>
+     * </ul>
+     *
+     * @param key   the lookup key.
+     * @param nodes the score nodes.
+     */
+    public void addScoreNodes(Object key, ScoreNode[] nodes) {
+        Object existing = map.get(key);
+        if (existing == null) {
+            existing = nodes;
+            map.put(key, existing);
+        } else if (existing instanceof List) {
+            ((List) existing).add(nodes);
+        } else {
+            // ScoreNode[]
+            ArrayList tmp = new ArrayList();
+            tmp.add(existing);
+            tmp.add(nodes);
+            existing = tmp;
+            map.put(key, existing);
+        }
+    }
+
+    /**
+     * Returns an array of <code>ScoreNode[]</code> for the given
+     * <code>key</code>.
+     *
+     * @param key the key.
+     * @return an array of <code>ScoreNode[]</code> that match the given
+     *         <code>key</code> or <code>null</code> if there is none.
+     */
+    public ScoreNode[][] getScoreNodes(Object key) {
+        Object sn = map.get(key);
+        if (sn == null) {
+            return null;
+        } else if (sn instanceof List) {
+            List list = (List) sn;
+            return (ScoreNode[][]) list.toArray(new ScoreNode[list.size()][]);
+        } else {
+            // ScoreNode[]
+            return new ScoreNode[][]{(ScoreNode[]) sn};
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ScoreNodeMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractJoinTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractJoinTest.java?rev=768954&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractJoinTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractJoinTest.java Mon Apr 27 12:43:28 2009
@@ -0,0 +1,89 @@
+/*
+ * 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.api.jsr283.query.qom;
+
+import javax.jcr.query.QueryResult;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.commons.query.jsr283.qom.QueryObjectModel;
+import org.apache.jackrabbit.spi.commons.query.jsr283.qom.Constraint;
+import org.apache.jackrabbit.spi.commons.query.jsr283.qom.JoinCondition;
+
+/**
+ * <code>AbstractJoinTest</code> provides utility methods for join related
+ * tests.
+ */
+public abstract class AbstractJoinTest extends AbstractQOMTest {
+
+    /**
+     * Name of the left selector.
+     */
+    protected static final String LEFT = "left";
+
+    /**
+     * Name of the right selector.
+     */
+    protected static final String RIGHT = "right";
+
+    /**
+     * The selector names for the join.
+     */
+    protected static final String[] SELECTOR_NAMES = new String[]{LEFT, RIGHT};
+
+    //--------------------------< utilities >-----------------------------------
+
+    protected void checkResult(QueryResult result, Node[][] nodes)
+            throws RepositoryException {
+        checkResult(result, SELECTOR_NAMES, nodes);
+    }
+
+    protected QueryObjectModel createQuery(int joinType,
+                                           JoinCondition condition)
+            throws RepositoryException {
+        return createQuery(joinType, condition, null, null);
+    }
+
+    protected QueryObjectModel createQuery(int joinType,
+                                           JoinCondition condition,
+                                           Constraint left,
+                                           Constraint right)
+            throws RepositoryException {
+        // only consider nodes under test root
+        Constraint constraint;
+        if (joinType == JOIN_TYPE_LEFT_OUTER) {
+            constraint = qomFactory.descendantNode(LEFT, testRoot);
+        } else {
+            constraint = qomFactory.descendantNode(RIGHT, testRoot);
+        }
+
+        if (left != null) {
+            constraint = qomFactory.and(constraint, left);
+        }
+        if (right != null) {
+            constraint = qomFactory.and(constraint, right);
+        }
+        return qomFactory.createQuery(
+                qomFactory.join(
+                        qomFactory.selector(testNodeType, LEFT),
+                        qomFactory.selector(testNodeType, RIGHT),
+                        joinType,
+                        condition
+                ), constraint, null, null
+        );
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractJoinTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractQOMTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractQOMTest.java?rev=768954&r1=768218&r2=768954&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractQOMTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/AbstractQOMTest.java Mon Apr 27 12:43:28 2009
@@ -14,7 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core.query.qom;
+package org.apache.jackrabbit.api.jsr283.query.qom;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Iterator;
 
 import org.apache.jackrabbit.test.api.query.AbstractQueryTest;
 
@@ -22,10 +26,14 @@
 import org.apache.jackrabbit.spi.commons.query.jsr283.qom.QueryObjectModelConstants;
 import org.apache.jackrabbit.core.query.QueryManagerImpl;
 import org.apache.jackrabbit.core.query.QueryImpl;
+import org.apache.jackrabbit.api.jsr283.query.Row;
 
 import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
+import javax.jcr.Node;
 
 /**
  * <code>AbstractQOMTest</code> is a base class for test cases on the JQOM.
@@ -58,4 +66,64 @@
         // TODO: remove cast when bindValue() is available on JSR 283 Query
         ((QueryImpl) q).bindValue(var, value);
     }
+
+    protected void checkResult(QueryResult result,
+                               String[] selectorNames,
+                               Node[][] nodes)
+            throws RepositoryException {
+        // collect rows
+        Set expectedPaths = new HashSet();
+        log.println("expected:");
+        for (int i = 0; i < nodes.length; i++) {
+            StringBuffer aggregatedPaths = new StringBuffer();
+            for (int j = 0; j < nodes[i].length; j++) {
+                aggregatedPaths.append(getPath(nodes[i][j]));
+                aggregatedPaths.append("|");
+            }
+            expectedPaths.add(aggregatedPaths.toString());
+            log.println(aggregatedPaths.toString());
+        }
+
+        Set resultPaths = new HashSet();
+        log.println("result:");
+        for (RowIterator it = result.getRows(); it.hasNext();) {
+            Row r = (Row) it.nextRow();
+            StringBuffer aggregatedPaths = new StringBuffer();
+            for (int i = 0; i < selectorNames.length; i++) {
+                aggregatedPaths.append(getPath(r.getNode(selectorNames[i])));
+                aggregatedPaths.append("|");
+            }
+            resultPaths.add(aggregatedPaths.toString());
+            log.println(aggregatedPaths.toString());
+        }
+
+        // check if all expected are in result
+        for (Iterator it = expectedPaths.iterator(); it.hasNext();) {
+            String path = (String) it.next();
+            assertTrue(path + " is not part of the result set", resultPaths.contains(path));
+        }
+        // check result does not contain more than expected
+        for (Iterator it = resultPaths.iterator(); it.hasNext();) {
+            String path = (String) it.next();
+            assertTrue(path + " is not expected to be part of the result set", expectedPaths.contains(path));
+        }
+    }
+
+    /**
+     * Returns the path of the <code>node</code> or an empty string if
+     * <code>node</code> is <code>null</code>.
+     *
+     * @param node a node or <code>null</code>.
+     * @return the path of the node or an empty string if <code>node</code> is
+     *         <code>null</code>.
+     * @throws RepositoryException if an error occurs while reading from the
+     *                             repository.
+     */
+    protected static String getPath(Node node) throws RepositoryException {
+        if (node != null) {
+            return node.getPath();
+        } else {
+            return "";
+        }
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/BindVariableValueTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/BindVariableValueTest.java?rev=768954&r1=768218&r2=768954&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/BindVariableValueTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/BindVariableValueTest.java Mon Apr 27 12:43:28 2009
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core.query.qom;
+package org.apache.jackrabbit.api.jsr283.query.qom;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Node;

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/ChildNodeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/ChildNodeTest.java?rev=768954&r1=768218&r2=768954&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/ChildNodeTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/jsr283/query/qom/ChildNodeTest.java Mon Apr 27 12:43:28 2009
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jackrabbit.core.query.qom;
+package org.apache.jackrabbit.api.jsr283.query.qom;
 
 import org.apache.jackrabbit.test.NotExecutableException;