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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS 026 */ 027package org.opends.server.schema; 028 029import org.opends.server.admin.std.server.AttributeSyntaxCfg; 030import org.forgerock.opendj.ldap.schema.Schema; 031import org.forgerock.opendj.ldap.schema.Syntax; 032import org.opends.server.api.AttributeSyntax; 033import static org.opends.messages.SchemaMessages.*; 034 035import org.forgerock.i18n.LocalizableMessageBuilder; 036 037import static org.opends.server.schema.SchemaConstants.*; 038import static org.opends.server.util.StaticUtils.*; 039 040/** 041 * This class implements the guide attribute syntax, which may be used to 042 * provide criteria for generating search filters for entries, optionally tied 043 * to a specified objectclass. 044 */ 045public class GuideSyntax 046 extends AttributeSyntax<AttributeSyntaxCfg> 047{ 048 049 /** 050 * Creates a new instance of this syntax. Note that the only thing that 051 * should be done here is to invoke the default constructor for the 052 * superclass. All initialization should be performed in the 053 * <CODE>initializeSyntax</CODE> method. 054 */ 055 public GuideSyntax() 056 { 057 super(); 058 } 059 060 /** {@inheritDoc} */ 061 @Override 062 public Syntax getSDKSyntax(Schema schema) 063 { 064 return schema.getSyntax(SchemaConstants.SYNTAX_GUIDE_OID); 065 } 066 067 /** 068 * Retrieves the common name for this attribute syntax. 069 * 070 * @return The common name for this attribute syntax. 071 */ 072 @Override 073 public String getName() 074 { 075 return SYNTAX_GUIDE_NAME; 076 } 077 078 /** 079 * Retrieves the OID for this attribute syntax. 080 * 081 * @return The OID for this attribute syntax. 082 */ 083 @Override 084 public String getOID() 085 { 086 return SYNTAX_GUIDE_OID; 087 } 088 089 /** 090 * Retrieves a description for this attribute syntax. 091 * 092 * @return A description for this attribute syntax. 093 */ 094 @Override 095 public String getDescription() 096 { 097 return SYNTAX_GUIDE_DESCRIPTION; 098 } 099 100 /** 101 * Determines whether the provided string represents a valid criteria 102 * according to the guide syntax. 103 * 104 * @param criteria The portion of the criteria for which to make the 105 * determination. 106 * @param valueStr The complete guide value provided by the client. 107 * @param invalidReason The buffer to which to append the reason that the 108 * criteria is invalid if a problem is found. 109 * 110 * @return <CODE>true</CODE> if the provided string does contain a valid 111 * criteria, or <CODE>false</CODE> if not. 112 */ 113 public static boolean criteriaIsValid(String criteria, String valueStr, 114 LocalizableMessageBuilder invalidReason) 115 { 116 // See if the criteria starts with a '!'. If so, then just evaluate 117 // everything after that as a criteria. 118 char c = criteria.charAt(0); 119 if (c == '!') 120 { 121 return criteriaIsValid(criteria.substring(1), valueStr, invalidReason); 122 } 123 124 125 // See if the criteria starts with a '('. If so, then find the 126 // corresponding ')' and parse what's in between as a criteria. 127 if (c == '(') 128 { 129 int length = criteria.length(); 130 int depth = 1; 131 132 for (int i=1; i < length; i++) 133 { 134 c = criteria.charAt(i); 135 if (c == ')') 136 { 137 depth--; 138 if (depth == 0) 139 { 140 String subCriteria = criteria.substring(1, i); 141 if (! criteriaIsValid(subCriteria, valueStr, invalidReason)) 142 { 143 return false; 144 } 145 146 // If we are at the end of the value, then it was valid. Otherwise, 147 // the next character must be a pipe or an ampersand followed by 148 // another set of criteria. 149 if (i == (length-1)) 150 { 151 return true; 152 } 153 else 154 { 155 c = criteria.charAt(i+1); 156 if (c == '|' || c == '&') 157 { 158 return criteriaIsValid(criteria.substring(i+2), valueStr, 159 invalidReason); 160 } 161 else 162 { 163 invalidReason.append( 164 ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 165 valueStr, criteria, c, i+1)); 166 return false; 167 } 168 } 169 } 170 } 171 else if (c == '(') 172 { 173 depth++; 174 } 175 } 176 177 178 // If we've gotten here, then we went through the entire value without 179 // finding the appropriate closing parenthesis. 180 181 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_MISSING_CLOSE_PAREN.get( 182 valueStr, criteria)); 183 return false; 184 } 185 186 187 // See if the criteria starts with a '?'. If so, then it must be either 188 // "?true" or "?false". 189 if (c == '?') 190 { 191 if (criteria.startsWith("?true")) 192 { 193 if (criteria.length() == 5) 194 { 195 return true; 196 } 197 else 198 { 199 // The only characters allowed next are a pipe or an ampersand. 200 c = criteria.charAt(5); 201 if (c == '|' || c == '&') 202 { 203 return criteriaIsValid(criteria.substring(6), valueStr, 204 invalidReason); 205 } 206 else 207 { 208 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 209 valueStr, criteria, c, 5)); 210 return false; 211 } 212 } 213 } 214 else if (criteria.startsWith("?false")) 215 { 216 if (criteria.length() == 6) 217 { 218 return true; 219 } 220 else 221 { 222 // The only characters allowed next are a pipe or an ampersand. 223 c = criteria.charAt(6); 224 if (c == '|' || c == '&') 225 { 226 return criteriaIsValid(criteria.substring(7), valueStr, 227 invalidReason); 228 } 229 else 230 { 231 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 232 valueStr, criteria, c, 6)); 233 return false; 234 } 235 } 236 } 237 else 238 { 239 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_QUESTION_MARK.get( 240 valueStr, criteria)); 241 return false; 242 } 243 } 244 245 246 // See if the criteria is either "true" or "false". If so, then it is 247 // valid. 248 if (criteria.equals("true") || criteria.equals("false")) 249 { 250 return true; 251 } 252 253 254 // The only thing that will be allowed is an attribute type name or OID 255 // followed by a dollar sign and a match type. Find the dollar sign and 256 // verify whether the value before it is a valid attribute type name or OID. 257 int dollarPos = criteria.indexOf('$'); 258 if (dollarPos < 0) 259 { 260 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_DOLLAR.get( 261 valueStr, criteria)); 262 return false; 263 } 264 else if (dollarPos == 0) 265 { 266 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_ATTR.get( 267 valueStr, criteria)); 268 return false; 269 } 270 else if (dollarPos == (criteria.length()-1)) 271 { 272 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_MATCH_TYPE.get( 273 valueStr, criteria)); 274 return false; 275 } 276 else 277 { 278 if (! isValidSchemaElement(criteria, 0, dollarPos, invalidReason)) 279 { 280 return false; 281 } 282 } 283 284 285 // The substring immediately after the dollar sign must be one of "eq", 286 // "substr", "ge", "le", or "approx". It may be followed by the end of the 287 // value, a pipe, or an ampersand. 288 int endPos; 289 c = criteria.charAt(dollarPos+1); 290 switch (c) 291 { 292 case 'e': 293 if (criteria.startsWith("eq", dollarPos+1)) 294 { 295 endPos = dollarPos + 3; 296 break; 297 } 298 else 299 { 300 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 301 valueStr, criteria, dollarPos+1)); 302 return false; 303 } 304 305 case 's': 306 if (criteria.startsWith("substr", dollarPos+1)) 307 { 308 endPos = dollarPos + 7; 309 break; 310 } 311 else 312 { 313 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 314 valueStr, criteria, dollarPos+1)); 315 return false; 316 } 317 318 case 'g': 319 if (criteria.startsWith("ge", dollarPos+1)) 320 { 321 endPos = dollarPos + 3; 322 break; 323 } 324 else 325 { 326 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 327 valueStr, criteria, dollarPos+1)); 328 return false; 329 } 330 331 case 'l': 332 if (criteria.startsWith("le", dollarPos+1)) 333 { 334 endPos = dollarPos + 3; 335 break; 336 } 337 else 338 { 339 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 340 valueStr, criteria, dollarPos+1)); 341 return false; 342 } 343 344 case 'a': 345 if (criteria.startsWith("approx", dollarPos+1)) 346 { 347 endPos = dollarPos + 7; 348 break; 349 } 350 else 351 { 352 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 353 valueStr, criteria, dollarPos+1)); 354 return false; 355 } 356 357 default: 358 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE.get( 359 valueStr, criteria, dollarPos+1)); 360 return false; 361 } 362 363 364 // See if we are at the end of the value. If so, then it is valid. 365 // Otherwise, the next character must be a pipe or an ampersand. 366 if (endPos >= criteria.length()) 367 { 368 return true; 369 } 370 else 371 { 372 c = criteria.charAt(endPos); 373 if (c == '|' || c == '&') 374 { 375 return criteriaIsValid(criteria.substring(endPos+1), valueStr, 376 invalidReason); 377 } 378 else 379 { 380 invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get( 381 valueStr, criteria, c, endPos)); 382 return false; 383 } 384 } 385 } 386} 387