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