mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
10.10.2014 b24aef17bc8d92cc5438783fc38bc290c562b3bf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2014 ForgeRock AS
 */
package org.opends.server.api;
 
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
 
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
 
/**
 * This class defines the set of methods and structures that must be
 * implemented by a Directory Server module that implements a matching
 * rule used for substring matching.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.VOLATILE,
     mayInstantiate=false,
     mayExtend=true,
     mayInvoke=false)
public abstract class SubstringMatchingRule
        extends AbstractMatchingRule
        implements MatchingRule
{
  /**
   * Normalizes the provided value fragment into a form that can be
   * used to efficiently compare values.
   *
   * @param  substring  The value fragment to be normalized.
   *
   * @return  The normalized form of the value fragment.
   *
   * @throws  DecodeException  If the provided value fragment is
   *                              not acceptable according to the
   *                              associated syntax.
   */
  public abstract ByteString normalizeSubstring(
      ByteSequence substring) throws DecodeException;
 
 
 
  /**
   * Determines whether the provided value matches the given substring
   * filter components.  Note that any of the substring filter
   * components may be {@code null} but at least one of them must be
   * non-{@code null}.
   *
   * @param  value           The normalized value against which to
   *                         compare the substring components.
   * @param  subInitial      The normalized substring value fragment
   *                         that should appear at the beginning of
   *                         the target value.
   * @param  subAnyElements  The normalized substring value fragments
   *                         that should appear in the middle of the
   *                         target value.
   * @param  subFinal        The normalized substring value fragment
   *                         that should appear at the end of the
   *                         target value.
   *
   * @return  {@code true} if the provided value does match the given
   *          substring components, or {@code false} if not.
   */
  public boolean valueMatchesSubstring(ByteSequence value,
                                    ByteSequence subInitial,
                                    List<ByteSequence> subAnyElements,
                                    ByteSequence subFinal)
  {
    int valueLength = value.length();
 
    int pos = 0;
    if (subInitial != null)
    {
      int initialLength = subInitial.length();
      if (initialLength > valueLength)
      {
        return false;
      }
 
      for (; pos < initialLength; pos++)
      {
        if (subInitial.byteAt(pos) != value.byteAt(pos))
        {
          return false;
        }
      }
    }
 
 
    if ((subAnyElements != null) && (! subAnyElements.isEmpty()))
    {
      for (ByteSequence element : subAnyElements)
      {
        int anyLength = element.length();
        if(anyLength == 0)
            continue;
        int end = valueLength - anyLength;
        boolean match = false;
        for (; pos <= end; pos++)
        {
          if (element.byteAt(0) == value.byteAt(pos))
          {
            boolean subMatch = true;
            for (int i=1; i < anyLength; i++)
            {
              if (element.byteAt(i) != value.byteAt(pos+i))
              {
                subMatch = false;
                break;
              }
            }
 
            if (subMatch)
            {
              match = subMatch;
              break;
            }
          }
        }
 
        if (match)
        {
          pos += anyLength;
        }
        else
        {
          return false;
        }
      }
    }
 
 
    if (subFinal != null)
    {
      int finalLength = subFinal.length();
 
      if ((valueLength - finalLength) < pos)
      {
        return false;
      }
 
      pos = valueLength - finalLength;
      for (int i=0; i < finalLength; i++,pos++)
      {
        if (subFinal.byteAt(i) != value.byteAt(pos))
        {
          return false;
        }
      }
    }
 
 
    return true;
  }
 
  /**
   * Default assertion implementation for substring matching rules.
   * For example, with the assertion value "initial*any1*any2*any3*final",
   * the assertion will be decomposed like this:
   * <ul>
   * <li>normInitial will contain "initial"</li>
   * <li>normAnys will contain [ "any1", "any2", "any3" ]</li>
   * <li>normFinal will contain "final"</li>
   * </ul>
   */
  static final class DefaultSubstringAssertion implements Assertion {
      /** Normalized substring for the text before the first '*' character. */
      private final ByteString normInitial;
      /** Normalized substrings for all text chunks in between '*' characters. */
      private final ByteString[] normAnys;
      /** Normalized substring for the text after the last '*' character. */
      private final ByteString normFinal;
 
      private DefaultSubstringAssertion(final ByteString normInitial,
              final ByteString[] normAnys, final ByteString normFinal) {
          this.normInitial = normInitial;
          this.normAnys = normAnys;
          this.normFinal = normFinal;
      }
 
      /** {@inheritDoc} */
      @Override
      public ConditionResult matches(final ByteSequence normalizedAttributeValue) {
          final int valueLength = normalizedAttributeValue.length();
 
          int pos = 0;
          if (normInitial != null) {
              final int initialLength = normInitial.length();
              if (initialLength > valueLength) {
                  return ConditionResult.FALSE;
              }
 
              for (; pos < initialLength; pos++) {
                  if (normInitial.byteAt(pos) != normalizedAttributeValue.byteAt(pos)) {
                      return ConditionResult.FALSE;
                  }
              }
          }
 
          if (normAnys != null) {
          matchEachSubstring:
              for (final ByteSequence element : normAnys) {
                  final int anyLength = element.length();
                  final int end = valueLength - anyLength;
              matchCurrentSubstring:
                  for (; pos <= end; pos++) {
                      // Try to match all characters from the substring
                      for (int i = 0; i < anyLength; i++) {
                          if (element.byteAt(i) != normalizedAttributeValue.byteAt(pos + i)) {
                              // not a match,
                              // try to find a match in the rest of this value
                              continue matchCurrentSubstring;
                          }
                      }
                      // we just matched current substring,
                      // go try to match the next substring
                      pos += anyLength;
                      continue matchEachSubstring;
                  }
                  // Could not match current substring
                  return ConditionResult.FALSE;
              }
          }
 
          if (normFinal != null) {
              final int finalLength = normFinal.length();
 
              if (valueLength - finalLength < pos) {
                  return ConditionResult.FALSE;
              }
 
              pos = valueLength - finalLength;
              for (int i = 0; i < finalLength; i++, pos++) {
                  if (normFinal.byteAt(i) != normalizedAttributeValue.byteAt(pos)) {
                      return ConditionResult.FALSE;
                  }
              }
          }
 
          return ConditionResult.TRUE;
      }
 
      /** {@inheritDoc} */
      @Override
      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
          final Collection<T> subqueries = new LinkedList<T>();
          if (normInitial != null) {
              // relies on the fact that equality indexes are also ordered
              subqueries.add(rangeMatch(factory, "equality", normInitial));
          }
          if (normAnys != null) {
              for (ByteString normAny : normAnys) {
                  substringMatch(factory, normAny, subqueries);
              }
          }
          if (normFinal != null) {
              substringMatch(factory, normFinal, subqueries);
          }
          if (normInitial != null) {
              // Add this one last to minimize the risk to run the same search twice
              // (possible overlapping with the use of equality index at the start of this method)
              substringMatch(factory, normInitial, subqueries);
          }
          return factory.createIntersectionQuery(subqueries);
      }
 
      private <T> T rangeMatch(IndexQueryFactory<T> factory, String indexID, ByteSequence lower) {
          // Iterate through all the keys that have this value as the prefix.
 
          // Set the upper bound for a range search.
          // We need a key for the upper bound that is of equal length
          // but slightly greater than the lower bound.
          final ByteStringBuilder upper = new ByteStringBuilder(lower);
 
          for (int i = upper.length() - 1; i >= 0; i--) {
              if (upper.byteAt(i) == (byte) 0xFF) {
                  // We have to carry the overflow to the more significant byte.
                  upper.setByte(i, (byte) 0);
              } else {
                  // No overflow, we can stop.
                  upper.setByte(i, (byte) (upper.byteAt(i) + 1));
                  break;
              }
          }
 
          // Read the range: lower <= keys < upper.
          return factory.createRangeMatchQuery(indexID, lower, upper, true, false);
      }
 
      private <T> void substringMatch(final IndexQueryFactory<T> factory, final ByteString normSubstring,
              final Collection<T> subqueries) {
          int substrLength = factory.getIndexingOptions().substringKeySize();
 
          // There are two cases, depending on whether the user-provided
          // substring is smaller than the configured index substring length or not.
          if (normSubstring.length() < substrLength) {
              subqueries.add(rangeMatch(factory, "substring", normSubstring));
          } else {
              // Break the value up into fragments of length equal to the
              // index substring length, and read those keys.
 
              // Eliminate duplicates by putting the keys into a set.
              final TreeSet<ByteSequence> substringKeys = new TreeSet<ByteSequence>();
 
              // Example: The value is ABCDE and the substring length is 3.
              // We produce the keys ABC BCD CDE.
              for (int first = 0, last = substrLength;
                   last <= normSubstring.length(); first++, last++) {
                  substringKeys.add(normSubstring.subSequence(first, first + substrLength));
              }
 
              for (ByteSequence key : substringKeys) {
                  subqueries.add(factory.createExactMatchQuery("substring", key));
              }
          }
      }
 
  }
 
  /** {@inheritDoc} */
  @Override
  public Assertion getSubstringAssertion(ByteSequence subInitial,
      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal)
      throws DecodeException
  {
    final ByteString normInitial = subInitial == null ? null : normalizeSubstring(subInitial);
 
    ByteString[] normAnys = null;
    if (subAnyElements != null && !subAnyElements.isEmpty())
    {
      normAnys = new ByteString[subAnyElements.size()];
      for (int i = 0; i < subAnyElements.size(); i++)
      {
        normAnys[i] = normalizeSubstring(subAnyElements.get(i));
      }
    }
    final ByteString normFinal = subFinal == null ? null : normalizeSubstring(subFinal);
 
    return new DefaultSubstringAssertion(normInitial, normAnys, normFinal);
  }
 
}