001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.schema;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.Map;
028 import java.util.LinkedHashMap;
029
030 import com.unboundid.ldap.sdk.LDAPException;
031 import com.unboundid.ldap.sdk.ResultCode;
032
033 import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
034 import static com.unboundid.util.StaticUtils.*;
035 import static com.unboundid.util.Validator.*;
036
037
038
039 /**
040 * This class provides a data structure that describes an LDAP DIT content rule
041 * schema element.
042 */
043 public final class DITContentRuleDefinition
044 extends SchemaElement
045 {
046 /**
047 * The serial version UID for this serializable class.
048 */
049 private static final long serialVersionUID = 3224440505307817586L;
050
051
052
053 // Indicates whether this DIT content rule is declared obsolete.
054 private final boolean isObsolete;
055
056 // The set of extensions for this DIT content rule.
057 private final Map<String,String[]> extensions;
058
059 // The description for this DIT content rule.
060 private final String description;
061
062 // The string representation of this DIT content rule.
063 private final String ditContentRuleString;
064
065 // The OID of the structural object class with which this DIT content rule is
066 // associated.
067 private final String oid;
068
069 // The names/OIDs of the allowed auxiliary classes.
070 private final String[] auxiliaryClasses;
071
072 // The set of names for this DIT content rule.
073 private final String[] names;
074
075 // The names/OIDs of the optional attributes.
076 private final String[] optionalAttributes;
077
078 // The names/OIDs of the prohibited attributes.
079 private final String[] prohibitedAttributes;
080
081 // The names/OIDs of the required attributes.
082 private final String[] requiredAttributes;
083
084
085
086 /**
087 * Creates a new DIT content rule from the provided string representation.
088 *
089 * @param s The string representation of the DIT content rule to create,
090 * using the syntax described in RFC 4512 section 4.1.6. It must
091 * not be {@code null}.
092 *
093 * @throws LDAPException If the provided string cannot be decoded as a DIT
094 * content rule definition.
095 */
096 public DITContentRuleDefinition(final String s)
097 throws LDAPException
098 {
099 ensureNotNull(s);
100
101 ditContentRuleString = s.trim();
102
103 // The first character must be an opening parenthesis.
104 final int length = ditContentRuleString.length();
105 if (length == 0)
106 {
107 throw new LDAPException(ResultCode.DECODING_ERROR,
108 ERR_DCR_DECODE_EMPTY.get());
109 }
110 else if (ditContentRuleString.charAt(0) != '(')
111 {
112 throw new LDAPException(ResultCode.DECODING_ERROR,
113 ERR_DCR_DECODE_NO_OPENING_PAREN.get(
114 ditContentRuleString));
115 }
116
117
118 // Skip over any spaces until we reach the start of the OID, then read the
119 // OID until we find the next space.
120 int pos = skipSpaces(ditContentRuleString, 1, length);
121
122 StringBuilder buffer = new StringBuilder();
123 pos = readOID(ditContentRuleString, pos, length, buffer);
124 oid = buffer.toString();
125
126
127 // Technically, DIT content elements are supposed to appear in a specific
128 // order, but we'll be lenient and allow remaining elements to come in any
129 // order.
130 final ArrayList<String> nameList = new ArrayList<String>(1);
131 final ArrayList<String> reqAttrs = new ArrayList<String>();
132 final ArrayList<String> optAttrs = new ArrayList<String>();
133 final ArrayList<String> notAttrs = new ArrayList<String>();
134 final ArrayList<String> auxOCs = new ArrayList<String>();
135 final Map<String,String[]> exts = new LinkedHashMap<String,String[]>();
136 Boolean obsolete = null;
137 String descr = null;
138
139 while (true)
140 {
141 // Skip over any spaces until we find the next element.
142 pos = skipSpaces(ditContentRuleString, pos, length);
143
144 // Read until we find the next space or the end of the string. Use that
145 // token to figure out what to do next.
146 final int tokenStartPos = pos;
147 while ((pos < length) && (ditContentRuleString.charAt(pos) != ' '))
148 {
149 pos++;
150 }
151
152 // It's possible that the token could be smashed right up against the
153 // closing parenthesis. If that's the case, then extract just the token
154 // and handle the closing parenthesis the next time through.
155 String token = ditContentRuleString.substring(tokenStartPos, pos);
156 if ((token.length() > 1) && (token.endsWith(")")))
157 {
158 token = token.substring(0, token.length() - 1);
159 pos--;
160 }
161
162 final String lowerToken = toLowerCase(token);
163 if (lowerToken.equals(")"))
164 {
165 // This indicates that we're at the end of the value. There should not
166 // be any more closing characters.
167 if (pos < length)
168 {
169 throw new LDAPException(ResultCode.DECODING_ERROR,
170 ERR_DCR_DECODE_CLOSE_NOT_AT_END.get(
171 ditContentRuleString));
172 }
173 break;
174 }
175 else if (lowerToken.equals("name"))
176 {
177 if (nameList.isEmpty())
178 {
179 pos = skipSpaces(ditContentRuleString, pos, length);
180 pos = readQDStrings(ditContentRuleString, pos, length, nameList);
181 }
182 else
183 {
184 throw new LDAPException(ResultCode.DECODING_ERROR,
185 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
186 ditContentRuleString, "NAME"));
187 }
188 }
189 else if (lowerToken.equals("desc"))
190 {
191 if (descr == null)
192 {
193 pos = skipSpaces(ditContentRuleString, pos, length);
194
195 buffer = new StringBuilder();
196 pos = readQDString(ditContentRuleString, pos, length, buffer);
197 descr = buffer.toString();
198 }
199 else
200 {
201 throw new LDAPException(ResultCode.DECODING_ERROR,
202 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
203 ditContentRuleString, "DESC"));
204 }
205 }
206 else if (lowerToken.equals("obsolete"))
207 {
208 if (obsolete == null)
209 {
210 obsolete = true;
211 }
212 else
213 {
214 throw new LDAPException(ResultCode.DECODING_ERROR,
215 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
216 ditContentRuleString, "OBSOLETE"));
217 }
218 }
219 else if (lowerToken.equals("aux"))
220 {
221 if (auxOCs.isEmpty())
222 {
223 pos = skipSpaces(ditContentRuleString, pos, length);
224 pos = readOIDs(ditContentRuleString, pos, length, auxOCs);
225 }
226 else
227 {
228 throw new LDAPException(ResultCode.DECODING_ERROR,
229 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
230 ditContentRuleString, "AUX"));
231 }
232 }
233 else if (lowerToken.equals("must"))
234 {
235 if (reqAttrs.isEmpty())
236 {
237 pos = skipSpaces(ditContentRuleString, pos, length);
238 pos = readOIDs(ditContentRuleString, pos, length, reqAttrs);
239 }
240 else
241 {
242 throw new LDAPException(ResultCode.DECODING_ERROR,
243 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
244 ditContentRuleString, "MUST"));
245 }
246 }
247 else if (lowerToken.equals("may"))
248 {
249 if (optAttrs.isEmpty())
250 {
251 pos = skipSpaces(ditContentRuleString, pos, length);
252 pos = readOIDs(ditContentRuleString, pos, length, optAttrs);
253 }
254 else
255 {
256 throw new LDAPException(ResultCode.DECODING_ERROR,
257 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
258 ditContentRuleString, "MAY"));
259 }
260 }
261 else if (lowerToken.equals("not"))
262 {
263 if (notAttrs.isEmpty())
264 {
265 pos = skipSpaces(ditContentRuleString, pos, length);
266 pos = readOIDs(ditContentRuleString, pos, length, notAttrs);
267 }
268 else
269 {
270 throw new LDAPException(ResultCode.DECODING_ERROR,
271 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
272 ditContentRuleString, "NOT"));
273 }
274 }
275 else if (lowerToken.startsWith("x-"))
276 {
277 pos = skipSpaces(ditContentRuleString, pos, length);
278
279 final ArrayList<String> valueList = new ArrayList<String>();
280 pos = readQDStrings(ditContentRuleString, pos, length, valueList);
281
282 final String[] values = new String[valueList.size()];
283 valueList.toArray(values);
284
285 if (exts.containsKey(token))
286 {
287 throw new LDAPException(ResultCode.DECODING_ERROR,
288 ERR_DCR_DECODE_DUP_EXT.get(
289 ditContentRuleString, token));
290 }
291
292 exts.put(token, values);
293 }
294 else
295 {
296 throw new LDAPException(ResultCode.DECODING_ERROR,
297 ERR_DCR_DECODE_DUP_EXT.get(
298 ditContentRuleString, token));
299 }
300 }
301
302 description = descr;
303
304 names = new String[nameList.size()];
305 nameList.toArray(names);
306
307 auxiliaryClasses = new String[auxOCs.size()];
308 auxOCs.toArray(auxiliaryClasses);
309
310 requiredAttributes = new String[reqAttrs.size()];
311 reqAttrs.toArray(requiredAttributes);
312
313 optionalAttributes = new String[optAttrs.size()];
314 optAttrs.toArray(optionalAttributes);
315
316 prohibitedAttributes = new String[notAttrs.size()];
317 notAttrs.toArray(prohibitedAttributes);
318
319 isObsolete = (obsolete != null);
320
321 extensions = Collections.unmodifiableMap(exts);
322 }
323
324
325
326 /**
327 * Creates a new DIT content rule with the provided information.
328 *
329 * @param oid The OID for the structural object class with
330 * which this DIT content rule is associated.
331 * It must not be {@code null}.
332 * @param names The set of names for this DIT content rule.
333 * It may be {@code null} or empty if the DIT
334 * content rule should only be referenced by
335 * OID.
336 * @param description The description for this DIT content rule.
337 * It may be {@code null} if there is no
338 * description.
339 * @param isObsolete Indicates whether this DIT content rule is
340 * declared obsolete.
341 * @param auxiliaryClasses The names/OIDs of the auxiliary object
342 * classes that may be present in entries
343 * containing this DIT content rule.
344 * @param requiredAttributes The names/OIDs of the attributes which must
345 * be present in entries containing this DIT
346 * content rule.
347 * @param optionalAttributes The names/OIDs of the attributes which may be
348 * present in entries containing this DIT
349 * content rule.
350 * @param prohibitedAttributes The names/OIDs of the attributes which may
351 * not be present in entries containing this DIT
352 * content rule.
353 * @param extensions The set of extensions for this DIT content
354 * rule. It may be {@code null} or empty if
355 * there should not be any extensions.
356 */
357 public DITContentRuleDefinition(final String oid, final String[] names,
358 final String description,
359 final boolean isObsolete,
360 final String[] auxiliaryClasses,
361 final String[] requiredAttributes,
362 final String[] optionalAttributes,
363 final String[] prohibitedAttributes,
364 final Map<String,String[]> extensions)
365 {
366 ensureNotNull(oid);
367
368 this.oid = oid;
369 this.isObsolete = isObsolete;
370 this.description = description;
371
372 if (names == null)
373 {
374 this.names = NO_STRINGS;
375 }
376 else
377 {
378 this.names = names;
379 }
380
381 if (auxiliaryClasses == null)
382 {
383 this.auxiliaryClasses = NO_STRINGS;
384 }
385 else
386 {
387 this.auxiliaryClasses = auxiliaryClasses;
388 }
389
390 if (requiredAttributes == null)
391 {
392 this.requiredAttributes = NO_STRINGS;
393 }
394 else
395 {
396 this.requiredAttributes = requiredAttributes;
397 }
398
399 if (optionalAttributes == null)
400 {
401 this.optionalAttributes = NO_STRINGS;
402 }
403 else
404 {
405 this.optionalAttributes = optionalAttributes;
406 }
407
408 if (prohibitedAttributes == null)
409 {
410 this.prohibitedAttributes = NO_STRINGS;
411 }
412 else
413 {
414 this.prohibitedAttributes = prohibitedAttributes;
415 }
416
417 if (extensions == null)
418 {
419 this.extensions = Collections.emptyMap();
420 }
421 else
422 {
423 this.extensions = Collections.unmodifiableMap(extensions);
424 }
425
426 final StringBuilder buffer = new StringBuilder();
427 createDefinitionString(buffer);
428 ditContentRuleString = buffer.toString();
429 }
430
431
432
433 /**
434 * Constructs a string representation of this DIT content rule definition in
435 * the provided buffer.
436 *
437 * @param buffer The buffer in which to construct a string representation of
438 * this DIT content rule definition.
439 */
440 private void createDefinitionString(final StringBuilder buffer)
441 {
442 buffer.append("( ");
443 buffer.append(oid);
444
445 if (names.length == 1)
446 {
447 buffer.append(" NAME '");
448 buffer.append(names[0]);
449 buffer.append('\'');
450 }
451 else if (names.length > 1)
452 {
453 buffer.append(" NAME (");
454 for (final String name : names)
455 {
456 buffer.append(" '");
457 buffer.append(name);
458 buffer.append('\'');
459 }
460 buffer.append(" )");
461 }
462
463 if (description != null)
464 {
465 buffer.append(" DESC '");
466 encodeValue(description, buffer);
467 buffer.append('\'');
468 }
469
470 if (isObsolete)
471 {
472 buffer.append(" OBSOLETE");
473 }
474
475 if (auxiliaryClasses.length == 1)
476 {
477 buffer.append(" AUX ");
478 buffer.append(auxiliaryClasses[0]);
479 }
480 else if (auxiliaryClasses.length > 1)
481 {
482 buffer.append(" AUX (");
483 for (int i=0; i < auxiliaryClasses.length; i++)
484 {
485 if (i >0)
486 {
487 buffer.append(" $ ");
488 }
489 else
490 {
491 buffer.append(' ');
492 }
493 buffer.append(auxiliaryClasses[i]);
494 }
495 buffer.append(" )");
496 }
497
498 if (requiredAttributes.length == 1)
499 {
500 buffer.append(" MUST ");
501 buffer.append(requiredAttributes[0]);
502 }
503 else if (requiredAttributes.length > 1)
504 {
505 buffer.append(" MUST (");
506 for (int i=0; i < requiredAttributes.length; i++)
507 {
508 if (i >0)
509 {
510 buffer.append(" $ ");
511 }
512 else
513 {
514 buffer.append(' ');
515 }
516 buffer.append(requiredAttributes[i]);
517 }
518 buffer.append(" )");
519 }
520
521 if (optionalAttributes.length == 1)
522 {
523 buffer.append(" MAY ");
524 buffer.append(optionalAttributes[0]);
525 }
526 else if (optionalAttributes.length > 1)
527 {
528 buffer.append(" MAY (");
529 for (int i=0; i < optionalAttributes.length; i++)
530 {
531 if (i > 0)
532 {
533 buffer.append(" $ ");
534 }
535 else
536 {
537 buffer.append(' ');
538 }
539 buffer.append(optionalAttributes[i]);
540 }
541 buffer.append(" )");
542 }
543
544 if (prohibitedAttributes.length == 1)
545 {
546 buffer.append(" NOT ");
547 buffer.append(prohibitedAttributes[0]);
548 }
549 else if (prohibitedAttributes.length > 1)
550 {
551 buffer.append(" NOT (");
552 for (int i=0; i < prohibitedAttributes.length; i++)
553 {
554 if (i > 0)
555 {
556 buffer.append(" $ ");
557 }
558 else
559 {
560 buffer.append(' ');
561 }
562 buffer.append(prohibitedAttributes[i]);
563 }
564 buffer.append(" )");
565 }
566
567 for (final Map.Entry<String,String[]> e : extensions.entrySet())
568 {
569 final String name = e.getKey();
570 final String[] values = e.getValue();
571 if (values.length == 1)
572 {
573 buffer.append(' ');
574 buffer.append(name);
575 buffer.append(" '");
576 encodeValue(values[0], buffer);
577 buffer.append('\'');
578 }
579 else
580 {
581 buffer.append(' ');
582 buffer.append(name);
583 buffer.append(" (");
584 for (final String value : values)
585 {
586 buffer.append(" '");
587 encodeValue(value, buffer);
588 buffer.append('\'');
589 }
590 buffer.append(" )");
591 }
592 }
593
594 buffer.append(" )");
595 }
596
597
598
599 /**
600 * Retrieves the OID for the structural object class associated with this
601 * DIT content rule.
602 *
603 * @return The OID for the structural object class associated with this DIT
604 * content rule.
605 */
606 public String getOID()
607 {
608 return oid;
609 }
610
611
612
613 /**
614 * Retrieves the set of names for this DIT content rule.
615 *
616 * @return The set of names for this DIT content rule, or an empty array if
617 * it does not have any names.
618 */
619 public String[] getNames()
620 {
621 return names;
622 }
623
624
625
626 /**
627 * Retrieves the primary name that can be used to reference this DIT content
628 * rule. If one or more names are defined, then the first name will be used.
629 * Otherwise, the structural object class OID will be returned.
630 *
631 * @return The primary name that can be used to reference this DIT content
632 * rule.
633 */
634 public String getNameOrOID()
635 {
636 if (names.length == 0)
637 {
638 return oid;
639 }
640 else
641 {
642 return names[0];
643 }
644 }
645
646
647
648 /**
649 * Indicates whether the provided string matches the OID or any of the names
650 * for this DIT content rule.
651 *
652 * @param s The string for which to make the determination. It must not be
653 * {@code null}.
654 *
655 * @return {@code true} if the provided string matches the OID or any of the
656 * names for this DIT content rule, or {@code false} if not.
657 */
658 public boolean hasNameOrOID(final String s)
659 {
660 for (final String name : names)
661 {
662 if (s.equalsIgnoreCase(name))
663 {
664 return true;
665 }
666 }
667
668 return s.equalsIgnoreCase(oid);
669 }
670
671
672
673 /**
674 * Retrieves the description for this DIT content rule, if available.
675 *
676 * @return The description for this DIT content rule, or {@code null} if
677 * there is no description defined.
678 */
679 public String getDescription()
680 {
681 return description;
682 }
683
684
685
686 /**
687 * Indicates whether this DIT content rule is declared obsolete.
688 *
689 * @return {@code true} if this DIT content rule is declared obsolete, or
690 * {@code false} if it is not.
691 */
692 public boolean isObsolete()
693 {
694 return isObsolete;
695 }
696
697
698
699 /**
700 * Retrieves the names or OIDs of the auxiliary object classes that may be
701 * present in entries containing the structural class for this DIT content
702 * rule.
703 *
704 * @return The names or OIDs of the auxiliary object classes that may be
705 * present in entries containing the structural class for this DIT
706 * content rule.
707 */
708 public String[] getAuxiliaryClasses()
709 {
710 return auxiliaryClasses;
711 }
712
713
714
715 /**
716 * Retrieves the names or OIDs of the attributes that are required to be
717 * present in entries containing the structural object class for this DIT
718 * content rule.
719 *
720 * @return The names or OIDs of the attributes that are required to be
721 * present in entries containing the structural object class for this
722 * DIT content rule, or an empty array if there are no required
723 * attributes.
724 */
725 public String[] getRequiredAttributes()
726 {
727 return requiredAttributes;
728 }
729
730
731
732 /**
733 * Retrieves the names or OIDs of the attributes that are optionally allowed
734 * to be present in entries containing the structural object class for this
735 * DIT content rule.
736 *
737 * @return The names or OIDs of the attributes that are optionally allowed to
738 * be present in entries containing the structural object class for
739 * this DIT content rule, or an empty array if there are no required
740 * attributes.
741 */
742 public String[] getOptionalAttributes()
743 {
744 return optionalAttributes;
745 }
746
747
748
749 /**
750 * Retrieves the names or OIDs of the attributes that are not allowed to be
751 * present in entries containing the structural object class for this DIT
752 * content rule.
753 *
754 * @return The names or OIDs of the attributes that are not allowed to be
755 * present in entries containing the structural object class for this
756 * DIT content rule, or an empty array if there are no required
757 * attributes.
758 */
759 public String[] getProhibitedAttributes()
760 {
761 return prohibitedAttributes;
762 }
763
764
765
766 /**
767 * Retrieves the set of extensions for this DIT content rule. They will be
768 * mapped from the extension name (which should start with "X-") to the set of
769 * values for that extension.
770 *
771 * @return The set of extensions for this DIT content rule.
772 */
773 public Map<String,String[]> getExtensions()
774 {
775 return extensions;
776 }
777
778
779
780 /**
781 * {@inheritDoc}
782 */
783 @Override()
784 public int hashCode()
785 {
786 return oid.hashCode();
787 }
788
789
790
791 /**
792 * {@inheritDoc}
793 */
794 @Override()
795 public boolean equals(final Object o)
796 {
797 if (o == null)
798 {
799 return false;
800 }
801
802 if (o == this)
803 {
804 return true;
805 }
806
807 if (! (o instanceof DITContentRuleDefinition))
808 {
809 return false;
810 }
811
812 final DITContentRuleDefinition d = (DITContentRuleDefinition) o;
813 return (oid.equals(d.oid) &&
814 stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
815 stringsEqualIgnoreCaseOrderIndependent(auxiliaryClasses,
816 d.auxiliaryClasses) &&
817 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
818 d.requiredAttributes) &&
819 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
820 d.optionalAttributes) &&
821 stringsEqualIgnoreCaseOrderIndependent(prohibitedAttributes,
822 d.prohibitedAttributes) &&
823 bothNullOrEqualIgnoreCase(description, d.description) &&
824 (isObsolete == d.isObsolete) &&
825 extensionsEqual(extensions, d.extensions));
826 }
827
828
829
830 /**
831 * Retrieves a string representation of this DIT content rule definition, in
832 * the format described in RFC 4512 section 4.1.6.
833 *
834 * @return A string representation of this DIT content rule definition.
835 */
836 @Override()
837 public String toString()
838 {
839 return ditContentRuleString;
840 }
841 }