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