All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.hk2.component.matcher.SimpleLdapMatcher Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.jvnet.hk2.component.matcher;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.jvnet.hk2.component.Constants;
import org.jvnet.hk2.component.MultiMap;

/**
 * This thing filters things based on the input LDAP style string. Note that
 * this is restricted LDAP, in that it only handles "&".
 * 
 * @since 3.1
 */
public class SimpleLdapMatcher {
  private static final char LEFT = '(';

  private static final char RIGHT = ')';

  private static final char AND = '&';

  private static final char OR = '|';

  private static final char EQ = '=';

  private static final char NOT = '!';

  private static final int UNKNOWN_TYPE = -1;

  private static final int BASE_TYPE = 0;

  private static final int AND_TYPE = 1;

  private static final int OR_TYPE = 2;

  private static final int NEST_TYPE = 3;

  private static final int FALSE_TYPE = 4;

  private final String originalLDAP;

  private final Node topNode;

  /* package */SimpleLdapMatcher(String paramOriginalLDAP) {
    originalLDAP = paramOriginalLDAP;

    if (originalLDAP != null) {
      ActiveString workString = new ActiveString(paramOriginalLDAP);
      topNode = parse(workString);
      workString.finish();
    } else {
      topNode = new Node();
      topNode.setType(FALSE_TYPE);
    }
  }

  /* package */SimpleLdapMatcher(Node node) {
    originalLDAP = null;
    topNode = node;
  }

  public static SimpleLdapMatcher create(String ldapFilter) {
    return new SimpleLdapMatcher(ldapFilter);
  }

  public static SimpleLdapMatcher create(Map props) {
    Node node = new Node();
    for (Entry entry : props.entrySet()) {
      Node child = new Node(entry.getKey(), true, stringOf(entry.getValue()));
      node.addChild(child);
    }
    return new SimpleLdapMatcher(node);
  }

  public static SimpleLdapMatcher createClassFilter(String clazzName) {
    Map props = new HashMap();
    props.put(Constants.OBJECTCLASS, clazzName);
    return create(props);

  }

  private static String stringOf(Object obj) {
    return (null == obj) ? null : obj.toString();
  }

  /* package */String getOriginalFilterString() {
    return originalLDAP;
  }

  /**
   * An or-type match checker
   * @param properties
   * @return
   */
  public boolean matches(MultiMap properties) {
    if (properties == null) {
      throw new IllegalArgumentException();
    }
    Map> matchingMap = getMatchingMap(properties);
    return matches(matchingMap);
  }

  public boolean matches(Map> toMatch) {
    return topNode.doesMatch(toMatch);
  }
  
  public Set getTheAndSetFor(String propName, boolean removeIt) {
    Set andSet = new HashSet();
    getTheAndSetFor(andSet, propName.toLowerCase(), removeIt, topNode);
    return andSet;
  }
  
  protected boolean getTheAndSetFor(Set result, String propName, boolean removeIt, Node node) {
    boolean hadMatch = false;
    if (node.type == BASE_TYPE) {
      if (node.leftHandSide.equals(propName)) {
        result.add(node.rightHandSide);
        hadMatch = true;
        if (removeIt) {
          node.leftHandSide="";
        }
      }
    } else if (node.type == AND_TYPE) {
      Iterator iter = node.children.iterator();
      while (iter.hasNext()) {
        Node andNode = iter.next();
        boolean matched = getTheAndSetFor(result, propName, removeIt, andNode);
        hadMatch |= matched;
        if (matched && removeIt) {
          iter.remove();
        }
      }
    }
    return hadMatch;
  }


  private Map> getMatchingMap(MultiMap props) {
    Map> result = new HashMap>();
    for (Entry> entry : props.entrySet()) {
      String key = entry.getKey().toLowerCase();
      Set set = result.get(key);
      if (null == set) {
        set = new HashSet();
        result.put(entry.getKey().toLowerCase(), set);
      }
      set.addAll(entry.getValue());
    }
    return result;
  }

  /**
   * Static method that helps find a match
   * 
   * @param filter
   *          The filter to match
   * @param properties
   *          The properties to match them against
   * @return true if there is a match, false otherwise
   */
  /* package */static boolean filterMatch(String filter,
      Map properties) {
    if (filter == null || properties == null) {
      throw new IllegalArgumentException();
    }

    ActiveString workString = new ActiveString(filter);
    Node topNode = parse(workString);

    Map> matchingMap = getMatchingMap(properties);
    return topNode.doesMatch(matchingMap);
  }

