Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java Tue May 05 10:34:57 CEST 2009 @@ -37,6 +37,7 @@ import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.QValue; import org.apache.jackrabbit.spi.QValueFactory; +import org.apache.jackrabbit.util.ISO8601; /** * This class implements the ValueFactory interface, @@ -64,6 +65,16 @@ } /** + * The QValueFactory that is wrapped by this ValueFactory + * instance. + * + * @return qfactory The QValueFactory wrapped by this instance. + */ + public QValueFactory getQValueFactory() { + return qfactory; + } + + /** * Create a new Value based on an existing * QValue * @param qvalue existing QValue @@ -128,6 +139,7 @@ */ public Value createValue(Calendar value) { try { + ISO8601.getYear(value); QValue qvalue = qfactory.create(value); return new QValueValue(qvalue, resolver); } catch (RepositoryException ex) { @@ -168,7 +180,7 @@ Name name = resolver.getQName(value); qvalue = qfactory.create(name); } else if (type == PropertyType.PATH) { - Path path = resolver.getQPath(value); + Path path = resolver.getQPath(value, false); qvalue = qfactory.create(path); } else { qvalue = qfactory.create(value, type); Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathFactoryImpl.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathFactoryImpl.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathFactoryImpl.java Wed May 06 18:55:55 CEST 2009 @@ -137,7 +137,7 @@ } /** - * @see PathFactory#create(Path.Element[]) + * @see PathFactory#create(org.apache.jackrabbit.spi.Path.Element[]) */ public Path create(Path.Element[] elements) throws IllegalArgumentException { return new Builder(elements).getPath(); @@ -157,11 +157,11 @@ while (lastPos >= 0) { Path.Element elem; if (pos >= 0) { - elem = createElement(pathString.substring(lastPos, pos)); + elem = createElementFromString(pathString.substring(lastPos, pos)); lastPos = pos + 1; pos = pathString.indexOf(Path.DELIMITER, lastPos); } else { - elem = createElement(pathString.substring(lastPos)); + elem = createElementFromString(pathString.substring(lastPos)); lastPos = -1; } list.add(elem); @@ -204,10 +204,18 @@ } } + public Path.Element createElement(String identifier) throws IllegalArgumentException { + if (identifier == null) { + throw new IllegalArgumentException("The id must not be null."); + } else { + return new IdentifierElement(identifier); + } + } + /** * Create an element from the element string */ - private Path.Element createElement(String elementString) { + private Path.Element createElementFromString(String elementString) { if (elementString == null) { throw new IllegalArgumentException("null PathElement literal"); } @@ -217,6 +225,8 @@ return CURRENT_ELEMENT; } else if (elementString.equals(PARENT_LITERAL)) { return PARENT_ELEMENT; + } else if (elementString.startsWith("[") && elementString.endsWith("]") && elementString.length() > 2) { + return new IdentifierElement(elementString.substring(1, elementString.length()-1)); } int pos = elementString.indexOf('['); @@ -301,7 +311,7 @@ throw new IllegalArgumentException("Empty paths are not allowed"); } this.elements = elements; - this.absolute = elements[0].denotesRoot(); + this.absolute = elements[0].denotesRoot() || elements[0].denotesIdentifier(); this.normalized = isNormalized; } @@ -309,10 +319,17 @@ * @see Path#denotesRoot() */ public boolean denotesRoot() { - return absolute && elements.length == 1; + return absolute && elements.length == 1 && elements[0].denotesRoot(); } /** + * @see Path#denotesIdentifier() + */ + public boolean denotesIdentifier() { + return elements.length == 1 && elements[0].denotesIdentifier(); + } + + /** * @see Path#isAbsolute() */ public boolean isAbsolute() { @@ -336,10 +353,13 @@ /** * @see Path#getNormalizedPath() */ - public Path getNormalizedPath() { + public Path getNormalizedPath() throws RepositoryException { if (isNormalized()) { return this; } + if (denotesIdentifier()) { + throw new RepositoryException("Identifier-based path cannot be normalized."); + } LinkedList queue = new LinkedList(); Path.Element last = PARENT_ELEMENT; for (int i = 0; i < elements.length; i++) { @@ -373,6 +393,9 @@ if (!isAbsolute()) { throw new RepositoryException("Only an absolute path can be canonicalized."); } + if (denotesIdentifier()) { + throw new RepositoryException("Identifier-based path cannot be canonicalized."); + } return getNormalizedPath(); } @@ -384,10 +407,13 @@ throw new IllegalArgumentException("null argument"); } - // make sure both paths are absolute + // make sure both paths are absolute and not id-based if (!isAbsolute() || !other.isAbsolute()) { throw new RepositoryException("Cannot compute relative path from relative paths"); } + if (denotesIdentifier() || other.denotesIdentifier()) { + throw new RepositoryException("Cannot compute relative path from identifier-based paths"); + } // make sure we're comparing canonical paths Path p0 = getCanonicalPath(); @@ -430,11 +456,11 @@ /** * @see Path#getAncestor(int) */ - public Path getAncestor(int degree) throws IllegalArgumentException, PathNotFoundException { + public Path getAncestor(int degree) throws IllegalArgumentException, PathNotFoundException, RepositoryException { if (degree < 0) { throw new IllegalArgumentException("degree must be >= 0"); } else if (degree == 0) { - return this.getNormalizedPath(); + return getNormalizedPath(); } if (isAbsolute()) { @@ -461,8 +487,13 @@ * @see Path#getAncestorCount() */ public int getAncestorCount() { - return (isAbsolute()) ? getDepth() : -1; + try { + return (isAbsolute() && !denotesIdentifier()) ? getDepth() : -1; + } catch (RepositoryException e) { + // never gets here. + return -1; - } + } + } /** * @see Path#getLength() @@ -474,7 +505,10 @@ /** * @see Path#getDepth() */ - public int getDepth() { + public int getDepth() throws RepositoryException { + if (denotesIdentifier()) { + throw new RepositoryException("Cannot determine depth of an identifier based path."); + } int depth = ROOT_DEPTH; for (int i = 0; i < elements.length; i++) { if (elements[i].denotesParent()) { @@ -750,6 +784,14 @@ } /** + * @return always returns false. + * @see Path.Element#denotesIdentifier() + */ + public boolean denotesIdentifier() { + return false; + } + + /** * @see Path.Element#getString() */ public String getString() { @@ -871,6 +913,86 @@ } /** + * + */ + private static final class IdentifierElement extends Element { + + private final String identifier; + /** + * + * @param identifier + */ + private IdentifierElement(String identifier) { + super(null, Path.INDEX_UNDEFINED); + this.identifier = identifier; + } + + /** + * @return Always returns true. + * @see Path.Element#denotesIdentifier() + */ + public boolean denotesIdentifier() { + return true; + } + + /** + * @return Always returns false. + * @see Path.Element#denotesName() + */ + public boolean denotesName() { + return false; + } + + /** + * Returns a string representation of this path element. Note that + * the path element name is expressed using the {uri}name + * syntax. + * + * @return string representation of the path element + * @see Object#toString() + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('['); + sb.append(identifier); + sb.append(']'); + return sb.toString(); + } + + /** + * Computes a hash code for this path element. + * + * @return hash code + * @see Object#hashCode() + */ + public int hashCode() { + int h = 37 * 17 + identifier.hashCode(); + return h; + } + + /** + * Check for path element equality. Returns true if the given + * object is a PathElement and contains the same name and index + * as this one. + * + * @param obj the object to compare with + * @return true if the path elements are equal + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof IdentifierElement) { + return identifier.equals(((IdentifierElement) obj).identifier); + } if (obj instanceof Path.Element) { + Path.Element other = (Path.Element) obj; + return other.denotesIdentifier() && getString().equals(other.getString()); + } + return false; + } + } + /** * Builder internal class */ private static final class Builder { @@ -912,7 +1034,7 @@ this.elements = elements; if (elements.length == 1) { - isNormalized = true; + isNormalized = !elements[0].denotesIdentifier(); } else { boolean absolute = elements[0].denotesRoot(); isNormalized = true; @@ -926,6 +1048,8 @@ if (i > 0) { throw new IllegalArgumentException("Invalid path: The root element may only occur at the beginning."); } + } else if (elem.denotesIdentifier()) { + throw new IllegalArgumentException("Invalid path: The identifier element may only occur at the beginning of a single element path."); } else if (elem.denotesParent()) { parents++; if (absolute || named > 0) { Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/DefaultNamePathResolver.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/DefaultNamePathResolver.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/DefaultNamePathResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -42,7 +42,7 @@ } public DefaultNamePathResolver(Session session) { - this(new SessionNamespaceResolver(session)); + this(new SessionNamespaceResolver(session), ((session instanceof IdentifierResolver)? (IdentifierResolver) session : null), false); } public DefaultNamePathResolver(NamespaceRegistry registry) { @@ -50,8 +50,12 @@ } public DefaultNamePathResolver(NamespaceResolver nsResolver, boolean enableCaching) { + this(nsResolver, null, enableCaching); + } + + public DefaultNamePathResolver(NamespaceResolver nsResolver, IdentifierResolver idResolver, boolean enableCaching) { NameResolver nr = new ParsingNameResolver(NameFactoryImpl.getInstance(), nsResolver); - PathResolver pr = new ParsingPathResolver(PathFactoryImpl.getInstance(), nr); + PathResolver pr = new ParsingPathResolver(PathFactoryImpl.getInstance(), nr, idResolver); if (enableCaching) { this.nResolver = new CachingNameResolver(nr); this.pResolver = new CachingPathResolver(pr); @@ -78,6 +82,10 @@ return pResolver.getQPath(path); } + public Path getQPath(String path, boolean normalizeIdentifier) throws MalformedPathException, IllegalNameException, NamespaceException { + return pResolver.getQPath(path, normalizeIdentifier); + } + public String getJCRPath(Path path) throws NamespaceException { return pResolver.getJCRPath(path); } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValueFactory.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValueFactory.java Fri Apr 17 10:40:20 CEST 2009 +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValueFactory.java Fri Apr 17 10:40:20 CEST 2009 @@ -0,0 +1,74 @@ +/* + * 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.value; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.spi.QValueFactory; +import org.apache.jackrabbit.spi.QValue; +import org.apache.jackrabbit.spi.QPropertyDefinition; +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.PathFactory; +import org.apache.jackrabbit.spi.NameFactory; +import org.apache.jackrabbit.spi.commons.name.NameConstants; +import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; +import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; +import org.apache.jackrabbit.uuid.UUID; + +import javax.jcr.RepositoryException; +import javax.jcr.PropertyType; +import java.util.Calendar; + +/** + * AbstractQValueFactory... + */ +public abstract class AbstractQValueFactory implements QValueFactory { + + /** + * logger instance + */ + private static final Logger log = LoggerFactory.getLogger(AbstractQValueFactory.class); + + + /** + * the default encoding + */ + public static final String DEFAULT_ENCODING = "UTF-8"; + + protected static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance(); + protected static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance(); + + + //------------------------------------------------------< QValueFactory >--- + /** + * @see QValueFactory#computeAutoValues(org.apache.jackrabbit.spi.QPropertyDefinition) + */ + public QValue[] computeAutoValues(QPropertyDefinition propertyDefinition) throws RepositoryException { + Name nodeType = propertyDefinition.getDeclaringNodeType(); + Name name = propertyDefinition.getName(); + + if (NameConstants.NT_HIERARCHYNODE.equals(nodeType) && NameConstants.JCR_CREATED.equals(name)) { + return new QValue[] { create(Calendar.getInstance()) }; + } else if (NameConstants.NT_RESOURCE.equals(nodeType) && NameConstants.JCR_LASTMODIFIED.equals(name)) { + return new QValue[] { create(Calendar.getInstance()) }; + } else if (NameConstants.MIX_REFERENCEABLE.equals(nodeType) && NameConstants.JCR_UUID.equals(name)) { + return new QValue[] { create(UUID.randomUUID().toString(), PropertyType.STRING) }; + } else { + throw new RepositoryException("createFromDefinition not implemented for: " + name); + } + } +} \ No newline at end of file Index: jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java =================================================================== --- jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java (revision 772129) +++ jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatedBatchTest.java Wed May 06 18:51:27 CEST 2009 @@ -16,7 +16,6 @@ */ package org.apache.jackrabbit.spi.commons.batch; -import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; @@ -379,7 +378,7 @@ return createNodeId(createPath(nodeId)); } - public PropertyId createPropertyId(String propertyId) throws PathNotFoundException { + public PropertyId createPropertyId(String propertyId) throws RepositoryException { Path path = createPath(propertyId); return idFactory.createPropertyId(createNodeId(path.getAncestor(1)), path.getNameElement().getName()); } Index: jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyIdentifierResolver.java =================================================================== --- jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyIdentifierResolver.java Wed Apr 15 17:30:24 CEST 2009 +++ jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/DummyIdentifierResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -0,0 +1,117 @@ +/* + * 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.conversion; + +import org.apache.jackrabbit.spi.Path; +import org.apache.jackrabbit.spi.PathFactory; +import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; +import org.apache.jackrabbit.uuid.UUID; + +import javax.jcr.RepositoryException; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * DummyIdentifierResolver... + */ +class DummyIdentifierResolver implements IdentifierResolver { + + private static final PathFactory FACTORY = PathFactoryImpl.getInstance(); + public static final String JCR_PATH = "/a/b/c"; + + private final List validIds; + private final List invalidFormats; + private final List invalidPaths; + private final Path path; + + DummyIdentifierResolver() throws RepositoryException { + path = FACTORY.create(FACTORY.getRootPath(), FACTORY.create("{}a\t{}b\t{}c"), true); + validIds = new ArrayList(); + validIds.add(UUID.randomUUID().toString()); + validIds.add("a:b"); + validIds.add("a[3]"); + validIds.add("34a[2["); + validIds.add("34a/]"); + validIds.add(" 3-4a/'\"]"); + validIds.add("/a[3]/b/c:d/"); + validIds.add("{}\"\"\t{}a[3]\t{}b\t{}c:d"); + + String invalidID = UUID.randomUUID().toString(); + String invalidIdSegment = "["+invalidID+"]"; + String validSegment = "[" + validIds.get(0).toString() + "]"; + + invalidFormats = new ArrayList(); + invalidPaths = new ArrayList(); + + for (Iterator it = validIds.iterator(); it.hasNext();) { + String validId = it.next().toString(); + if (!validId.endsWith("]")) { + invalidFormats.add("[" + validId); + } else { + invalidPaths.add("[" + validId); + } + + if (!validId.startsWith("[")) { + invalidFormats.add(validId + "]"); + } else { + invalidPaths.add(validId + "]"); + } + } + invalidFormats.add(validSegment + "abc/abc"); + invalidFormats.add(validSegment + "/a/b/c"); + invalidFormats.add("/" + validSegment); + invalidFormats.add("/a/b/" + validSegment + "/c"); + invalidFormats.add("/a/b/c" + validSegment); + invalidFormats.add("/" + invalidIdSegment); + + // path starting with [ and ending with ] -> valid format but + // might be invalid path. + invalidPaths.add(validSegment + "/a/b[2]"); + invalidPaths.add(validSegment + "/" + validSegment); + invalidPaths.add(validSegment + "[2]"); + invalidPaths.add(invalidIdSegment); + invalidPaths.addAll(invalidFormats); + } + + List getValidIdentifiers() { + return validIds; + } + + List getInvalidIdentifierPaths() { + return invalidPaths; + } + + List getInvalidIdentifierFormats() { + return invalidFormats; + } + + public Path getPath(String identifier) throws MalformedPathException { + if (validIds.contains(identifier)) { + return path; + } else { + throw new MalformedPathException("Invalid path: identifier '"+ identifier +"' cannot be resolved."); + } + } + + public void checkFormat(String identifier) throws MalformedPathException { + if (validIds.contains(identifier)) { + return; + } + throw new MalformedPathException("Invalid identifier."); + } +} \ No newline at end of file Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolver.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolver.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -35,20 +35,40 @@ /** * Name resolver. */ - private final NameResolver resolver; + private final NameResolver nameResolver; /** + * Identifier resolver. + */ + private final IdentifierResolver idResolver; + + /** * Creates a parsing path resolver. * + * @param pathFactory path factory. * @param resolver name resolver */ public ParsingPathResolver(PathFactory pathFactory, NameResolver resolver) { + this(pathFactory, resolver, null); + } + + /** + * Creates a parsing path resolver. + * + * @param pathFactory path factory. + * @param nameResolver name resolver. + * @param idResolver identifier resolver. + * @since JCR 2.0 + */ + public ParsingPathResolver(PathFactory pathFactory, NameResolver nameResolver, + IdentifierResolver idResolver) { this.pathFactory = pathFactory; - this.resolver = resolver; + this.nameResolver = nameResolver; + this.idResolver = idResolver; } /** - * Parses the prefixed JCR path and returns the resolved qualified path. + * Parses the given JCR path and returns the resolved qualified path. * * @param path prefixed JCR path * @return qualified path @@ -57,12 +77,22 @@ * @throws NamespaceException if a namespace prefix can not be resolved */ public Path getQPath(String path) throws MalformedPathException, IllegalNameException, NamespaceException { - return PathParser.parse(path, resolver, pathFactory); + return PathParser.parse(path, nameResolver, idResolver, pathFactory); } + /** + * Calls {@link PathParser#parse(String, NameResolver, IdentifierResolver, org.apache.jackrabbit.spi.PathFactory)} + * from the given path. + * + * @see PathResolver#getQPath(String, boolean) + */ + public Path getQPath(String path, boolean normalizeIdentifier) throws MalformedPathException, IllegalNameException, NamespaceException { + return PathParser.parse(path, nameResolver, idResolver, pathFactory, normalizeIdentifier); + } + /** - * Returns the prefixed JCR path for the given qualified path. + * Returns the given JCR path for the given qualified path. * * @param path qualified path * @return prefixed JCR path @@ -82,8 +112,10 @@ buffer.append('.'); } else if (elements[i].denotesParent()) { buffer.append(".."); + } else if (elements[i].denotesIdentifier()) { + buffer.append(elements[i].getString()); } else { - buffer.append(resolver.getJCRName(elements[i].getName())); + buffer.append(nameResolver.getJCRName(elements[i].getName())); /** * FIXME the [1] subscript should only be suppressed if the * item in question can't have same-name siblings. Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java (revision 772129) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java Fri Apr 17 12:12:06 CEST 2009 @@ -197,6 +197,10 @@ * if any of the set values are illegal or out of range */ cal.getTime(); + /** + * in addition check the validity of the year + */ + getYear(cal); } catch (IllegalArgumentException e) { return null; } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathResolver.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathResolver.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -26,10 +26,10 @@ public interface PathResolver { /** - * Returns the qualified path for the given prefixed JCR path. + * Returns the path object for the given JCR path string. * * @param path prefixed JCR path - * @return qualified path + * @return a Path object. * @throws MalformedPathException if the JCR path format is invalid. * @throws IllegalNameException if any of the JCR names contained in the path are invalid. * @throws NamespaceException if a namespace prefix can not be resolved. @@ -37,10 +37,22 @@ Path getQPath(String path) throws MalformedPathException, IllegalNameException, NamespaceException; /** - * Returns the prefixed JCR path for the given qualified path. + * Returns the path object for the given JCR path string. * - * @param path qualified path - * @return prefixed JCR path + * @param path prefixed JCR path + * @param normalizeIdentifier + * @return a Path object. + * @throws MalformedPathException if the JCR path format is invalid. + * @throws IllegalNameException if any of the JCR names contained in the path are invalid. + * @throws NamespaceException if a namespace prefix can not be resolved. + */ + Path getQPath(String path, boolean normalizeIdentifier) throws MalformedPathException, IllegalNameException, NamespaceException; + + /** + * Returns the given JCR path string for the given path object. + * + * @param path a Path object. + * @return a JCR path string * @throws NamespaceException if a namespace URI can not be resolved */ String getJCRPath(Path path) throws NamespaceException; Index: jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/name/PathFactoryTest.java =================================================================== --- jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/name/PathFactoryTest.java (revision 772129) +++ jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/name/PathFactoryTest.java Wed Apr 15 17:30:24 CEST 2009 @@ -23,8 +23,10 @@ import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver; import org.apache.jackrabbit.spi.commons.conversion.PathResolver; import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; +import org.apache.jackrabbit.uuid.UUID; import javax.jcr.NamespaceException; +import javax.jcr.RepositoryException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -84,7 +86,7 @@ public void testCreateElementNullName() { try { - factory.createElement(null); + factory.createElement((Name) null); fail("Creating element with null name is invalid"); } catch (IllegalArgumentException e) { // ok @@ -197,6 +199,87 @@ } } + public void testIdentifier() { + String identifier = UUID.randomUUID().toString(); + + Path.Element elem = factory.createElement(identifier); + assertTrue(elem.denotesIdentifier()); + assertFalse(elem.denotesCurrent()); + assertFalse(elem.denotesName()); + assertFalse(elem.denotesParent()); + assertFalse(elem.denotesRoot()); + assertNull(elem.getName()); + assertNotNull(elem.getString()); + assertEquals(Path.INDEX_UNDEFINED, elem.getIndex()); + assertEquals(Path.INDEX_DEFAULT, elem.getNormalizedIndex()); + + Path p = factory.create(new Path.Element[] {elem}); + assertTrue(p.denotesIdentifier()); + assertTrue(p.isAbsolute()); + + assertFalse(p.denotesRoot()); + assertFalse(p.isCanonical()); + assertFalse(p.isNormalized()); + + assertEquals(1, p.getLength()); + assertEquals(-1, p.getAncestorCount()); + + Path.Element lastElem = p.getNameElement(); + assertNotNull(lastElem); + assertTrue(lastElem.denotesIdentifier()); + + assertEquals(1, p.getElements().length); + + try { + p.getDepth(); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.getNormalizedPath(); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.getAncestor(1); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.isAncestorOf(factory.getRootPath()); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.computeRelativePath(factory.getRootPath()); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.getCanonicalPath(); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.isDescendantOf(factory.getRootPath()); + fail(); + } catch (RepositoryException e) { + //expected + } + try { + p.isEquivalentTo(factory.getRootPath()); + fail(); + } catch (RepositoryException e) { + //expected + } + } + public void testCreateInvalidPath() throws NamespaceException { Path.Element rootEl = factory.getRootElement(); Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingPathResolver.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingPathResolver.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingPathResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -72,12 +72,33 @@ * @throws NamespaceException if a namespace prefix can not be resolved */ public Path getQPath(String path) throws MalformedPathException, IllegalNameException, NamespaceException { - Path qpath = (Path) cache.get(path); + return getQPath(path, true); + } + + /** + * @see PathResolver#getQPath(String, boolean) + */ + public Path getQPath(String path, boolean normalizeIdentifier) throws MalformedPathException, IllegalNameException, NamespaceException { + Path qpath; + /* + * Jcr paths consisting of an identifier segment have 2 different + * path object representations depending on the given resolution flag: + * 1) a normalized absolute path if resolveIdentifier is true + * 2) a path denoting an identifier if resolveIdentifier is false. + * The latter are not cached in order not to return a wrong resolution + * when calling getQPath with the same identifier-jcr-path. + */ + if (path.startsWith("[") && !normalizeIdentifier) { + qpath = resolver.getQPath(path, normalizeIdentifier); + } else { + qpath = (Path) cache.get(path); - if (qpath == null) { + if (qpath == null) { - qpath = resolver.getQPath(path); + qpath = resolver.getQPath(path, normalizeIdentifier); - cache.put(path, qpath); - } + cache.put(path, qpath); + } + } return qpath; + } Index: jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java =================================================================== --- jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java (revision 772129) +++ jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java Wed Apr 15 17:30:24 CEST 2009 @@ -28,6 +28,8 @@ import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.PathFactory; +import javax.jcr.RepositoryException; + /** * PathParserTest */ @@ -207,4 +209,86 @@ assertFalse("path is canonical: " + path, path.isCanonical()); } } + + public void testIdentifierParse() throws RepositoryException { + DummyIdentifierResolver idResolver = new DummyIdentifierResolver(); + List valid = idResolver.getValidIdentifiers(); + for (Iterator it = valid.iterator(); it.hasNext();) { + String jcrPath = "[" + it.next() + "]"; + try { + PathParser.parse(jcrPath, resolver, factory); + fail("Parsing an identifier-based jcr path needs a IdentifierResolver"); + } catch (MalformedPathException e) { + // success: cannot parse identifier path if idResolver is missing. -} + } + try { + PathParser.parse(factory.getRootPath(), jcrPath, resolver, factory); + fail("Parsing an identifier-based jcr path needs a IdentifierResolver"); + } catch (MalformedPathException e) { + // success: cannot parse identifier path if idResolver is missing. + } + + Path p = PathParser.parse(jcrPath, resolver, idResolver, factory, true); + assertFalse(p.denotesIdentifier()); + + p = PathParser.parse(jcrPath, resolver, idResolver, factory, false); + assertTrue(p.denotesIdentifier()); + + try { + PathParser.parse(factory.getRootPath(), jcrPath, resolver, idResolver, factory); + fail("Cannot parser an identifier-based path to a relative path."); + } catch (MalformedPathException e) { + // success: invalid argument parent-path if the jcr-path is an identifier-based path. + } + + try { + PathParser.parse(jcrPath, resolver, factory); + fail("Parsing an identifier-based jcr path needs a IdentifierResolver"); + } catch (MalformedPathException e) { + // success: cannot parse identifier path if idResolver is missing. + } + } + } + + public void testInvalidIdentifierParse() throws RepositoryException { + DummyIdentifierResolver idResolver = new DummyIdentifierResolver(); + + List invalid = idResolver.getInvalidIdentifierPaths(); + for (Iterator it = invalid.iterator(); it.hasNext();) { + String jcrPath = it.next().toString(); + try { + Path p = PathParser.parse(jcrPath, resolver, idResolver, factory, true); + fail("Invalid identifier based path"); + } catch (MalformedPathException e) { + // ok + } + try { + Path p = PathParser.parse(jcrPath, resolver, idResolver, factory, false); + fail("Invalid identifier based path"); + } catch (MalformedPathException e) { + // ok + } + } + } + + public void testIdentifierCheckFormat() throws RepositoryException { + DummyIdentifierResolver idResolver = new DummyIdentifierResolver(); + List valid = idResolver.getValidIdentifiers(); + for (Iterator it = valid.iterator(); it.hasNext();) { + String jcrPath = "[" + it.next() + "]"; + PathParser.checkFormat(jcrPath); + } + + List invalid = idResolver.getInvalidIdentifierFormats(); + for (Iterator it = invalid.iterator(); it.hasNext();) { + String jcrPath = it.next().toString(); + try { + // passing null-nameResolver -> executes check-format only + PathParser.checkFormat(jcrPath); + fail(jcrPath); + } catch (MalformedPathException e) { + // success + } + } + } +} Index: jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolverTest.java =================================================================== --- jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolverTest.java (revision 772129) +++ jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/ParsingPathResolverTest.java Wed Apr 15 17:30:24 CEST 2009 @@ -18,35 +18,58 @@ import junit.framework.TestCase; +import javax.jcr.RepositoryException; import javax.jcr.NamespaceException; import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.jackrabbit.spi.Path; +import java.util.List; +import java.util.Iterator; + /** * Test cases for the {@link ParsingPathResolver} class. */ public class ParsingPathResolverTest extends TestCase { + private static NameResolver nameResolver = new ParsingNameResolver(NameFactoryImpl.getInstance(), new DummyNamespaceResolver()); + private DummyIdentifierResolver idResolver; + /** * Path resolver being tested. */ - private PathResolver resolver = new ParsingPathResolver(PathFactoryImpl.getInstance(), - new ParsingNameResolver(NameFactoryImpl.getInstance(), new DummyNamespaceResolver())); + private PathResolver resolver = new ParsingPathResolver(PathFactoryImpl.getInstance(), nameResolver); + private PathResolver resolverV2; + + protected void setUp() throws Exception { + super.setUp(); + idResolver = new DummyIdentifierResolver(); + resolverV2 = new ParsingPathResolver(PathFactoryImpl.getInstance(), nameResolver, idResolver); + } + /** * Checks that the given path resolves properly. * * @param path JCR path */ private void assertValidPath(String path) { + assertValidPath(path, path); + } + + private void assertValidPath(String path, String expectedResult) { try { Path qpath = resolver.getQPath(path); - assertEquals(path, path, resolver.getJCRPath(qpath)); - } catch (NameException e) { + assertEquals(path, expectedResult, resolver.getJCRPath(qpath)); + } catch (RepositoryException e) { fail(path); - } catch (NamespaceException e) { + } + + try { + Path qpath = resolverV2.getQPath(path); + assertEquals(path, expectedResult, resolver.getJCRPath(qpath)); + } catch (RepositoryException e) { fail(path); } } @@ -60,10 +83,16 @@ try { resolver.getQPath(path); fail(path); - } catch (NameException e) { - } catch (NamespaceException e) { + } catch (RepositoryException e) { + // success } + try { + resolverV2.getQPath(path); + fail(path); + } catch (RepositoryException e) { + // success - } + } + } /** * Tests that valid paths are properly resolved. @@ -87,6 +116,12 @@ assertValidPath("name[2]/name[2]"); assertValidPath("prefix:name[2]/prefix:name[2]"); + // trailing slash is valid + assertValidPath("a/", "a"); + assertValidPath("prefix:name/", "prefix:name"); + assertValidPath("/prefix:name[1]/", "/prefix:name"); + assertValidPath("/a/../b/", "/a/../b"); + assertValidPath("/a/../b"); assertValidPath("./../."); assertValidPath("/a/./b"); @@ -103,12 +138,9 @@ public void testInvalidPaths() { assertInvalidPath(""); assertInvalidPath("//"); - //assertInvalidPath("x/"); // TODO: was valid with jcr-commons-path but not with pathresolver? check again assertInvalidPath("x:"); assertInvalidPath("x:/"); assertInvalidPath("x[]"); - //assertInvalidPath("x:y/"); // TODO: was valid with jcr-commons-path but not with pathresolver? check again - //assertInvalidPath("x:y[1]/"); // TODO: was valid with jcr-commons-path but not with pathresolver? check again assertInvalidPath("x:y["); assertInvalidPath("x:y[]"); assertInvalidPath("x:y[1"); @@ -129,4 +161,38 @@ assertInvalidPath("prefix:name[2]foo/prefix:name[2]"); } + public void testValidIdentifierPaths() throws MalformedPathException, IllegalNameException, NamespaceException { + for (Iterator it = idResolver.getValidIdentifiers().iterator(); it.hasNext();) { + String jcrPath = "[" + it.next().toString() + "]"; + + Path p = resolverV2.getQPath(jcrPath, true); + assertFalse(p.denotesIdentifier()); + assertTrue(p.isAbsolute()); + assertTrue(p.isNormalized()); + assertTrue(p.isCanonical()); + assertEquals(DummyIdentifierResolver.JCR_PATH, resolverV2.getJCRPath(p)); + + p = resolverV2.getQPath(jcrPath, false); + assertTrue(p.denotesIdentifier()); + assertEquals(1, p.getLength()); + assertTrue(p.isAbsolute()); + assertFalse(p.isNormalized()); + assertFalse(p.isCanonical()); + assertEquals(jcrPath, resolverV2.getJCRPath(p)); -} + } + } + + public void testInvalidIdentifierPaths() throws MalformedPathException, IllegalNameException, NamespaceException { + List l = idResolver.getInvalidIdentifierPaths(); + + for (Iterator it = l.iterator(); it.hasNext();) { + String path = it.next().toString(); + try { + resolverV2.getQPath(path); + fail(path); + } catch (MalformedPathException e) { + // success + } + } + } +} Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java Wed Apr 15 17:30:24 CEST 2009 @@ -39,15 +39,20 @@ private static final int STATE_INDEX_END = 5; private static final int STATE_DOT = 6; private static final int STATE_DOTDOT = 7; + private static final int STATE_IDENTIFIER = 8; + private static final int STATE_URI = 9; + private static final int STATE_URI_END = 10; /** * Parses jcrPath into a qualified path using - * resolver to convert prefixes into namespace URI's. + * resolver to convert prefixes into namespace URI's. If + * resolver is null this method only checks the format of the + * passed String and returns null. * * @param jcrPath the jcr path. * @param resolver the namespace resolver. * @param factory - * @return qualified path. + * @return A path object. * @throws MalformedPathException If the jcrPath is malformed. * @throws IllegalNameException if any of the jcrNames is malformed. * @throws NamespaceException If an unresolvable prefix is encountered. @@ -58,17 +63,76 @@ } /** - * Parses the give jcrPath and returns a Path. If + * Parses jcrPath into a qualified path using + * resolver to convert prefixes into namespace URI's. If the + * specified jcrPath is an identifier based absolute path + * beginning with an identifier segment the specified + * IdentifierResolver will be used to resolve it to an + * absolute path.

