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