  /* package */static Map> getMatchingMap(
      Map paramProperties) {
    HashMap> retVal = new HashMap>();

    // Get the "fixed" map AFTER adding the system generated properties
    for (Map.Entry entry : paramProperties.entrySet()) {
      String key = entry.getKey();
      if (key == null) {
        throw new RuntimeException("Null key in property map");
      }

      Object value = entry.getValue();
      if (value == null) {
        throw new RuntimeException("Null value in property map for " + key);
      }

      String toLowerKey = key.toLowerCase();
      Set valueSet = new HashSet();

      Class valueClass = value.getClass();
      if (valueClass.isArray()) {
        int valueLength = Array.getLength(value);
        for (int lcv = 0; lcv < valueLength; lcv++) {
          Object arrayValue = Array.get(value, lcv);
          if (arrayValue == null) {
            throw new RuntimeException("Null value in array for " + key
                + " at index " + lcv);
          }

          String arrayValueString = arrayValue.toString();
          valueSet.add(arrayValueString);
        }
      } else {
        String valueString = value.toString();
        valueSet.add(valueString);
      }

      retVal.put(toLowerKey, valueSet);
    }

    return retVal;
  }

  private static Node parse(ActiveString workString) {
    Node retVal = new Node();

    // I am looking for the start of an expression
    workString.toStatementStart();

    int type = workString.getStatementType();
    switch (type) {
    case BASE_TYPE:
      retVal.setType(BASE_TYPE);
      String lhs = workString.getLeftHandSide();
      boolean isEquals = workString.getEqualsOrNotEquals();
      String rhs = workString.getRightHandSide();

      retVal.setLeftHandSide(lhs);
      retVal.setIsEquals(isEquals);
      retVal.setRightHandSide(rhs);
      break;
    case AND_TYPE:
    case OR_TYPE:
      retVal.setType(type);
      while (workString.finishedOrNewStatement()) {
        Node child = parse(workString);
        retVal.addChild(child);
      }

      if (retVal.getNumChildren() <= 1) {
        throw new RuntimeException(
            "There was zero or one children of an AND or OR construct");
      }

      break;
    case NEST_TYPE:
      // This type was NOT incremented past the '('
      Node lastNode = parse(workString);
      retVal.copyNode(lastNode);
      break;
    case FALSE_TYPE: // getStatementType cannot return FALSE_TYPE
    default:
      throw new AssertionError("Unknown node type " + type);
    }

    return retVal;
  }


  private static class Node {

    private int type = UNKNOWN_TYPE;

    // For base type
    private String leftHandSide;

    private boolean equals;

    private String rightHandSide;

    // For and and or types
    private final List children = new LinkedList();

    private Node() {
      this.type = AND_TYPE;
    }

    private Node(String lhs, boolean equals, String rhs) {
      this.type = BASE_TYPE;
      this.leftHandSide = lhs;
      this.equals = equals;
      this.rightHandSide = rhs;
    }

    private void setType(int paramType) {
      type = paramType;
    }

    private void setLeftHandSide(String lhs) {
      leftHandSide = lhs.toLowerCase();
    }

    private void setRightHandSide(String rhs) {
      rightHandSide = rhs;
    }

    private void setIsEquals(boolean isEquals) {
      equals = isEquals;
    }

    private void addChild(Node child) {
      children.add(child);
    }

    private int getNumChildren() {
      return children.size();
    }

    private void copyNode(Node copyNode) {
      type = copyNode.type;

      leftHandSide = copyNode.leftHandSide;
      equals = copyNode.equals;
      rightHandSide = copyNode.rightHandSide;

      children.clear();
      children.addAll(copyNode.children);
    }

    private boolean doesMatch(Map> toMatch) {
      if (type == BASE_TYPE) {
        Set valueSet = toMatch.get(leftHandSide);
        if (valueSet == null) {
          if (equals) {
            return (null == leftHandSide || leftHandSide.length() == 0);
          }

          return true;
        }

        WildcardMatcher matcher = new WildcardMatcher(rightHandSide);
        for (String val : valueSet) {
          if (matcher.matches(val)) {
            return equals;
          }
        }

        // valueSet does *not* contain the value
        if (equals) {
          return false;
        }

        return true;
      }

      if (type == AND_TYPE) {
        for (Node andNode : children) {
          if (!andNode.doesMatch(toMatch)) {
            return false;
          }
        }

        return true;
      }

      if (type == OR_TYPE) {
        for (Node orNode : children) {
          if (orNode.doesMatch(toMatch)) {
            return true;
          }
        }

        return false;
      }

      if (type == FALSE_TYPE) {
        return false;
      }

      throw new AssertionError("Unknown node type " + type);
    }

