/*
* 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 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.types;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SchemaConfigManager;
import org.opends.server.schema.AttributeTypeSyntax;
import org.opends.server.schema.DITContentRuleSyntax;
import org.opends.server.schema.DITStructureRuleSyntax;
import org.opends.server.schema.MatchingRuleUseSyntax;
import org.opends.server.schema.NameFormSyntax;
import org.opends.server.schema.ObjectClassSyntax;
import org.opends.server.util.StaticUtils;
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.types.CommonSchemaElements.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a data structure that holds information about
* the components of the Directory Server schema. It includes the
* following kinds of elements:
*
* <UL>
* <LI>Attribute type definitions</LI>
* <LI>Objectclass definitions</LI>
* <LI>Attribute syntax definitions</LI>
* <LI>Matching rule definitions</LI>
* <LI>Matching rule use definitions</LI>
* <LI>DIT content rule definitions</LI>
* <LI>DIT structure rule definitions</LI>
* <LI>Name form definitions</LI>
* </UL>
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
mayInstantiate=false,
mayExtend=false,
mayInvoke=true)
public final class Schema
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
* The set of subordinate attribute types registered within the server schema.
*/
private ConcurrentHashMap<AttributeType,List<AttributeType>>
subordinateTypes;
/**
* The set of attribute type definitions for this schema, mapped between the
* lowercase names and OID for the definition and the attribute type itself.
*/
private ConcurrentHashMap<String,AttributeType> attributeTypes;
/**
* The set of objectclass definitions for this schema, mapped between the
* lowercase names and OID for the definition and the objectclass itself.
*/
private ConcurrentHashMap<String,ObjectClass> objectClasses;
/**
* The set of attribute syntaxes for this schema, mapped between the OID for
* the syntax and the syntax itself.
*/
private ConcurrentHashMap<String,Syntax> syntaxes;
/**
* The default attribute syntax to use for attributes with no defined syntax.
*/
private Syntax defaultSyntax;
/**
* The entire set of matching rules for this schema, mapped between the
* lowercase names and OID for the definition and the matching rule itself.
*/
private ConcurrentHashMap<String,MatchingRule> matchingRules;
/**
* The set of matching rule uses for this schema, mapped between the matching
* rule for the definition and the matching rule use itself.
*/
private ConcurrentHashMap<MatchingRule,MatchingRuleUse>
matchingRuleUses;
/**
* The set of DIT content rules for this schema, mapped between the structural
* objectclass for the definition and the DIT content rule itself.
*/
private ConcurrentHashMap<ObjectClass,DITContentRule>
ditContentRules;
/**
* The set of DIT structure rules for this schema, mapped between the name
* form for the definition and the DIT structure rule itself.
*/
private ConcurrentHashMap<Integer,DITStructureRule>
ditStructureRulesByID;
/**
* The set of DIT structure rules for this schema, mapped between the name
* form for the definition and the DIT structure rule itself.
*/
private ConcurrentHashMap<NameForm,DITStructureRule>
ditStructureRulesByNameForm;
/**
* The set of name forms for this schema, mapped between the structural
* objectclass for the definition and the list of name forms.
*/
private ConcurrentHashMap<ObjectClass,List<NameForm>>
nameFormsByOC;
/**
* The set of name forms for this schema, mapped between the names/OID and the
* name form itself.
*/
private ConcurrentHashMap<String,NameForm> nameFormsByName;
/**
* The set of ldap syntax descriptions for this schema, mapped the OID and the
* ldap syntax description itself.
*/
private ConcurrentHashMap<String,LDAPSyntaxDescription>
ldapSyntaxDescriptions;
/** The oldest modification timestamp for any schema configuration file. */
private long oldestModificationTime;
/** The youngest modification timestamp for any schema configuration file. */
private long youngestModificationTime;
/**
* A set of extra attributes that are not used directly by the schema but may
* be used by other component to store information in the schema.
* <p>
* ex : Replication uses this to store its state and GenerationID.
*/
private Map<String, Attribute> extraAttributes = new HashMap<>();
/** Creates a new schema structure with all elements initialized but empty. */
public Schema()
{
attributeTypes = new ConcurrentHashMap<>();
objectClasses = new ConcurrentHashMap<>();
syntaxes = new ConcurrentHashMap<>();
matchingRules = new ConcurrentHashMap<>();
matchingRuleUses = new ConcurrentHashMap<>();
ditContentRules = new ConcurrentHashMap<>();
ditStructureRulesByID = new ConcurrentHashMap<>();
ditStructureRulesByNameForm = new ConcurrentHashMap<>();
nameFormsByOC = new ConcurrentHashMap<>();
nameFormsByName = new ConcurrentHashMap<>();
ldapSyntaxDescriptions = new ConcurrentHashMap<>();
subordinateTypes = new ConcurrentHashMap<>();
oldestModificationTime = System.currentTimeMillis();
youngestModificationTime = oldestModificationTime;
}
/**
* Retrieves the attribute type definitions for this schema, as a
* mapping between the lowercase names and OIDs for the attribute
* type and the attribute type itself. Each attribute type may be
* associated with multiple keys (once for the OID and again for
* each name). The contents of the returned mapping must not be
* altered.
*
* @return The attribute type definitions for this schema.
*/
public ConcurrentHashMap<String,AttributeType> getAttributeTypes()
{
return attributeTypes;
}
/**
* Indicates whether this schema definition includes an attribute
* type with the provided name or OID.
*
* @param lowerName The name or OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains an attribute type
* with the provided name or OID, or {@code false} if not.
*/
public boolean hasAttributeType(String lowerName)
{
return attributeTypes.containsKey(lowerName);
}
/**
* Retrieves the attribute type definition with the specified name
* or OID.
*
* @param lowerName The name or OID of the attribute type to
* retrieve, formatted in all lowercase
* characters.
*
* @return The requested attribute type, or <CODE>null</CODE> if no
* type is registered with the provided name or OID.
*/
public AttributeType getAttributeType(String lowerName)
{
return attributeTypes.get(lowerName);
}
/**
* Registers the provided attribute type definition with this
* schema.
*
* @param attributeType The attribute type to register with
* this schema.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another attribute
* type with the same OID or name).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerAttributeType(AttributeType attributeType,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (attributeTypes)
{
if (! overwriteExisting)
{
String oid = toLowerCase(attributeType.getOID());
if (attributeTypes.containsKey(oid))
{
AttributeType conflictingType = attributeTypes.get(oid);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_ATTRIBUTE_OID.
get(attributeType.getNameOrOID(), oid,
conflictingType.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
for (String name : attributeType.getNormalizedNames())
{
if (attributeTypes.containsKey(name))
{
AttributeType conflictingType = attributeTypes.get(name);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_ATTRIBUTE_NAME.
get(attributeType.getNameOrOID(), name,
conflictingType.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
}
AttributeType old = attributeTypes.put(
toLowerCase(attributeType.getOID()), attributeType);
if (old != null && old != attributeType)
{
// Mark the old attribute type as stale so that caches (such as
// compressed schema) can detect changes.
old.setDirty();
}
for (String name : attributeType.getNormalizedNames())
{
attributeTypes.put(name, attributeType);
}
AttributeType superiorType = attributeType.getSuperiorType();
if (superiorType != null)
{
registerSubordinateType(attributeType, superiorType);
}
}
}
/**
* Deregisters the provided attribute type definition with this
* schema.
*
* @param attributeType The attribute type to deregister with this
* schema.
*/
public void deregisterAttributeType(AttributeType attributeType)
{
synchronized (attributeTypes)
{
if (attributeTypes.remove(toLowerCase(attributeType.getOID()),
attributeType))
{
// Mark the old attribute type as stale so that caches (such as
// compressed schema) can detect changes.
attributeType.setDirty();
}
for (String name : attributeType.getNormalizedNames())
{
attributeTypes.remove(name, attributeType);
}
AttributeType superiorType = attributeType.getSuperiorType();
if (superiorType != null)
{
deregisterSubordinateType(attributeType, superiorType);
}
}
}
/**
* Registers the provided attribute type as a subtype of the given
* superior attribute type, recursively following any additional
* elements in the superior chain.
*
* @param attributeType The attribute type to be registered as a
* subtype for the given superior type.
* @param superiorType The superior type for which to register
* the given attribute type as a subtype.
*/
private void registerSubordinateType(AttributeType attributeType,
AttributeType superiorType)
{
List<AttributeType> subTypes = subordinateTypes.get(superiorType);
if (subTypes == null)
{
superiorType.setMayHaveSubordinateTypes();
subordinateTypes.put(superiorType, newLinkedList(attributeType));
}
else if (! subTypes.contains(attributeType))
{
superiorType.setMayHaveSubordinateTypes();
subTypes.add(attributeType);
AttributeType higherSuperior = superiorType.getSuperiorType();
if (higherSuperior != null)
{
registerSubordinateType(attributeType, higherSuperior);
}
}
}
/**
* Deregisters the provided attribute type as a subtype of the given
* superior attribute type, recursively following any additional
* elements in the superior chain.
*
* @param attributeType The attribute type to be deregistered as a
* subtype for the given superior type.
* @param superiorType The superior type for which to deregister
* the given attribute type as a subtype.
*/
private void deregisterSubordinateType(AttributeType attributeType,
AttributeType superiorType)
{
List<AttributeType> subTypes = subordinateTypes.get(superiorType);
if (subTypes != null && subTypes.remove(attributeType))
{
AttributeType higherSuperior = superiorType.getSuperiorType();
if (higherSuperior != null)
{
deregisterSubordinateType(attributeType, higherSuperior);
}
}
}
/**
* Retrieves the set of subtypes registered for the given attribute
* type.
*
* @param attributeType The attribute type for which to retrieve
* the set of registered subtypes.
*
* @return The set of subtypes registered for the given attribute
* type, or an empty set if there are no subtypes
* registered for the attribute type.
*/
public List<AttributeType> getSubTypes(AttributeType attributeType)
{
List<AttributeType> subTypes = subordinateTypes.get(attributeType);
if (subTypes == null)
{
return Collections.emptyList();
}
else
{
return subTypes;
}
}
/**
* Retrieves the objectclass definitions for this schema, as a
* mapping between the lowercase names and OIDs for the objectclass
* and the objectclass itself. Each objectclass may be associated
* with multiple keys (once for the OID and again for each name).
* The contents of the returned mapping must not be altered.
*
* @return The objectclass definitions for this schema.
*/
public ConcurrentHashMap<String,ObjectClass> getObjectClasses()
{
return objectClasses;
}
/**
* Indicates whether this schema definition includes an objectclass
* with the provided name or OID.
*
* @param lowerName The name or OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains an objectclass with
* the provided name or OID, or {@code false} if not.
*/
public boolean hasObjectClass(String lowerName)
{
return objectClasses.containsKey(lowerName);
}
/**
* Retrieves the objectclass definition with the specified name or
* OID.
*
* @param lowerName The name or OID of the objectclass to
* retrieve, formatted in all lowercase
* characters.
*
* @return The requested objectclass, or <CODE>null</CODE> if no
* class is registered with the provided name or OID.
*/
public ObjectClass getObjectClass(String lowerName)
{
return objectClasses.get(lowerName);
}
/**
* Registers the provided objectclass definition with this schema.
*
* @param objectClass The objectclass to register with this
* schema.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another objectclass
* with the same OID or name).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>.
*/
public void registerObjectClass(ObjectClass objectClass,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (objectClasses)
{
if (! overwriteExisting)
{
String oid = toLowerCase(objectClass.getOID());
if (objectClasses.containsKey(oid))
{
ObjectClass conflictingClass = objectClasses.get(oid);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID.
get(objectClass.getNameOrOID(), oid,
conflictingClass.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
for (String name : objectClass.getNormalizedNames())
{
if (objectClasses.containsKey(name))
{
ObjectClass conflictingClass = objectClasses.get(name);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_OBJECTCLASS_NAME.
get(objectClass.getNameOrOID(), name,
conflictingClass.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
}
ObjectClass old = objectClasses.put(toLowerCase(objectClass.getOID()),
objectClass);
if (old != null && old != objectClass)
{
// Mark the old object class as stale so that caches (such as compressed
// schema) can detect changes.
old.setDirty();
}
for (String name : objectClass.getNormalizedNames())
{
objectClasses.put(name, objectClass);
}
}
}
/**
* Deregisters the provided objectclass definition with this schema.
*
* @param objectClass The objectclass to deregister with this
* schema.
*/
public void deregisterObjectClass(ObjectClass objectClass)
{
synchronized (objectClasses)
{
if (objectClasses.remove(toLowerCase(objectClass.getOID()), objectClass))
{
// Mark the old object class as stale so that caches (such as
// compressed schema) can detect changes.
objectClass.setDirty();
}
for (String name : objectClass.getNormalizedNames())
{
objectClasses.remove(name, objectClass);
}
}
}
/**
* Retrieves the attribute syntax definitions for this schema, as a
* mapping between the OID for the syntax and the syntax itself.
* Each syntax should only be present once, since its only key is
* its OID. The contents of the returned mapping must not be
* altered.
*
* @return The attribute syntax definitions for this schema.
*/
public ConcurrentHashMap<String,Syntax> getSyntaxes()
{
return syntaxes;
}
/**
* Indicates whether this schema definition includes an attribute
* syntax with the provided name or OID.
*
* @param lowerName The name or OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains an attribute syntax
* with the provided name or OID, or {@code false} if not.
*/
public boolean hasSyntax(String lowerName)
{
return syntaxes.containsKey(lowerName);
}
/**
* Retrieves the requested attribute syntax.
*
* @param oid
* The OID of the syntax to retrieve.
* @param allowDefault
* Indicates whether to return the default attribute syntax if the
* requested syntax is unknown.
* @return The requested attribute syntax, the default syntax if the requested
* syntax is unknown and the caller has indicated that the default is
* acceptable, or <CODE>null</CODE> otherwise.
*/
public Syntax getSyntax(String oid, boolean allowDefault)
{
Syntax syntax = getSyntax(oid);
if (syntax == null && allowDefault)
{
return getDefaultSyntax();
}
return syntax;
}
/**
* Retrieves the attribute syntax definition with the OID.
*
* @param lowerName The OID of the attribute syntax to retrieve,
* formatted in all lowercase characters.
*
* @return The requested attribute syntax, or <CODE>null</CODE> if
* no syntax is registered with the provided OID.
*/
public Syntax getSyntax(String lowerName)
{
return syntaxes.get(lowerName);
}
/**
* Retrieves the default attribute syntax that should be used for attributes
* that are not defined in the server schema.
*
* @return The default attribute syntax that should be used for attributes
* that are not defined in the server schema.
*/
public Syntax getDefaultSyntax()
{
return defaultSyntax;
}
/**
* Registers the defaut syntax for this schema.
*
* @param defaultSyntax
* The defautl syntax to use.
*/
public void registerDefaultSyntax(
Syntax defaultSyntax)
{
this.defaultSyntax = defaultSyntax;
}
/**
* Registers the provided attribute syntax definition with this
* schema.
*
* @param syntax The attribute syntax to register with
* this schema.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another attribute
* syntax with the same OID).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerSyntax(Syntax syntax,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (syntaxes)
{
if (! overwriteExisting)
{
String oid = toLowerCase(syntax.getOID());
if (syntaxes.containsKey(oid))
{
Syntax conflictingSyntax = syntaxes.get(oid);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_SYNTAX_OID.
get(syntax.getName(), oid,
conflictingSyntax.getName());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
syntaxes.put(toLowerCase(syntax.getOID()), syntax);
}
}
/**
* Deregisters the provided attribute syntax definition with this
* schema.
*
* @param syntax The attribute syntax to deregister with this
* schema.
*/
public void deregisterSyntax(Syntax syntax)
{
synchronized (syntaxes)
{
syntaxes.remove(toLowerCase(syntax.getOID()), syntax);
}
}
/**
* Retrieves the ldap syntax definitions for this schema, as a
* mapping between the OID for the syntax and the ldap syntax
* definition itself. Each ldap syntax should only be present once,
* since its only key is its OID. The contents of the returned
* mapping must not be altered.
*
* @return The ldap syntax definitions for this schema.
*/
public ConcurrentHashMap<String,LDAPSyntaxDescription>
getLdapSyntaxDescriptions()
{
return ldapSyntaxDescriptions;
}
/**
* Indicates whether this schema definition includes an ldap
* syntax description with the provided name or OID.
*
* @param lowerName The OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains an ldap syntax
* with the provided name or OID, or {@code false} if not.
*/
public boolean hasLdapSyntaxDescription(String lowerName)
{
return ldapSyntaxDescriptions.containsKey(lowerName);
}
/**
* Retrieves the ldap syntax definition with the OID.
*
* @param lowerName The OID of the ldap syntax to retrieve,
* formatted in all lowercase characters.
*
* @return The requested ldap syntax, or <CODE>null</CODE> if
* no syntax is registered with the provided OID.
*/
public LDAPSyntaxDescription getLdapSyntaxDescription(
String lowerName)
{
return ldapSyntaxDescriptions.get(lowerName);
}
/**
* Registers the provided ldap syntax description with this
* schema.
*
* @param syntax The ldap syntax description to register
* with this schema.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another ldap
* syntax with the same OID).
*
* @throws DirectoryException If a conflict is encountered and
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerLdapSyntaxDescription(
LDAPSyntaxDescription syntax,
boolean overwriteExisting)
throws DirectoryException
{
/**
* ldapsyntaxes is part real and part virtual. For any
* ldapsyntaxes attribute this is real, an LDAPSyntaxDescription
* object is created and stored with the schema. Also, the
* associated LDAPSyntaxDescriptionSyntax is added into the
* virtual syntax set to make this available through virtual
* ldapsyntaxes attribute.
*/
synchronized (ldapSyntaxDescriptions)
{
String oid = toLowerCase(syntax.getSyntax().getOID());
if (! overwriteExisting && ldapSyntaxDescriptions.containsKey(oid))
{
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
ERR_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_LDAP_SYNTAX.get(oid));
}
ldapSyntaxDescriptions.put(oid, syntax);
//Register the attribute syntax with the schema. It will ensure
// syntax is available along with the other virtual values for
// ldapsyntaxes.
registerSyntax(syntax.getSyntax(), overwriteExisting);
}
}
/**
* Deregisters the provided ldap syntax description with this
* schema.
*
* @param syntax The ldap syntax to deregister with this
* schema.
*/
public void deregisterLdapSyntaxDescription(
LDAPSyntaxDescription syntax)
{
synchronized (ldapSyntaxDescriptions)
{
//Remove the real value.
ldapSyntaxDescriptions.remove(
toLowerCase(syntax.getSyntax().getOID()),
syntax);
try
{
//Get rid of this from the virtual ldapsyntaxes.
deregisterSyntax(syntax.getSyntax());
}
catch (Exception e)
{
deregisterSyntax(syntax.getSyntax());
}
}
}
/**
* Retrieves the entire set of matching rule definitions for this
* schema, as a mapping between the lowercase names and OIDs for the
* matching rule and the matching rule itself. Each matching rule
* may be associated with multiple keys (once for the OID and again
* for each name). This should be a superset of the sets of
* approximate, equality, ordering, and substring matching rules.
* The contents of the returned mapping must not be altered.
*
* @return The matching rule definitions for this schema.
*/
public ConcurrentHashMap<String,MatchingRule> getMatchingRules()
{
return matchingRules;
}
/**
* Indicates whether this schema definition includes a matching rule
* with the provided name or OID.
*
* @param lowerName The name or OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains a matching rule
* with the provided name or OID, or {@code false} if not.
*/
public boolean hasMatchingRule(String lowerName)
{
return matchingRules.containsKey(lowerName);
}
/**
* Retrieves the matching rule definition with the specified name or
* OID.
*
* @param lowerName The name or OID of the matching rule to
* retrieve, formatted in all lowercase
* characters.
*
* @return The requested matching rule, or <CODE>null</CODE> if no
* rule is registered with the provided name or OID.
*/
public MatchingRule getMatchingRule(String lowerName)
{
return matchingRules.get(lowerName);
}
/**
* Registers the provided matching rule definition with this schema.
*
* @param matchingRule The matching rule to register with
* this schema.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e.,
* another matching rule with the same
* OID or name).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerMatchingRule(MatchingRule matchingRule, boolean overwriteExisting)
throws DirectoryException
{
synchronized (matchingRules)
{
if (!overwriteExisting)
{
String oid = toLowerCase(matchingRule.getOID());
if (matchingRules.containsKey(oid))
{
MatchingRule conflictingRule = matchingRules.get(oid);
LocalizableMessage message =
ERR_SCHEMA_CONFLICTING_MR_OID.get(matchingRule.getNameOrOID(),
oid, conflictingRule.getNameOrOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
}
for (String name : matchingRule.getNames())
{
if (name != null)
{
name = toLowerCase(name);
if (matchingRules.containsKey(name))
{
MatchingRule conflictingRule = matchingRules.get(name);
LocalizableMessage message =
ERR_SCHEMA_CONFLICTING_MR_NAME.get(matchingRule.getOID(),
name, conflictingRule.getOID());
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
message);
}
}
}
}
matchingRules.put(toLowerCase(matchingRule.getOID()), matchingRule);
for (String name : matchingRule.getNames())
{
if (name != null)
{
matchingRules.put(toLowerCase(name), matchingRule);
}
}
}
}
/**
* Deregisters the provided matching rule definition with this
* schema.
*
* @param matchingRule The matching rule to deregister with this
* schema.
*/
public void deregisterMatchingRule(MatchingRule matchingRule)
{
synchronized (matchingRules)
{
matchingRules.remove(toLowerCase(matchingRule.getOID()), matchingRule);
for (String name : matchingRule.getNames())
{
if (name != null)
{
matchingRules.remove(toLowerCase(name), matchingRule);
}
}
}
}
/**
* Retrieves the matching rule use definitions for this schema, as a
* mapping between the matching rule for the matching rule use
* definition and the matching rule use itself. Each matching rule
* use should only be present once, since its only key is its
* matching rule. The contents of the returned mapping must not be
* altered.
*
* @return The matching rule use definitions for this schema.
*/
public ConcurrentHashMap<MatchingRule,MatchingRuleUse>
getMatchingRuleUses()
{
return matchingRuleUses;
}
/**
* Indicates whether this schema definition includes a matching rule
* use for the provided matching rule.
*
* @param matchingRule The matching rule for which to make the
* determination.
*
* @return {@code true} if this schema contains a matching rule use
* for the provided matching rule, or {@code false} if not.
*/
public boolean hasMatchingRuleUse(MatchingRule matchingRule)
{
return matchingRuleUses.containsKey(matchingRule);
}
/**
* Retrieves the matching rule use definition for the specified
* matching rule.
*
* @param matchingRule The matching rule for which to retrieve the
* matching rule use definition.
*
* @return The matching rule use definition, or <CODE>null</CODE>
* if none exists for the specified matching rule.
*/
public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
{
return matchingRuleUses.get(matchingRule);
}
/**
* Registers the provided matching rule use definition with this
* schema.
*
* @param matchingRuleUse The matching rule use definition to
* register.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another matching rule
* use with the same matching rule).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerMatchingRuleUse(MatchingRuleUse matchingRuleUse,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (matchingRuleUses)
{
MatchingRule matchingRule = matchingRuleUse.getMatchingRule();
if (!overwriteExisting && matchingRuleUses.containsKey(matchingRule))
{
MatchingRuleUse conflictingUse = matchingRuleUses.get(matchingRule);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE.
get(matchingRuleUse.getNameOrOID(),
matchingRule.getNameOrOID(),
conflictingUse.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
matchingRuleUses.put(matchingRule, matchingRuleUse);
}
}
/**
* Deregisters the provided matching rule use definition with this
* schema.
*
* @param matchingRuleUse The matching rule use to deregister with
* this schema.
*/
public void deregisterMatchingRuleUse(
MatchingRuleUse matchingRuleUse)
{
synchronized (matchingRuleUses)
{
matchingRuleUses.remove(matchingRuleUse.getMatchingRule(),
matchingRuleUse);
}
}
/**
* Retrieves the DIT content rule definitions for this schema, as a
* mapping between the objectclass for the rule and the DIT content
* rule itself. Each DIT content rule should only be present once,
* since its only key is its objectclass. The contents of the
* returned mapping must not be altered.
*
* @return The DIT content rule definitions for this schema.
*/
public ConcurrentHashMap<ObjectClass,DITContentRule>
getDITContentRules()
{
return ditContentRules;
}
/**
* Indicates whether this schema definition includes a DIT content
* rule for the provided objectclass.
*
* @param objectClass The objectclass for which to make the
* determination.
*
* @return {@code true} if this schema contains a DIT content rule
* for the provided objectclass, or {@code false} if not.
*/
public boolean hasDITContentRule(ObjectClass objectClass)
{
return ditContentRules.containsKey(objectClass);
}
/**
* Retrieves the DIT content rule definition for the specified
* objectclass.
*
* @param objectClass The objectclass for the DIT content rule to
* retrieve.
*
* @return The requested DIT content rule, or <CODE>null</CODE> if
* no DIT content rule is registered with the provided
* objectclass.
*/
public DITContentRule getDITContentRule(ObjectClass objectClass)
{
return ditContentRules.get(objectClass);
}
/**
* Registers the provided DIT content rule definition with this
* schema.
*
* @param ditContentRule The DIT content rule to register.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another DIT content
* rule with the same objectclass).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerDITContentRule(DITContentRule ditContentRule,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (ditContentRules)
{
ObjectClass objectClass = ditContentRule.getStructuralClass();
if (! overwriteExisting && ditContentRules.containsKey(objectClass))
{
DITContentRule conflictingRule =
ditContentRules.get(objectClass);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE.
get(ditContentRule.getNameOrOID(),
objectClass.getNameOrOID(),
conflictingRule.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
ditContentRules.put(objectClass, ditContentRule);
}
}
/**
* Deregisters the provided DIT content rule definition with this
* schema.
*
* @param ditContentRule The DIT content rule to deregister with
* this schema.
*/
public void deregisterDITContentRule(DITContentRule ditContentRule)
{
synchronized (ditContentRules)
{
ditContentRules.remove(ditContentRule.getStructuralClass(),
ditContentRule);
}
}
/**
* Retrieves the DIT structure rule definitions for this schema, as
* a mapping between the rule ID for the rule and the DIT structure
* rule itself. Each DIT structure rule should only be present
* once, since its only key is its rule ID. The contents of the
* returned mapping must not be altered.
*
* @return The DIT structure rule definitions for this schema.
*/
public ConcurrentHashMap<Integer,DITStructureRule>
getDITStructureRulesByID()
{
return ditStructureRulesByID;
}
/**
* Retrieves the DIT structure rule definitions for this schema, as
* a mapping between the name form for the rule and the DIT
* structure rule itself. Each DIT structure rule should only be
* present once, since its only key is its name form. The contents
* of the returned mapping must not be altered.
*
* @return The DIT structure rule definitions for this schema.
*/
public ConcurrentHashMap<NameForm,DITStructureRule>
getDITStructureRulesByNameForm()
{
return ditStructureRulesByNameForm;
}
/**
* Indicates whether this schema definition includes a DIT structure
* rule with the provided rule ID.
*
* @param ruleID The rule ID for which to make the determination.
*
* @return {@code true} if this schema contains a DIT structure
* rule with the provided rule ID, or {@code false} if not.
*/
public boolean hasDITStructureRule(int ruleID)
{
return ditStructureRulesByID.containsKey(ruleID);
}
/**
* Indicates whether this schema definition includes a DIT structure
* rule for the provided name form.
*
* @param nameForm The name form for which to make the
* determination.
*
* @return {@code true} if this schema contains a DIT structure
* rule for the provided name form, or {@code false} if
* not.
*/
public boolean hasDITStructureRule(NameForm nameForm)
{
return ditStructureRulesByNameForm.containsKey(nameForm);
}
/**
* Retrieves the DIT structure rule definition with the provided
* rule ID.
*
* @param ruleID The rule ID for the DIT structure rule to
* retrieve.
*
* @return The requested DIT structure rule, or <CODE>null</CODE>
* if no DIT structure rule is registered with the provided
* rule ID.
*/
public DITStructureRule getDITStructureRule(int ruleID)
{
return ditStructureRulesByID.get(ruleID);
}
/**
* Retrieves the DIT structure rule definition for the provided name
* form.
*
* @param nameForm The name form for the DIT structure rule to
* retrieve.
*
* @return The requested DIT structure rule, or <CODE>null</CODE>
* if no DIT structure rule is registered with the provided
* name form.
*/
public DITStructureRule getDITStructureRule(NameForm nameForm)
{
return ditStructureRulesByNameForm.get(nameForm);
}
/**
* Registers the provided DIT structure rule definition with this
* schema.
*
* @param ditStructureRule The DIT structure rule to register.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another DIT structure
* rule with the same name form).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerDITStructureRule(
DITStructureRule ditStructureRule,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (ditStructureRulesByNameForm)
{
NameForm nameForm = ditStructureRule.getNameForm();
int ruleID = ditStructureRule.getRuleID();
if (! overwriteExisting)
{
if (ditStructureRulesByNameForm.containsKey(nameForm))
{
DITStructureRule conflictingRule =
ditStructureRulesByNameForm.get(nameForm);
LocalizableMessage message =
ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_NAME_FORM.
get(ditStructureRule.getNameOrRuleID(),
nameForm.getNameOrOID(),
conflictingRule.getNameOrRuleID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
if (ditStructureRulesByID.containsKey(ruleID))
{
DITStructureRule conflictingRule =
ditStructureRulesByID.get(ruleID);
LocalizableMessage message =
ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID.
get(ditStructureRule.getNameOrRuleID(), ruleID,
conflictingRule.getNameOrRuleID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
ditStructureRulesByNameForm.put(nameForm, ditStructureRule);
ditStructureRulesByID.put(ruleID, ditStructureRule);
}
}
/**
* Deregisters the provided DIT structure rule definition with this
* schema.
*
* @param ditStructureRule The DIT structure rule to deregister
* with this schema.
*/
public void deregisterDITStructureRule(
DITStructureRule ditStructureRule)
{
synchronized (ditStructureRulesByNameForm)
{
ditStructureRulesByNameForm.remove(
ditStructureRule.getNameForm(), ditStructureRule);
ditStructureRulesByID.remove(ditStructureRule.getRuleID(),
ditStructureRule);
}
}
/**
* Retrieves the name form definitions for this schema, as a mapping
* between the objectclass for the name forms and the name forms
* themselves.
*
* @return The name form definitions for this schema.
*/
public ConcurrentHashMap<ObjectClass,List<NameForm>>
getNameFormsByObjectClass()
{
return nameFormsByOC;
}
/**
* Retrieves the name form definitions for this schema, as a mapping
* between the names/OID for the name form and the name form itself.
* Each name form may be present multiple times with different names
* and its OID. The contents of the returned mapping must not be
* altered.
*
* @return The name form definitions for this schema.
*/
public ConcurrentHashMap<String,NameForm> getNameFormsByNameOrOID()
{
return nameFormsByName;
}
/**
* Indicates whether this schema definition includes a name form for
* the specified objectclass.
*
* @param objectClass The objectclass for which to make the
* determination.
*
* @return {@code true} if this schema contains a name form for the
* provided objectclass, or {@code false} if not.
*/
public boolean hasNameForm(ObjectClass objectClass)
{
return nameFormsByOC.containsKey(objectClass);
}
/**
* Indicates whether this schema definition includes a name form
* with the specified name or OID.
*
* @param lowerName The name or OID for which to make the
* determination, formatted in all lowercase
* characters.
*
* @return {@code true} if this schema contains a name form with
* the provided name or OID, or {@code false} if not.
*/
public boolean hasNameForm(String lowerName)
{
return nameFormsByName.containsKey(lowerName);
}
/**
* Retrieves the name forms definition for the specified
* objectclass.
*
* @param objectClass The objectclass for the name form to
* retrieve.
*
* @return The requested name forms, or <CODE>null</CODE> if no
* name forms are registered with the provided
* objectClass.
*/
public List<NameForm> getNameForm(ObjectClass objectClass)
{
return nameFormsByOC.get(objectClass);
}
/**
* Retrieves the name form definition with the provided name or OID.
*
* @param lowerName The name or OID of the name form to retrieve,
* formatted in all lowercase characters.
*
* @return The requested name form, or <CODE>null</CODE> if no name
* form is registered with the provided name or OID.
*/
public NameForm getNameForm(String lowerName)
{
return nameFormsByName.get(lowerName);
}
/**
* Registers the provided name form definition with this schema.
*
* @param nameForm The name form definition to register.
* @param overwriteExisting Indicates whether to overwrite an
* existing mapping if there are any
* conflicts (i.e., another name form
* with the same objectclass).
*
* @throws DirectoryException If a conflict is encountered and the
* <CODE>overwriteExisting</CODE> flag
* is set to <CODE>false</CODE>
*/
public void registerNameForm(NameForm nameForm,
boolean overwriteExisting)
throws DirectoryException
{
synchronized (nameFormsByOC)
{
ObjectClass objectClass = nameForm.getStructuralClass();
List<NameForm> mappedForms = nameFormsByOC.get(objectClass);
if (! overwriteExisting)
{
if(mappedForms !=null)
{
//Iterate over the forms to make sure we aren't adding a
//duplicate.
for(NameForm nf : mappedForms)
{
if(nf.equals(nameForm))
{
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OC.
get(nameForm.getNameOrOID(),
objectClass.getNameOrOID(),
nf.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
}
String oid = toLowerCase(nameForm.getOID());
if (nameFormsByName.containsKey(oid))
{
NameForm conflictingNameForm = nameFormsByName.get(oid);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_OID.
get(nameForm.getNameOrOID(), oid,
conflictingNameForm.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
for (String name : nameForm.getNames().keySet())
{
if (nameFormsByName.containsKey(name))
{
NameForm conflictingNameForm = nameFormsByName.get(name);
LocalizableMessage message = ERR_SCHEMA_CONFLICTING_NAME_FORM_NAME.
get(nameForm.getNameOrOID(), oid,
conflictingNameForm.getNameOrOID());
throw new DirectoryException(
ResultCode.CONSTRAINT_VIOLATION, message);
}
}
}
if(mappedForms == null)
{
mappedForms = new ArrayList<>();
}
mappedForms.add(nameForm);
nameFormsByOC.put(objectClass, mappedForms);
nameFormsByName.put(toLowerCase(nameForm.getOID()), nameForm);
for (String name : nameForm.getNames().keySet())
{
nameFormsByName.put(name, nameForm);
}
}
}
/**
* Deregisters the provided name form definition with this schema.
*
* @param nameForm The name form definition to deregister.
*/
public void deregisterNameForm(NameForm nameForm)
{
synchronized (nameFormsByOC)
{
List<NameForm> mappedForms = nameFormsByOC.get(
nameForm.getStructuralClass());
if(mappedForms != null)
{
mappedForms.remove(nameForm);
if(mappedForms.isEmpty())
{
nameFormsByOC.remove(nameForm.getStructuralClass());
}
}
nameFormsByOC.remove(nameForm.getStructuralClass());
nameFormsByName.remove(toLowerCase(nameForm.getOID()),
nameForm);
for (String name : nameForm.getNames().keySet())
{
nameFormsByName.remove(name, nameForm);
}
}
}
/**
* Retrieves the modification timestamp for the file in the schema
* configuration directory with the oldest last modified time.
*
* @return The modification timestamp for the file in the schema
* configuration directory with the oldest last modified
* time.
*/
public long getOldestModificationTime()
{
return oldestModificationTime;
}
/**
* Sets the modification timestamp for the oldest file in the schema
* configuration directory.
*
* @param oldestModificationTime The modification timestamp for
* the oldest file in the schema
* configuration directory.
*/
public void setOldestModificationTime(long oldestModificationTime)
{
this.oldestModificationTime = oldestModificationTime;
}
/**
* Retrieves the modification timestamp for the file in the schema
* configuration directory with the youngest last modified time.
*
* @return The modification timestamp for the file in the schema
* configuration directory with the youngest last modified
* time.
*/
public long getYoungestModificationTime()
{
return youngestModificationTime;
}
/**
* Sets the modification timestamp for the youngest file in the
* schema configuration directory.
*
* @param youngestModificationTime The modification timestamp for
* the youngest file in the schema
* configuration directory.
*/
public void setYoungestModificationTime(
long youngestModificationTime)
{
this.youngestModificationTime = youngestModificationTime;
}
/**
* Recursively rebuilds all schema elements that are dependent upon
* the provided element. This must be invoked whenever an existing
* schema element is modified in order to ensure that any elements
* that depend on it should also be recreated to reflect the change.
* <BR><BR>
* The following conditions create dependencies between schema
* elements:
* <UL>
* <LI>If an attribute type references a superior attribute type,
* then it is dependent upon that superior attribute
* type.</LI>
* <LI>If an objectclass requires or allows an attribute type,
* then it is dependent upon that attribute type.</LI>
* <LI>If a name form requires or allows an attribute type in the
* RDN, then it is dependent upon that attribute type.</LI>
* <LI>If a DIT content rule requires, allows, or forbids the use
* of an attribute type, then it is dependent upon that
* attribute type.</LI>
* <LI>If a matching rule use references an attribute type, then
* it is dependent upon that attribute type.</LI>
* <LI>If an objectclass references a superior objectclass, then
* it is dependent upon that superior objectclass.</LI>
* <LI>If a name form references a structural objectclass, then it
* is dependent upon that objectclass.</LI>
* <LI>If a DIT content rule references a structural or auxiliary
* objectclass, then it is dependent upon that
* objectclass.</LI>
* <LI>If a DIT structure rule references a name form, then it is
* dependent upon that name form.</LI>
* <LI>If a DIT structure rule references a superior DIT structure
* rule, then it is dependent upon that superior DIT structure
* rule.</LI>
* </UL>
*
* @param element The element for which to recursively rebuild all
* dependent elements.
*
* @throws DirectoryException If a problem occurs while rebuilding
* any of the schema elements.
*/
public void rebuildDependentElements(SchemaFileElement element)
throws DirectoryException
{
try
{
rebuildDependentElements(element, 0);
}
catch (DirectoryException de)
{
// If we got an error as a result of a circular reference, then
// we want to make sure that the schema element we call out is
// the one that is at the root of the problem.
if (StaticUtils.hasDescriptor(de.getMessageObject(),
ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE))
{
LocalizableMessage message =
ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element);
throw new DirectoryException(de.getResultCode(), message,
de);
}
// It wasn't a circular reference error, so just re-throw the
// exception.
throw de;
}
}
/**
* Recursively rebuilds all schema elements that are dependent upon
* the provided element, increasing the depth for each level of
* recursion to protect against errors due to circular references.
*
* @param element The element for which to recursively rebuild all
* dependent elements.
* @param depth The current recursion depth.
*
* @throws DirectoryException If a problem occurs while rebuilding
* any of the schema elements.
*/
private void rebuildDependentElements(SchemaFileElement element,
int depth)
throws DirectoryException
{
if (depth > 20)
{
// FIXME -- Is this an appropriate maximum depth for detecting
// circular references?
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
ERR_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE.get(element));
}
// Figure out what type of element we're dealing with and make the
// appropriate determinations for that element.
if (element instanceof AttributeType)
{
AttributeType t = (AttributeType) element;
for (AttributeType at : attributeTypes.values())
{
if (at.getSuperiorType() != null && at.getSuperiorType().equals(t))
{
AttributeType newAT = recreateFromDefinition(at);
deregisterAttributeType(at);
registerAttributeType(newAT, true);
rebuildDependentElements(at, depth+1);
}
}
for (ObjectClass oc : objectClasses.values())
{
if (oc.getRequiredAttributes().contains(t) ||
oc.getOptionalAttributes().contains(t))
{
ObjectClass newOC = recreateFromDefinition(oc);
deregisterObjectClass(oc);
registerObjectClass(newOC, true);
rebuildDependentElements(oc, depth+1);
}
}
for (List<NameForm> mappedForms : nameFormsByOC.values())
{
for(NameForm nf : mappedForms)
{
if (nf.getRequiredAttributes().contains(t) ||
nf.getOptionalAttributes().contains(t))
{
NameForm newNF = recreateFromDefinition(nf);
deregisterNameForm(nf);
registerNameForm(newNF, true);
rebuildDependentElements(nf, depth+1);
}
}
}
for (DITContentRule dcr : ditContentRules.values())
{
if (dcr.getRequiredAttributes().contains(t) ||
dcr.getOptionalAttributes().contains(t) ||
dcr.getProhibitedAttributes().contains(t))
{
DITContentRule newDCR = recreateFromDefinition(dcr);
deregisterDITContentRule(dcr);
registerDITContentRule(newDCR, true);
rebuildDependentElements(dcr, depth+1);
}
}
for (MatchingRuleUse mru : matchingRuleUses.values())
{
if (mru.getAttributes().contains(t))
{
MatchingRuleUse newMRU = recreateFromDefinition(mru);
deregisterMatchingRuleUse(mru);
registerMatchingRuleUse(newMRU, true);
rebuildDependentElements(mru, depth+1);
}
}
}
else if (element instanceof ObjectClass)
{
ObjectClass c = (ObjectClass) element;
for (ObjectClass oc : objectClasses.values())
{
if (oc.getSuperiorClasses().contains(c))
{
ObjectClass newOC = recreateFromDefinition(oc);
deregisterObjectClass(oc);
registerObjectClass(newOC, true);
rebuildDependentElements(oc, depth+1);
}
}
List<NameForm> mappedForms = nameFormsByOC.get(c);
if(mappedForms != null)
{
for(NameForm nf : mappedForms)
{
if (nf != null)
{
NameForm newNF = recreateFromDefinition(nf);
deregisterNameForm(nf);
registerNameForm(newNF, true);
rebuildDependentElements(nf, depth+1);
}
}
}
for (DITContentRule dcr : ditContentRules.values())
{
if (dcr.getStructuralClass().equals(c) ||
dcr.getAuxiliaryClasses().contains(c))
{
DITContentRule newDCR = recreateFromDefinition(dcr);
deregisterDITContentRule(dcr);
registerDITContentRule(newDCR, true);
rebuildDependentElements(dcr, depth+1);
}
}
}
else if (element instanceof NameForm)
{
NameForm n = (NameForm) element;
DITStructureRule dsr = ditStructureRulesByNameForm.get(n);
if (dsr != null)
{
DITStructureRule newDSR = recreateFromDefinition(dsr);
deregisterDITStructureRule(dsr);
registerDITStructureRule(newDSR, true);
rebuildDependentElements(dsr, depth+1);
}
}
else if (element instanceof DITStructureRule)
{
DITStructureRule d = (DITStructureRule) element;
for (DITStructureRule dsr : ditStructureRulesByID.values())
{
if (dsr.getSuperiorRules().contains(d))
{
DITStructureRule newDSR = recreateFromDefinition(dsr);
deregisterDITStructureRule(dsr);
registerDITStructureRule(newDSR, true);
rebuildDependentElements(dsr, depth+1);
}
}
}
}
private AttributeType recreateFromDefinition(AttributeType attrType)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(attrType.toString());
AttributeType copy =
AttributeTypeSyntax.decodeAttributeType(value, this, false);
setSchemaFile(copy, getSchemaFile(attrType));
if (attrType.mayHaveSubordinateTypes())
{
copy.setMayHaveSubordinateTypes();
}
return copy;
}
private DITContentRule recreateFromDefinition(DITContentRule dcr)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(dcr.toString());
DITContentRule copy =
DITContentRuleSyntax.decodeDITContentRule(value, this, false);
setSchemaFile(copy, getSchemaFile(dcr));
return copy;
}
private DITStructureRule recreateFromDefinition(DITStructureRule dsr)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(dsr.toString());
DITStructureRule copy =
DITStructureRuleSyntax.decodeDITStructureRule(value, this, false);
setSchemaFile(copy, getSchemaFile(dsr));
return copy;
}
private MatchingRuleUse recreateFromDefinition(MatchingRuleUse mru)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(mru.toString());
MatchingRuleUse copy =
MatchingRuleUseSyntax.decodeMatchingRuleUse(value, this, false);
setSchemaFile(copy, getSchemaFile(mru));
return copy;
}
private NameForm recreateFromDefinition(NameForm nf)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(nf.toString());
NameForm copy = NameFormSyntax.decodeNameForm(value, this, false);
setSchemaFile(copy, getSchemaFile(nf));
return copy;
}
private ObjectClass recreateFromDefinition(ObjectClass oc)
throws DirectoryException
{
ByteString value = ByteString.valueOfUtf8(oc.toString());
ObjectClass copy = ObjectClassSyntax.decodeObjectClass(value, this, false);
setSchemaFile(copy, getSchemaFile(oc));
return copy;
}
/**
* Creates a new <CODE>Schema</CODE> object that is a duplicate of
* this one. It elements may be added and removed from the
* duplicate without impacting this version.
*
* @return A new <CODE>Schema</CODE> object that is a duplicate of
* this one.
*/
public Schema duplicate()
{
Schema dupSchema = new Schema();
dupSchema.attributeTypes.putAll(attributeTypes);
dupSchema.subordinateTypes.putAll(subordinateTypes);
dupSchema.objectClasses.putAll(objectClasses);
dupSchema.syntaxes.putAll(syntaxes);
dupSchema.matchingRules.putAll(matchingRules);
dupSchema.matchingRuleUses.putAll(matchingRuleUses);
dupSchema.ditContentRules.putAll(ditContentRules);
dupSchema.ditStructureRulesByID.putAll(ditStructureRulesByID);
dupSchema.ditStructureRulesByNameForm.putAll(
ditStructureRulesByNameForm);
dupSchema.nameFormsByOC.putAll(nameFormsByOC);
dupSchema.nameFormsByName.putAll(nameFormsByName);
dupSchema.ldapSyntaxDescriptions.putAll(ldapSyntaxDescriptions);
dupSchema.oldestModificationTime = oldestModificationTime;
dupSchema.youngestModificationTime = youngestModificationTime;
if (extraAttributes != null)
{
dupSchema.extraAttributes = new HashMap<>(extraAttributes);
}
return dupSchema;
}
/**
* Get the extraAttributes stored in this schema.
*
* @return The extraAttributes stored in this schema.
*/
public Map<String, Attribute> getExtraAttributes()
{
return extraAttributes;
}
/**
* Add a new extra Attribute for this schema.
*
* @param name The identifier of the extra Attribute.
*
* @param attr The extra attribute that must be added to
* this Schema.
*/
public void addExtraAttribute(String name, Attribute attr)
{
extraAttributes.put(name, attr);
}
/**
* Writes a single file containing all schema element definitions,
* which can be used on startup to determine whether the schema
* files were edited with the server offline.
*/
public static void writeConcatenatedSchema()
{
String concatFilePath = null;
try
{
Set<String> attributeTypes = new LinkedHashSet<>();
Set<String> objectClasses = new LinkedHashSet<>();
Set<String> nameForms = new LinkedHashSet<>();
Set<String> ditContentRules = new LinkedHashSet<>();
Set<String> ditStructureRules = new LinkedHashSet<>();
Set<String> matchingRuleUses = new LinkedHashSet<>();
Set<String> ldapSyntaxes = new LinkedHashSet<>();
genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
ditContentRules, ditStructureRules,
matchingRuleUses,ldapSyntaxes);
File configFile = new File(DirectoryServer.getConfigFile());
File configDirectory = configFile.getParentFile();
File upgradeDirectory = new File(configDirectory, "upgrade");
upgradeDirectory.mkdir();
File concatFile = new File(upgradeDirectory,
SCHEMA_CONCAT_FILE_NAME);
concatFilePath = concatFile.getAbsolutePath();
File tempFile = new File(concatFilePath + ".tmp");
BufferedWriter writer =
new BufferedWriter(new FileWriter(tempFile, false));
writer.write("dn: " + DirectoryServer.getSchemaDN());
writer.newLine();
writer.write("objectClass: top");
writer.newLine();
writer.write("objectClass: ldapSubentry");
writer.newLine();
writer.write("objectClass: subschema");
writer.newLine();
for (String line : attributeTypes)
{
writer.write(ATTR_ATTRIBUTE_TYPES);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : objectClasses)
{
writer.write(ATTR_OBJECTCLASSES);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : nameForms)
{
writer.write(ATTR_NAME_FORMS);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : ditContentRules)
{
writer.write(ATTR_DIT_CONTENT_RULES);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : ditStructureRules)
{
writer.write(ATTR_DIT_STRUCTURE_RULES);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : matchingRuleUses)
{
writer.write(ATTR_MATCHING_RULE_USE);
writer.write(": ");
writer.write(line);
writer.newLine();
}
for (String line : ldapSyntaxes)
{
writer.write(ATTR_LDAP_SYNTAXES);
writer.write(": ");
writer.write(line);
writer.newLine();
}
writer.close();
if (concatFile.exists())
{
concatFile.delete();
}
tempFile.renameTo(concatFile);
}
catch (Exception e)
{
logger.traceException(e);
// This is definitely not ideal, but it's not the end of the
// world. The worst that should happen is that the schema
// changes could potentially be sent to the other servers again
// when this server is restarted, which shouldn't hurt anything.
// Still, we should log a warning message.
logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
}
}
/**
* Reads the files contained in the schema directory and generates a
* concatenated view of their contents in the provided sets.
*
* @param attributeTypes The set into which to place the
* attribute types read from the schema
* files.
* @param objectClasses The set into which to place the object
* classes read from the schema files.
* @param nameForms The set into which to place the name
* forms read from the schema files.
* @param ditContentRules The set into which to place the DIT
* content rules read from the schema
* files.
* @param ditStructureRules The set into which to place the DIT
* structure rules read from the schema
* files.
* @param matchingRuleUses The set into which to place the
* matching rule uses read from the
* schema files.
* @param ldapSyntaxes The set into which to place the
* ldap syntaxes read from the
* schema files.
*
* @throws IOException If a problem occurs while reading the
* schema file elements.
*/
public static void genConcatenatedSchema(
Set<String> attributeTypes,
Set<String> objectClasses,
Set<String> nameForms,
Set<String> ditContentRules,
Set<String> ditStructureRules,
Set<String> matchingRuleUses,
Set<String> ldapSyntaxes)
throws IOException
{
// Get a sorted list of the files in the schema directory.
TreeSet<File> schemaFiles = new TreeSet<>();
String schemaDirectory =
SchemaConfigManager.getSchemaDirectoryPath();
final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
for (File f : new File(schemaDirectory).listFiles(filter))
{
if (f.isFile())
{
schemaFiles.add(f);
}
}
// Open each of the files in order and read the elements that they
// contain, appending them to the appropriate lists.
for (File f : schemaFiles)
{
// Read the contents of the file into a list with one schema
// element per list element.
LinkedList<StringBuilder> lines = new LinkedList<>();
BufferedReader reader = new BufferedReader(new FileReader(f));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
else if (line.startsWith("#") || line.length() == 0)
{
continue;
}
else if (line.startsWith(" "))
{
lines.getLast().append(line.substring(1));
}
else
{
lines.add(new StringBuilder(line));
}
}
reader.close();
// Iterate through each line in the list. Find the colon and
// get the attribute name at the beginning. If it's something
// that we don't recognize, then skip it. Otherwise, add the
// X-SCHEMA-FILE extension and add it to the appropriate schema
// element list.
for (StringBuilder buffer : lines)
{
// Get the line and add the X-SCHEMA-FILE extension to the end
// of it. All of them should end with " )" but some might
// have the parenthesis crammed up against the last character
// so deal with that as well.
String line = buffer.toString().trim();
if (line.endsWith(" )"))
{
line = line.substring(0, line.length()-1) +
SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )";
}
else if (line.endsWith(")"))
{
line = line.substring(0, line.length()-1) + " " +
SCHEMA_PROPERTY_FILENAME + " '" + f.getName() + "' )";
}
else
{
continue;
}
parseSchemaLine(line, attributeTypes, objectClasses,
nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
ldapSyntaxes);
}
}
}
/**
* Reads data from the specified concatenated schema file into the
* provided sets.
*
* @param concatSchemaFile The path to the concatenated schema
* file to be read.
* @param attributeTypes The set into which to place the
* attribute types read from the
* concatenated schema file.
* @param objectClasses The set into which to place the object
* classes read from the concatenated
* schema file.
* @param nameForms The set into which to place the name
* forms read from the concatenated
* schema file.
* @param ditContentRules The set into which to place the DIT
* content rules read from the
* concatenated schema file.
* @param ditStructureRules The set into which to place the DIT
* structure rules read from the
* concatenated schema file.
* @param matchingRuleUses The set into which to place the
* matching rule uses read from the
* concatenated schema file.
* @param ldapSyntaxes The set into which to place the
* ldap syntaxes read from the
* concatenated schema file.
*
* @throws IOException If a problem occurs while reading the
* schema file elements.
*/
public static void readConcatenatedSchema(String concatSchemaFile,
Set<String> attributeTypes,
Set<String> objectClasses,
Set<String> nameForms,
Set<String> ditContentRules,
Set<String> ditStructureRules,
Set<String> matchingRuleUses,
Set<String> ldapSyntaxes)
throws IOException
{
BufferedReader reader =
new BufferedReader(new FileReader(concatSchemaFile));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
parseSchemaLine(line, attributeTypes, objectClasses,
nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
ldapSyntaxes);
}
reader.close();
}
/**
* Parse a line of a schema file into the provided sets.
*
* @param line The current line of schema.
* @param attributeTypes The set into which to place the
* attribute type if the line represents
* one.
* @param objectClasses The set into which to place the object
* class if the line represents one.
* @param nameForms The set into which to place the name
* form if the line represents one.
* @param ditContentRules The set into which to place the DIT
* content rule if the line represents one.
* @param ditStructureRules The set into which to place the DIT
* structure rule if the line represents one.
* @param matchingRuleUses The set into which to place the
* matching rule use if the line represents
* one.
* @param ldapSyntaxes The set into which to place the ldap
* syntax if the line represents one.
*/
private static void parseSchemaLine(String line,
Set<String> attributeTypes,
Set<String> objectClasses,
Set<String> nameForms,
Set<String> ditContentRules,
Set<String> ditStructureRules,
Set<String> matchingRuleUses,
Set<String> ldapSyntaxes)
{
String value;
String lowerLine = toLowerCase(line);
if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
{
value =
line.substring(ATTR_ATTRIBUTE_TYPES.length()+1).trim();
attributeTypes.add(value);
}
else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
{
value = line.substring(ATTR_OBJECTCLASSES.length()+1).trim();
objectClasses.add(value);
}
else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
{
value = line.substring(ATTR_NAME_FORMS.length()+1).trim();
nameForms.add(value);
}
else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
{
value = line.substring(
ATTR_DIT_CONTENT_RULES.length()+1).trim();
ditContentRules.add(value);
}
else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
{
value = line.substring(
ATTR_DIT_STRUCTURE_RULES.length()+1).trim();
ditStructureRules.add(value);
}
else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
{
value = line.substring(
ATTR_MATCHING_RULE_USE.length()+1).trim();
matchingRuleUses.add(value);
}
else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
{
value = line.substring(
ATTR_LDAP_SYNTAXES.length()+1).trim();
ldapSyntaxes.add(value);
}
}
/**
* Compares the provided sets of schema element definitions and
* writes any differences found into the given list of
* modifications.
*
* @param oldElements The set of elements of the specified type
* read from the previous concatenated schema
* files.
* @param newElements The set of elements of the specified type
* read from the server's current schema.
* @param elementType The attribute type associated with the
* schema element being compared.
* @param mods The list of modifications into which any
* identified differences should be written.
*/
public static void compareConcatenatedSchema(
Set<String> oldElements,
Set<String> newElements,
AttributeType elementType,
List<Modification> mods)
{
AttributeBuilder builder = new AttributeBuilder(elementType);
for (String s : oldElements)
{
if (!newElements.contains(s))
{
builder.add(s);
}
}
if (!builder.isEmpty())
{
mods.add(new Modification(ModificationType.DELETE,
builder.toAttribute()));
}
builder.setAttributeType(elementType);
for (String s : newElements)
{
if (!oldElements.contains(s))
{
builder.add(s);
}
}
if (!builder.isEmpty())
{
mods.add(new Modification(ModificationType.ADD,
builder.toAttribute()));
}
}
/**
* Destroys the structures maintained by the schema so that they are
* no longer usable. This should only be called at the end of the
* server shutdown process, and it can help detect inappropriate
* cached references.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.PRIVATE,
mayInstantiate=false,
mayExtend=false,
mayInvoke=true)
public synchronized void destroy()
{
if (attributeTypes != null)
{
attributeTypes.clear();
attributeTypes = null;
}
if (ditContentRules != null)
{
ditContentRules.clear();
ditContentRules = null;
}
if (ditStructureRulesByID != null)
{
ditStructureRulesByID.clear();
ditStructureRulesByID = null;
}
if (ditStructureRulesByNameForm != null)
{
ditStructureRulesByNameForm.clear();
ditStructureRulesByNameForm = null;
}
if (matchingRules != null)
{
matchingRules.clear();
matchingRules = null;
}
if (matchingRuleUses != null)
{
matchingRuleUses.clear();
matchingRuleUses = null;
}
if (nameFormsByName != null)
{
nameFormsByName.clear();
nameFormsByName = null;
}
if (nameFormsByOC != null)
{
nameFormsByOC.clear();
nameFormsByOC = null;
}
if (objectClasses != null)
{
objectClasses.clear();
objectClasses = null;
}
if (subordinateTypes != null)
{
subordinateTypes.clear();
subordinateTypes = null;
}
if (extraAttributes != null)
{
extraAttributes.clear();
extraAttributes = null;
}
if (syntaxes != null)
{
syntaxes.clear();
syntaxes = null;
}
if(ldapSyntaxDescriptions != null)
{
ldapSyntaxDescriptions.clear();
ldapSyntaxDescriptions = null;
}
}
}