/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions copyright 2013 ForgeRock AS. */ package org.opends.server.types; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.opends.server.schema.ObjectClassSyntax; import static org.opends.server.loggers.debug.DebugLogger.*; import org.opends.server.loggers.debug.DebugTracer; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.Validator.*; /** * This class defines a data structure for storing and interacting * with an objectclass, which contains a collection of attributes that * must and/or may be present in an entry with that objectclass. * <p> * Any methods which accesses the set of names associated with this * object class, will retrieve the primary name as the first name, * regardless of whether or not it was contained in the original set * of <code>names</code> passed to the constructor. * <p> * Where ordered sets of names, attribute types, or extra properties * are provided, the ordering will be preserved when the associated * fields are accessed via their getters or via the * {@link #toString()} methods. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, mayInstantiate=false, mayExtend=false, mayInvoke=true) public final class ObjectClass extends CommonSchemaElements implements SchemaFileElement { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The set of optional attribute types for this objectclass. private final Set<AttributeType> optionalAttributes; // The set of optional attribute types for this objectclass and its // superclasses. private final Set<AttributeType> optionalAttributesChain; // The set of required attribute types for this objectclass. private final Set<AttributeType> requiredAttributes; // The set of required attribute types for this objectclass and its // superclasses. private final Set<AttributeType> requiredAttributesChain; // The set of required and optional attributes for this objectclass // and its superclasses. private final Set<AttributeType> requiredAndOptionalChain; // The reference to one or more superior objectclasses. private final Set<ObjectClass> superiorClasses; // The objectclass type for this objectclass. private final ObjectClassType objectClassType; // Indicates whether or not this object class is allowed to // contain any attribute. private final boolean isExtensibleObject; // The definition string used to create this objectclass. private final String definition; // True once this object class has been removed from the schema. private volatile boolean isDirty = false; /** * Creates a new objectclass definition with the provided * information. * <p> * If no <code>primaryName</code> is specified, but a set of * <code>names</code> is specified, then the first name retrieved * from the set of <code>names</code> will be used as the primary * name. * * @param definition * The definition string used to create this objectclass. * It must not be {@code null}. * @param primaryName * The primary name for this objectclass, or * {@code null} if there is no primary name. * @param names * The set of names that may be used to reference this * objectclass. * @param oid * The OID for this objectclass. It must not be * {@code null}. * @param description * The description for this objectclass, or {@code null} if * there is no description. * @param superiorClasses * The superior classes for this objectclass, or * {@code null} if there is no superior object class. * @param requiredAttributes * The set of required attribute types for this * objectclass. * @param optionalAttributes * The set of optional attribute types for this * objectclass. * @param objectClassType * The objectclass type for this objectclass, or * {@code null} to default to structural. * @param isObsolete * Indicates whether this objectclass is declared * "obsolete". * @param extraProperties * A set of extra properties for this objectclass. */ public ObjectClass(String definition, String primaryName, Collection<String> names, String oid, String description, Set<ObjectClass> superiorClasses, Set<AttributeType> requiredAttributes, Set<AttributeType> optionalAttributes, ObjectClassType objectClassType, boolean isObsolete, Map<String, List<String>> extraProperties) { super(primaryName, names, oid, description, isObsolete, extraProperties); ensureNotNull(definition, oid); // Construct unmodifiable views of the superior classes. if (superiorClasses != null) { this.superiorClasses = Collections .unmodifiableSet(new LinkedHashSet<ObjectClass>( superiorClasses)); } else { this.superiorClasses = Collections.emptySet(); } int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME); if (schemaFilePos > 0) { String defStr; try { int firstQuotePos = definition.indexOf('\'', schemaFilePos); int secondQuotePos = definition.indexOf('\'', firstQuotePos+1); defStr = definition.substring(0, schemaFilePos).trim() + " " + definition.substring(secondQuotePos+1).trim(); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } defStr = definition; } this.definition = defStr; } else { this.definition = definition; } // Set flag indicating whether or not this object class allows any // attributes. if (hasName(OC_EXTENSIBLE_OBJECT_LC) || oid.equals(OID_EXTENSIBLE_OBJECT)) { this.isExtensibleObject = true; } else { this.isExtensibleObject = false; } // Construct unmodifiable views of the required attributes. if (requiredAttributes != null) { this.requiredAttributes = Collections .unmodifiableSet(new LinkedHashSet<AttributeType>( requiredAttributes)); } else { this.requiredAttributes = Collections.emptySet(); } if (this.superiorClasses.isEmpty()) { this.requiredAttributesChain = this.requiredAttributes; } else { Set<AttributeType> tmp = new HashSet<AttributeType>( this.requiredAttributes); for(ObjectClass oc: this.superiorClasses) { tmp.addAll(oc.getRequiredAttributeChain()); } this.requiredAttributesChain = Collections.unmodifiableSet(tmp); } // Construct unmodifiable views of the optional attributes. if (optionalAttributes != null) { this.optionalAttributes = Collections .unmodifiableSet(new LinkedHashSet<AttributeType>( optionalAttributes)); } else { this.optionalAttributes = Collections.emptySet(); } if (this.superiorClasses.isEmpty()) { this.optionalAttributesChain = this.optionalAttributes; } else { Set<AttributeType> tmp = new HashSet<AttributeType>( this.optionalAttributes); for(ObjectClass oc : this.superiorClasses) { tmp.addAll(oc.getOptionalAttributeChain()); } this.optionalAttributesChain = Collections.unmodifiableSet(tmp); } // Construct unmodifiable views of the required and optional // attribute chains. HashSet<AttributeType> reqAndOptSet = new HashSet<AttributeType>(requiredAttributesChain.size() + optionalAttributesChain.size()); reqAndOptSet.addAll(requiredAttributesChain); reqAndOptSet.addAll(optionalAttributesChain); requiredAndOptionalChain = Collections.<AttributeType>unmodifiableSet(reqAndOptSet); // Object class type defaults to structural. if (objectClassType != null) { this.objectClassType = objectClassType; } else { this.objectClassType = ObjectClassType.STRUCTURAL; } } /** * Retrieves the definition string used to create this objectclass. * * @return The definition string used to create this objectclass. */ public String getDefinition() { return definition; } /** * Retrieves the definition string used to create this objectclass * including the X-SCHEMA-FILE extension. * * @return The definition string used to create this objectclass * including the X-SCHEMA-FILE extension. */ public String getDefinitionWithFileName() { if (getSchemaFile() != null) { int pos = definition.lastIndexOf(')'); String defStr = definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + getSchemaFile() + "' )"; return defStr; } else return definition; } /** * {@inheritDoc} */ public ObjectClass recreateFromDefinition(Schema schema) throws DirectoryException { ByteString value = ByteString.valueOf(definition); ObjectClass oc = ObjectClassSyntax.decodeObjectClass(value, schema, false); oc.setSchemaFile(getSchemaFile()); return oc; } /** * Retrieves an unmodifiable view of the set of direct superior * classes for this objectclass. * * @return An unmodifiable view of the set of direct superior * classes for this objectlass, */ public Set<ObjectClass> getSuperiorClasses() { return superiorClasses; } /** * Indicates whether this objectclass is a descendant of the * provided class. * * @param objectClass * The objectClass for which to make the determination. * @return <code>true</code> if this objectclass is a descendant * of the provided class, or <code>false</code> if not. */ public boolean isDescendantOf(ObjectClass objectClass) { for(ObjectClass oc : superiorClasses) { if(oc.equals(objectClass) || oc.isDescendantOf(objectClass)) { return true; } } return false; } /** * Retrieves an unmodifiable view of the set of required attributes * for this objectclass. Note that this set will not automatically * include any required attributes for superior objectclasses. * * @return Returns an unmodifiable view of the set of required * attributes for this objectclass. */ public Set<AttributeType> getRequiredAttributes() { return requiredAttributes; } /** * Retrieves an unmodifiable view of the set of all required * attributes for this objectclass and any superior objectclasses * that it might have. * * @return Returns an unmodifiable view of the set of all required * attributes for this objectclass and any superior * objectclasses that it might have. */ public Set<AttributeType> getRequiredAttributeChain() { return requiredAttributesChain; } /** * Indicates whether the provided attribute type is included in the * required attribute list for this or any of its superior * objectclasses. * * @param attributeType * The attribute type for which to make the determination. * @return <code>true</code> if the provided attribute type is * required by this objectclass or any of its superior * classes, or <code>false</code> if not. */ public boolean isRequired(AttributeType attributeType) { return requiredAttributesChain.contains(attributeType); } /** * Retrieves an unmodifiable view of the set of optional attributes * for this objectclass. Note that this list will not automatically * include any optional attributes for superior objectclasses. * * @return Returns an unmodifiable view of the set of optional * attributes for this objectclass. */ public Set<AttributeType> getOptionalAttributes() { return optionalAttributes; } /** * Retrieves an unmodifiable view of the set of optional attributes * for this objectclass and any superior objectclasses that it might * have. * * @return Returns an unmodifiable view of the set of optional * attributes for this objectclass and any superior * objectclasses that it might have. */ public Set<AttributeType> getOptionalAttributeChain() { return optionalAttributesChain; } /** * Indicates whether the provided attribute type is included in the * optional attribute list for this or any of its superior * objectclasses. * * @param attributeType * The attribute type for which to make the determination. * @return <code>true</code> if the provided attribute type is * optional for this objectclass or any of its superior * classes, or <code>false</code> if not. */ public boolean isOptional(AttributeType attributeType) { if (optionalAttributesChain.contains(attributeType)) { return true; } if (isExtensibleObject && !requiredAttributesChain.contains(attributeType)) { // FIXME -- Do we need to do other checks here, like whether the // attribute type is actually defined in the schema? // What about DIT content rules? return true; } return false; } /** * Indicates whether the provided attribute type is in the list of * required or optional attributes for this objectclass or any of * its superior classes. * * @param attributeType * The attribute type for which to make the determination. * @return <code>true</code> if the provided attribute type is * required or allowed for this objectclass or any of its * superior classes, or <code>false</code> if it is not. */ public boolean isRequiredOrOptional(AttributeType attributeType) { // FIXME -- Do we need to do any other checks here, like whether // the attribute type is actually defined in the schema? return (isExtensibleObject || requiredAndOptionalChain.contains(attributeType)); } /** * Retrieves the objectclass type for this objectclass. * * @return The objectclass type for this objectclass. */ public ObjectClassType getObjectClassType() { return objectClassType; } /** * Indicates whether this objectclass is the extensibleObject * objectclass. * * @return <code>true</code> if this objectclass is the * extensibleObject objectclass, or <code>false</code> if * it is not. */ public boolean isExtensibleObject() { return isExtensibleObject; } /** * Appends a string representation of this schema definition's * non-generic properties to the provided buffer. * * @param buffer The buffer to which the information should be * appended. */ protected void toStringContent(StringBuilder buffer) { if (!superiorClasses.isEmpty()) { buffer.append(" SUP "); Iterator<ObjectClass> iterator = superiorClasses.iterator(); ObjectClass oc = iterator.next(); if(iterator.hasNext()) { buffer.append("( "); buffer.append(oc.getNameOrOID()); while(iterator.hasNext()) { buffer.append(" $ "); buffer.append(iterator.next().getNameOrOID()); } buffer.append(" )"); } else { buffer.append(oc.getNameOrOID()); } } if (objectClassType != null) { buffer.append(" "); buffer.append(objectClassType.toString()); } if (!requiredAttributes.isEmpty()) { Iterator<AttributeType> iterator = requiredAttributes .iterator(); String firstName = iterator.next().getNameOrOID(); if (iterator.hasNext()) { buffer.append(" MUST ( "); buffer.append(firstName); while (iterator.hasNext()) { buffer.append(" $ "); buffer.append(iterator.next().getNameOrOID()); } buffer.append(" )"); } else { buffer.append(" MUST "); buffer.append(firstName); } } if (!optionalAttributes.isEmpty()) { Iterator<AttributeType> iterator = optionalAttributes .iterator(); String firstName = iterator.next().getNameOrOID(); if (iterator.hasNext()) { buffer.append(" MAY ( "); buffer.append(firstName); while (iterator.hasNext()) { buffer.append(" $ "); buffer.append(iterator.next().getNameOrOID()); } buffer.append(" )"); } else { buffer.append(" MAY "); buffer.append(firstName); } } } /** * Marks this object class as dirty, indicating that it has been removed or * replaced in the schema. * * @return A reference to this object class. */ public ObjectClass setDirty() { isDirty = true; return this; } /** * Returns {@code true} if this object class has been removed or replaced in * the schema. * * @return {@code true} if this object class has been removed or replaced in * the schema. */ public boolean isDirty() { return isDirty; } }