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    
029    
030    
031    import java.util.List;
032    
033    import org.opends.server.admin.std.server.SubstringMatchingRuleCfg;
034    import org.opends.server.api.SubstringMatchingRule;
035    import org.opends.server.config.ConfigException;
036    import org.opends.server.core.DirectoryServer;
037    
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.InitializationException;
042    import org.opends.server.types.ResultCode;
043    
044    import static org.opends.server.loggers.ErrorLogger.*;
045    import static org.opends.messages.SchemaMessages.*;
046    import org.opends.messages.Message;
047    import static org.opends.server.schema.SchemaConstants.*;
048    
049    
050    /**
051     * This class implements the caseExactIA5SubstringsMatch matching rule.  This
052     * matching rule actually isn't defined in any official specification, but some
053     * directory vendors do provide an implementation using an OID from their own
054     * private namespace.
055     */
056    public class CaseExactIA5SubstringMatchingRule
057           extends SubstringMatchingRule
058    {
059      /**
060       * Creates a new instance of this caseExactSubstringsMatch matching rule.
061       */
062      public CaseExactIA5SubstringMatchingRule()
063      {
064        super();
065      }
066    
067    
068    
069      /**
070       * {@inheritDoc}
071       */
072      public void initializeMatchingRule(SubstringMatchingRuleCfg configuration)
073             throws ConfigException, InitializationException
074      {
075        // No initialization is required.
076      }
077    
078    
079    
080      /**
081       * Retrieves the common name for this matching rule.
082       *
083       * @return  The common name for this matching rule, or <CODE>null</CODE> if
084       * it does not have a name.
085       */
086      public String getName()
087      {
088        return SMR_CASE_EXACT_IA5_NAME;
089      }
090    
091    
092    
093      /**
094       * Retrieves the OID for this matching rule.
095       *
096       * @return  The OID for this matching rule.
097       */
098      public String getOID()
099      {
100        return SMR_CASE_EXACT_IA5_OID;
101      }
102    
103    
104    
105      /**
106       * Retrieves the description for this matching rule.
107       *
108       * @return  The description for this matching rule, or <CODE>null</CODE> if
109       *          there is none.
110       */
111      public String getDescription()
112      {
113        // There is no standard description for this matching rule.
114        return null;
115      }
116    
117    
118    
119      /**
120       * Retrieves the OID of the syntax with which this matching rule is
121       * associated.
122       *
123       * @return  The OID of the syntax with which this matching rule is associated.
124       */
125      public String getSyntaxOID()
126      {
127        return SYNTAX_SUBSTRING_ASSERTION_OID;
128      }
129    
130    
131    
132      /**
133       * Retrieves the normalized form of the provided value, which is best suited
134       * for efficiently performing matching operations on that value.
135       *
136       * @param  value  The value to be normalized.
137       *
138       * @return  The normalized version of the provided value.
139       *
140       * @throws  DirectoryException  If the provided value is invalid according to
141       *                              the associated attribute syntax.
142       */
143      public ByteString normalizeValue(ByteString value)
144             throws DirectoryException
145      {
146        StringBuilder buffer = new StringBuilder();
147        buffer.append(value.stringValue().trim());
148    
149        int bufferLength = buffer.length();
150        if (bufferLength == 0)
151        {
152          if (value.value().length > 0)
153          {
154            // This should only happen if the value is composed entirely of spaces.
155            // In that case, the normalized value is a single space.
156            return new ASN1OctetString(" ");
157          }
158          else
159          {
160            // The value is empty, so it is already normalized.
161            return new ASN1OctetString();
162          }
163        }
164    
165    
166        // Replace any consecutive spaces with a single space, and watch out for
167        // non-ASCII characters.
168        boolean logged = false;
169        for (int pos = bufferLength-1; pos > 0; pos--)
170        {
171          char c = buffer.charAt(pos);
172          if (c == ' ')
173          {
174            if (buffer.charAt(pos-1) == ' ')
175            {
176              buffer.delete(pos, pos+1);
177            }
178          }
179          else if ((c & 0x7F) != c)
180          {
181            // This is not a valid character for an IA5 string.  If strict syntax
182            // enforcement is enabled, then we'll throw an exception.  Otherwise,
183            // we'll get rid of the character.
184            Message message = WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
185                    value.stringValue(), String.valueOf(c));
186    
187            switch (DirectoryServer.getSyntaxEnforcementPolicy())
188            {
189              case REJECT:
190                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
191                                             message);
192              case WARN:
193                if (! logged)
194                {
195                  logError(message);
196                  logged = true;
197                }
198    
199                buffer.delete(pos, pos+1);
200                break;
201    
202              default:
203                buffer.delete(pos, pos+1);
204                break;
205            }
206          }
207        }
208    
209        return new ASN1OctetString(buffer.toString());
210      }
211    
212    
213    
214      /**
215       * Normalizes the provided value fragment into a form that can be used to
216       * efficiently compare values.
217       *
218       * @param  substring  The value fragment to be normalized.
219       *
220       * @return  The normalized form of the value fragment.
221       *
222       * @throws  DirectoryException  If the provided value fragment is not
223       *                              acceptable according to the associated syntax.
224       */
225      public ByteString normalizeSubstring(ByteString substring)
226             throws DirectoryException
227      {
228        // In this case, the process for normalizing a substring is the same as
229        // normalizing a full value with the exception that it may include an
230        // opening or trailing space.
231        StringBuilder buffer = new StringBuilder();
232        buffer.append(substring.stringValue());
233    
234        int bufferLength = buffer.length();
235        if (bufferLength == 0)
236        {
237          if (substring.value().length > 0)
238          {
239            // This should only happen if the value is composed entirely of spaces.
240            // In that case, the normalized value is a single space.
241            return new ASN1OctetString(" ");
242          }
243          else
244          {
245            // The value is empty, so it is already normalized.
246            return substring;
247          }
248        }
249    
250    
251        // Replace any consecutive spaces with a single space, and watch out for
252        // non-ASCII characters.
253        boolean logged = false;
254        for (int pos = bufferLength-1; pos > 0; pos--)
255        {
256          char c = buffer.charAt(pos);
257          if (c == ' ')
258          {
259            if (buffer.charAt(pos-1) == ' ')
260            {
261              buffer.delete(pos, pos+1);
262            }
263          }
264          else if ((c & 0x7F) != c)
265          {
266            // This is not a valid character for an IA5 string.  If strict syntax
267            // enforcement is enabled, then we'll throw an exception.  Otherwise,
268            // we'll get rid of the character.
269            Message message = WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
270                    substring.stringValue(), String.valueOf(c));
271    
272            switch (DirectoryServer.getSyntaxEnforcementPolicy())
273            {
274              case REJECT:
275                throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
276                                             message);
277              case WARN:
278                if (! logged)
279                {
280                  logError(message);
281                  logged = true;
282                }
283    
284                buffer.delete(pos, pos+1);
285                break;
286    
287              default:
288                buffer.delete(pos, pos+1);
289                break;
290            }
291          }
292        }
293    
294        return new ASN1OctetString(buffer.toString());
295      }
296    
297    
298    
299      /**
300       * Determines whether the provided value matches the given substring filter
301       * components.  Note that any of the substring filter components may be
302       * <CODE>null</CODE> but at least one of them must be non-<CODE>null</CODE>.
303       *
304       * @param  value           The normalized value against which to compare the
305       *                         substring components.
306       * @param  subInitial      The normalized substring value fragment that should
307       *                         appear at the beginning of the target value.
308       * @param  subAnyElements  The normalized substring value fragments that
309       *                         should appear in the middle of the target value.
310       * @param  subFinal        The normalized substring value fragment that should
311       *                         appear at the end of the target value.
312       *
313       * @return  <CODE>true</CODE> if the provided value does match the given
314       *          substring components, or <CODE>false</CODE> if not.
315       */
316      public boolean valueMatchesSubstring(ByteString value, ByteString subInitial,
317                                           List<ByteString> subAnyElements,
318                                           ByteString subFinal)
319      {
320        byte[] valueBytes = value.value();
321        int valueLength = valueBytes.length;
322    
323        int pos = 0;
324        if (subInitial != null)
325        {
326          byte[] initialBytes = subInitial.value();
327          int initialLength = initialBytes.length;
328          if (initialLength > valueLength)
329          {
330            return false;
331          }
332    
333          for (; pos < initialLength; pos++)
334          {
335            if (initialBytes[pos] != valueBytes[pos])
336            {
337              return false;
338            }
339          }
340        }
341    
342    
343        if ((subAnyElements != null) && (! subAnyElements.isEmpty()))
344        {
345          for (ByteString element : subAnyElements)
346          {
347            byte[] anyBytes = element.value();
348            int anyLength = anyBytes.length;
349    
350            int end = valueLength - anyLength;
351            boolean match = false;
352            for (; pos <= end; pos++)
353            {
354              if (anyBytes[0] == valueBytes[pos])
355              {
356                boolean subMatch = true;
357                for (int i=1; i < anyLength; i++)
358                {
359                  if (anyBytes[i] != valueBytes[pos+i])
360                  {
361                    subMatch = false;
362                    break;
363                  }
364                }
365    
366                if (subMatch)
367                {
368                  match = subMatch;
369                  break;
370                }
371              }
372            }
373    
374            if (match)
375            {
376              pos += anyLength;
377            }
378            else
379            {
380              return false;
381            }
382          }
383        }
384    
385    
386        if (subFinal != null)
387        {
388          byte[] finalBytes = subFinal.value();
389          int finalLength = finalBytes.length;
390    
391          if ((valueLength - finalLength) < pos)
392          {
393            return false;
394          }
395    
396          pos = valueLength - finalLength;
397          for (int i=0; i < finalLength; i++,pos++)
398          {
399            if (finalBytes[i] != valueBytes[pos])
400            {
401              return false;
402            }
403          }
404        }
405    
406    
407        return true;
408      }
409    }
410