You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-dev@jackrabbit.apache.org by "gianluca.soffredini@metaframe.it" <gi...@metaframe.it> on 2016/03/30 18:54:19 UTC

Problems with OAK restrictions

Good evening,

I still write for the problem I found in the management of the rep:glob 
restriction  set to empty string.
I made more tests and I also used the ReadTest class of OAK.

In effect the restriction rep:glob set to empty string works on nodes.
If I apply the restriction on path//my_folder /by adding the permission 
of READ, both with the call to Session.getNode ("/ my_folder") that with 
the call to
Session.getRootNode (). getNodes () the system can access to the path.

The problem is when I run the query "/select F. * from [nt: folder] as 
F/". The query result is empty.
It seems to me weird because if I access to the node I should also have 
access to the folder that the node represents.

Analyzing the OAK code in version 1.4.0 I have found the GlobPattern class.
In the case of restriction rep:glob set to empty string that class 
always uses the PathPattern class and always makes the call 
/path.equals(toMatch)/.
This call works when the path//my_folder/ is used in nodes and it works
even when the path is evaluated in the query "/select * from F. [nt: 
folder] as F/".
The problem is the following: during query execution is also rightly 
evaluated
the jcr: primaryType node. In this case, the primary type is nt: folder.
It is called again the following method of GlobPattern class:

/*@Override*//*
*//*    public boolean matches(@Nonnull Tree tree, @Nullable 
PropertyState property) {*//*
*//*        String itemPath = (property == null) ? tree.getPath() : 
PathUtils.concat(tree.getPath(), property.getName());*//*
*//*        return matches(itemPath);*//*
*//*    }

*/and then the matches method of the class PathPattern:

/*@Override*//*
*//*        boolean matches(@Nonnull String toMatch) {*//*
*//*            if (patternStr.isEmpty()) {*//*
*//*                return path.equals(toMatch);*//*
*//*            } else {*//*
*//*                // no wildcard contained in restriction: use path 
defined*//*
*//*                // by path + restriction to calculate the match*//*
*//*                return Text.isDescendantOrEqual(patternStr, 
toMatch);*//*
*//*            }*//*
*//*        }

*/This call can not works because in the case in which 
/patternStr.isEmpty()/ is called the method /path.equals(toMatch)/.

But the path class attribute is set to /*/my_folder*/ and the toMatch 
variable is set to /*/my_folder/jrc: primaryType*/. It 's clear that the 
call to the equals method will return false.

To solve the problem I changed the class OAK GlobPattern as follows, and 
now the query "/select * from F. [nt: folder] as F/" correctly returns 
in its results the folder//my_folder/.

/*
  * 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.oak.security.authorization.restriction;

import static com.google.common.base.Preconditions.checkNotNull;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.jcr.security.AccessControlException;

import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
import org.apache.jackrabbit.util.Text;

import com.google.common.base.Objects;

/**
  * {@code GlobPattern} defines a simplistic pattern matching. It consists
  * of a mandatory (leading) path and an optional "glob" that may 
contain one or
  * more wildcard characters ("{@code *}") according to the glob matching
  * defined by {@link javax.jcr.Node#getNodes(String[])}. In contrast to 
that
  * method the {@code GlobPattern} operates on path (not only names).
  * <p>
  *
  * <p>
  * Please note the following special cases:
  * <pre>
  * NodePath     |   Restriction   |   Matches
  * 
-----------------------------------------------------------------------------
  * /foo         |   null          |   matches /foo and all children of /foo
  * /foo         |   ""            |   matches /foo only
  * </pre>
  * </p>
  *
  * <p>
  * Examples without wildcard char:
  * <pre>
  * NodePath = "/foo"
  * Restriction   |   Matches
  * 
-----------------------------------------------------------------------------
  * /cat          |   the node /foo/cat and all it's children
  * /cat/         |   the descendants of the node /foo/cat
  * cat           |   the node /foocat and all it's children
  * cat/          |   all descendants of the node /foocat
  * </pre>
  * </p>
  *
  * <p>
  * Examples including wildcard char:
  * <pre>
  * NodePath = "/foo"
  * Restriction   |   Matches
  * 
-----------------------------------------------------------------------------
  * &#42;         |   foo, all siblings of foo and their descendants
  * /&#42;cat     |   all children of /foo whose path ends with "cat"
  * /&#42;/cat    |   all non-direct descendants of /foo named "cat"
  * /cat&#42;     |   all descendant path of /foo that have the direct 
foo-descendant segment starting with "cat"
  * &#42;cat      |   all siblings and descendants of foo that have a 
name ending with cat
  * &#42;/cat     |   all descendants of /foo and foo's siblings that 
have a name segment "cat"
  * cat/&#42;     |   all descendants of '/foocat'
  * /cat/&#42;    |   all descendants of '/foo/cat'
  * &#42;cat/&#42;    |   all siblings and descendants of foo that have 
an intermediate segment ending with 'cat'
  * /&#42;cat/&#42;   |   all descendants of /foo that have an 
intermediate segment ending with 'cat'
  * </pre>
  * </p>
  */
