Index: src/main/java/org/apache/jackrabbit/spi/commons/query/OrderQueryNode.java =================================================================== --- src/main/java/org/apache/jackrabbit/spi/commons/query/OrderQueryNode.java (revision 744052) +++ src/main/java/org/apache/jackrabbit/spi/commons/query/OrderQueryNode.java (working copy) @@ -22,6 +22,9 @@ import javax.jcr.RepositoryException; import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; +import org.apache.jackrabbit.spi.commons.name.PathBuilder; +import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; /** * Implements a query node that defines the order of nodes according to the @@ -59,8 +62,20 @@ * @param property the name of the property. * @param ascending if true values of this properties are * ordered ascending; descending if false. + * @deprecated use {@link #addOrderSpec(Path , boolean)} instead. */ public void addOrderSpec(Name property, boolean ascending) { + addOrderSpec(createPath(property), ascending); + } + + /** + * Adds an order specification to this query node. + * + * @param property the relative path of the property. + * @param ascending if true values of this properties are + * ordered ascending; descending if false. + */ + public void addOrderSpec(Path property, boolean ascending) { specs.add(new OrderSpec(property, ascending)); } @@ -124,9 +139,9 @@ public static final class OrderSpec { /** - * The name of the property + * The relative path to of the property */ - private final Name property; + private final Path property; /** * If true this property is orderd ascending @@ -139,8 +154,20 @@ * @param property the name of the property. * @param ascending if true the property is ordered * ascending, otherwise descending. + * @deprecated use {@link OrderSpec#OrderSpec(Path, boolean)} instead. */ public OrderSpec(Name property, boolean ascending) { + this(createPath(property), ascending); + } + + /** + * Creates a new OrderSpec for property. + * + * @param property the relative path of the property. + * @param ascending if true the property is ordered + * ascending, otherwise descending. + */ + public OrderSpec(Path property, boolean ascending) { this.property = property; this.ascending = ascending; } @@ -149,8 +176,18 @@ * Returns the name of the property. * * @return the name of the property. + * @deprecated use {@link #getPropertyPath()} instead. */ public Name getProperty() { + return property.getNameElement().getName(); + } + + /** + * Returns the relative path of the property. + * + * @return the relative path of the property. + */ + public Path getPropertyPath() { return property; } @@ -199,4 +236,22 @@ return false; } + //--------------------------------< internal >------------------------------ + + /** + * Creates a path with a single element out of the given name. + * + * @param name the name to create the path from. + * @return a path with a single element. + */ + private static Path createPath(Name name) { + try { + PathBuilder builder = new PathBuilder(); + builder.addLast(name); + return builder.getPath(); + } catch (MalformedPathException e) { + // never happens, we just added an element + throw new InternalError(); + } + } } Index: src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java =================================================================== --- src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java (revision 744052) +++ src/main/java/org/apache/jackrabbit/spi/commons/query/QueryTreeDump.java (working copy) @@ -210,16 +210,7 @@ if (relPath == null) { buffer.append(relPath); } else { - Path.Element[] elements = relPath.getElements(); - String slash = ""; - for (int i = 0; i < elements.length; i++) { - buffer.append(slash); - slash = "/"; - if (i == elements.length - 1) { - buffer.append("@"); - } - buffer.append(elements[i]); - } + appendPath(relPath, buffer); } buffer.append(" Type=").append(QueryConstants.TYPE_NAMES.getName(node.getValueType())); if (node.getValueType() == QueryConstants.TYPE_DATE) { @@ -249,7 +240,8 @@ OrderQueryNode.OrderSpec[] specs = node.getOrderSpecs(); for (int i = 0; i < specs.length; i++) { buffer.append(PADDING, 0, indent); - buffer.append(" ").append(specs[i].getProperty()); + buffer.append(" "); + appendPath(specs[i].getPropertyPath(), buffer); buffer.append(" asc=").append(specs[i].isAscending()); buffer.append("\n"); } @@ -301,4 +293,21 @@ } indent -= 2; } + + /** + * Appends the relative path to the buffer using '/' as the + * delimiter for path elements. + * + * @param relPath a relative path. + * @param buffer the buffer where to append the path. + */ + private static void appendPath(Path relPath, StringBuffer buffer) { + Path.Element[] elements = relPath.getElements(); + String slash = ""; + for (int i = 0; i < elements.length; i++) { + buffer.append(slash); + slash = "/"; + buffer.append(elements[i]); + } + } } Index: src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java =================================================================== --- src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java (revision 744052) +++ src/main/java/org/apache/jackrabbit/spi/commons/query/sql/QueryFormat.java (working copy) @@ -484,7 +484,12 @@ String comma = ""; for (int i = 0; i < specs.length; i++) { sb.append(comma).append(" "); - appendName(specs[i].getProperty(), resolver, sb); + Path propPath = specs[i].getPropertyPath(); + if (propPath.getLength() > 1) { + exceptions.add(new InvalidQueryException("SQL does not support relative paths in order by clause")); + return sb; + } + appendName(propPath.getNameElement().getName(), resolver, sb); if (!specs[i].isAscending()) { sb.append(" DESC"); } Index: src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java =================================================================== --- src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java (revision 744052) +++ src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java (working copy) @@ -445,9 +445,21 @@ try { for (int i = 0; i < specs.length; i++) { sb.append(comma); - Name prop = encode(specs[i].getProperty()); - sb.append(" @"); - sb.append(resolver.getJCRName(prop)); + Path propPath = specs[i].getPropertyPath(); + Path.Element[] elements = propPath.getElements(); + sb.append(" "); + String slash = ""; + for (int j = 0; j < elements.length; j++) { + sb.append(slash); + slash = "/"; + Path.Element element = elements[j]; + Name name = encode(element.getName()); + if (j == elements.length - 1) { + // last + sb.append("@"); + } + sb.append(resolver.getJCRName(name)); + } if (!specs[i].isAscending()) { sb.append(" descending"); } Index: src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java =================================================================== --- src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java (revision 744052) +++ src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java (working copy) @@ -563,17 +563,32 @@ * @param node a relation query node. */ private void applyRelativePath(RelationQueryNode node) { - if (tmpRelPath != null) { - try { - Path relPath = tmpRelPath.getPath(); - for (int i = 0; i < relPath.getLength(); i++) { - node.addPathElement(relPath.getElements()[i]); - } - } catch (MalformedPathException e) { - // should never happen + Path relPath = getRelativePath(); + if (relPath != null) { + for (int i = 0; i < relPath.getLength(); i++) { + node.addPathElement(relPath.getElements()[i]); } + } + } + + /** + * Returns {@link #tmpRelPath} or null if there is none set. + * When this method returns {@link #tmpRelPath} will have been set + * null. + * + * @return {@link #tmpRelPath}. + */ + private Path getRelativePath() { + try { + if (tmpRelPath != null) { + return tmpRelPath.getPath(); + } + } catch (MalformedPathException e) { + // should never happen + } finally { tmpRelPath = null; } + return null; } /** @@ -1099,8 +1114,14 @@ // cut off left parenthesis at end propName = propName.substring(0, propName.length() - 1); } + PathBuilder builder = new PathBuilder(); Name name = decode(resolver.getQName(propName)); - spec = new OrderQueryNode.OrderSpec(name, true); + Path relPath = getRelativePath(); + if (relPath != null) { + builder.addAll(relPath.getElements()); + } + builder.addLast(name); + spec = new OrderQueryNode.OrderSpec(builder.getPath(), true); queryNode.addOrderSpec(spec); } catch (NameException e) { exceptions.add(new InvalidQueryException("Illegal name: " + child.getValue())); Index: src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyNamespaceResolver.java =================================================================== --- src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyNamespaceResolver.java (revision 744052) +++ src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyNamespaceResolver.java (working copy) @@ -25,7 +25,7 @@ * maps each valid XML prefix string to the same string as the namespace URI * and vice versa. */ -class DummyNamespaceResolver implements NamespaceResolver { +public class DummyNamespaceResolver implements NamespaceResolver { /** * Returns the given prefix. Index: src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathOrderByTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathOrderByTest.java (revision 0) +++ src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathOrderByTest.java (revision 0) @@ -0,0 +1,100 @@ +/* + * 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.spi.commons.query.xpath; + +import java.util.Collections; + +import javax.jcr.query.Query; + +import org.apache.jackrabbit.spi.commons.conversion.NameResolver; +import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver; +import org.apache.jackrabbit.spi.commons.conversion.DummyNamespaceResolver; +import org.apache.jackrabbit.spi.commons.query.QueryNodeFactory; +import org.apache.jackrabbit.spi.commons.query.DefaultQueryNodeFactory; +import org.apache.jackrabbit.spi.commons.query.QueryRootNode; +import org.apache.jackrabbit.spi.commons.query.QueryParser; +import org.apache.jackrabbit.spi.commons.query.OrderQueryNode; +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; + +import junit.framework.TestCase; + +/** + * XPathOrderByTest performs various tests related to parsing an + * XPath statement with order by. + */ +public class XPathOrderByTest extends TestCase { + + private static final NameResolver RESOLVER = new DefaultNamePathResolver( + new DummyNamespaceResolver()); + + private static final QueryNodeFactory FACTORY = new DefaultQueryNodeFactory(Collections.EMPTY_LIST); + + public void testSimpleOrderBy() throws Exception { + String stmt = "//* order by @bar"; + QueryRootNode root = QueryParser.parse(stmt, Query.XPATH, RESOLVER, FACTORY); + OrderQueryNode.OrderSpec[] specs = root.getOrderNode().getOrderSpecs(); + assertEquals(1, specs.length); + assertTrue(specs[0].isAscending()); + checkName(Name.NS_DEFAULT_URI, "bar", specs[0].getProperty()); + Path propPath = specs[0].getPropertyPath(); + assertEquals(1, propPath.getLength()); + checkName(Name.NS_DEFAULT_URI, "bar", propPath.getNameElement().getName()); + } + + public void testAscending() throws Exception { + String stmt = "//* order by @bar ascending"; + QueryRootNode root = QueryParser.parse(stmt, Query.XPATH, RESOLVER, FACTORY); + OrderQueryNode.OrderSpec[] specs = root.getOrderNode().getOrderSpecs(); + assertEquals(1, specs.length); + assertTrue(specs[0].isAscending()); + } + + public void testDescending() throws Exception { + String stmt = "//* order by @bar descending"; + QueryRootNode root = QueryParser.parse(stmt, Query.XPATH, RESOLVER, FACTORY); + OrderQueryNode.OrderSpec[] specs = root.getOrderNode().getOrderSpecs(); + assertEquals(1, specs.length); + assertFalse(specs[0].isAscending()); + } + + public void testChildAxis() throws Exception { + String stmt = "//* order by foo_x0020_bar/@bar"; + QueryRootNode root = QueryParser.parse(stmt, Query.XPATH, RESOLVER, FACTORY); + assertEquals(1, root.getOrderNode().getOrderSpecs().length); + OrderQueryNode.OrderSpec[] specs = root.getOrderNode().getOrderSpecs(); + assertEquals(1, specs.length); + assertTrue(specs[0].isAscending()); + checkName(Name.NS_DEFAULT_URI, "bar", specs[0].getProperty()); + Path propPath = specs[0].getPropertyPath(); + Path.Element[] elements = propPath.getElements(); + assertEquals(2, elements.length); + checkName(Name.NS_DEFAULT_URI, "foo bar", elements[0].getName()); + checkName(Name.NS_DEFAULT_URI, "bar", elements[1].getName()); + } + + public void testRoundTrip() throws Exception { + String stmt = "//* order by foo_x0020_bar/@bar"; + QueryRootNode root = QueryParser.parse(stmt, Query.XPATH, RESOLVER, FACTORY); + assertEquals(stmt, QueryFormat.toString(root, RESOLVER)); + } + + private void checkName(String uri, String localName, Name name) { + assertEquals(uri, name.getNamespaceURI()); + assertEquals(localName, name.getLocalName()); + } +} Property changes on: src\test\java\org\apache\jackrabbit\spi\commons\query\xpath\XPathOrderByTest.java ___________________________________________________________________ Added: svn:eol-style + native