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