Index: src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractNamespaceMappings.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractNamespaceMappings.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractNamespaceMappings.java (revision 0) @@ -0,0 +1,72 @@ +/* + * 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; + +import javax.jcr.NamespaceException; + +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; +import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException; +import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; +import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; + +/** + * AbstractNamespaceMappings is the base class for index internal + * namespace mappings. + */ +public abstract class AbstractNamespaceMappings + implements NamespaceMappings, NamespaceResolver { + + /** + * The name resolver used to translate the qualified name to JCR name + */ + private final NamePathResolver resolver; + + public AbstractNamespaceMappings() { + this.resolver = NamePathResolverImpl.create(this); + } + + //----------------------------< NamespaceMappings >------------------------- + + /** + * {@inheritDoc} + */ + public String translateName(Name qName) + throws IllegalNameException { + try { + return resolver.getJCRName(qName); + } catch (NamespaceException e) { + // should never happen actually, because we create yet unknown + // uri mappings on the fly. + throw new IllegalNameException("Internal error.", e); + } + } + + /** + * {@inheritDoc} + */ + public String translatePath(Path path) throws IllegalNameException { + try { + return resolver.getJCRPath(path); + } catch (NamespaceException e) { + // should never happen actually, because we create yet unknown + // uri mappings on the fly. + throw new IllegalNameException("Internal error.", e); + } + } + +} Property changes on: src\main\java\org\apache\jackrabbit\core\query\lucene\AbstractNamespaceMappings.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java (working copy) @@ -18,6 +18,7 @@ import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.ItemStateException; +import org.apache.jackrabbit.core.state.PropertyState; import javax.jcr.RepositoryException; @@ -55,4 +56,17 @@ */ NodeState[] getAggregatedNodeStates(NodeState nodeState) throws ItemStateException; + + /** + * Returns the property states that are part of the indexing aggregate of + * the nodeState. + * + * @param nodeState a node state + * @return the property states that are part of the indexing aggregate of + * nodeState. Returns null if this + * aggregate does not apply to nodeState. + * @throws ItemStateException if an error occurs. + */ + public PropertyState[] getAggregatedPropertyStates(NodeState nodeState) + throws ItemStateException; } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java (working copy) @@ -27,8 +27,10 @@ import org.apache.jackrabbit.core.state.ItemStateManager; import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.core.state.ChildNodeEntry; +import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.HierarchyManager; import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; import org.apache.jackrabbit.util.Text; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -36,6 +38,8 @@ import javax.jcr.RepositoryException; import javax.jcr.NamespaceException; +import javax.jcr.PathNotFoundException; + import java.util.ArrayList; import java.util.List; import java.util.Arrays; @@ -60,11 +64,16 @@ private final Name nodeTypeName; /** - * The rules that define this indexing aggregate. + * The node includes of this indexing aggregate. */ - private final Rule[] rules; + private final NodeInclude[] nodeIncludes; /** + * The property includes of this indexing aggregate. + */ + private final PropertyInclude[] propertyIncludes; + + /** * The item state manager to retrieve additional item states. */ private final ItemStateManager ism; @@ -91,11 +100,12 @@ AggregateRuleImpl(Node config, NameResolver resolver, ItemStateManager ism, - HierarchyManager hmgr) - throws MalformedPathException, IllegalNameException, NamespaceException { + HierarchyManager hmgr) throws MalformedPathException, + IllegalNameException, NamespaceException, PathNotFoundException { this.resolver = resolver; this.nodeTypeName = getNodeTypeName(config); - this.rules = getRules(config); + this.nodeIncludes = getNodeIncludes(config); + this.propertyIncludes = getPropertyIncludes(config); this.ism = ism; this.hmgr = hmgr; } @@ -104,7 +114,7 @@ * Returns root node state for the indexing aggregate where * nodeState belongs to. * - * @param nodeState + * @param nodeState the node state. * @return the root node state of the indexing aggregate or * null if nodeState does not belong to an * indexing aggregate. @@ -113,13 +123,21 @@ */ public NodeState getAggregateRoot(NodeState nodeState) throws ItemStateException, RepositoryException { - for (int i = 0; i < rules.length; i++) { - NodeState aggregateRoot = rules[i].matches(nodeState); + for (int i = 0; i < nodeIncludes.length; i++) { + NodeState aggregateRoot = nodeIncludes[i].matches(nodeState); if (aggregateRoot != null && aggregateRoot.getNodeTypeName().equals(nodeTypeName)) { return aggregateRoot; } } + // check property includes + for (int i = 0; i < propertyIncludes.length; i++) { + NodeState aggregateRoot = propertyIncludes[i].matches(nodeState); + if (aggregateRoot != null + && aggregateRoot.getNodeTypeName().equals(nodeTypeName)) { + return aggregateRoot; + } + } return null; } @@ -137,8 +155,8 @@ throws ItemStateException { if (nodeState.getNodeTypeName().equals(nodeTypeName)) { List nodeStates = new ArrayList(); - for (int i = 0; i < rules.length; i++) { - nodeStates.addAll(Arrays.asList(rules[i].resolve(nodeState))); + for (int i = 0; i < nodeIncludes.length; i++) { + nodeStates.addAll(Arrays.asList(nodeIncludes[i].resolve(nodeState))); } if (nodeStates.size() > 0) { return (NodeState[]) nodeStates.toArray(new NodeState[nodeStates.size()]); @@ -147,6 +165,25 @@ return null; } + /** + * {@inheritDoc} + */ + public PropertyState[] getAggregatedPropertyStates(NodeState nodeState) + throws ItemStateException { + if (nodeState.getNodeTypeName().equals(nodeTypeName)) { + List propStates = new ArrayList(); + for (int i = 0; i < propertyIncludes.length; i++) { + propStates.addAll(Arrays.asList( + propertyIncludes[i].resolvePropertyStates(nodeState))); + } + if (propStates.size() > 0) { + return (PropertyState[]) propStates.toArray( + new PropertyState[propStates.size()]); + } + } + return null; + } + //---------------------------< internal >----------------------------------- /** @@ -166,10 +203,10 @@ } /** - * Creates rules defined in the config. + * Creates node includes defined in the config. * * @param config the indexing aggregate configuration. - * @return the rules defined in the config. + * @return the node includes defined in the config. * @throws MalformedPathException if a path in the configuration is * malformed. * @throws IllegalNameException if the node type name contains illegal @@ -177,9 +214,9 @@ * @throws NamespaceException if the node type contains an unknown * prefix. */ - private Rule[] getRules(Node config) + private NodeInclude[] getNodeIncludes(Node config) throws MalformedPathException, IllegalNameException, NamespaceException { - List rules = new ArrayList(); + List includes = new ArrayList(); NodeList childNodes = config.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node n = childNodes.item(i); @@ -198,12 +235,46 @@ builder.addLast(resolver.getQName(elements[j])); } } - rules.add(new Rule(builder.getPath(), ntName)); + includes.add(new NodeInclude(builder.getPath(), ntName)); } } - return (Rule[]) rules.toArray(new Rule[rules.size()]); + return (NodeInclude[]) includes.toArray(new NodeInclude[includes.size()]); } + /** + * Creates property includes defined in the config. + * + * @param config the indexing aggregate configuration. + * @return the property includes defined in the config. + * @throws MalformedPathException if a path in the configuration is + * malformed. + * @throws IllegalNameException if the node type name contains illegal + * characters. + * @throws NamespaceException if the node type contains an unknown + * prefix. + */ + private PropertyInclude[] getPropertyIncludes(Node config) throws + MalformedPathException, IllegalNameException, NamespaceException, + PathNotFoundException { + List includes = new ArrayList(); + NodeList childNodes = config.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node n = childNodes.item(i); + if (n.getNodeName().equals("include-property")) { + String[] elements = Text.explode(getTextContent(n), '/'); + PathBuilder builder = new PathBuilder(); + for (int j = 0; j < elements.length; j++) { + if (elements[j].equals("*")) { + throw new IllegalNameException("* not supported in include-property"); + } + builder.addLast(resolver.getQName(elements[j])); + } + includes.add(new PropertyInclude(builder.getPath())); + } + } + return (PropertyInclude[]) includes.toArray(new PropertyInclude[includes.size()]); + } + //---------------------------< internal >----------------------------------- /** @@ -222,17 +293,17 @@ return content.toString(); } - private final class Rule { + private abstract class AbstractInclude { /** * Optional node type name. */ - private final Name nodeTypeName; + protected final Name nodeTypeName; /** * A relative path pattern. */ - private final Path pattern; + protected final Path pattern; /** * Creates a new rule with a relative path pattern and an optional node @@ -242,7 +313,7 @@ * types are allowed. * @param pattern a relative path pattern. */ - private Rule(Path pattern, Name nodeTypeName) { + AbstractInclude(Path pattern, Name nodeTypeName) { this.nodeTypeName = nodeTypeName; this.pattern = pattern; } @@ -255,6 +326,9 @@ * @return the root node state of the indexing aggregate or * null if nodeState does not belong * to an indexing aggregate defined by this rule. + * @throws ItemStateException if an error occurs while accessing node + * states. + * @throws RepositoryException if another error occurs. */ NodeState matches(NodeState nodeState) throws ItemStateException, RepositoryException { @@ -290,20 +364,6 @@ return null; } - /** - * Resolves the nodeState using this rule. - * - * @param nodeState the root node of the enclosing indexing aggregate. - * @return the descendant node states as defined by this rule. - * @throws ItemStateException if an error occurs while resolving the - * node states. - */ - NodeState[] resolve(NodeState nodeState) throws ItemStateException { - List nodeStates = new ArrayList(); - resolve(nodeState, nodeStates, 0); - return (NodeState[]) nodeStates.toArray(new NodeState[nodeStates.size()]); - } - //-----------------------------< internal >----------------------------- /** @@ -316,7 +376,7 @@ * @throws ItemStateException if an error occurs while accessing node * states. */ - private void resolve(NodeState nodeState, List collector, int offset) + protected void resolve(NodeState nodeState, List collector, int offset) throws ItemStateException { Name currentName = pattern.getElements()[offset].getName(); List cne; @@ -347,4 +407,68 @@ } } } + + private final class NodeInclude extends AbstractInclude { + + /** + * Creates a new node include with a relative path pattern and an + * optional node type name. + * + * @param nodeTypeName node type name or null if all node + * types are allowed. + * @param pattern a relative path pattern. + */ + NodeInclude(Path pattern, Name nodeTypeName) { + super(pattern, nodeTypeName); + } + + /** + * Resolves the nodeState using this rule. + * + * @param nodeState the root node of the enclosing indexing aggregate. + * @return the descendant node states as defined by this rule. + * @throws ItemStateException if an error occurs while resolving the + * node states. + */ + NodeState[] resolve(NodeState nodeState) throws ItemStateException { + List nodeStates = new ArrayList(); + resolve(nodeState, nodeStates, 0); + return (NodeState[]) nodeStates.toArray(new NodeState[nodeStates.size()]); + } + } + + private final class PropertyInclude extends AbstractInclude { + + private final Name propertyName; + + PropertyInclude(Path pattern) + throws PathNotFoundException { + super(pattern.getAncestor(1), null); + this.propertyName = pattern.getNameElement().getName(); + } + + /** + * Resolves the nodeState using this rule. + * + * @param nodeState the root node of the enclosing indexing aggregate. + * @return the descendant property states as defined by this rule. + * @throws ItemStateException if an error occurs while resolving the + * property states. + */ + PropertyState[] resolvePropertyStates(NodeState nodeState) + throws ItemStateException { + List nodeStates = new ArrayList(); + resolve(nodeState, nodeStates, 0); + List propStates = new ArrayList(); + for (Iterator it = nodeStates.iterator(); it.hasNext(); ) { + NodeState state = (NodeState) it.next(); + if (state.hasPropertyName(propertyName)) { + PropertyId propId = new PropertyId(state.getNodeId(), propertyName); + propStates.add(ism.getItemState(propId)); + } + } + return (PropertyState[]) propStates.toArray( + new PropertyState[propStates.size()]); + } + } } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/ComparableBoolean.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/ComparableBoolean.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/ComparableBoolean.java (revision 0) @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Represents a boolean that implement {@link Comparable}. This class can + * be removed when we move to Java 5. + */ +public final class ComparableBoolean implements Comparable { + + private static final ComparableBoolean TRUE = new ComparableBoolean(true); + + private static final ComparableBoolean FALSE = new ComparableBoolean(false); + + private final boolean value; + + private ComparableBoolean(boolean value) { + this.value = value; + } + + public int compareTo(Object o) { + ComparableBoolean b = (ComparableBoolean) o; + return (b.value == value ? 0 : (value ? 1 : -1)); + } + + public static ComparableBoolean valueOf(boolean value) { + return value ? TRUE : FALSE; + } +} Property changes on: src\main\java\org\apache\jackrabbit\core\query\lucene\ComparableBoolean.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java (working copy) @@ -16,10 +16,7 @@ */ package org.apache.jackrabbit.core.query.lucene; -import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException; -import org.apache.jackrabbit.spi.commons.conversion.NameResolver; import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; -import org.apache.jackrabbit.spi.Name; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,8 +34,8 @@ import java.util.Properties; /** - * The class NamespaceMappings implements a {@link - * org.apache.jackrabbit.core.NamespaceResolver} that holds a namespace + * The class NamespaceMappings implements a + * {@link NamespaceResolver} that holds a namespace * mapping that is used internally in the search index. Storing paths with the * full uri of a namespace would require too much space in the search index. *

@@ -46,8 +43,7 @@ * prefix is created on the fly and associated with the namespace. Known * namespace mappings are stored in a properties file. */ -public class FileBasedNamespaceMappings - implements NamespaceResolver, NamespaceMappings { +public class FileBasedNamespaceMappings extends AbstractNamespaceMappings { /** * Default logger instance for this class @@ -60,11 +56,6 @@ private final File storage; /** - * The name resolver used to translate the qualified name to JCR name - */ - private final NameResolver nameResolver; - - /** * Map of uris indexed by prefixes */ private Map prefixToURI = new HashMap(); @@ -90,7 +81,6 @@ public FileBasedNamespaceMappings(File file) throws IOException { storage = file; load(); - nameResolver = NamePathResolverImpl.create(this); } /** @@ -138,26 +128,6 @@ return prefix; } - //----------------------------< NamespaceMappings >------------------------- - - /** - * Translates a property name from a session local namespace mapping - * into a search index private namespace mapping. - * - * @param qName the property name to translate - * @return the translated property name - */ - public String translatePropertyName(Name qName) - throws IllegalNameException { - try { - return nameResolver.getJCRName(qName); - } catch (NamespaceException e) { - // should never happen actually, because we create yet unknown - // uri mappings on the fly. - throw new IllegalNameException("Internal error.", e); - } - } - //-----------------------< internal >--------------------------------------- /** Index: src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java (working copy) @@ -79,6 +79,18 @@ } /** + * Returns true if this version is at least as high as the + * given version. + * + * @param version the other version to compare. + * @return true if this version is at least as high as the + * provided; false otherwise. + */ + public boolean isAtLeast(IndexFormatVersion version) { + return this.version >= version.getVersion(); + } + + /** * @return a string representation of this index format version. */ public String toString() { Index: src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java (working copy) @@ -45,6 +45,9 @@ systemIds.put( "http://jackrabbit.apache.org/dtd/indexing-configuration-1.1.dtd", "indexing-configuration-1.1.dtd"); + systemIds.put( + "http://jackrabbit.apache.org/dtd/indexing-configuration-1.2.dtd", + "indexing-configuration-1.2.dtd"); SYSTEM_IDS = Collections.unmodifiableMap(systemIds); } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java (working copy) @@ -162,7 +162,7 @@ if (propertyNode.getNodeName().equals("property")) { // get property name Name propName = resolver.getQName(getTextContent(propertyNode)); - String fieldName = nsMappings.translatePropertyName(propName); + String fieldName = nsMappings.translateName(propName); // set analyzer for the fulltext property fieldname int idx = fieldName.indexOf(':'); fieldName = fieldName.substring(0, idx + 1) Index: src/main/java/org/apache/jackrabbit/core/query/lucene/JQOM2LuceneQueryBuilder.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/JQOM2LuceneQueryBuilder.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/JQOM2LuceneQueryBuilder.java (working copy) @@ -690,7 +690,7 @@ NodeType[] superTypes = nt.getSupertypes(); if (Arrays.asList(superTypes).contains(base)) { Name n = session.getQName(nt.getName()); - String ntName = nsMappings.translatePropertyName(n); + String ntName = nsMappings.translateName(n); Term t; if (nt.isMixin()) { // search on jcr:mixinTypes @@ -740,7 +740,7 @@ return LongField.longToString(value.getLong()); case PropertyType.NAME: Name n = session.getQName(value.getString()); - return nsMappings.translatePropertyName(n); + return nsMappings.translateName(n); case PropertyType.PATH: Path p = session.getQPath(value.getString()); return npResolver.getJCRPath(p); Index: src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (working copy) @@ -330,7 +330,7 @@ NodeType[] superTypes = nt.getSupertypes(); if (Arrays.asList(superTypes).contains(base)) { Name n = session.getQName(nt.getName()); - String ntName = nsMappings.translatePropertyName(n); + String ntName = nsMappings.translateName(n); Term t; if (nt.isMixin()) { // search on jcr:mixinTypes @@ -954,7 +954,7 @@ // try to translate name try { Name n = session.getQName(literal); - values.add(nsMappings.translatePropertyName(n)); + values.add(nsMappings.translateName(n)); log.debug("Coerced " + literal + " into NAME."); } catch (NameException e) { log.debug("Unable to coerce '" + literal + "' into a NAME: " + e.toString()); @@ -1028,7 +1028,7 @@ // might be a name try { Name n = session.getQName(literal); - values.add(nsMappings.translatePropertyName(n)); + values.add(nsMappings.translateName(n)); log.debug("Coerced " + literal + " into NAME."); } catch (Exception e) { // not a name Index: src/main/java/org/apache/jackrabbit/core/query/lucene/NameQuery.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/NameQuery.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/NameQuery.java (working copy) @@ -87,7 +87,7 @@ // use LABEL field try { return new TermQuery(new Term(FieldNames.LABEL, - nsMappings.translatePropertyName(nodeName))); + nsMappings.translateName(nodeName))); } catch (IllegalNameException e) { throw Util.createIOException(e); } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/NameRangeQuery.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/NameRangeQuery.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/NameRangeQuery.java (working copy) @@ -164,7 +164,7 @@ if (lowerName == null) { text = nsMappings.getPrefix(upperName.getNamespaceURI()) + ":"; } else { - text = nsMappings.translatePropertyName(lowerName); + text = nsMappings.translateName(lowerName); } return new Term(FieldNames.LABEL, text); } catch (RepositoryException e) { @@ -182,7 +182,7 @@ if (upperName == null) { text = nsMappings.getPrefix(lowerName.getNamespaceURI()) + ":\uFFFF"; } else { - text = nsMappings.translatePropertyName(upperName); + text = nsMappings.translateName(upperName); } return new Term(FieldNames.LABEL, text); } catch (RepositoryException e) { Index: src/main/java/org/apache/jackrabbit/core/query/lucene/NamespaceMappings.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/NamespaceMappings.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/NamespaceMappings.java (working copy) @@ -19,6 +19,7 @@ import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException; import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; /** * The class NamespaceMappings holds a namespace mapping that is @@ -28,12 +29,21 @@ public interface NamespaceMappings extends NamespaceResolver { /** - * Translates a property name from a session local namespace mapping into a - * search index private namespace mapping. + * Translates a name from a session local namespace mapping into a search + * index private namespace mapping. * - * @param qName the property name to translate - * @return the translated JCR property name + * @param name the name to translate + * @return the translated JCR name + * @throws IllegalNameException if the name cannot be translated. */ - String translatePropertyName(Name qName) throws IllegalNameException; + String translateName(Name name) throws IllegalNameException; + /** + * Translates a path into a search index private namespace mapping. + * + * @param path the path to translate + * @return the translated path. + * @throws IllegalNameException if the name cannot be translated. + */ + String translatePath(Path path) throws IllegalNameException; } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/NSRegistryBasedNamespaceMappings.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/NSRegistryBasedNamespaceMappings.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/NSRegistryBasedNamespaceMappings.java (working copy) @@ -16,11 +16,7 @@ */ package org.apache.jackrabbit.core.query.lucene; -import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException; -import org.apache.jackrabbit.spi.commons.conversion.NameResolver; import org.apache.jackrabbit.core.NamespaceRegistryImpl; -import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; -import org.apache.jackrabbit.spi.Name; import javax.jcr.NamespaceException; @@ -28,19 +24,13 @@ * NSRegistryBasedNamespaceMappings implements a namespace mapping * based on the stable index prefix provided by the namespace registry. */ -public class NSRegistryBasedNamespaceMappings - implements NamespaceResolver, NamespaceMappings { +public class NSRegistryBasedNamespaceMappings extends AbstractNamespaceMappings { /** * The namespace registry. */ private final NamespaceRegistryImpl nsReg; - /** - * The name resolver used to translate the qualified name to JCR name - */ - private final NameResolver nameResolver; - /** * Creates a new NSRegistryBasedNamespaceMappings. * @@ -48,7 +38,6 @@ */ NSRegistryBasedNamespaceMappings(NamespaceRegistryImpl nsReg) { this.nsReg = nsReg; - this.nameResolver = NamePathResolverImpl.create(this); } //-------------------------------< NamespaceResolver >---------------------- @@ -77,20 +66,4 @@ "Unknown namespace URI: " + uri, e); } } - - //-------------------------------< NamespaceMappings >---------------------- - - /** - * {@inheritDoc} - */ - public String translatePropertyName(Name qName) - throws IllegalNameException { - try { - return nameResolver.getJCRName(qName); - } catch (NamespaceException e) { - // should never happen actually, there is always a stable index - // prefix for a known namespace uri - throw new IllegalNameException("Internal error.", e); - } - } } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (revision 744889) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (working copy) @@ -20,6 +20,7 @@ import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.NodeId; import org.apache.jackrabbit.core.NodeIdIterator; +import org.apache.jackrabbit.core.HierarchyManager; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.fs.FileSystemException; @@ -33,6 +34,8 @@ import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.NodeStateIterator; import org.apache.jackrabbit.core.state.ItemStateManager; +import org.apache.jackrabbit.core.state.PropertyState; +import org.apache.jackrabbit.core.state.ItemStateException; import org.apache.jackrabbit.extractor.DefaultTextExtractor; import org.apache.jackrabbit.extractor.TextExtractor; import org.apache.jackrabbit.spi.Name; @@ -46,6 +49,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.Token; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.MultiReader; import org.apache.lucene.index.Term; @@ -163,6 +167,11 @@ public static final int DEFAULT_TERM_INFOS_INDEX_DIVISOR = 1; /** + * The path factory. + */ + protected static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance(); + + /** * The path of the root node. */ private static final Path ROOT_PATH; @@ -173,10 +182,9 @@ private static final Path JCR_SYSTEM_PATH; static { - PathFactory factory = PathFactoryImpl.getInstance(); - ROOT_PATH = factory.create(NameConstants.ROOT); + ROOT_PATH = PATH_FACTORY.create(NameConstants.ROOT); try { - JCR_SYSTEM_PATH = factory.create(ROOT_PATH, NameConstants.JCR_SYSTEM, false); + JCR_SYSTEM_PATH = PATH_FACTORY.create(ROOT_PATH, NameConstants.JCR_SYSTEM, false); } catch (RepositoryException e) { // should never happen, path is always valid throw new InternalError(e.getMessage()); @@ -1168,29 +1176,69 @@ return; } try { + ItemStateManager ism = getContext().getItemStateManager(); for (int i = 0; i < aggregateRules.length; i++) { + boolean ruleMatched = false; + // node includes NodeState[] aggregates = aggregateRules[i].getAggregatedNodeStates(state); - if (aggregates == null) { - continue; + if (aggregates != null) { + ruleMatched = true; + for (int j = 0; j < aggregates.length; j++) { + Document aDoc = createDocument(aggregates[j], + getNamespaceMappings(), + index.getIndexFormatVersion()); + // transfer fields to doc if there are any + Fieldable[] fulltextFields = aDoc.getFieldables(FieldNames.FULLTEXT); + if (fulltextFields != null) { + for (int k = 0; k < fulltextFields.length; k++) { + doc.add(fulltextFields[k]); + } + doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID, + aggregates[j].getNodeId().getUUID().toString(), + Field.Store.NO, + Field.Index.NO_NORMS)); + } + } } - for (int j = 0; j < aggregates.length; j++) { - Document aDoc = createDocument(aggregates[j], - getNamespaceMappings(), - index.getIndexFormatVersion()); - // transfer fields to doc if there are any - Fieldable[] fulltextFields = aDoc.getFieldables(FieldNames.FULLTEXT); - if (fulltextFields != null) { - for (int k = 0; k < fulltextFields.length; k++) { - doc.add(fulltextFields[k]); + // property includes + PropertyState[] propStates = aggregateRules[i].getAggregatedPropertyStates(state); + if (propStates != null) { + ruleMatched = true; + for (int j = 0; j < propStates.length; j++) { + PropertyState propState = propStates[j]; + String namePrefix = FieldNames.createNamedValue( + getNamespaceMappings().translateName(propState.getName()), ""); + NodeState parent = (NodeState) ism.getItemState(propState.getParentId()); + Document aDoc = createDocument(parent, getNamespaceMappings(), getIndex().getIndexFormatVersion()); + // find the right fields to transfer + Fieldable[] fields = aDoc.getFieldables(FieldNames.PROPERTIES); + for (int k = 0; k < fields.length; k++) { + Fieldable field = fields[k]; + // assume properties fields use SingleTokenStream + Token t = field.tokenStreamValue().next(); + String value = new String(t.termBuffer(), 0, t.termLength()); + if (value.startsWith(namePrefix)) { + // extract value + value = value.substring(namePrefix.length()); + // create new named value + Path p = getRelativePath(state, propState); + String path = getNamespaceMappings().translatePath(p); + value = FieldNames.createNamedValue(path, value); + t.setTermText(value); + doc.add(new Field(field.name(), new SingletonTokenStream(t))); + doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID, + parent.getNodeId().getUUID().toString(), + Field.Store.NO, + Field.Index.NO_NORMS)); + } } - doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID, - aggregates[j].getNodeId().getUUID().toString(), - Field.Store.NO, - Field.Index.NO_NORMS)); } } + // only use first aggregate definition that matches - break; + if (ruleMatched) { + break; + } } } catch (Exception e) { // do not fail if aggregate cannot be created @@ -1201,6 +1249,38 @@ } /** + * Returns the relative path from nodeState to + * propState. + * + * @param nodeState a node state. + * @param propState a property state. + * @return the relative path. + * @throws RepositoryException if an error occurs while resolving paths. + * @throws ItemStateException if an error occurs while reading item + * states. + */ + protected Path getRelativePath(NodeState nodeState, PropertyState propState) + throws RepositoryException, ItemStateException { + HierarchyManager hmgr = getContext().getHierarchyManager(); + Path nodePath = hmgr.getPath(nodeState.getId()); + Path propPath = hmgr.getPath(propState.getId()); + Path p = nodePath.computeRelativePath(propPath); + // make sure it does not contain indexes + boolean clean = true; + Path.Element[] elements = p.getElements(); + for (int i = 0; i < elements.length; i++) { + if (elements[i].getIndex() != 0) { + elements[i] = PATH_FACTORY.createElement(elements[i].getName()); + clean = false; + } + } + if (!clean) { + p = PATH_FACTORY.create(elements); + } + return p; + } + + /** * Retrieves the root of the indexing aggregate for state and * puts it into map. * Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java (revision 744889) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java (working copy) @@ -18,8 +18,9 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermEnum; +import org.apache.lucene.index.TermPositions; import org.apache.lucene.index.TermDocs; -import org.apache.lucene.index.TermEnum; import org.apache.lucene.search.SortComparator; import java.io.IOException; @@ -27,6 +28,8 @@ import java.util.Map; import java.util.WeakHashMap; +import javax.jcr.PropertyType; + /** * Implements a variant of the lucene class org.apache.lucene.search.FieldCacheImpl. * The lucene FieldCache class has some sort of support for custom comparators @@ -38,7 +41,7 @@ /** * Expert: Stores term text values and document ordering data. */ - public static class StringIndex { + public static class ValueIndex { /** * Some heuristic factor that determines whether the array is sparse. Note that if less then @@ -48,62 +51,62 @@ private static final int SPARSE_FACTOR = 100; /** - * Terms indexed by document id. + * Values indexed by document id. */ - private final String[] terms; + private final Comparable[] values; /** - * Terms map indexed by document id. + * Values (Comparable) map indexed by document id. */ - public final Map termsMap; + public final Map valuesMap; /** - * Boolean indicating whether the hashMap impl has to be used + * Boolean indicating whether the {@link #valuesMap} impl has to be used */ public final boolean sparse; /** * Creates one of these objects */ - public StringIndex(String[] terms, int setValues) { - if (isSparse(terms, setValues)) { + public ValueIndex(Comparable[] values, int setValues) { + if (isSparse(values, setValues)) { this.sparse = true; - this.terms = null; + this.values = null; if (setValues == 0) { - this.termsMap = null; + this.valuesMap = null; } else { - this.termsMap = getTermsMap(terms, setValues); + this.valuesMap = getValuesMap(values, setValues); } } else { this.sparse = false; - this.terms = terms; - this.termsMap = null; + this.values = values; + this.valuesMap = null; } } - public String getTerm(int i) { + public Comparable getValue(int i) { if (sparse) { - return termsMap == null ? null : (String) termsMap.get(new Integer(i)); + return valuesMap == null ? null : (Comparable) valuesMap.get(new Integer(i)); } else { - return terms[i]; + return values[i]; } } - private Map getTermsMap(String[] terms, int setValues) { + private Map getValuesMap(Comparable[] values, int setValues) { Map map = new HashMap(setValues); - for (int i = 0; i < terms.length && setValues > 0; i++) { - if (terms[i] != null) { - map.put(new Integer(i), terms[i]); + for (int i = 0; i < values.length && setValues > 0; i++) { + if (values[i] != null) { + map.put(new Integer(i), values[i]); setValues--; } } return map; } - private boolean isSparse(String[] terms, int setValues) { + private boolean isSparse(Comparable[] values, int setValues) { // some really simple test to test whether the array is sparse. Currently, when less then 1% is set, the array is already sparse // for this typical cache to avoid memory issues - if (setValues * SPARSE_FACTOR < terms.length) { + if (setValues * SPARSE_FACTOR < values.length) { return true; } return false; @@ -127,26 +130,24 @@ } /** - * Creates a StringIndex for a field and a term + * Creates a ValueIndex for a field and a term * prefix. The term prefix acts as the property name for the * shared field. *

* This method is an adapted version of: FieldCacheImpl.getStringIndex() - * The returned string index will not have a term lookup array! - * See {@link SharedFieldSortComparator} for more info. * * @param reader the IndexReader. * @param field name of the shared field. * @param prefix the property name, will be used as term prefix. * @param comparator the sort comparator instance. - * @return a StringIndex that contains the field values and order + * @return a ValueIndex that contains the field values and order * information. * @throws IOException if an error occurs while reading from the index. */ - public SharedFieldCache.StringIndex getStringIndex(IndexReader reader, - String field, - String prefix, - SortComparator comparator) + public ValueIndex getValueIndex(IndexReader reader, + String field, + String prefix, + SortComparator comparator) throws IOException { if (reader instanceof ReadOnlyIndexReader) { @@ -154,12 +155,22 @@ } field = field.intern(); - SharedFieldCache.StringIndex ret = lookup(reader, field, prefix, comparator); + ValueIndex ret = lookup(reader, field, prefix, comparator); if (ret == null) { - final String[] retArray = new String[reader.maxDoc()]; + Comparable[] retArray = new Comparable[reader.maxDoc()]; int setValues = 0; if (retArray.length > 0) { - TermDocs termDocs = reader.termDocs(); + IndexFormatVersion version = IndexFormatVersion.getVersion(reader); + boolean hasPayloads = version.isAtLeast(IndexFormatVersion.V3); + TermDocs termDocs; + byte[] payload = null; + int type; + if (hasPayloads) { + termDocs = reader.termPositions(); + payload = new byte[1]; + } else { + termDocs = reader.termDocs(); + } TermEnum termEnum = reader.terms(new Term(field, prefix)); char[] tmp = new char[16]; @@ -185,8 +196,17 @@ termDocs.seek(termEnum); while (termDocs.next()) { + type = PropertyType.UNDEFINED; + if (hasPayloads) { + TermPositions termPos = (TermPositions) termDocs; + termPos.nextPosition(); + if (termPos.isPayloadAvailable()) { + payload = termPos.getPayload(payload, 0); + type = PropertyMetaData.fromByteArray(payload).getPropertyType(); + } + } setValues++; - retArray[termDocs.doc()] = value; + retArray[termDocs.doc()] = getValue(value, type); } } while (termEnum.next()); } finally { @@ -194,7 +214,7 @@ termEnum.close(); } } - SharedFieldCache.StringIndex value = new SharedFieldCache.StringIndex(retArray, setValues); + ValueIndex value = new ValueIndex(retArray, setValues); store(reader, field, prefix, comparator, value); return value; } @@ -202,9 +222,9 @@ } /** - * See if a StringIndex object is in the cache. + * See if a ValueIndex object is in the cache. */ - SharedFieldCache.StringIndex lookup(IndexReader reader, String field, + ValueIndex lookup(IndexReader reader, String field, String prefix, SortComparator comparer) { Key key = new Key(field, prefix, comparer); synchronized (this) { @@ -212,15 +232,15 @@ if (readerCache == null) { return null; } - return (SharedFieldCache.StringIndex) readerCache.get(key); + return (ValueIndex) readerCache.get(key); } } /** - * Put a StringIndex value to cache. + * Put a ValueIndex value to cache. */ Object store(IndexReader reader, String field, String prefix, - SortComparator comparer, SharedFieldCache.StringIndex value) { + SortComparator comparer, ValueIndex value) { Key key = new Key(field, prefix, comparer); synchronized (this) { HashMap readerCache = (HashMap) cache.get(reader); @@ -233,6 +253,29 @@ } /** + * Returns a comparable for the given value that is read from + * the index. + * + * @param value the value as read from the index. + * @param type the property type. + * @return a comparable for the value. + */ + private Comparable getValue(String value, int type) { + switch (type) { + case PropertyType.BOOLEAN: + return ComparableBoolean.valueOf(Boolean.valueOf(value).booleanValue()); + case PropertyType.DATE: + return new Long(DateField.stringToTime(value)); + case PropertyType.LONG: + return new Long(LongField.stringToLong(value)); + case PropertyType.DOUBLE: + return new Double(DoubleField.stringToDouble(value)); + default: + return value; + } + } + + /** * A compound Key that consist of field * prefix and comparator. */ @@ -243,7 +286,7 @@ private final SortComparator comparator; /** - * Creates Key for StringIndex lookup. + * Creates Key for ValueIndex lookup. */ Key(String field, String prefix, SortComparator comparator) { this.field = field.intern(); Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java (revision 744889) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldSortComparator.java (working copy) @@ -44,13 +44,6 @@ /** * Implements a SortComparator which knows how to sort on a lucene * field that contains values for multiple properties. - *

- * Important: The ScoreDocComparator returned by {@link #newComparator} - * does not implement the contract for {@link ScoreDocComparator#sortValue(ScoreDoc)} - * properly. The method will always return an empty String to save memory consumption - * on large property ranges. Those values are only of relevance when queries - * are executed with a MultiSearcher, which is currently not the - * case in Jackrabbit. */ public class SharedFieldSortComparator extends SortComparator { @@ -108,15 +101,20 @@ throws IOException { PathFactory factory = PathFactoryImpl.getInstance(); Path p = factory.create(relPath); - if (p.getLength() == 1) { - try { - return new SimpleScoreDocComparator(reader, - nsMappings.translatePropertyName(p.getNameElement().getName())); - } catch (IllegalNameException e) { - throw Util.createIOException(e); + try { + ScoreDocComparator simple = new SimpleScoreDocComparator( + reader, nsMappings.translatePath(p)); + if (p.getLength() == 1) { + return simple; + } else { + return new CompoundScoreDocComparator(reader, + new ScoreDocComparator[]{ + simple, + new RelPathScoreDocComparator(reader, p) + }); } - } else { - return new RelPathScoreDocComparator(reader, p); + } catch (IllegalNameException e) { + throw Util.createIOException(e); } } @@ -250,17 +248,17 @@ /** * The term look ups of the index segments. */ - protected final SharedFieldCache.StringIndex[] indexes; + protected final SharedFieldCache.ValueIndex[] indexes; public SimpleScoreDocComparator(IndexReader reader, String propertyName) throws IOException { super(reader); - this.indexes = new SharedFieldCache.StringIndex[readers.size()]; + this.indexes = new SharedFieldCache.ValueIndex[readers.size()]; for (int i = 0; i < readers.size(); i++) { IndexReader r = (IndexReader) readers.get(i); - indexes[i] = SharedFieldCache.INSTANCE.getStringIndex(r, field, + indexes[i] = SharedFieldCache.INSTANCE.getValueIndex(r, field, FieldNames.createNamedValue(propertyName, ""), SharedFieldSortComparator.this); } @@ -274,7 +272,7 @@ */ public Comparable sortValue(ScoreDoc i) { int idx = readerIndex(i.doc); - return indexes[idx].getTerm(i.doc - starts[idx]); + return indexes[idx].getValue(i.doc - starts[idx]); } } @@ -359,28 +357,34 @@ } /** - * Represents a boolean that implement {@link Comparable}. This class can - * be removed when we move to Java 5. + * Implements a compound score doc comparator that delegates to several + * other comparators. The comparators are asked for a sort value in the + * sequence they are passed to the constructor. The first non-null value + * will be returned by {@link #sortValue(ScoreDoc)}. */ - private static final class ComparableBoolean implements Comparable { + private final class CompoundScoreDocComparator + extends AbstractScoreDocComparator { - private static final ComparableBoolean TRUE = new ComparableBoolean(true); + private final ScoreDocComparator[] comparators; - private static final ComparableBoolean FALSE = new ComparableBoolean(false); - - private final boolean value; - - private ComparableBoolean(boolean value) { - this.value = value; + public CompoundScoreDocComparator(IndexReader reader, + ScoreDocComparator[] comparators) + throws IOException { + super(reader); + this.comparators = comparators; } - public int compareTo(Object o) { - ComparableBoolean b = (ComparableBoolean) o; - return (b.value == value ? 0 : (value ? 1 : -1)); + /** + * {@inheritDoc} + */ + public Comparable sortValue(ScoreDoc i) { + for (int j = 0; j < comparators.length; j++) { + Comparable c = comparators[j].sortValue(i); + if (c != null) { + return c; + } + } + return null; } - - static ComparableBoolean valueOf(boolean value) { - return value ? TRUE : FALSE; - } } } Index: src/main/java/org/apache/jackrabbit/core/query/lucene/SingletonTokenStream.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/query/lucene/SingletonTokenStream.java (revision 744177) +++ src/main/java/org/apache/jackrabbit/core/query/lucene/SingletonTokenStream.java (working copy) @@ -46,6 +46,15 @@ } /** + * Creates a new SingleTokenStream with the given token. + * + * @param t the token. + */ + public SingletonTokenStream(Token t) { + this.t = t; + } + + /** * {@inheritDoc} */ public Token next() { Index: src/main/resources/org/apache/jackrabbit/core/query/lucene/indexing-configuration-1.2.dtd =================================================================== --- src/main/resources/org/apache/jackrabbit/core/query/lucene/indexing-configuration-1.2.dtd (revision 0) +++ src/main/resources/org/apache/jackrabbit/core/query/lucene/indexing-configuration-1.2.dtd (revision 0) @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Property changes on: src\main\resources\org\apache\jackrabbit\core\query\lucene\indexing-configuration-1.2.dtd ___________________________________________________________________ Added: svn:eol-style + native Index: src/test/java/org/apache/jackrabbit/core/query/IndexingAggregateTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/query/IndexingAggregateTest.java (revision 744177) +++ src/test/java/org/apache/jackrabbit/core/query/IndexingAggregateTest.java (working copy) @@ -18,12 +18,18 @@ import javax.jcr.RepositoryException; import javax.jcr.Node; +import javax.jcr.query.Query; + import java.io.ByteArrayOutputStream; import java.io.Writer; import java.io.OutputStreamWriter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Calendar; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; /** * IndexingAggregateTest checks if the nt:file nt:resource @@ -89,4 +95,96 @@ executeSQLQuery(sqlCat, new Node[]{file}); } + + public void testContentLastModified() throws RepositoryException { + List expected = new ArrayList(); + long time = System.currentTimeMillis(); + for (int i = 0; i < 10; i++) { + expected.add(addFile(testRootNode, "file" + i, time)); + time += 1000; + } + testRootNode.save(); + + String stmt = testPath + "/* order by jcr:content/@jcr:lastModified"; + Query q = qm.createQuery(stmt, Query.XPATH); + checkResultSequence(q.execute().getRows(), (Node[]) expected.toArray(new Node[expected.size()])); + + // descending + stmt = testPath + "/* order by jcr:content/@jcr:lastModified descending"; + q = qm.createQuery(stmt, Query.XPATH); + Collections.reverse(expected); + checkResultSequence(q.execute().getRows(), (Node[]) expected.toArray(new Node[expected.size()])); + + // reverse order in content + for (Iterator it = expected.iterator(); it.hasNext(); ) { + Node file = (Node) it.next(); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + file.getNode("jcr:content").setProperty("jcr:lastModified", cal); + time -= 1000; + } + testRootNode.save(); + + stmt = testPath + "/* order by jcr:content/@jcr:lastModified descending"; + q = qm.createQuery(stmt, Query.XPATH); + checkResultSequence(q.execute().getRows(), (Node[]) expected.toArray(new Node[expected.size()])); + } + + public void disabled_testPerformance() throws RepositoryException { + createNodes(testRootNode, 10, 4, 0, new NodeCreationCallback() { + public void nodeCreated(Node node, int count) throws + RepositoryException { + node.addNode("child").setProperty("property", "value" + count); + // save once in a while + if (count % 1000 == 0) { + session.save(); + System.out.println("added " + count + " nodes so far."); + } + } + }); + session.save(); + + String xpath = testPath + "//*[child/@property] order by child/@property"; + for (int i = 0; i < 3; i++) { + long time = System.currentTimeMillis(); + Query query = qm.createQuery(xpath, Query.XPATH); + ((QueryImpl) query).setLimit(20); + query.execute().getNodes().getSize(); + time = System.currentTimeMillis() - time; + System.out.println("executed query in " + time + " ms."); + } + } + + private static Node addFile(Node folder, String name, long lastModified) + throws RepositoryException { + Node file = folder.addNode(name, "nt:file"); + Node resource = file.addNode("jcr:content", "nt:resource"); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(lastModified); + resource.setProperty("jcr:lastModified", cal); + resource.setProperty("jcr:encoding", "UTF-8"); + resource.setProperty("jcr:mimeType", "text/plain"); + resource.setProperty("jcr:data", new ByteArrayInputStream("test".getBytes())); + return file; + } + + private int createNodes(Node n, int nodesPerLevel, int levels, + int count, NodeCreationCallback callback) + throws RepositoryException { + levels--; + for (int i = 0; i < nodesPerLevel; i++) { + Node child = n.addNode("node" + i); + count++; + callback.nodeCreated(child, count); + if (levels > 0) { + count = createNodes(child, nodesPerLevel, levels, count, callback); + } + } + return count; + } + + private static interface NodeCreationCallback { + + public void nodeCreated(Node node, int count) throws RepositoryException; + } } Index: src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java (revision 744889) +++ src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java (working copy) @@ -145,31 +145,6 @@ checkChildAxis(new Value[]{getValue(2.0), getValue(1)}); } - public void disabled_testPerformance() throws RepositoryException { - createNodes(testRootNode, 10, 4, 0, new NodeCreationCallback() { - public void nodeCreated(Node node, int count) throws - RepositoryException { - node.addNode("child").setProperty("property", "value" + count); - // save once in a while - if (count % 1000 == 0) { - superuser.save(); - System.out.println("added " + count + " nodes so far."); - } - } - }); - superuser.save(); - - String xpath = testPath + "//*[child/@property] order by child/@property"; - for (int i = 0; i < 3; i++) { - long time = System.currentTimeMillis(); - Query query = qm.createQuery(xpath, Query.XPATH); - ((QueryImpl) query).setLimit(20); - query.execute().getNodes().getSize(); - time = System.currentTimeMillis() - time; - System.out.println("executed query in " + time + " ms."); - } - } - //------------------------------< helper >---------------------------------- private Value getValue(String value) throws RepositoryException { @@ -208,27 +183,55 @@ * @throws RepositoryException if an error occurs. */ private void checkChildAxis(Value[] values) throws RepositoryException { + // child/prop is part of the test indexing configuration, + // this will use SimpleScoreDocComparator internally + checkChildAxis(values, "child", "prop"); + cleanUpTestRoot(superuser); + // c/p is not in the indexing configuration, + // this will use RelPathScoreDocComparator internally + checkChildAxis(values, "c", "p"); + } + + /** + * Checks if order by with a relative path works on the the passed values. + * The values are expected to be in ascending order. + * + * @param values the values in ascending order. + * @param child the name of the child node. + * @param property the name of the property. + * @throws RepositoryException if an error occurs. + */ + private void checkChildAxis(Value[] values, String child, String property) + throws RepositoryException { + List vals = new ArrayList(); + // add initial value null -> property not set + // inexistent property is always less than any property value set + vals.add(null); + vals.addAll(Arrays.asList(values)); + List expected = new ArrayList(); - for (int i = 0; i < values.length; i++) { + for (int i = 0; i < vals.size(); i++) { Node n = testRootNode.addNode("node" + i); expected.add(n.getPath()); - n.addNode("child").setProperty("prop", values[i]); + Node c = n.addNode(child); + if (vals.get(i) != null) { + c.setProperty(property, (Value) vals.get(i)); + } } testRootNode.save(); - String xpath = testPath + "/* order by child/@prop"; + String xpath = testPath + "/* order by " + child + "/@" + property; assertEquals(expected, collectPaths(executeQuery(xpath))); // descending Collections.reverse(expected); - xpath = testPath + "/* order by child/@prop descending"; + xpath += " descending"; assertEquals(expected, collectPaths(executeQuery(xpath))); - // reverse order in content - Collections.reverse(Arrays.asList(values)); - for (int i = 0; i < values.length; i++) { - Node child = testRootNode.getNode("node" + i).getNode("child"); - child.setProperty("prop", values[i]); + Collections.reverse(vals); + for (int i = 0; i < vals.size(); i++) { + Node c = testRootNode.getNode("node" + i).getNode(child); + c.setProperty(property, (Value) vals.get(i)); } testRootNode.save(); @@ -244,24 +247,4 @@ } return paths; } - - private int createNodes(Node n, int nodesPerLevel, int levels, - int count, NodeCreationCallback callback) - throws RepositoryException { - levels--; - for (int i = 0; i < nodesPerLevel; i++) { - Node child = n.addNode("node" + i); - count++; - callback.nodeCreated(child, count); - if (levels > 0) { - count = createNodes(child, nodesPerLevel, levels, count, callback); - } - } - return count; - } - - private static interface NodeCreationCallback { - - public void nodeCreated(Node node, int count) throws RepositoryException; - } } Index: src/test/repository/workspaces/default/indexing-configuration.xml =================================================================== --- src/test/repository/workspaces/default/indexing-configuration.xml (revision 744177) +++ src/test/repository/workspaces/default/indexing-configuration.xml (working copy) @@ -1,49 +1,10 @@ - + - - - text - - - - - text - - - - - text - - - - - text - - - - - text - - - - .*Text - - - - title - text - - - - - - - - jcr:content - jcr:content/* + + child/prop \ No newline at end of file Property changes on: src\test\repository\workspaces\default\indexing-configuration.xml ___________________________________________________________________ Added: svn:mergeinfo Merged /jackrabbit/branches/1.3/jackrabbit-core/src/test/repository/workspaces/indexing-test/indexing-configuration.xml:r631261 Index: src/test/repository/workspaces/default/workspace.xml =================================================================== --- src/test/repository/workspaces/default/workspace.xml (revision 744177) +++ src/test/repository/workspaces/default/workspace.xml (working copy) @@ -41,6 +41,7 @@ + Index: src/test/repository/workspaces/indexing-test/indexing-configuration.xml =================================================================== --- src/test/repository/workspaces/indexing-test/indexing-configuration.xml (revision 744177) +++ src/test/repository/workspaces/indexing-test/indexing-configuration.xml (working copy) @@ -1,5 +1,5 @@ - + @@ -44,6 +44,11 @@ jcr:content jcr:content/* + jcr:content/jcr:lastModified + + child/property + + \ No newline at end of file