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