/*
* 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 2009 Sun Microsystems, Inc.
*/
package org.opends.sdk.schema;
import static com.sun.opends.sdk.messages.Messages.*;
import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_NAME;
import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
import static org.opends.sdk.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
import java.util.*;
import org.opends.sdk.LocalizableMessage;
import com.sun.opends.sdk.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.
*
* 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.
*/
public final class ObjectClass extends SchemaElement
{
// The OID that may be used to reference this definition.
private final String oid;
// The set of user defined names for this definition.
private final List names;
// Indicates whether this definition is declared "obsolete".
private final boolean isObsolete;
// The reference to the superior objectclasses.
private final Set superiorClassOIDs;
// The objectclass type for this objectclass.
private final ObjectClassType objectClassType;
// The set of required attribute types for this objectclass.
private final Set requiredAttributeOIDs;
// The set of optional attribute types for this objectclass.
private final Set optionalAttributeOIDs;
// The definition string used to create this objectclass.
private final String definition;
private Set superiorClasses = Collections.emptySet();
private Set declaredRequiredAttributes = Collections
.emptySet();
private Set requiredAttributes = Collections.emptySet();
private Set declaredOptionalAttributes = Collections
.emptySet();
private Set optionalAttributes = Collections.emptySet();
private boolean validated = false;
ObjectClass(final String oid, final List names,
final String description, final boolean obsolete,
final Set superiorClassOIDs,
final Set requiredAttributeOIDs,
final Set optionalAttributeOIDs,
final ObjectClassType objectClassType,
final Map> extraProperties, final String definition)
{
super(description, extraProperties);
Validator.ensureNotNull(oid, names);
Validator.ensureNotNull(superiorClassOIDs, requiredAttributeOIDs,
optionalAttributeOIDs, objectClassType);
this.oid = oid;
this.names = names;
this.isObsolete = obsolete;
this.superiorClassOIDs = superiorClassOIDs;
this.objectClassType = objectClassType;
this.requiredAttributeOIDs = requiredAttributeOIDs;
this.optionalAttributeOIDs = optionalAttributeOIDs;
if (definition != null)
{
this.definition = definition;
}
else
{
this.definition = buildDefinition();
}
}
/**
* Construct a extensibleObject object class where the set of allowed
* attribute types of this object class is implicitly the set of all attribute
* types of userApplications usage.
*
* @param description
* The description for this schema definition
* @param extraProperties
* The map of "extra" properties for this schema definition
*/
ObjectClass(final String description,
final Map> extraProperties)
{
super(description, extraProperties);
this.oid = EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
this.names = Collections.singletonList(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME);
this.isObsolete = false;
this.superiorClassOIDs = Collections.singleton(TOP_OBJECTCLASS_NAME);
this.objectClassType = ObjectClassType.AUXILIARY;
this.requiredAttributeOIDs = Collections.emptySet();
this.optionalAttributeOIDs = Collections.emptySet();
this.definition = buildDefinition();
}
@Override
public boolean equals(final Object o)
{
if (this == o)
{
return true;
}
if (o instanceof ObjectClass)
{
final ObjectClass other = (ObjectClass) o;
return oid.equals(other.oid);
}
return false;
}
/**
* Returns an unmodifiable set containing the optional attributes for this
* object class. Note that this set will not automatically include any
* optional attributes for superior object classes.
*
* @return An unmodifiable set containing the optional attributes for this
* object class.
*/
public Set getDeclaredOptionalAttributes()
{
return declaredOptionalAttributes;
}
/**
* Returns an unmodifiable set containing the required attributes for this
* object class. Note that this set will not automatically include any
* required attributes for superior object classes.
*
* @return An unmodifiable set containing the required attributes for this
* object class.
*/
public Set getDeclaredRequiredAttributes()
{
return declaredRequiredAttributes;
}
/**
* Returns the name or OID for this schema definition. If it has one or more
* names, then the primary name will be returned. If it does not have any
* names, then the OID will be returned.
*
* @return The name or OID for this schema definition.
*/
public String getNameOrOID()
{
if (names.isEmpty())
{
return oid;
}
return names.get(0);
}
/**
* Returns an unmodifiable list containing the user-defined names that may be
* used to reference this schema definition.
*
* @return Returns an unmodifiable list containing the user-defined names that
* may be used to reference this schema definition.
*/
public List getNames()
{
return names;
}
/**
* Returns the objectclass type for this objectclass.
*
* @return The objectclass type for this objectclass.
*/
public ObjectClassType getObjectClassType()
{
return objectClassType;
}
/**
* Returns the OID for this schema definition.
*
* @return The OID for this schema definition.
*/
public String getOID()
{
return oid;
}
/**
* Returns an unmodifiable set containing the optional attributes for this
* object class and any superior object classes that it might have.
*
* @return An unmodifiable set containing the optional attributes for this
* object class and any superior object classes that it might have.
*/
public Set getOptionalAttributes()
{
return optionalAttributes;
}
/**
* Returns an unmodifiable set containing the required attributes for this
* object class and any superior object classes that it might have.
*
* @return An unmodifiable set containing the required attributes for this
* object class and any superior object classes that it might have.
*/
public Set getRequiredAttributes()
{
return requiredAttributes;
}
/**
* Returns an unmodifiable set containing the superior classes for this object
* class.
*
* @return An unmodifiable set containing the superior classes for this object
* class.
*/
public Set getSuperiorClasses()
{
return superiorClasses;
}
@Override
public int hashCode()
{
return oid.hashCode();
}
/**
* Indicates whether this schema definition has the specified name.
*
* @param name
* The name for which to make the determination.
* @return true if the specified name is assigned to this schema
* definition, or false if not.
*/
public boolean hasName(final String name)
{
for (final String n : names)
{
if (n.equalsIgnoreCase(name))
{
return true;
}
}
return false;
}
/**
* Indicates whether this schema definition has the specified name or OID.
*
* @param value
* The value for which to make the determination.
* @return true if the provided value matches the OID or one of
* the names assigned to this schema definition, or false
* if not.
*/
public boolean hasNameOrOID(final String value)
{
return hasName(value) || getOID().equals(value);
}
/**
* Indicates whether this objectclass is a descendant of the provided class.
*
* @param objectClass
* The objectClass for which to make the determination.
* @return true if this objectclass is a descendant of the
* provided class, or false if not.
*/
public boolean isDescendantOf(final ObjectClass objectClass)
{
for (final ObjectClass sup : superiorClasses)
{
if (sup.equals(objectClass) || sup.isDescendantOf(objectClass))
{
return true;
}
}
return false;
}
/**
* Indicates whether this schema definition is declared "obsolete".
*
* @return true if this schema definition is declared "obsolete",
* or false if not.
*/
public boolean isObsolete()
{
return isObsolete;
}
/**
* 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 true if the provided attribute type is optional for
* this objectclass or any of its superior classes, or
* false if not.
*/
public boolean isOptional(final AttributeType attributeType)
{
return optionalAttributes.contains(attributeType);
}
/**
* 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 true if the provided attribute type is required by
* this objectclass or any of its superior classes, or
* false if not.
*/
public boolean isRequired(final AttributeType attributeType)
{
return requiredAttributes.contains(attributeType);
}
/**
* 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 true if the provided attribute type is required or
* allowed for this objectclass or any of its superior classes, or
* false if it is not.
*/
public boolean isRequiredOrOptional(final AttributeType attributeType)
{
return isRequired(attributeType) || isOptional(attributeType);
}
/**
* Returns the string representation of this schema definition in the form
* specified in RFC 2252.
*
* @return The string representation of this schema definition in the form
* specified in RFC 2252.
*/
@Override
public String toString()
{
return definition;
}
ObjectClass duplicate()
{
return new ObjectClass(oid, names, description, isObsolete,
superiorClassOIDs, requiredAttributeOIDs, optionalAttributeOIDs,
objectClassType, extraProperties, definition);
}
@Override
void toStringContent(final StringBuilder buffer)
{
buffer.append(oid);
if (!names.isEmpty())
{
final Iterator iterator = names.iterator();
final String firstName = iterator.next();
if (iterator.hasNext())
{
buffer.append(" NAME ( '");
buffer.append(firstName);
while (iterator.hasNext())
{
buffer.append("' '");
buffer.append(iterator.next());
}
buffer.append("' )");
}
else
{
buffer.append(" NAME '");
buffer.append(firstName);
buffer.append("'");
}
}
if (description != null && description.length() > 0)
{
buffer.append(" DESC '");
buffer.append(description);
buffer.append("'");
}
if (isObsolete)
{
buffer.append(" OBSOLETE");
}
if (!superiorClassOIDs.isEmpty())
{
final Iterator iterator = superiorClassOIDs.iterator();
final String firstName = iterator.next();
if (iterator.hasNext())
{
buffer.append(" SUP ( ");
buffer.append(firstName);
while (iterator.hasNext())
{
buffer.append(" $ ");
buffer.append(iterator.next());
}
buffer.append(" )");
}
else
{
buffer.append(" SUP ");
buffer.append(firstName);
}
}
if (objectClassType != null)
{
buffer.append(" ");
buffer.append(objectClassType.toString());
}
if (!requiredAttributeOIDs.isEmpty())
{
final Iterator iterator = requiredAttributeOIDs.iterator();
final String firstName = iterator.next();
if (iterator.hasNext())
{
buffer.append(" MUST ( ");
buffer.append(firstName);
while (iterator.hasNext())
{
buffer.append(" $ ");
buffer.append(iterator.next());
}
buffer.append(" )");
}
else
{
buffer.append(" MUST ");
buffer.append(firstName);
}
}
if (!optionalAttributeOIDs.isEmpty())
{
final Iterator iterator = optionalAttributeOIDs.iterator();
final String firstName = iterator.next();
if (iterator.hasNext())
{
buffer.append(" MAY ( ");
buffer.append(firstName);
while (iterator.hasNext())
{
buffer.append(" $ ");
buffer.append(iterator.next());
}
buffer.append(" )");
}
else
{
buffer.append(" MAY ");
buffer.append(firstName);
}
}
}
@Override
void validate(final List warnings, final Schema schema)
throws SchemaException
{
if (validated)
{
return;
}
validated = true;
// Init a flag to check to inheritance from top (only needed for
// structural object classes) per RFC 4512
boolean derivesTop = objectClassType != ObjectClassType.STRUCTURAL;
if (!superiorClassOIDs.isEmpty())
{
superiorClasses = new HashSet(superiorClassOIDs.size());
ObjectClass superiorClass;
for (final String superClassOid : superiorClassOIDs)
{
try
{
superiorClass = schema.getObjectClass(superClassOid);
}
catch (final UnknownSchemaElementException e)
{
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_SUPERIOR_CLASS
.get(oid, superClassOid);
throw new SchemaException(message, e);
}
// Make sure that the inheritance configuration is acceptable.
final ObjectClassType superiorType = superiorClass.getObjectClassType();
switch (objectClassType)
{
case ABSTRACT:
// Abstract classes may only inherit from other abstract
// classes.
if (superiorType != ObjectClassType.ABSTRACT)
{
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE
.get(oid, objectClassType.toString(), superiorType.toString(),
superiorClass.getNameOrOID());
throw new SchemaException(message);
}
break;
case AUXILIARY:
// Auxiliary classes may only inherit from abstract classes or
// other auxiliary classes.
if (superiorType != ObjectClassType.ABSTRACT
&& superiorType != ObjectClassType.AUXILIARY)
{
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE
.get(oid, objectClassType.toString(), superiorType.toString(),
superiorClass.getNameOrOID());
throw new SchemaException(message);
}
break;
case STRUCTURAL:
// Structural classes may only inherit from abstract classes
// or other structural classes.
if (superiorType != ObjectClassType.ABSTRACT
&& superiorType != ObjectClassType.STRUCTURAL)
{
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE
.get(oid, objectClassType.toString(), superiorType.toString(),
superiorClass.getNameOrOID());
throw new SchemaException(message);
}
break;
}
// All existing structural object classes defined in this schema
// are implicitly guaranteed to inherit from top
if (!derivesTop && superiorType == ObjectClassType.STRUCTURAL)
{
derivesTop = true;
}
// Validate superior object class so we can inherit its
// attributes.
superiorClass.validate(warnings, schema);
// Inherit all required attributes from superior class.
Iterator i = superiorClass.getRequiredAttributes()
.iterator();
if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
{
requiredAttributes = new HashSet();
}
while (i.hasNext())
{
requiredAttributes.add(i.next());
}
// Inherit all optional attributes from superior class.
i = superiorClass.getRequiredAttributes().iterator();
if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
{
requiredAttributes = new HashSet();
}
while (i.hasNext())
{
requiredAttributes.add(i.next());
}
superiorClasses.add(superiorClass);
}
}
if (!derivesTop)
{
derivesTop = isDescendantOf(schema.getObjectClass("2.5.6.0"));
}
// Structural classes must have the "top" objectclass somewhere
// in the superior chain.
if (!derivesTop)
{
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_STRUCTURAL_SUPERIOR_NOT_TOP
.get(oid);
throw new SchemaException(message);
}
if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
{
declaredOptionalAttributes = new HashSet(
requiredAttributeOIDs.size());
for (final AttributeType attributeType : schema.getAttributeTypes())
{
if (attributeType.getUsage() == AttributeUsage.USER_APPLICATIONS)
{
declaredOptionalAttributes.add(attributeType);
}
}
optionalAttributes = declaredRequiredAttributes;
}
else
{
if (!requiredAttributeOIDs.isEmpty())
{
declaredRequiredAttributes = new HashSet(
requiredAttributeOIDs.size());
AttributeType attributeType;
for (final String requiredAttribute : requiredAttributeOIDs)
{
try
{
attributeType = schema.getAttributeType(requiredAttribute);
}
catch (final UnknownSchemaElementException e)
{
// This isn't good because it means that the objectclass
// requires an attribute type that we don't know anything
// about.
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR
.get(oid, requiredAttribute);
throw new SchemaException(message, e);
}
declaredRequiredAttributes.add(attributeType);
}
if (requiredAttributes == Collections.EMPTY_SET)
{
requiredAttributes = declaredRequiredAttributes;
}
else
{
requiredAttributes.addAll(declaredRequiredAttributes);
}
}
if (!optionalAttributeOIDs.isEmpty())
{
declaredOptionalAttributes = new HashSet(
optionalAttributeOIDs.size());
AttributeType attributeType;
for (final String optionalAttribute : optionalAttributeOIDs)
{
try
{
attributeType = schema.getAttributeType(optionalAttribute);
}
catch (final UnknownSchemaElementException e)
{
// This isn't good because it means that the objectclass
// requires an attribute type that we don't know anything
// about.
final LocalizableMessage message = WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR
.get(oid, optionalAttribute);
throw new SchemaException(message, e);
}
declaredOptionalAttributes.add(attributeType);
}
if (optionalAttributes == Collections.EMPTY_SET)
{
optionalAttributes = declaredOptionalAttributes;
}
else
{
optionalAttributes.addAll(declaredOptionalAttributes);
}
}
}
declaredOptionalAttributes = Collections
.unmodifiableSet(declaredOptionalAttributes);
declaredRequiredAttributes = Collections
.unmodifiableSet(declaredRequiredAttributes);
optionalAttributes = Collections.unmodifiableSet(optionalAttributes);
requiredAttributes = Collections.unmodifiableSet(requiredAttributes);
superiorClasses = Collections.unmodifiableSet(superiorClasses);
}
}