You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2006/07/12 15:33:27 UTC

svn commit: r421270 [11/23] - in /jackrabbit/trunk/contrib/spi: ./ commons/ commons/src/ commons/src/main/ commons/src/main/java/ commons/src/main/java/org/ commons/src/main/java/org/apache/ commons/src/main/java/org/apache/jackrabbit/ commons/src/main...

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,884 @@
+/*
+ * 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.jcr2spi.nodetype;
+
+import org.apache.jackrabbit.name.IllegalNameException;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+import org.apache.jackrabbit.name.UnknownPrefixException;
+import org.apache.jackrabbit.name.NameException;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.value.DateValue;
+import org.apache.jackrabbit.value.QValue;
+import org.apache.jackrabbit.util.ISO8601;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * <code>ValueConstraint</code> and its subclasses are used to check the
+ * syntax of a value constraint and to test if a specific value satisfies
+ * it.
+ */
+public abstract class ValueConstraint {
+
+    protected static Logger log = LoggerFactory.getLogger(ValueConstraint.class);
+
+    public static final ValueConstraint[] EMPTY_ARRAY = new ValueConstraint[0];
+
+    final String qualifiedDefinition;
+
+    protected ValueConstraint(String qualifiedDefinition) {
+        this.qualifiedDefinition = qualifiedDefinition;
+    }
+
+    /**
+     * For constraints that are not namespace prefix mapping sensitive this
+     * method returns the same result as <code>{@link #getQualifiedDefinition()}</code>.
+     * <p/>
+     * Those that are namespace prefix mapping sensitive (e.g.
+     * <code>NameConstraint</code>, <code>PathConstraint</code> and
+     * <code>ReferenceConstraint</code>) use the given <code>nsResolver</code>
+     * to reflect the current mapping in the returned value.
+     *
+     * @return the definition of this constraint.
+     * @see #getQualifiedDefinition()
+     */
+    public String getDefinition(NamespaceResolver nsResolver) {
+        return qualifiedDefinition;
+    }
+
+    // DIFF JACKRABBIT: added method
+    /**
+     * @return the qualified definition String
+     * @see #getDefinition(NamespaceResolver)
+     */
+    public String getQualifiedDefinition() {
+        return qualifiedDefinition;
+    }
+
+    /**
+     *
+     * @param value
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    abstract void check(QValue value) throws ConstraintViolationException, RepositoryException;
+
+
+    //-------------------------------------------< java.lang.Object overrides >
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        } else if (other instanceof ValueConstraint) {
+            return qualifiedDefinition.equals(((ValueConstraint) other).qualifiedDefinition);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the hashCode of the definition String
+     *
+     * @return the hashCode of the definition String
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        return qualifiedDefinition.hashCode();
+    }
+
+    //--------------------------------------------------------------------------
+    // DIFF JACKRABBIT: method added
+    /**
+     * Create a new <code>ValueConstraint</code> from the String representation.
+     * Note, that the definition must be in the qualified format in case the type
+     * indicates {@link PropertyType#NAME}, {@link PropertyType#PATH} or {@link PropertyType#REFERENCE}
+     *
+     * @param type
+     * @param qualifiedDefinition
+     * @return
+     * @throws InvalidConstraintException
+     */
+    public static ValueConstraint create(int type, String qualifiedDefinition)
+        throws InvalidConstraintException {
+        if (qualifiedDefinition == null) {
+            throw new IllegalArgumentException("illegal definition (null)");
+        }
+        switch (type) {
+            // constraints which are not qName senstive
+            case PropertyType.STRING:
+                return new StringConstraint(qualifiedDefinition);
+
+            case PropertyType.BOOLEAN:
+                return new BooleanConstraint(qualifiedDefinition);
+
+            case PropertyType.BINARY:
+                return new NumericConstraint(qualifiedDefinition);
+
+            case PropertyType.DATE:
+                return new DateConstraint(qualifiedDefinition);
+
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return new NumericConstraint(qualifiedDefinition);
+
+            // qName sensitive constraints: create from qualified string
+            case PropertyType.NAME:
+                return new NameConstraint(qualifiedDefinition);
+
+            case PropertyType.PATH:
+                return new PathConstraint(qualifiedDefinition);
+
+            case PropertyType.REFERENCE:
+                return new ReferenceConstraint(qualifiedDefinition);
+
+            default:
+                throw new IllegalArgumentException("unknown/unsupported target type for constraint: "
+                        + PropertyType.nameFromValue(type));
+        }
+    }
+
+    /**
+     * 
+     * @param type
+     * @param definition
+     * @param nsResolver
+     * @return
+     * @throws InvalidConstraintException
+     */
+    public static ValueConstraint create(int type, String definition,
+                                         NamespaceResolver nsResolver)
+            throws InvalidConstraintException {
+        if (definition == null) {
+            throw new IllegalArgumentException("illegal definition (null)");
+        }
+        switch (type) {
+            case PropertyType.STRING:
+                return new StringConstraint(definition);
+
+            case PropertyType.BOOLEAN:
+                return new BooleanConstraint(definition);
+
+            case PropertyType.BINARY:
+                return new NumericConstraint(definition);
+
+            case PropertyType.DATE:
+                return new DateConstraint(definition);
+
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return new NumericConstraint(definition);
+
+            case PropertyType.NAME:
+                return new NameConstraint(definition, nsResolver);
+
+            case PropertyType.PATH:
+                return new PathConstraint(definition, nsResolver);
+
+            case PropertyType.REFERENCE:
+                return new ReferenceConstraint(definition, nsResolver);
+
+            default:
+                throw new IllegalArgumentException("unknown/unsupported target type for constraint: "
+                        + PropertyType.nameFromValue(type));
+        }
+    }
+
+    // DIFF JACKRABBIT: moved from EffectiveNodeType
+    /**
+     * Tests if the value constraints defined in the property definition
+     * <code>pd</code> are satisfied by the the specified <code>values</code>.
+     * <p/>
+     * Note that the <i>protected</i> flag is not checked. Also note that no
+     * type conversions are attempted if the type of the given values does not
+     * match the required type as specified in the given definition.
+     *
+     * @param pd
+     * @param values
+     * @throws ConstraintViolationException
+     */
+    public static void checkValueConstraints(QPropertyDefinition pd, QValue[] values)
+            throws ConstraintViolationException, RepositoryException {
+        // check multi-value flag
+        if (!pd.isMultiple() && values != null && values.length > 1) {
+            throw new ConstraintViolationException("the property is not multi-valued");
+        }
+
+        String[] constraints = pd.getValueConstraints();
+        if (constraints == null || constraints.length == 0) {
+            // no constraints to check
+            return;
+        }
+        if (values != null && values.length > 0) {
+            // check value constraints on every value
+            for (int i = 0; i < values.length; i++) {
+                // constraints are OR-ed together
+                boolean satisfied = false;
+                ConstraintViolationException cve = null;
+                for (int j = 0; j < constraints.length; j++) {
+                    try {
+                        ValueConstraint cnstr = ValueConstraint.create(pd.getRequiredType(), constraints[j]);
+                        cnstr.check(values[i]);
+                        satisfied = true;
+                        break;
+                    } catch (ConstraintViolationException e) {
+                        cve = e;
+                        continue;
+                    } catch (InvalidConstraintException e) {
+                        cve = new ConstraintViolationException(e.getMessage(), e);
+                        continue;
+                    }
+                }
+                if (!satisfied) {
+                    // re-throw last exception we encountered
+                    throw cve;
+                }
+            }
+        }
+    }
+}
+
+/**
+ * <code>BooleanConstraint</code> ...
+ */
+class BooleanConstraint extends ValueConstraint {
+    final boolean reqBool;
+
+    BooleanConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // constraint format: 'true' or 'false'
+        if (definition.equals("true")) {
+            reqBool = true;
+        } else if (definition.equals("false")) {
+            reqBool = false;
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for BOOLEAN values";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    private void check(boolean bool) throws ConstraintViolationException {
+        if (bool != reqBool) {
+            throw new ConstraintViolationException("'" + bool + "' does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"  + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.BOOLEAN:
+                check(Boolean.valueOf(value.getString()).booleanValue());
+                return;
+
+            default:
+                String msg = "BOOLEAN constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>StringConstraint</code> ...
+ */
+class StringConstraint extends ValueConstraint {
+    final Pattern pattern;
+
+    StringConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // constraint format: regexp
+        try {
+            pattern = Pattern.compile(definition);
+        } catch (PatternSyntaxException pse) {
+            String msg = "'" + definition + "' is not valid regular expression syntax";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, pse);
+        }
+    }
+
+    private void check(String text) throws ConstraintViolationException {
+        if (text == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        Matcher matcher = pattern.matcher(text);
+        if (!matcher.matches()) {
+            throw new ConstraintViolationException("'" + text  + "' does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.STRING:
+                check(value.toString());
+                return;
+
+            default:
+                String msg = "STRING constraint can not be applied to value of type: " + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>NumericConstraint</code> ...
+ */
+class NumericConstraint extends ValueConstraint {
+    final boolean lowerInclusive;
+    final Double lowerLimit;
+    final boolean upperInclusive;
+    final Double upperLimit;
+
+    NumericConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // format: '(<min>, <max>)',  '[<min>, <max>]', '(, <max>)' etc.
+        Pattern pattern = Pattern.compile("([\\(\\[]) *(\\-?\\d+\\.?\\d*)? *, *(\\-?\\d+\\.?\\d*)? *([\\)\\]])");
+        Matcher matcher = pattern.matcher(definition);
+        if (matcher.matches()) {
+            try {
+                // group 1 is lower inclusive/exclusive
+                String s = matcher.group(1);
+                lowerInclusive = s.equals("[");
+                // group 2 is lower limit
+                s = matcher.group(2);
+                if (s == null || s.length() == 0) {
+                    lowerLimit = null;
+                } else {
+                    lowerLimit = Double.valueOf(matcher.group(2));
+                }
+                // group 3 is upper limit
+                s = matcher.group(3);
+                if (s == null || s.length() == 0) {
+                    upperLimit = null;
+                } else {
+                    upperLimit = Double.valueOf(matcher.group(3));
+                }
+                // group 4 is lower inclusive/exclusive
+                s = matcher.group(4);
+                upperInclusive = s.equals("]");
+                if (lowerLimit == null && upperLimit == null) {
+                    String msg = "'" + definition + "' is not a valid value constraint"
+                            + " format for numeric types: neither lower- nor upper-limit specified";
+                    log.debug(msg);
+                    throw new InvalidConstraintException(msg);
+                }
+                if (lowerLimit != null && upperLimit != null) {
+                    if (lowerLimit.doubleValue() > upperLimit.doubleValue()) {
+                        String msg = "'" + definition
+                                + "' is not a valid value constraint format for numeric types: lower-limit exceeds upper-limit";
+                        log.debug(msg);
+                        throw new InvalidConstraintException(msg);
+                    }
+                }
+            } catch (NumberFormatException nfe) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for numeric types";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, nfe);
+            }
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for numeric values";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    private void check(Double number) throws ConstraintViolationException {
+        if (number == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"
+                    + qualifiedDefinition + "'");
+        }
+        check(number.doubleValue());
+    }
+
+    private void check(Long number) throws ConstraintViolationException {
+        if (number == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"
+                    + qualifiedDefinition + "'");
+        }
+        check(number.doubleValue());
+    }
+
+    private void check(double number) throws ConstraintViolationException {
+        if (lowerLimit != null) {
+            if (lowerInclusive) {
+                if (number < lowerLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            } else {
+                if (number <= lowerLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            }
+        }
+        if (upperLimit != null) {
+            if (upperInclusive) {
+                if (number > upperLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            } else {
+                if (number >= upperLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            }
+        }
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"
+                    + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.LONG:
+                check(Long.parseLong(value.getString()));
+                return;
+
+            case PropertyType.DOUBLE:
+                check(Double.parseDouble(value.getString()));
+                return;
+
+            case PropertyType.BINARY:
+                long length = value.getLength();
+                if (length != -1) {
+                    check(length);
+                } else {
+                    log.warn("failed to determine length of binary value");
+                }
+                return;
+
+            default:
+                String msg = "numeric constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>DateConstraint</code> ...
+ */
+class DateConstraint extends ValueConstraint {
+    final boolean lowerInclusive;
+    final Calendar lowerLimit;
+    final boolean upperInclusive;
+    final Calendar upperLimit;
+
+    DateConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // format: '(<fromDate>, <toDate>)', '[<fromDate>, <toDate>]', '[, <toDate>]' etc.
+        Pattern pattern = Pattern.compile("([\\(\\[]) *([0-9TZ\\.\\+-:]*)? *, *([0-9TZ\\.\\+-:]*)? *([\\)\\]])");
+        Matcher matcher = pattern.matcher(definition);
+        if (matcher.matches()) {
+            try {
+                // group 1 is lower inclusive/exclusive
+                String s = matcher.group(1);
+                lowerInclusive = s.equals("[");
+                // group 2 is lower limit
+                s = matcher.group(2);
+                if (s == null || s.length() == 0) {
+                    lowerLimit = null;
+                } else {
+                    lowerLimit = DateValue.valueOf(matcher.group(2)).getDate();
+                }
+                // group 3 is upper limit
+                s = matcher.group(3);
+                if (s == null || s.length() == 0) {
+                    upperLimit = null;
+                } else {
+                    upperLimit = DateValue.valueOf(matcher.group(3)).getDate();
+                }
+                // group 4 is upepr inclusive/exclusive
+                s = matcher.group(4);
+                upperInclusive = s.equals("]");
+
+                if (lowerLimit == null && upperLimit == null) {
+                    String msg = "'" + definition
+                            + "' is not a valid value constraint format for dates: neither min- nor max-date specified";
+                    log.debug(msg);
+                    throw new InvalidConstraintException(msg);
+                }
+                if (lowerLimit != null && upperLimit != null) {
+                    if (lowerLimit.after(upperLimit)) {
+                        String msg = "'" + definition
+                                + "' is not a valid value constraint format for dates: min-date > max-date";
+                        log.debug(msg);
+                        throw new InvalidConstraintException(msg);
+                    }
+                }
+            } catch (ValueFormatException vfe) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for dates";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, vfe);
+            } catch (RepositoryException re) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for dates";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, re);
+            }
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for dates";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    private void check(Calendar cal) throws ConstraintViolationException {
+        if (cal == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        if (lowerLimit != null) {
+            if (lowerInclusive) {
+                if (cal.getTimeInMillis() < lowerLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            } else {
+                if (cal.getTimeInMillis() <= lowerLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            }
+        }
+        if (upperLimit != null) {
+            if (upperInclusive) {
+                if (cal.getTimeInMillis() > upperLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            } else {
+                if (cal.getTimeInMillis() >= upperLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+            }
+        }
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.DATE:
+                check(ISO8601.parse(value.getString()));
+                return;
+
+            default:
+                String msg = "DATE constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>PathConstraint</code> ...
+ */
+class PathConstraint extends ValueConstraint {
+    final Path path;
+    final boolean deep;
+
+    PathConstraint(String qualifiedDefinition) {
+        super(qualifiedDefinition);
+        // constraint format: qualified absolute or relative path with optional trailing wildcard
+        deep = qualifiedDefinition.endsWith("*");
+        path = Path.valueOf(qualifiedDefinition);
+    }
+
+    PathConstraint(String definition, NamespaceResolver nsResolver)
+            throws InvalidConstraintException {
+        super(definition);
+
+        // constraint format: absolute or relative path with optional trailing wildcard
+        deep = definition.endsWith("*");
+        if (deep) {
+            // trim trailing wildcard before building path
+            definition = definition.substring(0, definition.length() - 1);
+        }
+        try {
+            path = nsResolver.getQPath(definition);
+        } catch (MalformedPathException mpe) {
+            String msg = "Invalid path expression specified as value constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, mpe);
+        }
+    }
+
+    public String getDefinition(NamespaceResolver nsResolver) {
+        try {
+            String p = nsResolver.getJCRPath(path);
+            if (!deep) {
+                return p;
+            } else if (path.denotesRoot()) {
+                return p + "*";
+            } else {
+                return p + "/*";
+            }
+        } catch (NoPrefixDeclaredException npde) {
+            // should never get here, return raw definition as fallback
+            return qualifiedDefinition;
+        }
+    }
+
+    /**
+     * Returns the String representation of the path.
+     *
+     * @return String representation of the path.
+     */
+    public String getQualifiedDefinition() {
+        return path.toString();
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.PATH:
+                Path p = Path.valueOf(value.getString());
+                // normalize paths before comparing them
+                Path p0, p1;
+                try {
+                    p0 = path.getNormalizedPath();
+                    p1 = p.getNormalizedPath();
+                } catch (MalformedPathException e) {
+                    throw new ConstraintViolationException("path not valid: " + e);
+                }
+                if (deep) {
+                    try {
+                        if (!p0.isAncestorOf(p1)) {
+                            throw new ConstraintViolationException(p
+                                + " does not satisfy the constraint '"
+                                + qualifiedDefinition + "'");
+                        }
+                    } catch (MalformedPathException e) {
+                        // can't compare relative with absolute path
+                        throw new ConstraintViolationException(p
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                    }
+                } else {
+                    // exact match required
+                    if (!p0.equals(p1)) {
+                        throw new ConstraintViolationException(p
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                    }
+                }
+                return;
+
+            default:
+                String msg = "PATH constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>NameConstraint</code> ...
+ */
+class NameConstraint extends ValueConstraint {
+    final QName name;
+
+    NameConstraint(String qualifiedDefinition) {
+        super(qualifiedDefinition);
+        // constraint format: String representation of qualified name
+        name = QName.valueOf(qualifiedDefinition);
+    }
+
+    NameConstraint(String definition, NamespaceResolver nsResolver)
+            throws InvalidConstraintException {
+        super(definition);
+        // constraint format: JCR name in prefix form
+        try {
+            NameFormat.checkFormat(definition);
+            name = nsResolver.getQName(definition);
+        } catch (IllegalNameException ine) {
+            String msg = "invalid name specified as value constraint: "
+                    + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, ine);
+        } catch (NameException upe) {
+            String msg = "invalid name specified as value constraint: "
+                    + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, upe);
+        }
+    }
+
+    public String getDefinition(NamespaceResolver nsResolver) {
+        try {
+            return nsResolver.getJCRName(name);
+        } catch (NoPrefixDeclaredException npde) {
+            // should never get here, return raw definition as fallback
+            return qualifiedDefinition;
+        }
+    }
+
+    /**
+     * Returns the String representation of the qualified name
+     *
+     * @return String representation of the qualified name
+     */
+    public String getQualifiedDefinition() {
+        return name.toString();
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.NAME:
+                QName n = QName.valueOf(value.getString());
+                if (!name.equals(n)) {
+                    throw new ConstraintViolationException(n
+                            + " does not satisfy the constraint '"
+                            + qualifiedDefinition + "'");
+                }
+                return;
+
+            default:
+                String msg = "NAME constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+/**
+ * <code>ReferenceConstraint</code> ...
+ */
+class ReferenceConstraint extends ValueConstraint {
+    final QName ntName;
+
+    ReferenceConstraint(String qualifiedDefinition) {
+        super(qualifiedDefinition);
+        // format: qualified node type name
+        ntName = QName.valueOf(qualifiedDefinition);
+    }
+
+    ReferenceConstraint(String definition, NamespaceResolver nsResolver) throws InvalidConstraintException {
+        super(definition);
+
+        // format: node type name
+        try {
+            ntName = nsResolver.getQName(definition);
+        } catch (IllegalNameException ine) {
+            String msg = "invalid node type name specified as value constraint: "
+                    + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, ine);
+        } catch (UnknownPrefixException upe) {
+            String msg = "invalid node type name specified as value constraint: "
+                    + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, upe);
+        }
+    }
+
+    public String getDefinition(NamespaceResolver nsResolver) {
+        try {
+            return nsResolver.getJCRName(ntName);
+        } catch (NoPrefixDeclaredException npde) {
+            // should never get here, return raw definition as fallback
+            return qualifiedDefinition;
+        }
+    }
+
+    /**
+     * Returns the String representation of the qualified nodetype name.
+     *
+     * @return String representation of the qualified nodetype name.
+     */
+    public String getQualifiedDefinition() {
+        return ntName.toString();
+    }
+
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + qualifiedDefinition + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.REFERENCE:
+                // @todo check REFERENCE value constraint (requires a session)
+                /*
+                UUID targetUUID = (UUID) value.internalValue();
+                NodeImpl targetNode = (NodeImpl) session.getNodeByUUID(targetUUID.toString());
+                if (!targetNode.isNodeType(ntName)) {
+                    throw new ConstraintViolationException("the node with uuid "
+                            + targetUUID + " does not satisfy the constraint '" + definition + "'");
+                }
+                */
+                log.warn("validation of REFERENCE constraint is not yet implemented");
+                return;
+
+            default:
+                String msg = "REFERENCE constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,246 @@
+/*
+ * 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.jcr2spi.observation;
+
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.Event;
+import org.apache.jackrabbit.name.Path;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * The <code>EventFilter</code> class implements the filter logic based
+ * on the session's access rights and the specified filter rules.
+ */
+class EventFilter {
+
+    /**
+     * Logger instance for this class.
+     */
+    private static final Logger log = LoggerFactory.getLogger(EventFilter.class);
+
+    static final EventFilter BLOCK_ALL = new BlockAllFilter();
+
+    /**
+     * The namespaceResolver of the session this EventFilter belongs to.
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * The node type registry.
+     */
+    private final NodeTypeRegistry ntReg;
+
+    /**
+     * This <code>EventFilter</code> should only allow events with the
+     * specified types.
+     */
+    private final long eventTypes;
+
+    /**
+     * Only allow Items with the specified <code>path</code>
+     */
+    private final Path path;
+
+    /**
+     * If <code>isDeep</code> is <code>true</code> also Items under <code>absPath</code>
+     * are allowed.
+     */
+    private final boolean isDeep;
+
+    /**
+     * Only allow Nodes with the specified <code>uuids</code>.
+     */
+    private final String[] uuids;
+
+    /**
+     * Only allow Nodes with the specified node type name.
+     */
+    private final QName[] nodeTypes;
+
+    /**
+     * If <code>noLocal</code> is true this filter will block events from
+     * the session that registerd this filter.
+     */
+    private final boolean noLocal;
+
+    /**
+     * Creates a new <code>EventFilter</code> instance.
+     *
+     * @param nsResolver <code>NamespaceResolver</code> attached to the
+     *                   <code>Session</code> that registered the {@link
+     *                   javax.jcr.observation.EventListener}.
+     * @param ntReg      the node type registry.
+     * @param eventTypes only allow specified {@link javax.jcr.observation.Event} types.
+     * @param path       only allow {@link javax.jcr.Item} with
+     *                   <code>path</code>.
+     * @param isDeep     if <code>true</code> also allow events for {@link
+     *                   Item}s below <code>absPath</code>.
+     * @param uuids      only allow events for {@link javax.jcr.Node}s with
+     *                   specified UUIDs. If <code>null</code> is passed no
+     *                   restriction regarding UUIDs is applied.
+     * @param nodeTypes  only allow events for specified node types named:
+     *                   nodeTypes. If <code>null</code> no node type
+     *                   restriction is applied.
+     * @param noLocal    if <code>true</code> no events are allowed that were
+     *                   created from changes related to the <code>Session</code>
+     *                   that registered the {@link javax.jcr.observation.EventListener}.
+     */
+    EventFilter(NamespaceResolver nsResolver,
+                NodeTypeRegistry ntReg,
+                long eventTypes,
+                Path path,
+                boolean isDeep,
+                String[] uuids,
+                QName[] nodeTypes,
+                boolean noLocal) {
+
+        this.nsResolver = nsResolver;
+        this.ntReg = ntReg;
+        this.eventTypes = eventTypes;
+        this.path = path;
+        this.isDeep = isDeep;
+        this.uuids = uuids;
+        this.noLocal = noLocal;
+        this.nodeTypes = nodeTypes;
+    }
+
+    /**
+     * Returns the <code>NamespaceResolver</code> of the <code>Session</code>
+     * associated with this <code>EventFilter</code>.
+     *
+     * @return the <code>Session</code> associated with this
+     *         <code>EventFilter</code>.
+     */
+    NamespaceResolver getNamespaceResolver() {
+        return nsResolver;
+    }
+
+    /**
+     * Returns <code>true</code> if this <code>EventFilter</code> does not allow
+     * the specified <code>Event</code>; <code>false</code> otherwise.
+     *
+     * @param event the <code>EventState</code> in question.
+     * @param isLocal <code>true</code> if <code>event</code> is local.
+     * @return <code>true</code> if this <code>EventFilter</code> blocks the
+     *         <code>EventState</code>.
+     * @throws RepositoryException if an error occurs while checking.
+     */
+    boolean blocks(Event event, boolean isLocal) throws RepositoryException {
+        // first do cheap checks
+
+        if (isLocal && noLocal) {
+            return true;
+        }
+
+        // check event type
+        long type = event.getType();
+        if ((eventTypes & type) == 0) {
+            return true;
+        }
+
+        // check UUIDs
+        String uuid = event.getUUID();
+        if (uuids != null) {
+            boolean match = false;
+            for (int i = 0; i < uuids.length && !match && uuid != null; i++) {
+                match = uuids[i].equals(uuid);
+            }
+            if (!match) {
+                return true;
+            }
+        }
+
+        // check node types
+        if (nodeTypes != null) {
+            boolean match = false;
+            QName[] mixinNames = event.getMixinTypeNames();
+            QName[] typeNames = new QName[mixinNames.length + 1];
+            System.arraycopy(mixinNames, 0, typeNames, 0, mixinNames.length);
+            typeNames[typeNames.length - 1] = event.getPrimaryNodeTypeName();
+            EffectiveNodeType effectiveNt;
+            try {
+                effectiveNt = ntReg.getEffectiveNodeType(typeNames);
+            } catch (NodeTypeConflictException e) {
+                log.error("Internal error: conflicting node types", e);
+                // block this weird node
+                return true;
+            }
+            for (int i = 0; i < nodeTypes.length && !match; i++) {
+                match = effectiveNt.includesNodeType(nodeTypes[i]);
+            }
+            if (!match) {
+                return true;
+            }
+        }
+
+        // finally check path
+        // the relevant path for the path filter depends on the event type
+        // for node events, the relevant path is the one returned by
+        // Event.getPath().
+        // for property events, the relevant path is the path of the
+        // node where the property belongs to.
+        Path eventPath;
+        if (type == Event.NODE_ADDED || type == Event.NODE_REMOVED) {
+            eventPath = event.getQPath();
+        } else {
+            eventPath = event.getQPath().getAncestor(1);
+        }
+        boolean match = eventPath.equals(path);
+        if (!match && isDeep) {
+            try {
+                match = eventPath.isDescendantOf(path);
+            } catch (org.apache.jackrabbit.name.MalformedPathException e) {
+                e.printStackTrace();
+            }
+        }
+
+        return !match;
+    }
+
+
+
+    /**
+     * This class implements an <code>EventFilter</code> that blocks
+     * all {@link Event}s.
+     */
+    private static final class BlockAllFilter extends EventFilter {
+
+        /**
+         * Creates a new <code>BlockAllFilter</code>.
+         */
+        BlockAllFilter() {
+            super(null, null, 0, null, true, null, null, true);
+        }
+
+        /**
+         * Always return <code>true</code>.
+         *
+         * @return always <code>true</code>.
+         */
+        boolean blocks(Event event, boolean isLocal) {
+            return true;
+        }
+    }
+
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventFilter.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,140 @@
+/*
+ * 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.jcr2spi.observation;
+
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+import org.apache.jackrabbit.name.PathFormat;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+
+/**
+ * Implementation of the {@link javax.jcr.observation.Event} interface.
+ */
+final class EventImpl implements Event {
+
+    /**
+     * Logger instance for this class
+     */
+    private static final Logger log = LoggerFactory.getLogger(EventImpl.class);
+
+    /**
+     * The session of the {@link javax.jcr.observation.EventListener} this
+     * event will be delivered to.
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * The underlying SPI event.
+     */
+    private final org.apache.jackrabbit.spi.Event event;
+
+    /**
+     * Cached String value of this <code>Event</code> instance.
+     */
+    private String stringValue;
+
+    /**
+     * Creates a new {@link javax.jcr.observation.Event} instance based on an
+     * {@link org.apache.jackrabbit.spi.Event SPI Event}.
+     *
+     * @param nsResolver <code>NamespaceResolver</code> attached to the session
+     * of the registerd <code>EventListener</code>, where this <code>Event</code>
+     * will be delivered to.
+     * @param event   the underlying SPI <code>Event</code>.
+     */
+    EventImpl(NamespaceResolver nsResolver, org.apache.jackrabbit.spi.Event event) {
+        this.nsResolver = nsResolver;
+        this.event = event;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getType() {
+        return event.getType();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getPath() throws RepositoryException {
+        try {
+            return PathFormat.format(event.getQPath(), nsResolver);
+        } catch (NoPrefixDeclaredException e) {
+            String msg = "internal error: encountered unregistered namespace in path";
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getUserID() {
+        return event.getUserID();
+    }
+
+    /**
+     * Returns a String representation of this <code>Event</code>.
+     *
+     * @return a String representation of this <code>Event</code>.
+     */
+    public String toString() {
+        if (stringValue == null) {
+            StringBuffer sb = new StringBuffer();
+            sb.append("Event: Path: ");
+            try {
+                sb.append(getPath());
+            } catch (RepositoryException e) {
+                log.error("Exception retrieving path: " + e);
+                sb.append("[Error retrieving path]");
+            }
+            sb.append(", ").append(valueOf(getType())).append(": ");
+            sb.append(", UserId: ").append(getUserID());
+            stringValue = sb.toString();
+        }
+        return stringValue;
+    }
+
+    //----------------------------------< internal >----------------------------
+
+    /**
+     * Returns a String representation of <code>eventType</code>.
+     *
+     * @param eventType an event type defined by {@link Event}.
+     * @return a String representation of <code>eventType</code>.
+     */
+    private static String valueOf(int eventType) {
+        if (eventType == Event.NODE_ADDED) {
+            return "NodeAdded";
+        } else if (eventType == Event.NODE_REMOVED) {
+            return "NodeRemoved";
+        } else if (eventType == Event.PROPERTY_ADDED) {
+            return "PropertyAdded";
+        } else if (eventType == Event.PROPERTY_CHANGED) {
+            return "PropertyChanged";
+        } else if (eventType == Event.PROPERTY_REMOVED) {
+            return "PropertyRemoved";
+        } else {
+            return "UnknownEventType";
+        }
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/EventImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,163 @@
+/*
+ * 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.jcr2spi.observation;
+
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Collection;
+
+/**
+ */
+class FilteredEventIterator implements EventIterator {
+
+    /**
+     * Logger instance for this class
+     */
+    private static final Logger log = LoggerFactory.getLogger(FilteredEventIterator.class);
+
+    /**
+     * The actual {@link org.apache.jackrabbit.spi.Event}s fired by the repository service
+     * (unfiltered).
+     */
+    private final Iterator actualEvents;
+
+    /**
+     * For filtering the {@link javax.jcr.observation.Event}s.
+     */
+    private final EventFilter filter;
+
+    /**
+     * If <code>true</code> these events are local.
+     */
+    private final boolean isLocal;
+
+    /**
+     * The next {@link javax.jcr.observation.Event} in this iterator
+     */
+    private Event next;
+
+    /**
+     * Current position
+     */
+    private long pos = 0;
+
+    /**
+     * Creates a new <code>FilteredEventIterator</code>.
+     *
+     * @param c      an unmodifiable Collection of {@link org.apache.jackrabbit.spi.Event}s.
+     * @param filter only event that pass the filter will be dispatched to the
+     *               event listener.
+     * @param isLocal if <code>true</code> these are local events.
+     */
+    public FilteredEventIterator(Collection c,
+                                 EventFilter filter,
+                                 boolean isLocal) {
+        actualEvents = c.iterator();
+        this.filter = filter;
+        this.isLocal = isLocal;
+        fetchNext();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object next() {
+        if (next == null) {
+            throw new NoSuchElementException();
+        }
+        Event e = next;
+        fetchNext();
+        pos++;
+        return e;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Event nextEvent() {
+        return (Event) next();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void skip(long skipNum) {
+        while (skipNum-- > 0) {
+            next();
+        }
+    }
+
+    /**
+     * Always returns <code>-1</code>.
+     *
+     * @return <code>-1</code>.
+     */
+    public long getSize() {
+        return -1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getPosition() {
+        return pos;
+    }
+
+    /**
+     * This method is not supported.
+     * Always throws a <code>UnsupportedOperationException</code>.
+     */
+    public void remove() {
+        throw new UnsupportedOperationException("EventIterator.remove()");
+    }
+
+    /**
+     * Returns <tt>true</tt> if the iteration has more elements. (In other
+     * words, returns <tt>true</tt> if <tt>next</tt> would return an element
+     * rather than throwing an exception.)
+     *
+     * @return <tt>true</tt> if the iterator has more elements.
+     */
+    public boolean hasNext() {
+        return (next != null);
+    }
+
+    /**
+     * Fetches the next Event from the collection of events
+     * passed in the constructor of <code>FilteredEventIterator</code>
+     * that is allowed by the {@link EventFilter}.
+     */
+    private void fetchNext() {
+        org.apache.jackrabbit.spi.Event event;
+        next = null;
+        while (next == null && actualEvents.hasNext()) {
+            event = (org.apache.jackrabbit.spi.Event) actualEvents.next();
+            try {
+                next = filter.blocks(event, isLocal) ? null : new EventImpl(filter.getNamespaceResolver(), event);
+            } catch (RepositoryException e) {
+                log.error("Exception while applying filter.", e);
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/FilteredEventIterator.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,35 @@
+/*
+ * 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.jcr2spi.observation;
+
+import org.apache.jackrabbit.spi.EventIterator;
+
+/**
+ * <code>InternalEventListener</code> is similar to {@link org.apache.jackrabbit.spi.EventListener}
+ * but adds information about the location of the events. Whether they are local
+ * or external.
+ */
+public interface InternalEventListener {
+
+    /**
+     * Gets called when an event occurs.
+     *
+     * @param events the event set received.
+     * @param isLocal <code>true</code> if these are local changes.
+     */
+    public void onEvent(EventIterator events, boolean isLocal);
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/InternalEventListener.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,207 @@
+/*
+ * 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.jcr2spi.observation;
+
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.util.IteratorHelper;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.QName;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.EventListenerIterator;
+import javax.jcr.RepositoryException;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.NameException;
+import org.apache.jackrabbit.spi.EventIterator;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * <code>ObservationManagerImpl</code>...
+ */
+public class ObservationManagerImpl implements ObservationManager, InternalEventListener {
+
+    /**
+     * The logger instance for this class.
+     */
+    private static final Logger log = LoggerFactory.getLogger(ObservationManagerImpl.class);
+
+    /**
+     * The session this observation manager belongs to.
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * The <code>NodeTypeRegistry</code> of the session.
+     */
+    private final NodeTypeRegistry ntRegistry;
+
+    /**
+     * Live mapping of <code>EventListener</code> to <code>EventFilter</code>.
+     */
+    private final Map subscriptions = new HashMap();
+
+    /**
+     * A read only mapping of <code>EventListener</code> to <code>EventFilter</code>.
+     */
+    private Map readOnlySubscriptions;
+
+    /**
+     * Creates a new observation manager for <code>session</code>.
+     * @param nsResolver NamespaceResolver to be used by this observation manager
+     * is based on.
+     * @param ntRegistry The <code>NodeTypeRegistry</code> of the session.
+     */
+    public ObservationManagerImpl(NamespaceResolver nsResolver, NodeTypeRegistry ntRegistry) {
+        this.nsResolver = nsResolver;
+        this.ntRegistry = ntRegistry;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void addEventListener(EventListener listener,
+                                 int eventTypes,
+                                 String absPath,
+                                 boolean isDeep,
+                                 String[] uuid,
+                                 String[] nodeTypeName,
+                                 boolean noLocal) throws RepositoryException {
+        Path path;
+        try {
+            path = nsResolver.getQPath(absPath).getCanonicalPath();
+        } catch (MalformedPathException e) {
+            throw new RepositoryException("Malformed path: " + absPath);
+        }
+
+        // create NodeType instances from names
+        QName[] nodeTypeNames;
+        if (nodeTypeName == null) {
+            nodeTypeNames = null;
+        } else {
+            try {
+                nodeTypeNames = new QName[nodeTypeName.length];
+                for (int i = 0; i < nodeTypeName.length; i++) {
+                    QName ntName = NameFormat.parse(nodeTypeName[i], nsResolver);
+                    if (!ntRegistry.isRegistered(ntName)) {
+                        throw new RepositoryException("unknown node type: " + nodeTypeName[i]);
+                    }
+                    nodeTypeNames[i] = ntName;
+                }
+            } catch (NameException e) {
+                throw new RepositoryException(e.getMessage());
+            }
+        }
+
+        synchronized (subscriptions) {
+            EventFilter filter = new EventFilter(nsResolver, ntRegistry,
+                    eventTypes, path, isDeep, uuid, nodeTypeNames, noLocal);
+            subscriptions.put(listener, filter);
+            readOnlySubscriptions = null;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void removeEventListener(EventListener listener) throws RepositoryException {
+        synchronized (subscriptions) {
+            if (subscriptions.remove(listener) != null) {
+                readOnlySubscriptions = null;
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public EventListenerIterator getRegisteredEventListeners() throws RepositoryException {
+        Map activeListeners;
+        synchronized (subscriptions) {
+            ensureReadOnlyMap();
+            activeListeners = readOnlySubscriptions;
+        }
+        return new ListenerIterator(activeListeners.keySet());
+    }
+
+    //-----------------------< InternalEventListener >--------------------------
+
+    public void onEvent(EventIterator events, boolean isLocal) {
+        List eventList = new ArrayList();
+        while (events.hasNext()) {
+            eventList.add(events.nextEvent());
+        }
+        eventList = Collections.unmodifiableList(eventList);
+
+        // get active listeners
+        Map activeListeners;
+        synchronized (subscriptions) {
+            ensureReadOnlyMap();
+            activeListeners = readOnlySubscriptions;
+        }
+        for (Iterator it = activeListeners.keySet().iterator(); it.hasNext(); ) {
+            EventListener listener = (EventListener) it.next();
+            EventFilter filter = (EventFilter) activeListeners.get(listener);
+            FilteredEventIterator eventIter = new FilteredEventIterator(eventList, filter, isLocal);
+            if (eventIter.hasNext()) {
+                try {
+                    listener.onEvent(eventIter);
+                } catch (Throwable t) {
+                    log.warn("EventConsumer threw exception: " + t.toString());
+                    log.debug("Stacktrace: ", t);
+                    // move on to the next listener
+                }
+            }
+        }
+    }
+
+    //-------------------------< internal >-------------------------------------
+
+    /**
+     * Ensures that {@link #readOnlySubscriptions} is set. Callers of this
+     * method must own {@link #subscriptions} as a monitor to avoid concurrent
+     * access to {@link #subscriptions}.
+     */
+    private void ensureReadOnlyMap() {
+        if (readOnlySubscriptions == null) {
+            readOnlySubscriptions = new HashMap(subscriptions);
+        }
+    }
+
+    private static final class ListenerIterator extends IteratorHelper
+            implements EventListenerIterator {
+
+        public ListenerIterator(Collection c) {
+            super(c);
+        }
+
+        public EventListener nextEventListener() {
+            return (EventListener) next();
+        }
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/observation/ObservationManagerImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,91 @@
+/*
+ * 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.jcr2spi.operation;
+
+import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
+import org.apache.jackrabbit.jcr2spi.ManagerProvider;
+import org.apache.jackrabbit.spi.ItemId;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.NodeId;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>AbstractCopy</code>...
+ */
+public abstract class AbstractCopy extends AbstractOperation {
+
+    private static Logger log = LoggerFactory.getLogger(AbstractCopy.class);
+
+    private final NodeId srcId;
+    private final NodeId destParentId;
+    private final QName destName;
+    
+    private final String srcWorkspaceName;
+
+    /**
+     *
+     * @param srcPath
+     * @param destPath
+     * @param srcMgrProvider
+     */
+    AbstractCopy(Path srcPath, Path destPath, String srcWorkspaceName,
+                 ManagerProvider srcMgrProvider, ItemStateValidator validator)
+        throws RepositoryException {
+
+        ItemId srcItemId = srcMgrProvider.getHierarchyManager().getItemId(srcPath);
+        if (!srcItemId.denotesNode()) {
+            throw new PathNotFoundException("Source path " + validator.safeGetJCRPath(srcItemId) + " is not a valid path.");
+        }
+        this.srcId = (NodeId)srcItemId;
+        this.destParentId = validator.getNodeId(destPath.getAncestor(1));
+        addAffectedItemId(destParentId);
+
+        // check for illegal index present in destination path
+        Path.PathElement destElement = destPath.getNameElement();
+        int index = destElement.getIndex();
+        if (index > Path.INDEX_UNDEFINED) {
+            // subscript in name element
+            String msg = "invalid destination path (subscript in name element is not allowed)";
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        this.destName = destElement.getName();
+        this.srcWorkspaceName = srcWorkspaceName;
+    }
+
+    //----------------------------------------< Access Operation Parameters >---
+    public String getWorkspaceName() {
+        return srcWorkspaceName;
+    }
+
+    public NodeId getNodeId() {
+        return srcId;
+    }
+
+    public NodeId getDestinationParentId() {
+        return destParentId;
+    }
+
+    public QName getDestinationName() {
+        return destName;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractCopy.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,60 @@
+/*
+ * 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.jcr2spi.operation;
+
+import org.apache.jackrabbit.spi.ItemId;
+
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * <code>AbstractOperation</code>...
+ */
+public abstract class AbstractOperation implements Operation {
+
+    /**
+     * The collection of affected ItemIds.
+     */
+    private final Collection affectedIds = new ArrayList();
+
+    /**
+     * Returns the name of the class
+     *
+     * @return the class name
+     * @see #getClass()
+     */
+    public String getName() {
+        return getClass().getName();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public Collection getAffectedItemIds() {
+        return Collections.unmodifiableCollection(affectedIds);
+    }
+
+    /**
+     * Adds an affected <code>ItemId</code>.
+     *
+     * @param affectedId the <code>ItemId</code>s of the affected item.
+     */
+    protected void addAffectedItemId(ItemId affectedId) {
+        affectedIds.add(affectedId);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AbstractOperation.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,95 @@
+/*
+ * 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.jcr2spi.operation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.name.QName;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.version.VersionException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+
+/**
+ * <code>AddLabel</code>...
+ */
+public class AddLabel extends AbstractOperation {
+
+    private static Logger log = LoggerFactory.getLogger(AddLabel.class);
+
+    private final NodeId versionHistoryId;
+    private final NodeId versionId;
+    private final QName label;
+    private final boolean moveLabel;
+
+    private AddLabel(NodeId versionHistoryId, NodeId versionId, QName label, boolean moveLabel) {
+        this.versionHistoryId = versionHistoryId;
+        this.versionId = versionId;
+        this.label = label;
+        this.moveLabel = moveLabel;
+    }
+    //----------------------------------------------------------< Operation >---
+    /**
+     *
+     * @param visitor
+     * @throws RepositoryException
+     * @throws ConstraintViolationException
+     * @throws AccessDeniedException
+     * @throws ItemExistsException
+     * @throws NoSuchNodeTypeException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws VersionException
+     */
+    public void accept(OperationVisitor visitor) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
+        visitor.visit(this);
+    }
+
+    //----------------------------------------< Access Operation Parameters >---
+    public NodeId getVersionHistoryId() {
+        return versionHistoryId;
+    }
+
+    public NodeId getVersionId() {
+        return versionId;
+    }
+
+    public QName getLabel() {
+        return label;
+    }
+
+    public boolean moveLabel() {
+        return moveLabel;
+    }
+
+    //------------------------------------------------------------< Factory >---
+    /**
+     *
+     * @param versionHistoryId
+     * @param versionId
+     * @param label
+     * @param moveLabel
+     * @return
+     */
+    public static Operation create(NodeId versionHistoryId, NodeId versionId, QName label, boolean moveLabel) {
+        return new AddLabel(versionHistoryId, versionId, label, moveLabel);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddLabel.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.operation;
+
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.name.QName;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.version.VersionException;
+import javax.jcr.lock.LockException;
+import java.util.List;
+
+/**
+ * <code>AddNode</code>...
+ */
+public class AddNode extends AbstractOperation {
+
+    private static Logger log = LoggerFactory.getLogger(AddNode.class);
+
+    private final NodeId parentId;
+    private final QName nodeName;
+    private final QName nodeTypeName;
+    private final NodeId id;
+
+    private AddNode(NodeId parentId, QName nodeName, QName nodeTypeName, NodeId id) {
+        this.parentId = parentId;
+        this.nodeName = nodeName;
+        this.nodeTypeName = nodeTypeName;
+        this.id = id;
+        addAffectedItemId(parentId);
+        addAffectedItemId(id);
+    }
+
+    //----------------------------------------------------------< Operation >---
+    /**
+     *
+     * @param visitor
+     */
+    public void accept(OperationVisitor visitor) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        visitor.visit(this);
+    }
+
+    //----------------------------------------< Access Operation Parameters >---
+    public NodeId getParentId() {
+        return parentId;
+    }
+
+    public QName getNodeName() {
+        return nodeName;
+    }
+
+    public QName getNodeTypeName() {
+        return nodeTypeName;
+    }
+
+    public String getUuid() {
+        return (id != null) ? id.getUUID() : null;
+    }
+
+    //------------------------------------------------------------< Factory >---
+
+    public static Operation create(NodeState parentState, QName nodeName,
+                                   QName nodeTypeName, NodeId id) {
+        AddNode an = new AddNode(parentState.getNodeId(), nodeName, nodeTypeName, id);
+        return an;
+    }
+
+    public static NodeId getLastCreated(NodeState parentState, QName nodeName) {
+        // TODO: check if this really retrieves the child state that was created before
+        // problem: we don't know the id of the nodestate in advance -> retrieval of new state not possible.
+        List cne = parentState.getChildNodeEntries(nodeName);
+        NodeId childId = ((NodeState.ChildNodeEntry)cne.get(cne.size()-1)).getId();
+        return childId;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,92 @@
+/*
+ * 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.jcr2spi.operation;
+
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.value.QValue;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.version.VersionException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+/**
+ * <code>AddProperty</code>...
+ */
+public class AddProperty extends AbstractOperation {
+
+    private final NodeId parentId;
+    private final QName propertyName;
+    private final int propertyType;
+    private final QValue[] values;
+
+    private final QPropertyDefinition definition;
+
+    private AddProperty(PropertyId newPropertyId, int propertyType, QValue[] values, QPropertyDefinition definition) {
+        this.parentId = newPropertyId.getParentId();
+        this.propertyName = newPropertyId.getQName();
+        this.propertyType = propertyType;
+        this.values = values;
+        this.definition = definition;
+        addAffectedItemId(parentId);
+        addAffectedItemId(newPropertyId);
+    }
+
+    //----------------------------------------------------------< Operation >---
+    /**
+     *
+     * @param visitor
+     */
+    public void accept(OperationVisitor visitor) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        visitor.visit(this);
+    }
+
+    //----------------------------------------< Access Operation Parameters >---
+    public NodeId getParentId() {
+        return parentId;
+    }
+
+    public QName getPropertyName() {
+        return propertyName;
+    }
+
+    public int getPropertyType() {
+        return propertyType;
+    }
+
+    public QValue[] getValues() {
+        return values;
+    }
+
+    public boolean isMultiValued() {
+        return definition.isMultiple();
+    }
+
+    //------------------------------------------------------------< Factory >---
+    public static Operation create(PropertyId newPropertyId, int propertyType,
+                                   QPropertyDefinition def, QValue[] values) {
+        AddProperty ap = new AddProperty(newPropertyId, propertyType, values, def);
+        return ap;
+    }
+}
\ No newline at end of file