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 org.opends.server.admin.std.server.AttributeSyntaxCfg; 032 import org.opends.server.api.ApproximateMatchingRule; 033 import org.opends.server.api.AttributeSyntax; 034 import org.opends.server.api.EqualityMatchingRule; 035 import org.opends.server.api.OrderingMatchingRule; 036 import org.opends.server.api.SubstringMatchingRule; 037 import org.opends.server.config.ConfigException; 038 import org.opends.server.core.DirectoryServer; 039 import org.opends.server.types.ByteString; 040 041 042 043 import static org.opends.server.loggers.ErrorLogger.*; 044 import static org.opends.messages.SchemaMessages.*; 045 import org.opends.messages.MessageBuilder; 046 import static org.opends.server.schema.SchemaConstants.*; 047 import static org.opends.server.util.StaticUtils.*; 048 049 050 /** 051 * This class implements the guide attribute syntax, which may be used to 052 * provide criteria for generating search filters for entries, optionally tied 053 * to a specified objectclass. 054 */ 055 public class GuideSyntax 056 extends AttributeSyntax<AttributeSyntaxCfg> 057 { 058 // The default equality matching rule for this syntax. 059 private EqualityMatchingRule defaultEqualityMatchingRule; 060 061 // The default ordering matching rule for this syntax. 062 private OrderingMatchingRule defaultOrderingMatchingRule; 063 064 // The default substring matching rule for this syntax. 065 private SubstringMatchingRule defaultSubstringMatchingRule; 066 067 068 069 /** 070 * Creates a new instance of this syntax. Note that the only thing that 071 * should be done here is to invoke the default constructor for the 072 * superclass. All initialization should be performed in the 073 * <CODE>initializeSyntax</CODE> method. 074 */ 075 public GuideSyntax() 076 { 077 super(); 078 } 079 080 081 082 /** 083 * {@inheritDoc} 084 */ 085 public void initializeSyntax(AttributeSyntaxCfg configuration) 086 throws ConfigException 087 { 088 defaultEqualityMatchingRule = 089 DirectoryServer.getEqualityMatchingRule(EMR_OCTET_STRING_OID); 090 if (defaultEqualityMatchingRule == null) 091 { 092 logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get( 093 EMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME)); 094 } 095 096 defaultOrderingMatchingRule = 097 DirectoryServer.getOrderingMatchingRule(OMR_OCTET_STRING_OID); 098 if (defaultOrderingMatchingRule == null) 099 { 100 logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get( 101 OMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME)); 102 } 103 104 defaultSubstringMatchingRule = 105 DirectoryServer.getSubstringMatchingRule(SMR_OCTET_STRING_OID); 106 if (defaultSubstringMatchingRule == null) 107 { 108 logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get( 109 SMR_OCTET_STRING_OID, SYNTAX_GUIDE_NAME)); 110 } 111 } 112 113 114 115 /** 116 * Retrieves the common name for this attribute syntax. 117 * 118 * @return The common name for this attribute syntax. 119 */ 120 public String getSyntaxName() 121 { 122 return SYNTAX_GUIDE_NAME; 123 } 124 125 126 127 /** 128 * Retrieves the OID for this attribute syntax. 129 * 130 * @return The OID for this attribute syntax. 131 */ 132 public String getOID() 133 { 134 return SYNTAX_GUIDE_OID; 135 } 136 137 138 139 /** 140 * Retrieves a description for this attribute syntax. 141 * 142 * @return A description for this attribute syntax. 143 */ 144 public String getDescription() 145 { 146 return SYNTAX_GUIDE_DESCRIPTION; 147 } 148 149 150 151 /** 152 * Retrieves the default equality matching rule that will be used for 153 * attributes with this syntax. 154 * 155 * @return The default equality matching rule that will be used for 156 * attributes with this syntax, or <CODE>null</CODE> if equality 157 * matches will not be allowed for this type by default. 158 */ 159 public EqualityMatchingRule getEqualityMatchingRule() 160 { 161 return defaultEqualityMatchingRule; 162 } 163 164 165 166 /** 167 * Retrieves the default ordering matching rule that will be used for 168 * attributes with this syntax. 169 * 170 * @return The default ordering matching rule that will be used for 171 * attributes with this syntax, or <CODE>null</CODE> if ordering 172 * matches will not be allowed for this type by default. 173 */ 174 public OrderingMatchingRule getOrderingMatchingRule() 175 { 176 return defaultOrderingMatchingRule; 177 } 178 179 180 181 /** 182 * Retrieves the default substring matching rule that will be used for 183 * attributes with this syntax. 184 * 185 * @return The default substring matching rule that will be used for 186 * attributes with this syntax, or <CODE>null</CODE> if substring 187 * matches will not be allowed for this type by default. 188 */ 189 public SubstringMatchingRule getSubstringMatchingRule() 190 { 191 return defaultSubstringMatchingRule; 192 } 193 194 195 196 /** 197 * Retrieves the default approximate matching rule that will be used for 198 * attributes with this syntax. 199 * 200 * @return The default approximate matching rule that will be used for 201 * attributes with this syntax, or <CODE>null</CODE> if approximate 202 * matches will not be allowed for this type by default. 203 */ 204 public ApproximateMatchingRule getApproximateMatchingRule() 205 { 206 // There is no approximate matching rule by default. 207 return null; 208 } 209 210 211 212 /** 213 * Indicates whether the provided value is acceptable for use in an attribute 214 * with this syntax. If it is not, then the reason may be appended to the 215 * provided buffer. 216 * 217 * @param value The value for which to make the determination. 218 * @param invalidReason The buffer to which the invalid reason should be 219 * appended. 220 * 221 * @return <CODE>true</CODE> if the provided value is acceptable for use with 222 * this syntax, or <CODE>false</CODE> if not. 223 */ 224 public boolean valueIsAcceptable(ByteString value, 225 MessageBuilder invalidReason) 226 { 227 // Get a lowercase string version of the provided value. 228 String valueStr = toLowerCase(value.stringValue()); 229 230 231 // Find the position of the octothorpe. If there isn't one, then the entire 232 // value should be the criteria. 233 int sharpPos = valueStr.indexOf('#'); 234 if (sharpPos < 0) 235 { 236 return criteriaIsValid(valueStr, valueStr, invalidReason); 237 } 238 239 240 // Get the objectclass and see if it is a valid name or OID. 241 String ocName = valueStr.substring(0, sharpPos).trim(); 242 int ocLength = ocName.length(); 243 if (ocLength == 0) 244 { 245 246 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_OC.get(valueStr)); 247 return false; 248 } 249 250 if (! isValidSchemaElement(ocName, 0, ocLength, invalidReason)) 251 { 252 return false; 253 } 254 255 256 // The rest of the value must be the criteria. 257 return criteriaIsValid(valueStr.substring(sharpPos+1), valueStr, 258 invalidReason); 259 } 260 261 262 263 /** 264 * Determines whether the provided string represents a valid criteria 265 * according to the guide syntax. 266 * 267 * @param criteria The portion of the criteria for which to make the 268 * determination. 269 * @param valueStr The complete guide value provided by the client. 270 * @param invalidReason The buffer to which to append the reason that the 271 * criteria is invalid if a problem is found. 272 * 273 * @return <CODE>true</CODE> if the provided string does contain a valid 274 * criteria, or <CODE>false</CODE> if not. 275 */ 276 public static boolean criteriaIsValid(String criteria, String valueStr, 277 MessageBuilder invalidReason) 278 { 279 // See if the criteria starts with a '!'. If so, then just evaluate 280 // everything after that as a criteria. 281 char c = criteria.charAt(0); 282 if (c == '!') 283 { 284 return criteriaIsValid(criteria.substring(1), valueStr, invalidReason); 285 } 286 287 288 // See if the criteria starts with a '('. If so, then find the 289 // corresponding ')' and parse what's in between as a criteria. 290 if (c == '(') 291 { 292 int length = criteria.length(); 293 int depth = 1; 294 295 for (int i=1; i < length; i++) 296 { 297 c = criteria.charAt(i); 298 if (c == ')') 299 { 300 depth--; 301 if (depth == 0) 302 { 303 String subCriteria = criteria.substring(1, i); 304 if (! criteriaIsValid(subCriteria, valueStr, invalidReason)) 305 { 306 return false; 307 } 308 309 // If we are at the end of the value, then it was valid. Otherwise, 310 // the next character must be a pipe or an ampersand followed by 311 // another set of criteria. 312 if (i == (length-1)) 313 { 314 return true; 315 } 316 else 317 { 318 c = criteria.charAt(i+1); 319 if ((c == '|') || (c == '&')) 320 { 321 return criteriaIsValid(criteria.substring(i+2), valueStr, 322 invalidReason); 323 } 324 else 325 { 326 327 invalidReason.append( 328 ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 329 valueStr, criteria, c, (i+1))); 330 return false; 331 } 332 } 333 } 334 } 335 else if (c == '(') 336 { 337 depth++; 338 } 339 } 340 341 342 // If we've gotten here, then we went through the entire value without 343 // finding the appropriate closing parenthesis. 344 345 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_MISSING_CLOSE_PAREN.get( 346 valueStr, criteria)); 347 return false; 348 } 349 350 351 // See if the criteria starts with a '?'. If so, then it must be either 352 // "?true" or "?false". 353 if (c == '?') 354 { 355 if (criteria.startsWith("?true")) 356 { 357 if (criteria.length() == 5) 358 { 359 return true; 360 } 361 else 362 { 363 // The only characters allowed next are a pipe or an ampersand. 364 c = criteria.charAt(5); 365 if ((c == '|') || (c == '&')) 366 { 367 return criteriaIsValid(criteria.substring(6), valueStr, 368 invalidReason); 369 } 370 else 371 { 372 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 373 valueStr, criteria, c, 5)); 374 return false; 375 } 376 } 377 } 378 else if (criteria.startsWith("?false")) 379 { 380 if (criteria.length() == 6) 381 { 382 return true; 383 } 384 else 385 { 386 // The only characters allowed next are a pipe or an ampersand. 387 c = criteria.charAt(6); 388 if ((c == '|') || (c == '&')) 389 { 390 return criteriaIsValid(criteria.substring(7), valueStr, 391 invalidReason); 392 } 393 else 394 { 395 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 396 valueStr, criteria, c, 6)); 397 return false; 398 } 399 } 400 } 401 else 402 { 403 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_QUESTION_MARK.get( 404 valueStr, criteria)); 405 return false; 406 } 407 } 408 409 410 // See if the criteria is either "true" or "false". If so, then it is 411 // valid. 412 if (criteria.equals("true") || criteria.equals("false")) 413 { 414 return true; 415 } 416 417 418 // The only thing that will be allowed is an attribute type name or OID 419 // followed by a dollar sign and a match type. Find the dollar sign and 420 // verify whether the value before it is a valid attribute type name or OID. 421 int dollarPos = criteria.indexOf('$'); 422 if (dollarPos < 0) 423 { 424 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_DOLLAR.get( 425 valueStr, criteria)); 426 return false; 427 } 428 else if (dollarPos == 0) 429 { 430 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_ATTR.get( 431 valueStr, criteria)); 432 return false; 433 } 434 else if (dollarPos == (criteria.length()-1)) 435 { 436 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_MATCH_TYPE.get( 437 valueStr, criteria)); 438 return false; 439 } 440 else 441 { 442 if (! isValidSchemaElement(criteria, 0, dollarPos, invalidReason)) 443 { 444 return false; 445 } 446 } 447 448 449 // The substring immediately after the dollar sign must be one of "eq", 450 // "substr", "ge", "le", or "approx". It may be followed by the end of the 451 // value, a pipe, or an ampersand. 452 int endPos; 453 c = criteria.charAt(dollarPos+1); 454 switch (c) 455 { 456 case 'e': 457 if (criteria.startsWith("eq", dollarPos+1)) 458 { 459 endPos = dollarPos + 3; 460 break; 461 } 462 else 463 { 464 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 465 valueStr, criteria, dollarPos+1)); 466 return false; 467 } 468 469 case 's': 470 if (criteria.startsWith("substr", dollarPos+1)) 471 { 472 endPos = dollarPos + 7; 473 break; 474 } 475 else 476 { 477 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 478 valueStr, criteria, dollarPos+1)); 479 return false; 480 } 481 482 case 'g': 483 if (criteria.startsWith("ge", dollarPos+1)) 484 { 485 endPos = dollarPos + 3; 486 break; 487 } 488 else 489 { 490 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 491 valueStr, criteria, dollarPos+1)); 492 return false; 493 } 494 495 case 'l': 496 if (criteria.startsWith("le", dollarPos+1)) 497 { 498 endPos = dollarPos + 3; 499 break; 500 } 501 else 502 { 503 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 504 valueStr, criteria, dollarPos+1)); 505 return false; 506 } 507 508 case 'a': 509 if (criteria.startsWith("approx", dollarPos+1)) 510 { 511 endPos = dollarPos + 7; 512 break; 513 } 514 else 515 { 516 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 517 valueStr, criteria, dollarPos+1)); 518 return false; 519 } 520 521 default: 522 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 523 valueStr, criteria, dollarPos+1)); 524 return false; 525 } 526 527 528 // See if we are at the end of the value. If so, then it is valid. 529 // Otherwise, the next character must be a pipe or an ampersand. 530 if (endPos >= criteria.length()) 531 { 532 return true; 533 } 534 else 535 { 536 c = criteria.charAt(endPos); 537 if ((c == '|') || (c == '&')) 538 { 539 return criteriaIsValid(criteria.substring(endPos+1), valueStr, 540 invalidReason); 541 } 542 else 543 { 544 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 545 valueStr, criteria, c, endPos)); 546 return false; 547 } 548 } 549 } 550 } 551