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.types;
028    
029    
030    
031    import java.util.Collection;
032    import java.util.Collections;
033    import java.util.HashSet;
034    import java.util.Iterator;
035    import java.util.LinkedHashSet;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Set;
039    
040    import org.opends.server.schema.ObjectClassSyntax;
041    
042    import static org.opends.server.loggers.debug.DebugLogger.*;
043    import org.opends.server.loggers.debug.DebugTracer;
044    import static org.opends.server.util.ServerConstants.*;
045    import static org.opends.server.util.Validator.*;
046    
047    
048    
049    /**
050     * This class defines a data structure for storing and interacting
051     * with an objectclass, which contains a collection of attributes that
052     * must and/or may be present in an entry with that objectclass.
053     * <p>
054     * Any methods which accesses the set of names associated with this
055     * object class, will retrieve the primary name as the first name,
056     * regardless of whether or not it was contained in the original set
057     * of <code>names</code> passed to the constructor.
058     * <p>
059     * Where ordered sets of names, attribute types, or extra properties
060     * are provided, the ordering will be preserved when the associated
061     * fields are accessed via their getters or via the
062     * {@link #toString()} methods.
063     */
064    @org.opends.server.types.PublicAPI(
065         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
066         mayInstantiate=false,
067         mayExtend=false,
068         mayInvoke=true)
069    public final class ObjectClass
070           extends CommonSchemaElements
071           implements SchemaFileElement
072    {
073      /**
074       * The tracer object for the debug logger.
075       */
076      private static final DebugTracer TRACER = getTracer();
077    
078      // The set of optional attribute types for this objectclass.
079      private final Set<AttributeType> optionalAttributes;
080    
081      // The set of optional attribute types for this objectclass and its
082      // superclasses.
083      private final Set<AttributeType> optionalAttributesChain;
084    
085      // The set of required attribute types for this objectclass.
086      private final Set<AttributeType> requiredAttributes;
087    
088      // The set of required attribute types for this objectclass and its
089      // superclasses.
090      private final Set<AttributeType> requiredAttributesChain;
091    
092      // The set of required and optional attributes for this objectclass
093      // and its superclasses.
094      private final Set<AttributeType> requiredAndOptionalChain;
095    
096      // The reference to the superior objectclass.
097      private final ObjectClass superiorClass;
098    
099      // The objectclass type for this objectclass.
100      private final ObjectClassType objectClassType;
101    
102      // Indicates whether or not this object class is allowed to
103      // contain any attribute.
104      private final boolean isExtensibleObject;
105    
106      // The definition string used to create this objectclass.
107      private final String definition;
108    
109    
110    
111      /**
112       * Creates a new objectclass definition with the provided
113       * information.
114       * <p>
115       * If no <code>primaryName</code> is specified, but a set of
116       * <code>names</code> is specified, then the first name retrieved
117       * from the set of <code>names</code> will be used as the primary
118       * name.
119       *
120       * @param definition
121       *          The definition string used to create this objectclass.
122       *          It must not be {@code null}.
123       * @param primaryName
124       *          The primary name for this objectclass, or
125       *          {@code null} if there is no primary name.
126       * @param names
127       *          The set of names that may be used to reference this
128       *          objectclass.
129       * @param oid
130       *          The OID for this objectclass.  It must not be
131       *          {@code null}.
132       * @param description
133       *          The description for this objectclass, or {@code null} if
134       *          there is no description.
135       * @param superiorClass
136       *          The superior class for this objectclass, or {@code null}
137       *          if there is no superior object class.
138       * @param requiredAttributes
139       *          The set of required attribute types for this
140       *          objectclass.
141       * @param optionalAttributes
142       *          The set of optional attribute types for this
143       *          objectclass.
144       * @param objectClassType
145       *          The objectclass type for this objectclass, or
146       *          {@code null} to default to structural.
147       * @param isObsolete
148       *          Indicates whether this objectclass is declared
149       *          "obsolete".
150       * @param extraProperties
151       *          A set of extra properties for this objectclass.
152       */
153      public ObjectClass(String definition, String primaryName,
154                         Collection<String> names, String oid,
155                         String description, ObjectClass superiorClass,
156                         Set<AttributeType> requiredAttributes,
157                         Set<AttributeType> optionalAttributes,
158                         ObjectClassType objectClassType,
159                         boolean isObsolete,
160                         Map<String, List<String>> extraProperties)
161      {
162        super(primaryName, names, oid, description, isObsolete,
163            extraProperties);
164    
165    
166        ensureNotNull(definition, oid);
167    
168        this.superiorClass = superiorClass;
169    
170        int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
171        if (schemaFilePos > 0)
172        {
173          String defStr;
174          try
175          {
176            int firstQuotePos = definition.indexOf('\'', schemaFilePos);
177            int secondQuotePos = definition.indexOf('\'',
178                                                    firstQuotePos+1);
179    
180            defStr = definition.substring(0, schemaFilePos).trim() + " " +
181                     definition.substring(secondQuotePos+1).trim();
182          }
183          catch (Exception e)
184          {
185            if (debugEnabled())
186            {
187              TRACER.debugCaught(DebugLogLevel.ERROR, e);
188            }
189    
190            defStr = definition;
191          }
192    
193          this.definition = defStr;
194        }
195        else
196        {
197          this.definition = definition;
198        }
199    
200        // Set flag indicating whether or not this object class allows any
201        // attributes.
202        if (hasName(OC_EXTENSIBLE_OBJECT_LC)
203            || oid.equals(OID_EXTENSIBLE_OBJECT)) {
204          this.isExtensibleObject = true;
205        } else {
206          this.isExtensibleObject = false;
207        }
208    
209        // Construct unmodifiable views of the required attributes.
210        if (requiredAttributes != null) {
211          this.requiredAttributes = Collections
212              .unmodifiableSet(new LinkedHashSet<AttributeType>(
213                  requiredAttributes));
214        } else {
215          this.requiredAttributes = Collections.emptySet();
216        }
217    
218        if (this.superiorClass == null) {
219          this.requiredAttributesChain = this.requiredAttributes;
220        } else {
221          Set<AttributeType> tmp = new HashSet<AttributeType>(
222              this.requiredAttributes);
223          tmp.addAll(this.superiorClass.getRequiredAttributeChain());
224          this.requiredAttributesChain = Collections.unmodifiableSet(tmp);
225        }
226    
227        // Construct unmodifiable views of the optional attributes.
228        if (optionalAttributes != null) {
229          this.optionalAttributes = Collections
230              .unmodifiableSet(new LinkedHashSet<AttributeType>(
231                  optionalAttributes));
232        } else {
233          this.optionalAttributes = Collections.emptySet();
234        }
235    
236        if (this.superiorClass == null) {
237          this.optionalAttributesChain = this.optionalAttributes;
238        } else {
239          Set<AttributeType> tmp = new HashSet<AttributeType>(
240              this.optionalAttributes);
241          tmp.addAll(this.superiorClass.getOptionalAttributeChain());
242          this.optionalAttributesChain = Collections.unmodifiableSet(tmp);
243        }
244    
245        // Construct unmodifiable views of the required and optional
246        // attribute chains.
247        HashSet<AttributeType> reqAndOptSet =
248             new HashSet<AttributeType>(requiredAttributesChain.size() +
249                                        optionalAttributesChain.size());
250        reqAndOptSet.addAll(requiredAttributesChain);
251        reqAndOptSet.addAll(optionalAttributesChain);
252        requiredAndOptionalChain =
253             Collections.<AttributeType>unmodifiableSet(reqAndOptSet);
254    
255        // Object class type defaults to structural.
256        if (objectClassType != null) {
257          this.objectClassType = objectClassType;
258        } else {
259          this.objectClassType = ObjectClassType.STRUCTURAL;
260        }
261      }
262    
263    
264    
265      /**
266       * Retrieves the definition string used to create this objectclass.
267       *
268       * @return  The definition string used to create this objectclass.
269       */
270      public String getDefinition()
271      {
272        return definition;
273      }
274    
275    
276    
277      /**
278       * Retrieves the definition string used to create this objectclass
279       * including the X-SCHEMA-FILE extension.
280       *
281       * @return  The definition string used to create this objectclass
282       *          including the X-SCHEMA-FILE extension.
283       */
284      public String getDefinitionWithFileName()
285      {
286        if (getSchemaFile() != null)
287        {
288          int pos = definition.lastIndexOf(')');
289          String defStr = definition.substring(0, pos).trim() + " " +
290                          SCHEMA_PROPERTY_FILENAME + " '" +
291                          getSchemaFile() + "' )";
292          return defStr;
293        }
294        else
295          return definition;
296      }
297    
298    
299    
300      /**
301       * Creates a new instance of this objectclass based on the
302       * definition string.  It will also preserve other state information
303       * associated with this objectclass that is not included in the
304       * definition string (e.g., the name of the schema file with which
305       * it is associated).
306       *
307       * @return  The new instance of this objectclass based on the
308       *          definition string.
309       *
310       * @throws  DirectoryException  If a problem occurs while attempting
311       *                              to create a new objectclass instance
312       *                              from the definition string.
313       */
314      public ObjectClass recreateFromDefinition()
315             throws DirectoryException
316      {
317        ByteString value  = ByteStringFactory.create(definition);
318        Schema     schema = DirectoryConfig.getSchema();
319    
320        ObjectClass oc = ObjectClassSyntax.decodeObjectClass(value,
321                                                schema, false);
322        oc.setSchemaFile(getSchemaFile());
323    
324        return oc;
325      }
326    
327    
328    
329      /**
330       * Retrieves the reference to the superior class for this
331       * objectclass.
332       *
333       * @return The reference to the superior class for this objectlass,
334       *         or <code>null</code> if there is none.
335       */
336      public ObjectClass getSuperiorClass() {
337    
338        return superiorClass;
339      }
340    
341    
342    
343      /**
344       * Indicates whether this objectclass is a descendant of the
345       * provided class.
346       *
347       * @param objectClass
348       *          The objectClass for which to make the determination.
349       * @return <code>true</code> if this objectclass is a descendant
350       *         of the provided class, or <code>false</code> if not.
351       */
352      public boolean isDescendantOf(ObjectClass objectClass) {
353    
354        if (superiorClass == null) {
355          return false;
356        }
357    
358        return (superiorClass.equals(objectClass) || superiorClass
359            .isDescendantOf(objectClass));
360      }
361    
362    
363    
364      /**
365       * Retrieves an unmodifiable view of the set of required attributes
366       * for this objectclass. Note that this set will not automatically
367       * include any required attributes for superior objectclasses.
368       *
369       * @return Returns an unmodifiable view of the set of required
370       *         attributes for this objectclass.
371       */
372      public Set<AttributeType> getRequiredAttributes() {
373    
374        return requiredAttributes;
375      }
376    
377    
378    
379      /**
380       * Retrieves an unmodifiable view of the set of all required
381       * attributes for this objectclass and any superior objectclasses
382       * that it might have.
383       *
384       * @return Returns an unmodifiable view of the set of all required
385       *         attributes for this objectclass and any superior
386       *         objectclasses that it might have.
387       */
388      public Set<AttributeType> getRequiredAttributeChain() {
389    
390        return requiredAttributesChain;
391      }
392    
393    
394    
395      /**
396       * Indicates whether the provided attribute type is included in the
397       * required attribute list for this or any of its superior
398       * objectclasses.
399       *
400       * @param attributeType
401       *          The attribute type for which to make the determination.
402       * @return <code>true</code> if the provided attribute type is
403       *         required by this objectclass or any of its superior
404       *         classes, or <code>false</code> if not.
405       */
406      public boolean isRequired(AttributeType attributeType) {
407    
408        return requiredAttributesChain.contains(attributeType);
409      }
410    
411    
412    
413      /**
414       * Retrieves an unmodifiable view of the set of optional attributes
415       * for this objectclass. Note that this list will not automatically
416       * include any optional attributes for superior objectclasses.
417       *
418       * @return Returns an unmodifiable view of the set of optional
419       *         attributes for this objectclass.
420       */
421      public Set<AttributeType> getOptionalAttributes() {
422    
423        return optionalAttributes;
424      }
425    
426    
427    
428      /**
429       * Retrieves an unmodifiable view of the set of optional attributes
430       * for this objectclass and any superior objectclasses that it might
431       * have.
432       *
433       * @return Returns an unmodifiable view of the set of optional
434       *         attributes for this objectclass and any superior
435       *         objectclasses that it might have.
436       */
437      public Set<AttributeType> getOptionalAttributeChain() {
438    
439        return optionalAttributesChain;
440      }
441    
442    
443    
444      /**
445       * Indicates whether the provided attribute type is included in the
446       * optional attribute list for this or any of its superior
447       * objectclasses.
448       *
449       * @param attributeType
450       *          The attribute type for which to make the determination.
451       * @return <code>true</code> if the provided attribute type is
452       *         optional for this objectclass or any of its superior
453       *         classes, or <code>false</code> if not.
454       */
455      public boolean isOptional(AttributeType attributeType) {
456    
457        if (optionalAttributesChain.contains(attributeType)) {
458          return true;
459        }
460    
461        if (isExtensibleObject
462            && !requiredAttributesChain.contains(attributeType)) {
463          // FIXME -- Do we need to do other checks here, like whether the
464          // attribute type is actually defined in the schema?
465          // What about DIT content rules?
466          return true;
467        }
468    
469        return false;
470      }
471    
472    
473    
474      /**
475       * Indicates whether the provided attribute type is in the list of
476       * required or optional attributes for this objectclass or any of
477       * its superior classes.
478       *
479       * @param attributeType
480       *          The attribute type for which to make the determination.
481       * @return <code>true</code> if the provided attribute type is
482       *         required or allowed for this objectclass or any of its
483       *         superior classes, or <code>false</code> if it is not.
484       */
485      public boolean isRequiredOrOptional(AttributeType attributeType) {
486    
487        // FIXME -- Do we need to do any other checks here, like whether
488        // the attribute type is actually defined in the schema?
489        return (isExtensibleObject ||
490                requiredAndOptionalChain.contains(attributeType));
491      }
492    
493    
494    
495      /**
496       * Retrieves the objectclass type for this objectclass.
497       *
498       * @return The objectclass type for this objectclass.
499       */
500      public ObjectClassType getObjectClassType() {
501    
502        return objectClassType;
503      }
504    
505    
506    
507      /**
508       * Indicates whether this objectclass is the extensibleObject
509       * objectclass.
510       *
511       * @return <code>true</code> if this objectclass is the
512       *         extensibleObject objectclass, or <code>false</code> if
513       *         it is not.
514       */
515      public boolean isExtensibleObject() {
516    
517        return isExtensibleObject;
518      }
519    
520    
521    
522      /**
523       * Appends a string representation of this schema definition's
524       * non-generic properties to the provided buffer.
525       *
526       * @param  buffer  The buffer to which the information should be
527       *                 appended.
528       */
529      protected void toStringContent(StringBuilder buffer) {
530    
531        if (superiorClass != null) {
532          buffer.append(" SUP ");
533          buffer.append(superiorClass.getNameOrOID());
534        }
535    
536        if (objectClassType != null) {
537          buffer.append(" ");
538          buffer.append(objectClassType.toString());
539        }
540    
541        if (!requiredAttributes.isEmpty()) {
542          Iterator<AttributeType> iterator = requiredAttributes
543              .iterator();
544    
545          String firstName = iterator.next().getNameOrOID();
546          if (iterator.hasNext()) {
547            buffer.append(" MUST ( ");
548            buffer.append(firstName);
549    
550            while (iterator.hasNext()) {
551              buffer.append(" $ ");
552              buffer.append(iterator.next().getNameOrOID());
553            }
554    
555            buffer.append(" )");
556          } else {
557            buffer.append(" MUST ");
558            buffer.append(firstName);
559          }
560        }
561    
562        if (!optionalAttributes.isEmpty()) {
563          Iterator<AttributeType> iterator = optionalAttributes
564              .iterator();
565    
566          String firstName = iterator.next().getNameOrOID();
567          if (iterator.hasNext()) {
568            buffer.append(" MAY ( ");
569            buffer.append(firstName);
570    
571            while (iterator.hasNext()) {
572              buffer.append(" $ ");
573              buffer.append(iterator.next().getNameOrOID());
574            }
575    
576            buffer.append(" )");
577          } else {
578            buffer.append(" MAY ");
579            buffer.append(firstName);
580          }
581        }
582      }
583    }