/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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.forgerock.opendj.ldap.schema;
import static org.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_NAME;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
import com.forgerock.opendj.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();
// Indicates whether or not validation has been performed.
private boolean needsValidating = true;
// The indicates whether or not validation failed.
private boolean isValid = 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();
}
/**
* Returns {@code true} if the provided object is an object class having the
* same numeric OID as this object class.
*
* @param o
* The object to be compared.
* @return {@code true} if the provided object is a object class having the
* same numeric OID as this object class.
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
} else if (o instanceof ObjectClass) {
final ObjectClass other = (ObjectClass) o;
return oid.equals(other.oid);
} else {
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;
}
/**
* Returns the hash code for this object class. It will be calculated as the
* hash code of the numeric OID.
*
* @return The hash code for this object class.
*/
@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);
}
}
}
boolean validate(final Schema schema, final List invalidSchemaElements,
final List warnings) {
// Avoid validating this schema element more than once. This may occur
// if
// multiple object classes specify the same superior.
if (!needsValidating) {
return isValid;
}
// Prevent re-validation.
needsValidating = false;
// 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_CLASS1.get(
getNameOrOID(), superClassOid);
failValidation(invalidSchemaElements, warnings, message);
return false;
}
// 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_TYPE1.get(
getNameOrOID(), objectClassType.toString(), superiorType
.toString(), superiorClass.getNameOrOID());
failValidation(invalidSchemaElements, warnings, message);
return false;
}
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_TYPE1.get(
getNameOrOID(), objectClassType.toString(), superiorType
.toString(), superiorClass.getNameOrOID());
failValidation(invalidSchemaElements, warnings, message);
return false;
}
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_TYPE1.get(
getNameOrOID(), objectClassType.toString(), superiorType
.toString(), superiorClass.getNameOrOID());
failValidation(invalidSchemaElements, warnings, message);
return false;
}
break;
}
// All existing structural object classes defined in this schema
// are implicitly guaranteed to inherit from top
if (!derivesTop && superiorType == ObjectClassType.STRUCTURAL) {
derivesTop = true;
}
// First ensure that the superior has been validated and fail if
// it is
// invalid.
if (!superiorClass.validate(schema, invalidSchemaElements, warnings)) {
final LocalizableMessage message =
WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_CLASS.get(getNameOrOID(),
superClassOid);
failValidation(invalidSchemaElements, warnings, message);
return false;
}
// 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_TOP1.get(getNameOrOID());
failValidation(invalidSchemaElements, warnings, message);
return false;
}
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_ATTR1.get(
getNameOrOID(), requiredAttribute);
failValidation(invalidSchemaElements, warnings, message);
return false;
}
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_ATTR1.get(
getNameOrOID(), optionalAttribute);
failValidation(invalidSchemaElements, warnings, message);
return false;
}
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);
return (isValid = true);
}
private void failValidation(final List invalidSchemaElements,
final List warnings, final LocalizableMessage message) {
invalidSchemaElements.add(this);
warnings.add(ERR_OC_VALIDATION_FAIL.get(toString(), message));
}
}