Index: org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java (working copy) @@ -16,26 +16,40 @@ */ package org.apache.jackrabbit.core.query.lucene.join; +import java.io.IOException; import java.util.Map; +import java.util.Set; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Value; +import javax.jcr.ValueFactory; import javax.jcr.query.Row; import javax.jcr.query.qom.Operand; import javax.jcr.query.qom.PropertyValue; import org.apache.jackrabbit.commons.query.qom.OperandEvaluator; +import org.apache.jackrabbit.core.id.NodeId; +import org.apache.jackrabbit.core.query.lucene.ExcerptProvider; +import org.apache.jackrabbit.core.query.lucene.HighlightingExcerptProvider; +import org.apache.jackrabbit.util.ISO9075; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; abstract class AbstractRow implements Row { + private static Logger log = LoggerFactory.getLogger(AbstractRow.class); + + private static final String EXCERPT_PREFIX = "rep:excerpt("; + private final Map columns; private final OperandEvaluator evaluator; - protected AbstractRow( - Map columns, OperandEvaluator evaluator) { + protected AbstractRow(Map columns, OperandEvaluator evaluator) { this.columns = columns; this.evaluator = evaluator; } @@ -55,11 +69,192 @@ if (operand != null) { return evaluator.getValue(operand, this); } else { - throw new ItemNotFoundException( - "Column " + columnName + " is not included in this row"); + if (isExcerptFunction(columnName)) { + // excerpt function with parameter + return getExcerpt(columnName); + } else { + throw new ItemNotFoundException( + "Column " + columnName + " is not included in this row"); + } } } + /** + * @param name a String. + * @return true if name is the rep:excerpt function, false otherwise. + */ + private boolean isExcerptFunction(String name) { + return name.startsWith(EXCERPT_PREFIX); + } + + /** + * Returns an excerpt for the node indicated by the relative path parameter of the rep:excerpt function. The relative path is resolved against the node + * associated with this row. + * + * @param excerptCall + * the rep:excerpt function with the parameter as string. + * @return a StringValue or null if the excerpt cannot be created or an error occurs. + * @throws RepositoryException + * if the function call is not well-formed. + */ + private Value getExcerpt(String excerptCall) + throws RepositoryException { + int idx = excerptCall.indexOf(EXCERPT_PREFIX); + int end = excerptCall.lastIndexOf(')'); + if (end == -1) { + throw new RepositoryException("Missing right parenthesis"); + } + String pathStr = excerptCall.substring(idx + EXCERPT_PREFIX.length(), end).trim(); + String decodedPath = ISO9075.decode(pathStr); + + // get node of selector + Node n = null; + try { + n = getNode(decodedPath); + } catch (RepositoryException e) { + // bad selector name + } + if (n != null) { + NodeId id = new NodeId(n.getIdentifier()); + return createExcerpt(decodedPath, id); + } else { + // does not exist or references a property + try { + String sel; + String name; + idx = decodedPath.indexOf('.'); + if (idx >= 0) { + sel = decodedPath.substring(0, idx); + name = decodedPath.substring(idx + 1); + } else { + sel = null; + name = decodedPath; + } + if (sel == null) + return null; + + n = getNode(sel); + if (n != null) { + Property p = n.getProperty(name); + // JFM 20170505 : traitement des propriétés multivaluées + if (p.isMultiple()) { + StringBuilder text = new StringBuilder(); + Value[] values1 = p.getValues(); + for (Value value : values1) { + text.append(value.getString()); + text.append(' '); + } + return highlight(sel, text.toString()); + } else { + return highlight(sel, p.getValue().getString()); + } + } else + return null; + } catch (PathNotFoundException e1) { + // does not exist + return null; + } + } + } + + /** + * Creates an excerpt for node with the given id. + * + * @param id + * a node id. + * @return a StringValue or null if the excerpt cannot be created or an error occurs. + */ + private Value createExcerpt(String selector, NodeId id) { + try { + if (getExcerptProvider(selector) == null) { + return null; + } + long time = System.currentTimeMillis(); + + String sep = ""; + StringBuilder sb = new StringBuilder(); + for (ExcerptProvider ep : getExcerptProvider(selector)) { + String excerpt = ep.getExcerpt(id, 3, 150); + if (excerpt != null) { + sb.append(sep); + sb.append(excerpt); + sep = " "; + } + } + time = System.currentTimeMillis() - time; + log.debug("Created excerpt in {} ms.", time); + if (sb.length() > 0) { + return getValueFactory(selector).createValue(sb.toString()); + } else { + return null; + } + } catch (IOException | RepositoryException e) { + return null; + } + } + + /** + * Highlights the matching terms in the passed text. + * + * @param text + * the text where to apply highlighting. + * @return a StringValue or null if highlighting fails. + */ + private Value highlight(String selector, String text) { + try { + if (getExcerptProvider(selector) == null) { + return null; + } + long time = System.currentTimeMillis(); + String sep = ""; + StringBuilder sb = new StringBuilder(); + for (ExcerptProvider ep : getExcerptProvider(selector)) { + if (ep instanceof HighlightingExcerptProvider) { + HighlightingExcerptProvider hep = (HighlightingExcerptProvider) ep; + String highlight = hep.highlight(text); + if (highlight != null) { + sb.append(sep); + sb.append(highlight); + sep = " "; + } + } + } + time = System.currentTimeMillis() - time; + log.debug("Highlighted text in {} ms.", time); + if (sb.length() > 0) + return getValueFactory(selector).createValue(sb.toString()); + else + return null; + } catch (IOException | RepositoryException e) { + return null; + } + } + + public void mergeExcerptProviders(AbstractRow row) { + + for (String selector : row.getSelectors()) { + try { + Set providers = row.getExcerptProvider(selector); + if (providers != null) + for (ExcerptProvider ep : row.getExcerptProvider(selector)) + this.addExcerptProvider(selector, ep); + } catch (RepositoryException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + + protected abstract void addExcerptProvider(String selector, ExcerptProvider ep); + + protected abstract Set getExcerptProvider(String selector) throws RepositoryException; + + protected abstract ValueFactory getValueFactory(String selector) throws RepositoryException; + + protected abstract Set getSelectors(); + public String getPath() throws RepositoryException { Node node = getNode(); if (node != null) { Index: org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java (working copy) @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -46,9 +47,11 @@ import javax.jcr.query.qom.Selector; import javax.jcr.query.qom.Source; +import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.commons.iterator.RowIterable; import org.apache.jackrabbit.commons.iterator.RowIteratorAdapter; import org.apache.jackrabbit.commons.query.qom.OperandEvaluator; +import org.apache.jackrabbit.core.query.lucene.ExcerptProvider; /** * A join merger is used by the {@link QueryEngine} class to efficiently @@ -232,6 +235,7 @@ for (Row leftRow : new RowIterable(leftRows)) { Set leftValues = getLeftValues(leftRow); if(leftValues.isEmpty()){ + leftValues = new HashSet(); // Collections.emptySet().add throw UnsupportedOperationException leftValues.add(null); } for (String value : leftValues) { @@ -252,8 +256,18 @@ // of joins that are bigger than 2 way? // how does this perform for 3 way joins ? for (Row r : excludingOuterJoinRowsSet) { - if(rowComparator.compare(rightRow, r) == 0){ + if (rowComparator.compare(rightRow, r) == 0){ isIncluded = true; + + // JFM: 20190522 + // r Row can contains an excerpt provider usefull to FT query part + // we maintains a set of all the excerpt provider of a row + if (r instanceof AbstractRow && rightRow instanceof AbstractRow) { + AbstractRow aexclude = (AbstractRow) r; + AbstractRow aright = (AbstractRow) rightRow; + aright.mergeExcerptProviders(aexclude); + } + break; } } Index: org/apache/jackrabbit/core/query/lucene/join/JoinRow.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/join/JoinRow.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/join/JoinRow.java (working copy) @@ -16,15 +16,18 @@ */ package org.apache.jackrabbit.core.query.lucene.join; +import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.jcr.Node; import javax.jcr.RepositoryException; +import javax.jcr.ValueFactory; import javax.jcr.query.Row; import javax.jcr.query.qom.PropertyValue; import org.apache.jackrabbit.commons.query.qom.OperandEvaluator; +import org.apache.jackrabbit.core.query.lucene.ExcerptProvider; public class JoinRow extends AbstractRow { @@ -117,4 +120,45 @@ return builder.toString(); } + @Override + protected Set getExcerptProvider(String selectorName) throws RepositoryException { + Row row = getRow(selectorName); + if (row != null) { + return ((AbstractRow) row).getExcerptProvider(selectorName); + } else { + return null; + } + } + + @Override + protected ValueFactory getValueFactory(String selectorName) throws RepositoryException { + Row row = getRow(selectorName); + if (row != null) { + return ((AbstractRow) row).getValueFactory(selectorName); + } else { + return null; + } + } + + @Override + public Set getSelectors() { + Set sels = new HashSet(); + if (rightSelectors != null) + sels.addAll(rightSelectors); + if (leftSelectors != null) + sels.addAll(leftSelectors); + return sels; + } + + @Override + protected void addExcerptProvider(String selectorName, ExcerptProvider ep) { + Row row; + try { + row = getRow(selectorName); + if (row != null) + ((AbstractRow) row).addExcerptProvider(selectorName, ep); + } catch (RepositoryException e) { + } + } + } Index: org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java (working copy) @@ -73,17 +73,15 @@ /** * The logger instance for this class */ - private static final Logger log = LoggerFactory - .getLogger(QueryEngine.class); - - //TODO remove this when the implementation is stable + private static final Logger log = LoggerFactory.getLogger(QueryEngine.class); + + // TODO remove this when the implementation is stable public static final String NATIVE_SORT_SYSTEM_PROPERTY = "useNativeSort"; - private static final boolean NATIVE_SORT = Boolean.valueOf(System - .getProperty(NATIVE_SORT_SYSTEM_PROPERTY, "false")); + private static final boolean NATIVE_SORT = Boolean.valueOf(System.getProperty(NATIVE_SORT_SYSTEM_PROPERTY, "false")); private static final int printIndentStep = 4; - + private final Session session; private final LuceneQueryFactory lqf; @@ -187,8 +185,10 @@ QueryResult branch1 = execute(merger, csInfo.getLeftInnerConstraints(), isOuterJoin, printIndentation + printIndentStep); - Set allRows = new TreeSet(new RowPathComparator( - Arrays.asList(merger.getSelectorNames()))); + + Comparator allCo = new RowPathComparator( + Arrays.asList(merger.getSelectorNames())); + Set allRows = new TreeSet(allCo); RowIterator ri1 = branch1.getRows(); while (ri1.hasNext()) { Row r = ri1.nextRow(); @@ -206,7 +206,7 @@ RowIterator ri2 = branch2.getRows(); while (ri2.hasNext()) { Row r = ri2.nextRow(); - allRows.add(r); + updateSet(allRows, r, allCo); } log.debug("{} SQL2 JOIN executed second branch, took {} ms.", genString(printIndentation), System.currentTimeMillis() @@ -286,8 +286,11 @@ Set leftRows = new TreeSet(comparator); leftRows.addAll(buildLeftRowsJoin(csi.getLeftInnerConstraints(), comparator, printIndentation + printIndentStep)); - leftRows.addAll(buildLeftRowsJoin(csi.getRightInnerConstraints(), - comparator, printIndentation + printIndentStep)); + Set set2 = buildLeftRowsJoin(csi.getRightInnerConstraints(), + comparator, printIndentation + printIndentStep); + for (Row row : set2) { + updateSet(leftRows, row, comparator); + } return leftRows; } @@ -327,9 +330,12 @@ rightRows.addAll(buildRightRowsJoin(csi.getLeftInnerConstraints(), rightConstraints, ignoreWhereConstraints, comparator, printIndentation + printIndentStep)); - rightRows.addAll(buildRightRowsJoin(csi.getRightInnerConstraints(), + Set set2 = buildRightRowsJoin(csi.getRightInnerConstraints(), rightConstraints, ignoreWhereConstraints, comparator, - printIndentation + printIndentStep)); + printIndentation + printIndentStep); + for (Row row : set2) { + updateSet(rightRows, row, comparator); + } return rightRows; } @@ -371,7 +377,7 @@ QueryResult rightResult = execute(null, csi.getSource().getRight(), rightConstraint, null, 0, -1, printIndentation); for (Row row : JcrUtils.getRows(rightResult)) { - rightRows.add(row); + updateSet(rightRows, row, comparator); } } return rightRows; @@ -641,4 +647,26 @@ } } + private void updateSet(Set allRows, Row r, Comparator comparator) { + + if (allRows instanceof TreeSet) { + TreeSet treeSet = (TreeSet)allRows; + Row r1 = treeSet.floor(r); + if ((r1 != null) && treeSet.comparator().compare(r1, r) == 0) { + ((AbstractRow) r1).mergeExcerptProviders((AbstractRow) r); + }else + allRows.add(r); + + }else { + if (allRows.contains(r)) { + for (Row r1 : allRows) { + if (comparator.compare(r1, r) == 0) { + ((AbstractRow) r1).mergeExcerptProviders((AbstractRow) r); + break; + } + } + } else + allRows.add(r); + } + } } Index: org/apache/jackrabbit/core/query/lucene/join/SelectorRow.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/join/SelectorRow.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/join/SelectorRow.java (working copy) @@ -16,13 +16,17 @@ */ package org.apache.jackrabbit.core.query.lucene.join; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.jcr.Node; import javax.jcr.RepositoryException; +import javax.jcr.ValueFactory; import javax.jcr.query.qom.PropertyValue; import org.apache.jackrabbit.commons.query.qom.OperandEvaluator; +import org.apache.jackrabbit.core.query.lucene.ExcerptProvider; /** * A row implementation for a query with just a single selector. @@ -34,8 +38,15 @@ private final Node node; private final double score; + + private final Set excerptProviders = new HashSet(); + private final ValueFactory valueFactory; + + + public SelectorRow( + ValueFactory valueFactory, ExcerptProvider excerptProvider, Map columns, OperandEvaluator evaluator, String selector, Node node, double score) { super(columns, evaluator); @@ -42,6 +53,9 @@ this.selector = selector; this.node = node; this.score = score; + this.valueFactory = valueFactory; + if (excerptProvider != null) + this.excerptProviders.add(excerptProvider); } public Node getNode() { @@ -75,4 +89,30 @@ return "{ " + selector + ": " + node + " }"; } + public Set getExcerptProvider(String selectorName) + throws RepositoryException { + checkSelectorName(selectorName); + return excerptProviders; + } + + public ValueFactory getValueFactory(String selectorName) + throws RepositoryException { + checkSelectorName(selectorName); + return valueFactory; + } + + @Override + public Set getSelectors() { + Set sels = new HashSet(); + sels.add(selector); + return sels; + } + + @Override + protected void addExcerptProvider(String selectorName, ExcerptProvider ep) { + if (selector.equals(selectorName) && ep != null) + this.excerptProviders.add(ep); + } + + } Index: org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java =================================================================== --- org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java (revision 1825972) +++ org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java (working copy) @@ -49,6 +49,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -147,6 +148,11 @@ private final PerQueryCache cache = new PerQueryCache(); /** + * The excerpt provider or null if none was created yet. + */ + private Map excerptProviders = new HashMap(); + + /** * Creates a new lucene query factory. * * @param session the session that executes the query. @@ -224,7 +230,9 @@ while (node != null) { Row row = null; try { - row = new SelectorRow(columns, evaluator, + row = new SelectorRow(session.getValueFactory(), + getExcerptProvider(qp.mainQuery), + columns, evaluator, selector.getSelectorName(), session.getNodeById(node.getNodeId()), node.getScore()); @@ -260,6 +268,27 @@ } } + ExcerptProvider getExcerptProvider(Query query) { + + ExcerptProvider excerptProvider=null; + String s = query.toString(); + + // create excerptProvider if FT query + if ((s.indexOf(FieldNames.FULLTEXT) > -1) || (s.indexOf(FieldNames.FULLTEXT_PREFIX) > -1)) { + if ((excerptProvider = excerptProviders.get(query)) == null) { + try { + excerptProvider = index.createExcerptProvider(query); + excerptProviders.put(query, excerptProvider); + } catch (IOException e) { + } + } + + } + + return excerptProvider; + + } + /** * Creates a lucene query for the given QOM selector. *