+ * If namResolver is null or if identifierResolver + * is null and the path starts with an identifier segment, this + * method only checks the format of the string and returns null. + * + * @param jcrPath the jcr path. + * @param nameResolver the namespace resolver. + * @param identifierResolver the resolver to validate any trailing identifier + * segment and resolve to an absolute path. + * @param factory + * @return A path object. + * @throws MalformedPathException If the jcrPath is malformed. + * @throws IllegalNameException if any of the jcrNames is malformed. + * @throws NamespaceException If an unresolvable prefix is encountered. + * @since JCR 2.0 + */ + public static Path parse(String jcrPath, NameResolver nameResolver, + IdentifierResolver identifierResolver, PathFactory factory) + throws MalformedPathException, IllegalNameException, NamespaceException { + return parse(null, jcrPath, nameResolver, identifierResolver, factory); + } + + /** + * Parses jcrPath into a qualified path using + * resolver to convert prefixes into namespace URI's. If the + * specified jcrPath is an identifier based absolute path + * beginning with an identifier segment the specified + * IdentifierResolver will be used to resolve it to an + * absolute path.

+ * If namResolver is null or if identifierResolver + * is null and the path starts with an identifier segment, this + * method only checks the format of the string and returns null. + * + * @param jcrPath the jcr path. + * @param nameResolver the namespace resolver. + * @param identifierResolver the resolver to validate any trailing identifier + * segment and resolve to an absolute path. + * @param factory + * @param normalizeIdentifier + * @return A path object. + * @throws MalformedPathException If the jcrPath is malformed. + * @throws IllegalNameException if any of the jcrNames is malformed. + * @throws NamespaceException If an unresolvable prefix is encountered. + * @since JCR 2.0 + */ + public static Path parse(String jcrPath, NameResolver nameResolver, + IdentifierResolver identifierResolver, + PathFactory factory, boolean normalizeIdentifier) + throws MalformedPathException, IllegalNameException, NamespaceException { + return parse(null, jcrPath, nameResolver, identifierResolver, factory, normalizeIdentifier); + } + + /** + * Parses the given jcrPath and returns a Path. If * parent is not null, it is prepended to the - * returned list. If resolver is null, this method - * only checks the format of the string and returns null. + * built path before it is returned. If resolver is + * null, this method only checks the format of the string and + * returns null. * * @param parent the parent path * @param jcrPath the JCR path * @param resolver the namespace resolver to get prefixes for namespace * URI's. * @param factory - * @return the fully qualified Path. + * @return the Path object. * @throws MalformedPathException If the jcrPath is malformed. * @throws IllegalNameException if any of the jcrNames is malformed. * @throws NamespaceException If an unresolvable prefix is encountered. @@ -76,6 +140,65 @@ public static Path parse(Path parent, String jcrPath, NameResolver resolver, PathFactory factory) throws MalformedPathException, IllegalNameException, NamespaceException { + return parse(parent, jcrPath, resolver, null, factory); + } + + /** + * Parses the given jcrPath and returns a Path. If + * parent is not null, it is prepended to the + * built path before it is returned. If the specifed jcrPath + * is an identifier based absolute path beginning with an identifier segment + * the given identifierResolver will be used to resolve it to an + * absolute path.

+ * If nameResolver is null or if identifierResolver + * is null and the path starts with an identifier segment, this + * method only checks the format of the string and returns null. + * + * @param parent the parent path. + * @param jcrPath the jcr path. + * @param nameResolver the namespace resolver. + * @param identifierResolver the resolver to validate any trailing identifier + * segment and resolve it to an absolute path. + * @param factory The path factory. + * @return the Path object. + * @throws MalformedPathException + * @throws IllegalNameException + * @throws NamespaceException + */ + public static Path parse(Path parent, String jcrPath, NameResolver nameResolver, + IdentifierResolver identifierResolver, PathFactory factory) + throws MalformedPathException, IllegalNameException, NamespaceException { + return parse(parent, jcrPath, nameResolver, identifierResolver, factory, true); + } + + /** + * Parses the given jcrPath and returns a Path. If + * parent is not null, it is prepended to the + * built path before it is returned. If the specifed jcrPath + * is an identifier based absolute path beginning with an identifier segment + * the given identifierResolver will be used to resolve it to an + * absolute path.

+ * If nameResolver is null or if identifierResolver + * is null and the path starts with an identifier segment, this + * method only checks the format of the string and returns null. + * + * @param parent the parent path. + * @param jcrPath the jcr path. + * @param nameResolver the namespace resolver. + * @param identifierResolver the resolver to validate any trailing identifier + * segment and resolve it to an absolute path. + * @param factory The path factory. + * @param normalizeIdentifier + * @return the Path object. + * @throws MalformedPathException + * @throws IllegalNameException + * @throws NamespaceException + */ + private static Path parse(Path parent, String jcrPath, NameResolver nameResolver, + IdentifierResolver identifierResolver, PathFactory factory, + boolean normalizeIdentifier) + throws MalformedPathException, IllegalNameException, NamespaceException { + final char EOF = (char) -1; // check for length @@ -95,7 +218,7 @@ int pos = 0; if (jcrPath.charAt(0) == '/') { if (parent != null) { - throw new MalformedPathException("'" + jcrPath + "' is not a relative path"); + throw new MalformedPathException("'" + jcrPath + "' is not a relative path."); } builder.addRoot(); pos++; @@ -107,12 +230,26 @@ } // parse the path - int state = STATE_PREFIX_START; + int state; + if (jcrPath.charAt(0) == '[') { + if (parent != null) { + throw new MalformedPathException("'" + jcrPath + "' is not a relative path."); + } + state = STATE_IDENTIFIER; + pos++; + } else { + state = STATE_PREFIX_START; + } + int lastPos = pos; + String name = null; + int index = Path.INDEX_UNDEFINED; boolean wasSlash = false; + boolean checkFormat = (nameResolver == null); + while (pos <= len) { char c = pos == len ? EOF : jcrPath.charAt(pos); pos++; @@ -128,7 +265,8 @@ } if (state == STATE_PREFIX || state == STATE_NAME - || state == STATE_INDEX_END) { + || state == STATE_INDEX_END + || state == STATE_URI_END) { // eof pathelement if (name == null) { @@ -140,14 +278,38 @@ // only add element if resolver not null. otherwise this // is just a check for valid format. - if (resolver != null) { - Name qName = resolver.getQName(name); + if (checkFormat) { + NameParser.checkFormat(name); + } else { + Name qName = nameResolver.getQName(name); builder.addLast(qName, index); } state = STATE_PREFIX_START; lastPos = pos; name = null; index = Path.INDEX_UNDEFINED; + } else if (state == STATE_IDENTIFIER) { + if (c == EOF) { + // eof identifier reached + if (jcrPath.charAt(pos - 2) != ']') { + throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Unterminated identifier segment."); + } + String identifier = jcrPath.substring(lastPos, pos - 2); + if (checkFormat) { + if (identifierResolver != null) { + identifierResolver.checkFormat(identifier); + } // else ignore. TODO: rather throw? + } else if (identifierResolver == null) { + throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Identifier segments are not supported."); + } else if (normalizeIdentifier) { + builder.addAll(identifierResolver.getPath(identifier).getElements()); + } else { + identifierResolver.checkFormat(identifier); + builder.addLast(factory.createElement(identifier)); + } + state = STATE_PREFIX_START; + lastPos = pos; + } } else if (state == STATE_DOT) { builder.addLast(factory.getCurrentElement()); lastPos = pos; @@ -158,7 +320,7 @@ state = STATE_PREFIX_START; } else if (state == STATE_PREFIX_START && c == EOF) { // ignore trailing slash - } else { + } else if (state != STATE_URI) { throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character."); } break; @@ -184,6 +346,8 @@ } state = STATE_NAME_START; // don't reset the lastPos/pos since prefix+name are passed together to the NameResolver + } else if (state == STATE_IDENTIFIER || state == STATE_URI) { + // nothing do } else { throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid name character"); } @@ -197,6 +361,8 @@ state = STATE_INDEX; name = jcrPath.substring(lastPos, pos - 1); lastPos = pos; + } else if (state == STATE_IDENTIFIER) { + // nothing do } else { throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character."); } @@ -213,6 +379,8 @@ throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Index number invalid: " + index); } state = STATE_INDEX_END; + } else if (state == STATE_IDENTIFIER) { + // nothing do } else { throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character."); } @@ -225,19 +393,32 @@ throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid after index. '/' expected."); } else if (state == STATE_DOT || state == STATE_DOTDOT) { state = STATE_PREFIX; - } else if (state == STATE_INDEX_END) { - throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid after index. '/' expected."); } break; case '\t': + if (state != STATE_IDENTIFIER) { - throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Whitespace not a allowed in name."); + throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Whitespace not a allowed in name."); - + } case '*': case '\'': case '\"': + if (state != STATE_IDENTIFIER) { + // TODO for JCR 2.0 remove limitation of ' and " - throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character."); + throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character."); + } + case '{': + if (state == STATE_PREFIX_START) { + state = STATE_URI; + } + break; + case '}': + if (state == STATE_URI) { + state = STATE_URI_END; + } + break; + default: if (state == STATE_PREFIX_START || state == STATE_DOT || state == STATE_DOTDOT) { state = STATE_PREFIX; @@ -250,7 +431,7 @@ wasSlash = c == ' '; } - if (resolver == null) { + if (checkFormat) { // this was only for checking the format return null; } else { @@ -269,7 +450,7 @@ public static void checkFormat(String jcrPath) throws MalformedPathException { try { // since no path is created -> use default factory - parse(jcrPath, null, PathFactoryImpl.getInstance()); + parse(jcrPath, null, null, PathFactoryImpl.getInstance()); } catch (NamespaceException e) { // will never occur } catch (IllegalNameException e) { Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java Fri Apr 17 11:59:18 CEST 2009 +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java Fri Apr 17 11:59:18 CEST 2009 @@ -0,0 +1,247 @@ +/* + * 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.value; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.spi.QValue; +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; +import org.apache.jackrabbit.util.ISO8601; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.ValueFormatException; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * AbstractQValue... + */ +public abstract class AbstractQValue implements QValue { + + /** + * logger instance + */ + private static final Logger log = LoggerFactory.getLogger(AbstractQValue.class); + + protected Object val; + protected final int type; + + /** + * Create a new AbstractQValue. + * + * @param value The value. + * @param type The property type. + */ + protected AbstractQValue(Object value, int type) { + val = value; + this.type = type; + } + + protected AbstractQValue(String value, int type) { + if (!(type == PropertyType.STRING || type == PropertyType.REFERENCE)) { + throw new IllegalArgumentException(); + } + val = value; + this.type = type; + } + + protected AbstractQValue(Long value) { + val = value; + type = PropertyType.LONG; + } + + protected AbstractQValue(Double value) { + val = value; + type = PropertyType.DOUBLE; + } + + protected AbstractQValue(Boolean value) { + val = value; + type = PropertyType.BOOLEAN; + } + + protected AbstractQValue(Name value) { + val = value; + type = PropertyType.NAME; + } + + protected AbstractQValue(Path value) { + val = value; + type = PropertyType.PATH; + } + + //---------------------------------------------------------< QValue >--- + /** + * @see QValue#getType() + */ + public int getType() { + return type; + } + + /** + * @see QValue#getLength() + */ + public long getLength() throws RepositoryException { + return getString().length(); + } + + /** + * @see QValue#getName() + */ + public Name getName() throws RepositoryException { + if (type == PropertyType.NAME) { + return (Name) val; + } else { + try { + return AbstractQValueFactory.NAME_FACTORY.create(getString()); + } catch (IllegalArgumentException e) { + throw new ValueFormatException("not a valid Name value: " + getString(), e); + } + } + } + + /** + * @see QValue#getCalendar() + */ + public Calendar getCalendar() throws RepositoryException { + if (type == PropertyType.DATE) { + return (Calendar) ((Calendar) val).clone(); + } else if (type == PropertyType.DOUBLE) { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")); + cal.setTimeInMillis(((Double) val).longValue()); + return cal; + } else if (type == PropertyType.LONG) { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")); + cal.setTimeInMillis(((Long) val).longValue()); + return cal; + } else { + Calendar cal = ISO8601.parse(getString()); + if (cal == null) { + throw new ValueFormatException("not a date string: " + getString()); + } else { + return cal; + } + } + } + + /** + * @see QValue#getDouble() + */ + public double getDouble() throws RepositoryException { + if (type == PropertyType.DOUBLE) { + return ((Double) val).doubleValue(); + } else if (type == PropertyType.DATE) { + return ((Calendar) val).getTimeInMillis(); + } else { + try { + return Double.parseDouble(getString()); + } catch (NumberFormatException ex) { + throw new ValueFormatException("not a double: " + getString(), ex); + } + } + } + + /** + * @see QValue#getLong() + */ + public long getLong() throws RepositoryException { + if (type == PropertyType.LONG) { + return ((Long) val).longValue(); + } else if (type == PropertyType.DOUBLE) { + return ((Double) val).longValue(); + } else if (type == PropertyType.DATE) { + return ((Calendar) val).getTimeInMillis(); + } else { + try { + return Long.parseLong(getString()); + } catch (NumberFormatException ex) { + throw new ValueFormatException("not a long: " + getString(), ex); + } + } + } + + /** + * @throws RepositoryException + * @see QValue#getBoolean() + */ + public boolean getBoolean() throws RepositoryException { + if (type == PropertyType.BOOLEAN) { + return ((Boolean) val).booleanValue(); + } else { + return Boolean.valueOf(getString()).booleanValue(); + } + } + + /** + * @see QValue#getPath() + */ + public Path getPath() throws RepositoryException { + if (type == PropertyType.PATH) { + return (Path) val; + } else { + try { + return AbstractQValueFactory.PATH_FACTORY.create(getString()); + } catch (IllegalArgumentException e) { + throw new ValueFormatException("not a valid Path: " + getString(), e); + } + } + } + + /** + * @see QValue#discard() + */ + public void discard() { + // nothing to do + } + + //---------------------------------------------------------< Object >--- + /** + * Returns the string representation of this internal value. + * + * @return string representation of this internal value + */ + public String toString() { + return val.toString(); + } + + /** + * @param obj + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AbstractQValue) { + AbstractQValue other = (AbstractQValue) obj; + return type == other.type && val.equals(other.val); + } + return false; + } + + /** + * @return the hashCode of the internal value object. + * @see Object#hashCode() + */ + public int hashCode() { + return val.hashCode(); + } + + +} \ No newline at end of file Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFormat.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFormat.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFormat.java Wed May 06 18:53:34 CEST 2009 @@ -119,7 +119,7 @@ qValue = factory.create(qName); break; case PropertyType.PATH: - Path qPath = resolver.getQPath(jcrValue).getNormalizedPath(); + Path qPath = resolver.getQPath(jcrValue, false); qValue = factory.create(qPath); break; default: @@ -131,7 +131,8 @@ /** * @param qualifiedValue * @param resolver - * @return + * @param factory + * @return the JCR value created from the given qualifiedValue. * @throws RepositoryException */ public static Value getJCRValue(QValue qualifiedValue, Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java Fri Apr 17 13:17:04 CEST 2009 @@ -16,6 +16,16 @@ */ package org.apache.jackrabbit.spi.commons.value; +import org.apache.jackrabbit.spi.Name; +import org.apache.jackrabbit.spi.Path; +import org.apache.jackrabbit.spi.QValue; +import org.apache.jackrabbit.spi.QValueFactory; +import org.apache.jackrabbit.util.ISO8601; +import org.apache.jackrabbit.util.TransientFileFactory; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.ValueFormatException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -31,41 +41,14 @@ import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Calendar; -import java.util.TimeZone; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.ValueFormatException; - -import org.apache.jackrabbit.spi.Name; -import org.apache.jackrabbit.spi.NameFactory; -import org.apache.jackrabbit.spi.Path; -import org.apache.jackrabbit.spi.PathFactory; -import org.apache.jackrabbit.spi.QPropertyDefinition; -import org.apache.jackrabbit.spi.QValue; -import org.apache.jackrabbit.spi.QValueFactory; -import org.apache.jackrabbit.spi.commons.name.NameConstants; -import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; -import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl; -import org.apache.jackrabbit.util.ISO8601; -import org.apache.jackrabbit.util.TransientFileFactory; -import org.apache.jackrabbit.uuid.UUID; - /** * QValueFactoryImpl... */ -public final class QValueFactoryImpl implements QValueFactory { +public final class QValueFactoryImpl extends AbstractQValueFactory { private static final QValueFactory INSTANCE = new QValueFactoryImpl(); - private static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance(); - private static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance(); - - /** - * the default encoding - */ - private static final String DEFAULT_ENCODING = "UTF-8"; - private QValueFactoryImpl() { } @@ -207,93 +190,49 @@ return new BinaryQValue(value); } - /** - * @see QValueFactory#computeAutoValues(QPropertyDefinition) - */ - public QValue[] computeAutoValues(QPropertyDefinition propertyDefinition) throws RepositoryException { - Name nodeType = propertyDefinition.getDeclaringNodeType(); - Name name = propertyDefinition.getName(); - if (NameConstants.NT_HIERARCHYNODE.equals(nodeType) && NameConstants.JCR_CREATED.equals(name)) { - return new QValue[] { create(Calendar.getInstance()) }; - } else if (NameConstants.NT_RESOURCE.equals(nodeType) && NameConstants.JCR_LASTMODIFIED.equals(name)) { - return new QValue[] { create(Calendar.getInstance()) }; - } else if (NameConstants.MIX_REFERENCEABLE.equals(nodeType) && NameConstants.JCR_UUID.equals(name)) { - return new QValue[] { create(UUID.randomUUID().toString(), PropertyType.STRING) }; - } else { - throw new RepositoryException("createFromDefinition not implemented for: " + name); - } - } - //--------------------------------------------------------< Inner Class >--- /** * QValue implementation for all valid PropertyTypes - * except for BINARY. + * except for BINARY and DATE. * @see QValueFactoryImpl.BinaryQValue */ - private static class QValueImpl implements QValue, Serializable { + private static class QValueImpl extends AbstractQValue implements Serializable { private static final QValue TRUE = new QValueImpl(Boolean.TRUE); - private static final QValue FALSE = new QValueImpl(Boolean.FALSE); - private final Object val; - private final int type; private QValueImpl(Object value, int type) { - val = value; - this.type = type; + super(value, type); } private QValueImpl(String value, int type) { - if (!(type == PropertyType.STRING || type == PropertyType.REFERENCE)) { - throw new IllegalArgumentException(); + super(value, type); - } + } - val = value; - this.type = type; - } private QValueImpl(Long value) { - val = value; - type = PropertyType.LONG; + super(value); } private QValueImpl(Double value) { - val = value; - type = PropertyType.DOUBLE; + super(value); } private QValueImpl(Boolean value) { - val = value; - type = PropertyType.BOOLEAN; + super(value); } private QValueImpl(Name value) { - val = value; - type = PropertyType.NAME; + super(value); } private QValueImpl(Path value) { - val = value; - type = PropertyType.PATH; + super(value); } //---------------------------------------------------------< QValue >--- /** - * @see QValue#getType() - */ - public int getType() { - return type; - } - - /** - * @see QValue#getLength() - */ - public long getLength() throws RepositoryException { - return getString().length(); - } - - /** * @see QValue#getString() */ public String getString() throws RepositoryException { @@ -306,158 +245,13 @@ public InputStream getStream() throws RepositoryException { try { // convert via string - return new ByteArrayInputStream(getString().getBytes(QValueFactoryImpl.DEFAULT_ENCODING)); + return new ByteArrayInputStream(getString().getBytes(DEFAULT_ENCODING)); } catch (UnsupportedEncodingException e) { throw new RepositoryException(QValueFactoryImpl.DEFAULT_ENCODING + " is not supported encoding on this platform", e); } - } + } - - /** - * @see QValue#getName() - */ - public Name getName() throws RepositoryException { - if (type == PropertyType.NAME) { - return (Name) val; - } else { - try { - return NAME_FACTORY.create(getString()); - } catch (IllegalArgumentException e) { - throw new ValueFormatException("not a valid Name value: " + getString(), e); - } + } - } - } - /** - * @see QValue#getCalendar() - */ - public Calendar getCalendar() throws RepositoryException { - if (type == PropertyType.DATE) { - return (Calendar) ((Calendar) val).clone(); - } else if (type == PropertyType.DOUBLE) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")); - cal.setTimeInMillis(((Double) val).longValue()); - return cal; - } else if (type == PropertyType.LONG) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")); - cal.setTimeInMillis(((Long) val).longValue()); - return cal; - } else { - Calendar cal = ISO8601.parse(getString()); - if (cal == null) { - throw new ValueFormatException("not a date string: " + getString()); - } - else { - return cal; - } - } - } - - /** - * @see QValue#getDouble() - */ - public double getDouble() throws RepositoryException { - if (type == PropertyType.DOUBLE) { - return ((Double) val).doubleValue(); - } else if (type == PropertyType.DATE) { - return ((Calendar) val).getTimeInMillis(); - } else { - try { - return Double.parseDouble(getString()); - } catch (NumberFormatException ex) { - throw new ValueFormatException("not a double: " + getString(), ex); - } - } - } - - /** - * @see QValue#getLong() - */ - public long getLong() throws RepositoryException { - if (type == PropertyType.LONG) { - return ((Long) val).longValue(); - } else if (type == PropertyType.DOUBLE) { - return ((Double) val).longValue(); - } else if (type == PropertyType.DATE) { - return ((Calendar) val).getTimeInMillis(); - } else { - try { - return Long.parseLong(getString()); - } catch (NumberFormatException ex) { - throw new ValueFormatException("not a long: " + getString(), ex); - } - } - } - - /** - * @throws RepositoryException - * @see QValue#getBoolean() - */ - public boolean getBoolean() throws RepositoryException { - if (type == PropertyType.BOOLEAN) { - return ((Boolean) val).booleanValue(); - } else { - return Boolean.valueOf(getString()).booleanValue(); - } - } - - /** - * @see QValue#getPath() - */ - public Path getPath() throws RepositoryException { - if (type == PropertyType.PATH) { - return (Path) val; - } else { - try { - return PATH_FACTORY.create(getString()); - } catch (IllegalArgumentException e) { - throw new ValueFormatException("not a valid Path: " + getString(), e); - } - } - } - - /** - * @see QValue#discard() - */ - public void discard() { - // nothing to do - } - - //---------------------------------------------------------< Object >--- - /** - * Returns the string representation of this internal value. - * - * @return string representation of this internal value - */ - public String toString() { - return val.toString(); - } - - /** - * - * @param obj - * @see Object#equals(Object) - */ - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof QValueImpl) { - QValueImpl other = (QValueImpl) obj; - return type == other.type && val.equals(other.val); - } - return false; - } - - /** - * @return the hashCode of the internal value object. - * @see Object#hashCode() - */ - public int hashCode() { - return val.hashCode(); - } - - } - //--------------------------------------------------------< Inner Class >--- /** * Extension for values of type {@link PropertyType#DATE}. @@ -494,7 +288,14 @@ if (obj instanceof DateQValue) { DateQValue other = (DateQValue) obj; return formattedStr.equals(other.formattedStr); + } else if (obj instanceof QValue) { + QValue other = (QValue) obj; + try { + return PropertyType.DATE == other.getType() && formattedStr.equals(other.getString()); + } catch (RepositoryException e) { + // should never get here -> ignore. - } + } + } return false; } Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/PathFactoryLogger.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/PathFactoryLogger.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/PathFactoryLogger.java Wed Apr 15 17:30:24 CEST 2009 @@ -117,6 +117,13 @@ }}, "createElement(Name)", new Object[]{name, new Integer(index)}); } + public Element createElement(final String identifier) throws IllegalArgumentException { + return (Element) execute(new SafeCallable() { + public Object call() { + return pathFactory.createElement(identifier); + }}, "createElement(String)", new Object[]{identifier}); + } + public Element getCurrentElement() { return (Element) execute(new SafeCallable() { public Object call() { Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/IdentifierResolver.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/IdentifierResolver.java Wed Apr 15 17:30:24 CEST 2009 +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/IdentifierResolver.java Wed Apr 15 17:30:24 CEST 2009 @@ -0,0 +1,29 @@ +/* + * 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.conversion; + +import org.apache.jackrabbit.spi.Path; + +/** + * IdentifierResolver .... + */ +public interface IdentifierResolver { + + public Path getPath(String identifier) throws MalformedPathException; + + public void checkFormat(String identifier) throws MalformedPathException; +} Index: jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java =================================================================== --- jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java (revision 772129) +++ jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java Wed Apr 15 17:30:24 CEST 2009 @@ -152,6 +152,16 @@ public Path.Element createElement(Name name, int index) throws IllegalArgumentException; /** + * Creates a path element from the given identifier. + * + * @param identifier Node identifier for which the path element should be created. + * @return a path element. + * @throws IllegalArgumentException If the identifier is null. + * @since JCR 2.0 + */ + public Path.Element createElement(String identifier) throws IllegalArgumentException; + + /** * Return the current element. * * @return the current element. Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathBuilder.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathBuilder.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/PathBuilder.java Wed Apr 15 17:30:24 CEST 2009 @@ -46,16 +46,6 @@ private final LinkedList queue; /** - * flag indicating if the current path is normalized - */ - boolean isNormalized = true; - - /** - * flag indicating if the current path has leading parent '..' elements - */ - boolean leadingParent = true; - - /** * Creates a new PathBuilder to create a Path using the * {@link PathFactoryImpl default PathFactory}. See * {@link PathBuilder#PathBuilder(PathFactory)} for a constructor explicitely @@ -121,13 +111,6 @@ * @param elem */ public void addFirst(Path.Element elem) { - if (queue.isEmpty()) { - isNormalized &= !elem.denotesCurrent(); - leadingParent = elem.denotesParent(); - } else { - isNormalized &= !elem.denotesCurrent() && (!leadingParent || elem.denotesParent()); - leadingParent |= elem.denotesParent(); - } queue.addFirst(elem); } @@ -157,8 +140,6 @@ */ public void addLast(Path.Element elem) { queue.addLast(elem); - leadingParent &= elem.denotesParent(); - isNormalized &= !elem.denotesCurrent() && (leadingParent || !elem.denotesParent()); } /** Index: jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueValue.java =================================================================== --- jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueValue.java (revision 772129) +++ jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueValue.java Tue May 05 10:34:57 CEST 2009 @@ -176,9 +176,9 @@ */ public boolean equals(Object obj) { if (obj instanceof QValueValue) { + QValue qv = ((QValueValue) obj).qvalue; return qvalue.equals(((QValueValue) obj).qvalue); - } - else { + } else { return false; } } Index: jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java =================================================================== --- jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java (revision 772129) +++ jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java Wed Apr 15 17:30:24 CEST 2009 @@ -138,8 +138,17 @@ public boolean denotesRoot(); /** - * Tests whether this path is absolute, i.e. whether it starts with "/". + * Test if this path represents an unresolved identifier-based path. * + * @return true if this path represents an unresolved + * identifier-based path. + */ + public boolean denotesIdentifier(); + + /** + * Tests whether this path is absolute, i.e. whether it starts with "/" or + * is an identifier based path. + * * @return true if this path is absolute; false otherwise. */ public boolean isAbsolute(); @@ -229,8 +238,10 @@ * that there is no ancestor of the specified degree. In case of this * being an absolute path, this would be the case if degree is * greater that the {@link #getDepth() depth} of this path. + * @throws RepositoryException If the implementation is not able to determine + * the ancestor of the specified degree for some other reason. */ - public Path getAncestor(int degree) throws IllegalArgumentException, PathNotFoundException; + public Path getAncestor(int degree) throws IllegalArgumentException, PathNotFoundException, RepositoryException; /** * Returns the number of ancestors of this path. This is the equivalent @@ -273,10 +284,11 @@ * canonical, e.g. the depth of "../../a" is -1. * * @return the depth this path + * @throws RepositoryException If the depths cannot be determined. * @see #getLength() * @see #getAncestorCount() */ - public int getDepth(); + public int getDepth() throws RepositoryException; /** * Determines if the the other path would be equal to this @@ -447,6 +459,14 @@ public boolean denotesName(); /** + * Returns true if this element represents an identifier element. + * + * @return true if this element represents an identifier element. + * @since JCR 2.0 + */ + public boolean denotesIdentifier(); + + /** * Return the String presentation of a {@link Path.Element}. It must be * in the format "{namespaceURI}localPart" or * "{namespaceURI}localPart[index]" in case of an index