final class GlobPattern implements RestrictionPattern {

     private static final char WILDCARD_CHAR = '*';
     private static final int MAX_WILDCARD = 20;

     private final String path;
     private final String restriction;

*  private Pattern pattern;*

*private GlobPattern(@Nonnull String path, @Nonnull String restriction)  {**
**        this.path = checkNotNull(path);**
**        this.restriction = restriction;**
**    }*

     static GlobPattern create(@Nonnull String nodePath, @Nonnull String 
restrictions) {
         return new GlobPattern(nodePath, restrictions);
     }

     static void validate(@Nonnull String restriction) throws 
AccessControlException {
         int cnt = 0;
         for (int i = 0; i < restriction.length(); i++) {
             if (WILDCARD_CHAR == restriction.charAt(i)) {
                 cnt++;
             }
             if (cnt > MAX_WILDCARD) {
                 throw new AccessControlException("Number of wildcards 
in rep:glob exceeds allowed complexity.");
             }
         }
     }

*private Pattern getPattern(@Nullable PropertyState property){**
**        if (!restriction.isEmpty()) {**
**            StringBuilder b = new StringBuilder(path);**
**            b.append(restriction);**
**
**            int lastPos = restriction.lastIndexOf(WILDCARD_CHAR);**
**            if (lastPos >= 0) {**
**                String end;**
**                if (lastPos != restriction.length()-1) {**
**                    end = restriction.substring(lastPos + 1);**
**                } else {**
**                    end = null;**
**                }**
**                pattern = new WildcardPattern(b.toString(), end);**
**            } else {**
**                pattern = new PathPattern(b.toString());**
**            }**
**        } else {**
**            if(property == null){**
**                pattern = new PathPattern(restriction);**
**            }**
**            else{**
**                pattern = new PathPropertyPattern(restriction, 
property.getName());**
**            }**
**        }**
**        return pattern;**
**    }*

     //-------------------------------------------------< 
RestrictionPattern >---
     @Override
     public boolean matches(@Nonnull Tree tree, @Nullable PropertyState 
property) {
*pattern = getPattern(property);*
         String itemPath = (property == null) ? tree.getPath() : 
PathUtils.concat(tree.getPath(), property.getName());
         return matches(itemPath);
     }

     @Override
     public boolean matches(@Nonnull String path) {
         return pattern.matches(path);
     }

     @Override
     public boolean matches() {
         // repository level permissions never match any glob pattern
         return false;
     }

//-------------------------------------------------------------< Object >---
     /**
      * @see Object#hashCode()
      */
     @Override
     public int hashCode() {
         return Objects.hashCode(path, restriction);
     }

     /**
      * @see Object#toString()
      */
     @Override
     public String toString() {
         return path + " : " + restriction;
     }

     /**
      * @see Object#equals(Object)
      */
     @Override
     public boolean equals(Object obj) {
         if (obj == this) {
             return true;
         }
         if (obj instanceof GlobPattern) {
             GlobPattern other = (GlobPattern) obj;
             return path.equals(other.path) && 
restriction.equals(other.restriction);
         }
         return false;
     }

     //------------------------------------------------------< inner 
classes >---
     /**
      * Base for PathPattern and WildcardPattern
      */
     private abstract class Pattern {
         abstract boolean matches(@Nonnull String toMatch);
     }

     /**
      * Path pattern: The restriction is missing or doesn't contain any 
wildcard character.
      */
     private final class PathPattern extends Pattern {

         private final String patternStr;

         private PathPattern(@Nonnull String patternStr) {
             this.patternStr = patternStr;
         }

         @Override
         boolean matches(@Nonnull String toMatch) {
             if (patternStr.isEmpty()) {
                 return path.equals(toMatch);
             } else {
                 // no wildcard contained in restriction: use path defined
                 // by path + restriction to calculate the match
                 return Text.isDescendantOrEqual(patternStr, toMatch);
             }
         }
     }

*/****
**     * Path pattern: The restriction is missing or doesn't contain any 
wildcard character and manage PropertyState case.**
**     */**
**    private final class PathPropertyPattern extends Pattern {**
**
**        private final String patternStr;**
****
**        private final String propertyName;**
**
**        private PathPropertyPattern(@Nonnull String patternStr, 
@Nonnull String propertyName) {**
**            this.patternStr = patternStr;**
**            this.propertyName = propertyName;**
**        }**
**
**        @Override**
**        boolean matches(@Nonnull String toMatch) {**
**            if (patternStr.isEmpty()) {**
**                //return path.equals(toMatch);**
**                return evalutatePathAndProperty().equals(toMatch);**
**            } else {**
**                // no wildcard contained in restriction: use path 
defined**
**                // by path + restriction to calculate the match**
**                return Text.isDescendantOrEqual(patternStr, toMatch);**
**            }**
**        }**
****
**        private String evalutatePathAndProperty(){**
**            final StringBuilder builder = new StringBuilder();**
**            builder.append(path);**
**            if(!path.endsWith("/")){**
**                builder.append("/");**
**            }**
**            builder.append(propertyName);**
**            return builder.toString();**
**        }**
**    }*

     /**
      * Wildcard pattern: The specified restriction contains one or more 
wildcard character(s).
      */
     private final class WildcardPattern extends Pattern {

         private final String patternEnd;
         private final char[] patternChars;

         private WildcardPattern(@Nonnull String patternStr, @Nullable 
String patternEnd) {
             patternChars = patternStr.toCharArray();
             this.patternEnd = patternEnd;
         }

         @Override
         boolean matches(@Nonnull String toMatch) {
             if (patternEnd != null && !toMatch.endsWith(patternEnd)) {
                 // shortcut: verify if end of pattern matches end of 
toMatch
                 return false;
             }
             char[] tm = (toMatch.endsWith("/")) ? toMatch.substring(0, 
toMatch.length()-1).toCharArray() : toMatch.toCharArray();
             // shortcut didn't reveal mismatch -> need to process the 
internal match method.
             return matches(patternChars, 0, tm, 0, MAX_WILDCARD);
         }

         /**
          *
          * @param pattern The pattern
          * @param pOff
          * @param s
          * @param sOff
          * @return {@code true} if matches, {@code false} otherwise
          */
         private boolean matches(char[] pattern, int pOff,
                                 char[] s, int sOff, int cnt) {

             if (cnt <= 0) {
                 throw new IllegalArgumentException("Illegal glob 
pattern " + GlobPattern.this);
             }

             int pLength = pattern.length;
             int sLength = s.length;

             while (true) {
                 // end of pattern reached: matches only if sOff points 
at the end
                 // of the string to match.
                 if (pOff >= pLength) {
                     return sOff >= sLength;
                 }

                 // the end of the string to match has been reached but 
pattern
                 // doesn't have '*' at patternIndex -> no match
                 if (sOff >= sLength && pattern[pOff] != WILDCARD_CHAR) {
                     return false;
                 }

                 // the next character of the pattern is '*'
                 // -> recursively test if the rest of the specified 
string matches
                 if (pattern[pOff] == WILDCARD_CHAR) {
                     if (++pOff >= pLength) {
                         return true;
                     }

                     cnt--;
                     while (true) {
                         if (matches(pattern, pOff, s, sOff, cnt)) {
                             return true;
                         }
                         if (sOff >= sLength) {
                             return false;
                         }
                         sOff++;
                     }
                 }

                 // not yet reached end of patter nor string and not 
wildcard character.
                 // the 2 strings don't match in case the characters at 
the current
                 // position are not the same.
                 if (pOff < pLength && sOff < sLength) {
                     if (pattern[pOff] != s[sOff]) {
                         return false;
                     }
                 }
                 pOff++;
                 sOff++;
             }
         }
     }
}*

*My changes make sense in your opinion or behavior of which I spoke 
about is wanted?

Thanks in advance.

Gianluca Soffredini
Project Manager
Metaframe SPS S.r.l.
Via Toniolo, 13
30030 Vigonovo(VE)
mobile: +39 3342235291
email: gianluca.soffredini@metaframe.it 
<ma...@metaframe.it>
SKYPE ID: gianlucas72
Logo Metaframe SPS S.r.l.


Re: Problems with OAK restrictions

Posted by Angela Schreiber <an...@adobe.com>.
hi gianluca

the glob pattern doesn't distinguish between nodes and properties and
just verifies if the path of a given item matches the specified pattern.
so, having the pattern value set to empty string limits the effect of the
ace to that very node. the properties of that node are not accessible.
so, the outcome is expected.

i guess OAK-2437<https://issues.apache.org/jira/browse/OAK-2437> (https://issues.apache.org/jira/browse/OAK-2437) would
be the what you are looking for.

until this is resolved you can work around by adding an additional entry that
grants rep:readProperties privilege to you principal just for the 'jcr:primaryType'
property.

hope that helps
angela

From: "gianluca.soffredini@metaframe.it<ma...@metaframe.it>" <gi...@metaframe.it>>
Reply-To: "oak-dev@jackrabbit.apache.org<ma...@jackrabbit.apache.org>" <oa...@jackrabbit.apache.org>>
Date: Wednesday 30 March 2016 18:54
To: "oak-dev@jackrabbit.apache.org<ma...@jackrabbit.apache.org>" <oa...@jackrabbit.apache.org>>
Subject: Problems with OAK restrictions

Good evening,

I still write for the problem I found in the management of the rep:glob restriction  set to empty string.
I made more tests and I also used the ReadTest class of OAK.

In effect the restriction rep:glob set to empty string works on nodes.
If I apply the restriction on path /my_folder by adding the permission of READ, both with the call to Session.getNode ("/ my_folder") that with the call to
Session.getRootNode (). getNodes () the system can access to the path.

The problem is when I run the query "select F. * from [nt: folder] as F". The query result is empty.
It seems to me weird because if I access to the node I should also have access to the folder that the node represents.

Analyzing the OAK code in version 1.4.0 I have found the GlobPattern class.
In the case of restriction rep:glob set to empty string that class always uses the PathPattern class and always makes the call path.equals(toMatch).
This call works when the path /my_folder is used in nodes and it works
even when the path is evaluated in the query "select * from F. [nt: folder] as F".
The problem is the following: during query execution is also rightly evaluated
the jcr: primaryType node. In this case, the primary type is nt: folder.
It is called again the following method of GlobPattern class:

    @Override
    public boolean matches(@Nonnull Tree tree, @Nullable PropertyState property) {
        String itemPath = (property == null) ? tree.getPath() : PathUtils.concat(tree.getPath(), property.getName());
        return matches(itemPath);
    }

and then the matches method of the class PathPattern:

       @Override
        boolean matches(@Nonnull String toMatch) {
            if (patternStr.isEmpty()) {
                return path.equals(toMatch);
            } else {
                // no wildcard contained in restriction: use path defined
                // by path + restriction to calculate the match
                return Text.isDescendantOrEqual(patternStr, toMatch);
            }
        }

This call can not works because in the case in which patternStr.isEmpty() is called the method path.equals(toMatch).

But the path class attribute is set to  /my_folder and the toMatch variable is set to /my_folder/jrc: primaryType. It 's clear that the call to the equals method will return false.

To solve the problem I changed the class OAK GlobPattern as follows, and now the query "select * from F. [nt: folder] as F" correctly returns in its results the folder /my_folder.

/*
 * 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.oak.security.authorization.restriction;

import static com.google.common.base.Preconditions.checkNotNull;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.jcr.security.AccessControlException;

import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
import org.apache.jackrabbit.util.Text;

import com.google.common.base.Objects;

/**
 * {@code GlobPattern} defines a simplistic pattern matching. It consists
 * of a mandatory (leading) path and an optional "glob" that may contain one or
 * more wildcard characters ("{@code *}") according to the glob matching
 * defined by {@link javax.jcr.Node#getNodes(String[])}. In contrast to that
 * method the {@code GlobPattern} operates on path (not only names).
 * <p>
 *
 * <p>
 * Please note the following special cases:
 * <pre>
 * NodePath     |   Restriction   |   Matches
 * -----------------------------------------------------------------------------
 * /foo         |   null          |   matches /foo and all children of /foo
 * /foo         |   ""            |   matches /foo only
 * </pre>
 * </p>
 *
 * <p>
 * Examples without wildcard char:
 * <pre>
 * NodePath = "/foo"
 * Restriction   |   Matches
 * -----------------------------------------------------------------------------
 * /cat          |   the node /foo/cat and all it's children
 * /cat/         |   the descendants of the node /foo/cat
 * cat           |   the node /foocat and all it's children
 * cat/          |   all descendants of the node /foocat
 * </pre>
 * </p>
 *
 * <p>
 * Examples including wildcard char:
 * <pre>
 * NodePath = "/foo"
 * Restriction   |   Matches
 * -----------------------------------------------------------------------------
 * &#42;         |   foo, all siblings of foo and their descendants
 * /&#42;cat     |   all children of /foo whose path ends with "cat"
 * /&#42;/cat    |   all non-direct descendants of /foo named "cat"
 * /cat&#42;     |   all descendant path of /foo that have the direct foo-descendant segment starting with "cat"
 * &#42;cat      |   all siblings and descendants of foo that have a name ending with cat
 * &#42;/cat     |   all descendants of /foo and foo's siblings that have a name segment "cat"
 * cat/&#42;     |   all descendants of '/foocat'
 * /cat/&#42;    |   all descendants of '/foo/cat'
 * &#42;cat/&#42;    |   all siblings and descendants of foo that have an intermediate segment ending with 'cat'
 * /&#42;cat/&#42;   |   all descendants of /foo that have an intermediate segment ending with 'cat'
 * </pre>
 * </p>
 */
final class GlobPattern implements RestrictionPattern {

    private static final char WILDCARD_CHAR = '*';
    private static final int MAX_WILDCARD = 20;

    private final String path;
    private final String restriction;

    private Pattern pattern;

    private GlobPattern(@Nonnull String path, @Nonnull String restriction)  {
        this.path = checkNotNull(path);
        this.restriction = restriction;
    }

    static GlobPattern create(@Nonnull String nodePath, @Nonnull String restrictions) {
        return new GlobPattern(nodePath, restrictions);
    }

    static void validate(@Nonnull String restriction) throws AccessControlException {
        int cnt = 0;
        for (int i = 0; i < restriction.length(); i++) {
            if (WILDCARD_CHAR == restriction.charAt(i)) {
                cnt++;
            }
            if (cnt > MAX_WILDCARD) {
                throw new AccessControlException("Number of wildcards in rep:glob exceeds allowed complexity.");
            }
        }
    }

    private Pattern getPattern(@Nullable PropertyState property){
        if (!restriction.isEmpty()) {
            StringBuilder b = new StringBuilder(path);
            b.append(restriction);

            int lastPos = restriction.lastIndexOf(WILDCARD_CHAR);
            if (lastPos >= 0) {
                String end;
                if (lastPos != restriction.length()-1) {
                    end = restriction.substring(lastPos + 1);
                } else {
                    end = null;
                }
                pattern = new WildcardPattern(b.toString(), end);
            } else {
                pattern = new PathPattern(b.toString());
            }
        } else {
            if(property == null){
                pattern = new PathPattern(restriction);
            }
            else{
                pattern = new PathPropertyPattern(restriction, property.getName());
            }
        }
        return pattern;
    }

    //-------------------------------------------------< RestrictionPattern >---
    @Override
    public boolean matches(@Nonnull Tree tree, @Nullable PropertyState property) {
        pattern = getPattern(property);
        String itemPath = (property == null) ? tree.getPath() : PathUtils.concat(tree.getPath(), property.getName());
        return matches(itemPath);
    }

    @Override
    public boolean matches(@Nonnull String path) {
        return pattern.matches(path);
    }

    @Override
    public boolean matches() {
        // repository level permissions never match any glob pattern
        return false;
    }

    //-------------------------------------------------------------< Object >---
    /**
     * @see Object#hashCode()
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(path, restriction);
    }

    /**
     * @see Object#toString()
     */
    @Override
    public String toString() {
        return path + " : " + restriction;
    }

    /**
     * @see Object#equals(Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof GlobPattern) {
            GlobPattern other = (GlobPattern) obj;
            return path.equals(other.path) &&  restriction.equals(other.restriction);
        }
        return false;
    }

    //------------------------------------------------------< inner classes >---
    /**
     * Base for PathPattern and WildcardPattern
     */
    private abstract class Pattern {
        abstract boolean matches(@Nonnull String toMatch);
    }

    /**
     * Path pattern: The restriction is missing or doesn't contain any wildcard character.
     */
    private final class PathPattern extends Pattern {

        private final String patternStr;

        private PathPattern(@Nonnull String patternStr) {
            this.patternStr = patternStr;
        }

        @Override
        boolean matches(@Nonnull String toMatch) {
            if (patternStr.isEmpty()) {
                return path.equals(toMatch);
            } else {
                // no wildcard contained in restriction: use path defined
                // by path + restriction to calculate the match
                return Text.isDescendantOrEqual(patternStr, toMatch);
            }
        }
    }

    /**
     * Path pattern: The restriction is missing or doesn't contain any wildcard character and manage PropertyState case.
     */
    private final class PathPropertyPattern extends Pattern {

        private final String patternStr;

        private final String propertyName;

        private PathPropertyPattern(@Nonnull String patternStr, @Nonnull String propertyName) {
            this.patternStr = patternStr;
            this.propertyName = propertyName;
        }

        @Override
        boolean matches(@Nonnull String toMatch) {
            if (patternStr.isEmpty()) {
                //return path.equals(toMatch);
                return evalutatePathAndProperty().equals(toMatch);
            } else {
                // no wildcard contained in restriction: use path defined
                // by path + restriction to calculate the match
                return Text.isDescendantOrEqual(patternStr, toMatch);
            }
        }

        private String evalutatePathAndProperty(){
            final StringBuilder builder = new StringBuilder();
            builder.append(path);
            if(!path.endsWith("/")){
                builder.append("/");
            }
            builder.append(propertyName);
            return builder.toString();
        }
    }

    /**
     * Wildcard pattern: The specified restriction contains one or more wildcard character(s).
     */
    private final class WildcardPattern extends Pattern {

        private final String patternEnd;
        private final char[] patternChars;

        private WildcardPattern(@Nonnull String patternStr, @Nullable String patternEnd) {
            patternChars = patternStr.toCharArray();
            this.patternEnd = patternEnd;
        }

        @Override
        boolean matches(@Nonnull String toMatch) {
            if (patternEnd != null && !toMatch.endsWith(patternEnd)) {
                // shortcut: verify if end of pattern matches end of toMatch
                return false;
            }
            char[] tm = (toMatch.endsWith("/")) ? toMatch.substring(0, toMatch.length()-1).toCharArray() : toMatch.toCharArray();
            // shortcut didn't reveal mismatch -> need to process the internal match method.
            return matches(patternChars, 0, tm, 0, MAX_WILDCARD);
        }

        /**
         *
         * @param pattern The pattern
         * @param pOff
         * @param s
         * @param sOff
         * @return {@code true} if matches, {@code false} otherwise
         */
        private boolean matches(char[] pattern, int pOff,
                                char[] s, int sOff, int cnt) {

            if (cnt <= 0) {
                throw new IllegalArgumentException("Illegal glob pattern " + GlobPattern.this);
            }

            int pLength = pattern.length;
            int sLength = s.length;

            while (true) {
                // end of pattern reached: matches only if sOff points at the end
                // of the string to match.
                if (pOff >= pLength) {
                    return sOff >= sLength;
                }

                // the end of the string to match has been reached but pattern
                // doesn't have '*' at patternIndex -> no match
                if (sOff >= sLength && pattern[pOff] != WILDCARD_CHAR) {
                    return false;
                }

                // the next character of the pattern is '*'
                // -> recursively test if the rest of the specified string matches
                if (pattern[pOff] == WILDCARD_CHAR) {
                    if (++pOff >= pLength) {
                        return true;
                    }

                    cnt--;
                    while (true) {
                        if (matches(pattern, pOff, s, sOff, cnt)) {
                            return true;
                        }
                        if (sOff >= sLength) {
                            return false;
                        }
                        sOff++;
                    }
                }

                // not yet reached end of patter nor string and not wildcard character.
                // the 2 strings don't match in case the characters at the current
                // position are not the same.
                if (pOff < pLength && sOff < sLength) {
                    if (pattern[pOff] != s[sOff]) {
                        return false;
                    }
                }
                pOff++;
                sOff++;
            }
        }
    }
}

My changes make sense in your opinion or behavior of which I spoke about is wanted?

Thanks in advance.


Gianluca Soffredini
Project Manager
Metaframe SPS S.r.l.
Via Toniolo, 13
30030 Vigonovo(VE)
mobile: +39 3342235291
email: gianluca.soffredini@metaframe.it<ma...@metaframe.it>
SKYPE ID: gianlucas72
[Logo            Metaframe SPS S.r.l.]