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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.types;
018
019import org.forgerock.opendj.ldap.schema.AttributeType;
020
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.LinkedHashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.ldap.schema.ObjectClassType;
031
032import static org.forgerock.util.Reject.*;
033import static org.opends.server.util.ServerConstants.*;
034
035/**
036 * This class defines a data structure for storing and interacting
037 * with an objectclass, which contains a collection of attributes that
038 * must and/or may be present in an entry with that objectclass.
039 * <p>
040 * Any methods which accesses the set of names associated with this
041 * object class, will retrieve the primary name as the first name,
042 * regardless of whether or not it was contained in the original set
043 * of <code>names</code> passed to the constructor.
044 * <p>
045 * Where ordered sets of names, attribute types, or extra properties
046 * are provided, the ordering will be preserved when the associated
047 * fields are accessed via their getters or via the
048 * {@link #toString()} methods.
049 */
050@org.opends.server.types.PublicAPI(
051     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
052     mayInstantiate=false,
053     mayExtend=false,
054     mayInvoke=true)
055public final class ObjectClass
056       extends CommonSchemaElements
057{
058  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
059
060  /** The set of optional attribute types for this objectclass. */
061  private final Set<AttributeType> optionalAttributes;
062
063  /**
064   * The set of optional attribute types for this objectclass and its
065   * superclasses.
066   */
067  private final Set<AttributeType> optionalAttributesChain;
068
069  /** The set of required attribute types for this objectclass. */
070  private final Set<AttributeType> requiredAttributes;
071
072  /**
073   * The set of required attribute types for this objectclass and its
074   * superclasses.
075   */
076  private final Set<AttributeType> requiredAttributesChain;
077
078  /**
079   * The set of required and optional attributes for this objectclass
080   * and its superclasses.
081   */
082  private final Set<AttributeType> requiredAndOptionalChain;
083
084  /** The reference to one or more superior objectclasses. */
085  private final Set<ObjectClass> superiorClasses;
086
087  /** The objectclass type for this objectclass. */
088  private final ObjectClassType objectClassType;
089
090  /**
091   * Indicates whether or not this object class is allowed to
092   * contain any attribute.
093   */
094  private final boolean isExtensibleObject;
095
096  /** The definition string used to create this objectclass. */
097  private final String definition;
098
099  /** True once this object class has been removed from the schema. */
100  private volatile boolean isDirty;
101
102
103
104  /**
105   * Creates a new objectclass definition with the provided
106   * information.
107   * <p>
108   * If no <code>primaryName</code> is specified, but a set of
109   * <code>names</code> is specified, then the first name retrieved
110   * from the set of <code>names</code> will be used as the primary
111   * name.
112   *
113   * @param definition
114   *          The definition string used to create this objectclass.
115   *          It must not be {@code null}.
116   * @param primaryName
117   *          The primary name for this objectclass, or
118   *          {@code null} if there is no primary name.
119   * @param names
120   *          The set of names that may be used to reference this
121   *          objectclass.
122   * @param oid
123   *          The OID for this objectclass.  It must not be
124   *          {@code null}.
125   * @param description
126   *          The description for this objectclass, or {@code null} if
127   *          there is no description.
128   * @param superiorClasses
129   *          The superior classes for this objectclass, or
130   *          {@code null} if there is no superior object class.
131   * @param requiredAttributes
132   *          The set of required attribute types for this
133   *          objectclass.
134   * @param optionalAttributes
135   *          The set of optional attribute types for this
136   *          objectclass.
137   * @param objectClassType
138   *          The objectclass type for this objectclass, or
139   *          {@code null} to default to structural.
140   * @param isObsolete
141   *          Indicates whether this objectclass is declared
142   *          "obsolete".
143   * @param extraProperties
144   *          A set of extra properties for this objectclass.
145   */
146  public ObjectClass(String definition, String primaryName,
147                     Collection<String> names, String oid,
148                     String description,
149                     Set<ObjectClass> superiorClasses,
150                     Set<AttributeType> requiredAttributes,
151                     Set<AttributeType> optionalAttributes,
152                     ObjectClassType objectClassType,
153                     boolean isObsolete,
154                     Map<String, List<String>> extraProperties)
155  {
156    super(primaryName, names, oid, description, isObsolete,
157        extraProperties);
158
159
160    ifNull(definition, oid);
161
162    // Construct unmodifiable views of the superior classes.
163    if (superiorClasses != null) {
164      this.superiorClasses =  Collections
165          .unmodifiableSet(new LinkedHashSet<ObjectClass>(
166              superiorClasses));
167    } else {
168      this.superiorClasses = Collections.emptySet();
169    }
170
171    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
172    if (schemaFilePos > 0)
173    {
174      String defStr;
175      try
176      {
177        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
178        int secondQuotePos = definition.indexOf('\'',
179                                                firstQuotePos+1);
180
181        defStr = definition.substring(0, schemaFilePos).trim() + " " +
182                 definition.substring(secondQuotePos+1).trim();
183      }
184      catch (Exception e)
185      {
186        logger.traceException(e);
187
188        defStr = definition;
189      }
190
191      this.definition = defStr;
192    }
193    else
194    {
195      this.definition = definition;
196    }
197
198    // Set flag indicating whether or not this object class allows any attributes
199    this.isExtensibleObject = hasName(OC_EXTENSIBLE_OBJECT_LC)
200        || oid.equals(OID_EXTENSIBLE_OBJECT);
201
202    // Construct unmodifiable views of the required attributes.
203    if (requiredAttributes != null) {
204      this.requiredAttributes = Collections
205          .unmodifiableSet(new LinkedHashSet<AttributeType>(
206              requiredAttributes));
207    } else {
208      this.requiredAttributes = Collections.emptySet();
209    }
210
211    if (this.superiorClasses.isEmpty()) {
212      this.requiredAttributesChain = this.requiredAttributes;
213    } else {
214      Set<AttributeType> tmp = new HashSet<>(this.requiredAttributes);
215      for(ObjectClass oc: this.superiorClasses)
216      {
217        tmp.addAll(oc.getRequiredAttributeChain());
218      }
219      this.requiredAttributesChain = Collections.unmodifiableSet(tmp);
220    }
221
222    // Construct unmodifiable views of the optional attributes.
223    if (optionalAttributes != null) {
224      this.optionalAttributes = Collections
225          .unmodifiableSet(new LinkedHashSet<AttributeType>(
226              optionalAttributes));
227    } else {
228      this.optionalAttributes = Collections.emptySet();
229    }
230
231    if (this.superiorClasses.isEmpty()) {
232      this.optionalAttributesChain = this.optionalAttributes;
233    } else {
234      Set<AttributeType> tmp = new HashSet<>(this.optionalAttributes);
235      for(ObjectClass oc : this.superiorClasses)
236      {
237        tmp.addAll(oc.getOptionalAttributeChain());
238      }
239      this.optionalAttributesChain = Collections.unmodifiableSet(tmp);
240    }
241
242    // Construct unmodifiable views of the required and optional attribute chains.
243    int size = requiredAttributesChain.size() + optionalAttributesChain.size();
244    HashSet<AttributeType> reqAndOptSet = new HashSet<>(size);
245    reqAndOptSet.addAll(requiredAttributesChain);
246    reqAndOptSet.addAll(optionalAttributesChain);
247    requiredAndOptionalChain =
248         Collections.<AttributeType>unmodifiableSet(reqAndOptSet);
249
250    // Object class type defaults to structural.
251    if (objectClassType != null) {
252      this.objectClassType = objectClassType;
253    } else {
254      this.objectClassType = ObjectClassType.STRUCTURAL;
255    }
256  }
257
258
259
260  /**
261   * Retrieves an unmodifiable view of the set of direct superior
262   * classes for this objectclass.
263   *
264   * @return An unmodifiable view of the set of  direct superior
265   *                classes for this objectclass,
266   */
267  public Set<ObjectClass> getSuperiorClasses() {
268    return superiorClasses;
269  }
270
271
272
273  /**
274   * Indicates whether this objectclass is a descendant of the
275   * provided class.
276   *
277   * @param objectClass
278   *          The objectClass for which to make the determination.
279   * @return <code>true</code> if this objectclass is a descendant
280   *         of the provided class, or <code>false</code> if not.
281   */
282  public boolean isDescendantOf(ObjectClass objectClass) {
283
284    for(ObjectClass oc : superiorClasses) {
285      if(oc.equals(objectClass) || oc.isDescendantOf(objectClass)) {
286        return true;
287      }
288    }
289    return false;
290  }
291
292
293
294  /**
295   * Retrieves an unmodifiable view of the set of required attributes
296   * for this objectclass. Note that this set will not automatically
297   * include any required attributes for superior objectclasses.
298   *
299   * @return Returns an unmodifiable view of the set of required
300   *         attributes for this objectclass.
301   */
302  public Set<AttributeType> getRequiredAttributes() {
303
304    return requiredAttributes;
305  }
306
307
308
309  /**
310   * Retrieves an unmodifiable view of the set of all required
311   * attributes for this objectclass and any superior objectclasses
312   * that it might have.
313   *
314   * @return Returns an unmodifiable view of the set of all required
315   *         attributes for this objectclass and any superior
316   *         objectclasses that it might have.
317   */
318  public Set<AttributeType> getRequiredAttributeChain() {
319
320    return requiredAttributesChain;
321  }
322
323
324
325  /**
326   * Indicates whether the provided attribute type is included in the
327   * required attribute list for this or any of its superior
328   * objectclasses.
329   *
330   * @param attributeType
331   *          The attribute type for which to make the determination.
332   * @return <code>true</code> if the provided attribute type is
333   *         required by this objectclass or any of its superior
334   *         classes, or <code>false</code> if not.
335   */
336  public boolean isRequired(AttributeType attributeType) {
337
338    return requiredAttributesChain.contains(attributeType);
339  }
340
341
342
343  /**
344   * Retrieves an unmodifiable view of the set of optional attributes
345   * for this objectclass. Note that this list will not automatically
346   * include any optional attributes for superior objectclasses.
347   *
348   * @return Returns an unmodifiable view of the set of optional
349   *         attributes for this objectclass.
350   */
351  public Set<AttributeType> getOptionalAttributes() {
352
353    return optionalAttributes;
354  }
355
356
357
358  /**
359   * Retrieves an unmodifiable view of the set of optional attributes
360   * for this objectclass and any superior objectclasses that it might
361   * have.
362   *
363   * @return Returns an unmodifiable view of the set of optional
364   *         attributes for this objectclass and any superior
365   *         objectclasses that it might have.
366   */
367  public Set<AttributeType> getOptionalAttributeChain() {
368
369    return optionalAttributesChain;
370  }
371
372
373
374  /**
375   * Indicates whether the provided attribute type is included in the
376   * optional attribute list for this or any of its superior
377   * objectclasses.
378   *
379   * @param attributeType
380   *          The attribute type for which to make the determination.
381   * @return <code>true</code> if the provided attribute type is
382   *         optional for this objectclass or any of its superior
383   *         classes, or <code>false</code> if not.
384   */
385  public boolean isOptional(AttributeType attributeType) {
386
387    return optionalAttributesChain.contains(attributeType)
388        || (isExtensibleObject && !requiredAttributesChain.contains(attributeType));
389        // FIXME -- Do we need to do other checks here, like whether the
390        // attribute type is actually defined in the schema?
391        // What about DIT content rules?
392  }
393
394
395
396  /**
397   * Indicates whether the provided attribute type is in the list of
398   * required or optional attributes for this objectclass or any of
399   * its superior classes.
400   *
401   * @param attributeType
402   *          The attribute type for which to make the determination.
403   * @return <code>true</code> if the provided attribute type is
404   *         required or allowed for this objectclass or any of its
405   *         superior classes, or <code>false</code> if it is not.
406   */
407  public boolean isRequiredOrOptional(AttributeType attributeType) {
408
409    // FIXME -- Do we need to do any other checks here, like whether
410    // the attribute type is actually defined in the schema?
411    return isExtensibleObject || requiredAndOptionalChain.contains(attributeType);
412  }
413
414
415
416  /**
417   * Retrieves the objectclass type for this objectclass.
418   *
419   * @return The objectclass type for this objectclass.
420   */
421  public ObjectClassType getObjectClassType() {
422
423    return objectClassType;
424  }
425
426
427
428  /**
429   * Indicates whether this objectclass is the extensibleObject
430   * objectclass.
431   *
432   * @return <code>true</code> if this objectclass is the
433   *         extensibleObject objectclass, or <code>false</code> if
434   *         it is not.
435   */
436  public boolean isExtensibleObject() {
437
438    return isExtensibleObject;
439  }
440
441  /** {@inheritDoc} */
442  @Override
443  public String toString()
444  {
445    return definition;
446  }
447
448
449
450  /**
451   * Marks this object class as dirty, indicating that it has been removed or
452   * replaced in the schema.
453   *
454   * @return A reference to this object class.
455   */
456  public ObjectClass setDirty()
457  {
458    isDirty = true;
459    return this;
460  }
461
462
463
464  /**
465   * Returns {@code true} if this object class has been removed or replaced in
466   * the schema.
467   *
468   * @return {@code true} if this object class has been removed or replaced in
469   *         the schema.
470   */
471  public boolean isDirty()
472  {
473    return isDirty;
474  }
475}