001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.schema;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.Arrays;
033    
034    import org.opends.server.admin.std.server.EqualityMatchingRuleCfg;
035    import org.opends.server.api.EqualityMatchingRule;
036    import org.opends.server.config.ConfigException;
037    import org.opends.server.core.DirectoryServer;
038    import org.opends.server.protocols.asn1.ASN1OctetString;
039    import org.opends.server.types.ByteString;
040    import org.opends.server.types.DirectoryException;
041    import org.opends.server.types.DN;
042    import org.opends.server.types.InitializationException;
043    import org.opends.server.types.ResultCode;
044    
045    import static org.opends.server.loggers.debug.DebugLogger.*;
046    import org.opends.server.loggers.debug.DebugTracer;
047    import org.opends.server.loggers.ErrorLogger;
048    import org.opends.server.types.DebugLogLevel;
049    import static org.opends.messages.SchemaMessages.*;
050    import static org.opends.server.schema.SchemaConstants.*;
051    import static org.opends.server.util.StaticUtils.*;
052    
053    
054    
055    /**
056     * This class implements the uniqueMemberMatch matching rule defined in X.520
057     * and referenced in RFC 2252.  It is based on the name and optional UID syntax,
058     * and will compare values with a distinguished name and optional bit string
059     * suffix.
060     */
061    public class UniqueMemberEqualityMatchingRule
062           extends EqualityMatchingRule
063    {
064      /**
065       * The tracer object for the debug logger.
066       */
067      private static final DebugTracer TRACER = getTracer();
068    
069    
070    
071      /**
072       * Creates a new instance of this uniqueMemberMatch matching rule.
073       */
074      public UniqueMemberEqualityMatchingRule()
075      {
076        super();
077      }
078    
079    
080    
081      /**
082       * {@inheritDoc}
083       */
084      public void initializeMatchingRule(EqualityMatchingRuleCfg configuration)
085             throws ConfigException, InitializationException
086      {
087        // No initialization is required.
088      }
089    
090    
091    
092      /**
093       * Retrieves the common name for this matching rule.
094       *
095       * @return  The common name for this matching rule, or <CODE>null</CODE> if
096       * it does not have a name.
097       */
098      public String getName()
099      {
100        return EMR_UNIQUE_MEMBER_NAME;
101      }
102    
103    
104    
105      /**
106       * Retrieves the OID for this matching rule.
107       *
108       * @return  The OID for this matching rule.
109       */
110      public String getOID()
111      {
112        return EMR_UNIQUE_MEMBER_OID;
113      }
114    
115    
116    
117      /**
118       * Retrieves the description for this matching rule.
119       *
120       * @return  The description for this matching rule, or <CODE>null</CODE> if
121       *          there is none.
122       */
123      public String getDescription()
124      {
125        // There is no standard description for this matching rule.
126        return null;
127      }
128    
129    
130    
131      /**
132       * Retrieves the OID of the syntax with which this matching rule is
133       * associated.
134       *
135       * @return  The OID of the syntax with which this matching rule is associated.
136       */
137      public String getSyntaxOID()
138      {
139        return SYNTAX_NAME_AND_OPTIONAL_UID_OID;
140      }
141    
142    
143    
144      /**
145       * Retrieves the normalized form of the provided value, which is best suited
146       * for efficiently performing matching operations on that value.
147       *
148       * @param  value  The value to be normalized.
149       *
150       * @return  The normalized version of the provided value.
151       *
152       * @throws  DirectoryException  If the provided value is invalid according to
153       *                              the associated attribute syntax.
154       */
155      public ByteString normalizeValue(ByteString value)
156             throws DirectoryException
157      {
158        String valueString = value.stringValue().trim();
159        int    valueLength = valueString.length();
160    
161    
162        // See if the value contains the "optional uid" portion.  If we think it
163        // does, then mark its location.
164        int dnEndPos = valueLength;
165        int sharpPos = -1;
166        if (valueString.endsWith("'B") || valueString.endsWith("'b"))
167        {
168          sharpPos = valueString.lastIndexOf("#'");
169          if (sharpPos > 0)
170          {
171            dnEndPos = sharpPos;
172          }
173        }
174    
175    
176        // Take the DN portion of the string and try to normalize it.  If it fails,
177        // then this will throw an exception.
178        StringBuilder valueBuffer = new StringBuilder(valueLength);
179        try
180        {
181          DN dn = DN.decode(valueString.substring(0, dnEndPos));
182          dn.toNormalizedString(valueBuffer);
183        }
184        catch (Exception e)
185        {
186          if (debugEnabled())
187          {
188            TRACER.debugCaught(DebugLogLevel.ERROR, e);
189          }
190    
191          // We couldn't normalize the DN for some reason.  If we're supposed to use
192          // strict syntax enforcement, then throw an exception.  Otherwise, log a
193          // message and just try our best.
194          Message message = ERR_ATTR_SYNTAX_NAMEANDUID_INVALID_DN.get(
195                  valueString, getExceptionMessage(e));
196    
197          switch (DirectoryServer.getSyntaxEnforcementPolicy())
198          {
199            case REJECT:
200              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
201                                           message);
202            case WARN:
203              ErrorLogger.logError(message);
204    
205              valueBuffer.append(toLowerCase(valueString).substring(0, dnEndPos));
206              break;
207    
208            default:
209              valueBuffer.append(toLowerCase(valueString).substring(0, dnEndPos));
210              break;
211          }
212        }
213    
214    
215    
216        // If there is an "optional uid", then normalize it and make sure it only
217        // contains valid binary digits.
218        if (sharpPos > 0)
219        {
220          valueBuffer.append("#'");
221    
222          int     endPos = valueLength - 2;
223          boolean logged = false;
224          for (int i=sharpPos+2; i < endPos; i++)
225          {
226            char c = valueString.charAt(i);
227            if ((c == '0') || (c == '1'))
228            {
229              valueBuffer.append(c);
230            }
231            else
232            {
233              // There was an invalid binary digit.  We'll either throw an exception
234              // or log a message and continue, based on the server's configuration.
235              Message message = ERR_ATTR_SYNTAX_NAMEANDUID_ILLEGAL_BINARY_DIGIT.get(
236                      valueString, String.valueOf(c), i);
237    
238              switch (DirectoryServer.getSyntaxEnforcementPolicy())
239              {
240                case REJECT:
241                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
242                                               message);
243                case WARN:
244                  if (! logged)
245                  {
246                    ErrorLogger.logError(message);
247                    logged = true;
248                  }
249                  break;
250              }
251            }
252          }
253    
254          valueBuffer.append("'B");
255        }
256    
257        return new ASN1OctetString(valueBuffer.toString());
258      }
259    
260    
261    
262      /**
263       * Indicates whether the two provided normalized values are equal to each
264       * other.
265       *
266       * @param  value1  The normalized form of the first value to compare.
267       * @param  value2  The normalized form of the second value to compare.
268       *
269       * @return  <CODE>true</CODE> if the provided values are equal, or
270       *          <CODE>false</CODE> if not.
271       */
272      public boolean areEqual(ByteString value1, ByteString value2)
273      {
274        // Since the values are already normalized, we just need to compare the
275        // associated byte arrays.
276        return Arrays.equals(value1.value(), value2.value());
277      }
278    }
279