    @Override
    public String toString() {
      String typeString;

      switch (type) {
      case BASE_TYPE:
        typeString = "BASE_TYPE";
        break;
      case AND_TYPE:
        typeString = "AND_TYPE";
        break;
      case OR_TYPE:
        typeString = "OR_TYPE";
        break;
      case FALSE_TYPE:
        typeString = "FALSE_TYPE";
        break;
      default:
        typeString = "Unknown_TYPE(" + type + ")";
      }

      return "Node(" + typeString + "," + "lhs=" + leftHandSide + ","
          + "equals=" + equals + "," + "rhs=" + rightHandSide + ","
          + System.identityHashCode(this) + ")";
    }
  }

  private static class ActiveString {
    final String original;

    int dot = 0;

    char data[];

    private ActiveString(String fullString) {
      original = fullString;
      data = fullString.toCharArray();
    }

    /**
     * Increments dot safely
     * 
     * @param doThrow
     *          Tells this method to throw if it reaches the end of the string,
     *          the state machine knows that this should not reach the end
     * @return true if there are more characters, false if this is the end
     */
    private boolean incrementDot(boolean doThrow) {
      if (dot + 1 >= data.length) {
        // finished
        if (doThrow) {
          throw new RuntimeException("Invalid end of string reached in "
              + original + " at character " + dot);
        }

        return false;
      }

      dot++;
      return true;
    }

    /**
     * Skips white space
     * 
     * @param doThrow
     *          if true throw an exception if the end is reached
     * @return true if there are more characters, false if this is the end
     */
    private boolean skipWhiteSpace(boolean doThrow) {
      boolean retVal = false;

      while (Character.isWhitespace(data[dot])) {
        retVal = incrementDot(doThrow);
      }

      return retVal;
    }

    private void toStatementStart() {
      skipWhiteSpace(true);

      if (!(data[dot] == LEFT)) {
        throw new RuntimeException("Statement did not start with '(', found "
            + data[dot] + " at character " + dot);
      }

      // Get past the left parenthesis
      incrementDot(true);
    }

    private int getStatementType() {
      skipWhiteSpace(true);

      if (data[dot] == AND) {
        incrementDot(true);
        return AND_TYPE;
      }

      if (data[dot] == OR) {
        incrementDot(true);
        return OR_TYPE;
      }

      if (data[dot] == LEFT) {
        // Do not increment dot
        return NEST_TYPE;
      }

      // Do not increment dot
      return BASE_TYPE;
    }

    /**
     * 
     * @return false if this is finished (increments past the final ')' and true
     *         if there is another statement (does NOT increment past the '(')
     */
    private boolean finishedOrNewStatement() {
      skipWhiteSpace(true);

      if (data[dot] == RIGHT) {
        incrementDot(false);
        return false;
      }

      // Do not increment past the dot
      return true;
    }

    private String getLeftHandSide() {
      int startIndex = dot;
      int count = 0;

      while (data[dot] != EQ) {
        if ((data[dot] == NOT) && ((dot + 1) < data.length)
            && (data[dot + 1] == EQ)) {
          // Found a not equals
          return new String(data, startIndex, count);
        }

        count++;
        incrementDot(true);
      }

      return new String(data, startIndex, count);
    }

    private boolean getEqualsOrNotEquals() {
      if (data[dot] == EQ) {
        incrementDot(true);
        return true;
      }

      if (data[dot] == NOT) {
        incrementDot(true);
        if (!(data[dot] == EQ)) {
          throw new AssertionError("Invalid not equals at character " + dot);
        }

        incrementDot(true);
        return false;
      }

      throw new AssertionError(
          "Was looking for equals or not equals but found " + data[dot]
              + " at character " + dot);
    }

    private String getRightHandSide() {
      int startIndex = dot;
      int count = 0;

      while (data[dot] != RIGHT) {
        count++;
        incrementDot(true);
      }

      // This might be the end, but need to get past the )
      incrementDot(false);
      return new String(data, startIndex, count);
    }

    private void finish() {
      if (skipWhiteSpace(false) == true) {
        throw new RuntimeException(
            "Found extra characters at end of LDAP string " + original
                + " at character " + dot);
      }
    }

    public String toString() {
      char dataDot = (dot < data.length) ? data[dot] : ' ';

      return "ActiveString(" + original + "," + dot + "," + dataDot + ")";
    }
  }

  public String toString() {
    return "Filter(" + originalLDAP + "," + System.identityHashCode(this) + ")";
  }

  public String getLdapExpression() {
    return originalLDAP;
  }
  
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy