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