blob: dee972ec95342f9f47f8a41cd69495988051304f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.api.ldap.model.schema;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.directory.api.i18n.I18n;
import org.apache.directory.api.util.Strings;
/**
* Most schema objects have some common attributes. This class
* contains the minimum set of properties exposed by a SchemaObject.<br>
* We have 11 types of SchemaObjects :
* <ul>
* <li> AttributeType</li>
* <li> DitCOntentRule</li>
* <li> DitStructureRule</li>
* <li> LdapComparator (specific to ADS)</li>
* <li> LdapSyntaxe</li>
* <li> MatchingRule</li>
* <li> MatchingRuleUse</li>
* <li> NameForm</li>
* <li> Normalizer (specific to ADS)</li>
* <li> ObjectClass</li>
* <li> SyntaxChecker (specific to ADS)</li>
* </ul>
* <br>
* <br>
* This class provides accessors and setters for the following attributes,
* which are common to all those SchemaObjects :
* <ul>
* <li>oid : The numeric OID</li>
* <li>description : The SchemaObject description</li>
* <li>obsolete : Tells if the schema object is obsolete</li>
* <li>extensions : The extensions, a key/Values map</li>
* <li>schemaObjectType : The SchemaObject type (see upper)</li>
* <li>schema : The schema the SchemaObject is associated with (it's an extension).
* Can be null</li>
* <li>isEnabled : The SchemaObject status (it's related to the schema status)</li>
* <li>isReadOnly : Tells if the SchemaObject can be modified or not</li>
* </ul>
* <br><br>
* Some of those attributes are not used by some Schema elements, even if they should
* have been used. Here is the list :
* <ul>
* <li><b>name</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker</li>
* <li><b>numericOid</b> : DitStructureRule</li>
* <li><b>obsolete</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker</li>
* </ul>
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public abstract class AbstractSchemaObject implements SchemaObject, Serializable
{
/** The serial version UID */
private static final long serialVersionUID = 2L;
/** The SchemaObject numeric OID */
protected String oid;
/** The optional names for this SchemaObject */
protected transient List<String> names;
/** Whether or not this SchemaObject is enabled */
protected boolean isEnabled = true;
/** Whether or not this SchemaObject is obsolete */
protected boolean isObsolete = false;
/** A short description of this SchemaObject */
protected String description;
/** The SchemaObject specification */
protected String specification;
/** The name of the schema this object is associated with */
protected String schemaName;
/** The SchemaObjectType */
protected SchemaObjectType objectType;
/** A map containing the list of supported extensions */
protected transient Map<String, List<String>> extensions;
/** A locked to avoid modifications when set to true */
protected volatile boolean locked;
/** The hashcode for this schemaObject */
protected volatile int h = 0;
/**
* A constructor for a SchemaObject instance. It must be
* invoked by the inherited class.
*
* @param objectType The SchemaObjectType to create
* @param oid the SchemaObject numeric OID
*/
protected AbstractSchemaObject( SchemaObjectType objectType, String oid )
{
this.objectType = objectType;
this.oid = oid;
extensions = new HashMap<>();
names = new ArrayList<>();
computeHashCode();
}
/**
* Constructor used when a generic reusable SchemaObject is assigned an
* OID after being instantiated.
*
* @param objectType The SchemaObjectType to create
*/
protected AbstractSchemaObject( SchemaObjectType objectType )
{
this.objectType = objectType;
extensions = new HashMap<>();
names = new ArrayList<>();
}
/**
* Gets usually what is the numeric object identifier assigned to this
* SchemaObject. All schema objects except for MatchingRuleUses have an OID
* assigned specifically to then. A MatchingRuleUse's OID really is the OID
* of it's MatchingRule and not specific to the MatchingRuleUse. This
* effects how MatchingRuleUse objects are maintained by the system.
*
* @return an OID for this SchemaObject or its MatchingRule if this
* SchemaObject is a MatchingRuleUse object
*/
@Override
public String getOid()
{
return oid;
}
/**
* A special method used when renaming an SchemaObject: we may have to
* change it's OID
* @param oid The new OID
*/
@Override
public void setOid( String oid )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
this.oid = oid;
computeHashCode();
}
/**
* Gets short names for this SchemaObject if any exists for it, otherwise,
* returns an empty list.
*
* @return the names for this SchemaObject
*/
@Override
public List<String> getNames()
{
if ( names != null )
{
return Collections.unmodifiableList( names );
}
else
{
return Collections.emptyList();
}
}
/**
* Gets the first name in the set of short names for this SchemaObject if
* any exists for it.
*
* @return the first of the names for this SchemaObject or the oid
* if one does not exist
*/
@Override
public String getName()
{
if ( ( names != null ) && !names.isEmpty() )
{
return names.get( 0 );
}
else
{
return oid;
}
}
/**
* Add a new name to the list of names for this SchemaObject. The name
* is lower cased and trimmed.
*
* @param namesToAdd The names to add
*/
@Override
public void addName( String... namesToAdd )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
// We must avoid duplicated names, as names are case insensitive
Set<String> lowerNames = new HashSet<>();
// Fills a set with all the existing names
for ( String name : this.names )
{
lowerNames.add( Strings.toLowerCaseAscii( name ) );
}
for ( String name : namesToAdd )
{
if ( name != null )
{
String lowerName = Strings.toLowerCaseAscii( name );
// Check that the lower cased names is not already present
if ( !lowerNames.contains( lowerName ) )
{
this.names.add( name );
lowerNames.add( lowerName );
}
}
}
computeHashCode();
}
/**
* Sets the list of names for this SchemaObject. The names are
* lowercased and trimmed.
*
* @param names The list of names. Can be empty
*/
@Override
public void setNames( List<String> names )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
if ( names == null )
{
return;
}
this.names = new ArrayList<>( names.size() );
for ( String name : names )
{
if ( name != null )
{
this.names.add( name );
}
}
computeHashCode();
}
/**
* Sets the list of names for this SchemaObject. The names are
* lowercased and trimmed.
*
* @param names The list of names.
*/
public void setNames( String... names )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
if ( names == null )
{
return;
}
this.names.clear();
for ( String name : names )
{
if ( name != null )
{
this.names.add( name );
}
}
computeHashCode();
}
/**
* Gets a short description about this SchemaObject.
*
* @return a short description about this SchemaObject
*/
@Override
public String getDescription()
{
return description;
}
/**
* Sets the SchemaObject's description
*
* @param description The SchemaObject's description
*/
@Override
public void setDescription( String description )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
this.description = description;
computeHashCode();
}
/**
* Gets the SchemaObject specification.
*
* @return the SchemaObject specification
*/
@Override
public String getSpecification()
{
return specification;
}
/**
* Sets the SchemaObject's specification
*
* @param specification The SchemaObject's specification
*/
@Override
public void setSpecification( String specification )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
this.specification = specification;
computeHashCode();
}
/**
* Tells if this SchemaObject is enabled.
*
* @return true if the SchemaObject is enabled, or if it depends on
* an enabled schema
*/
@Override
public boolean isEnabled()
{
return isEnabled;
}
/**
* Tells if this SchemaObject is disabled.
*
* @return true if the SchemaObject is disabled
*/
@Override
public boolean isDisabled()
{
return !isEnabled;
}
/**
* Sets the SchemaObject state, either enabled or disabled.
*
* @param enabled The current SchemaObject state
*/
@Override
public void setEnabled( boolean enabled )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
isEnabled = enabled;
computeHashCode();
}
/**
* Gets whether or not this SchemaObject has been inactivated. All
* SchemaObjects except Syntaxes allow for this parameter within their
* definition. For Syntaxes this property should always return false in
* which case it is never included in the description.
*
* @return true if inactive, false if active
*/
@Override
public boolean isObsolete()
{
return isObsolete;
}
/**
* Sets the Obsolete flag.
*
* @param obsolete The Obsolete flag state
*/
@Override
public void setObsolete( boolean obsolete )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
this.isObsolete = obsolete;
computeHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, List<String>> getExtensions()
{
return extensions;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasExtension( String extension )
{
return extensions.containsKey( Strings.toUpperCaseAscii( extension ) );
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getExtension( String extension )
{
String name = Strings.toUpperCaseAscii( extension );
if ( hasExtension( name ) )
{
for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
{
String key = entry.getKey();
if ( name.equalsIgnoreCase( key ) )
{
return entry.getValue();
}
}
}
return null;
}
/**
* Add an extension with its values
* @param key The extension key
* @param values The associated values
*/
@Override
public void addExtension( String key, String... values )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
List<String> valueList = new ArrayList<>();
for ( String value : values )
{
valueList.add( value );
}
extensions.put( Strings.toUpperCaseAscii( key ), valueList );
computeHashCode();
}
/**
* Add an extension with its values
* @param key The extension key
* @param values The associated values
*/
@Override
public void addExtension( String key, List<String> values )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
extensions.put( Strings.toUpperCaseAscii( key ), values );
computeHashCode();
}
/**
* Add an extensions with their values. (Actually do a copy)
*
* @param extensions The extensions map
*/
@Override
public void setExtensions( Map<String, List<String>> extensions )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
if ( extensions != null )
{
this.extensions = new HashMap<>();
for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
{
List<String> values = new ArrayList<>();
for ( String value : entry.getValue() )
{
values.add( value );
}
this.extensions.put( Strings.toUpperCaseAscii( entry.getKey() ), values );
}
computeHashCode();
}
}
/**
* The SchemaObject type :
* <ul>
* <li> AttributeType
* <li> DitCOntentRule
* <li> DitStructureRule
* <li> LdapComparator (specific to ADS)
* <li> LdapSyntaxe
* <li> MatchingRule
* <li> MatchingRuleUse
* <li> NameForm
* <li> Normalizer (specific to ADS)
* <li> ObjectClass
* <li> SyntaxChecker (specific to ADS)
* </ul>
*
* @return the SchemaObject type
*/
@Override
public SchemaObjectType getObjectType()
{
return objectType;
}
/**
* Gets the name of the schema this SchemaObject is associated with.
*
* @return the name of the schema associated with this schemaObject
*/
@Override
public String getSchemaName()
{
return schemaName;
}
/**
* Sets the name of the schema this SchemaObject is associated with.
*
* @param schemaName the new schema name
*/
@Override
public void setSchemaName( String schemaName )
{
if ( locked )
{
throw new UnsupportedOperationException( I18n.err( I18n.ERR_13700_CANNOT_MODIFY_LOCKED_SCHEMA_OBJECT, getName() ) );
}
this.schemaName = schemaName;
computeHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object o1 )
{
if ( this == o1 )
{
return true;
}
if ( !( o1 instanceof AbstractSchemaObject ) )
{
return false;
}
AbstractSchemaObject that = ( AbstractSchemaObject ) o1;
// Two schemaObject are equals if their oid is equal,
// their ObjectType is equal, their names are equals
// their schema name is the same, all their flags are equals,
// the description is the same and their extensions are equals
if ( !compareOid( oid, that.oid ) )
{
return false;
}
// Compare the names
if ( names == null )
{
if ( that.names != null )
{
return false;
}
}
else if ( that.names == null )
{
return false;
}
else
{
int nbNames = 0;
for ( String name : names )
{
if ( !that.names.contains( name ) )
{
return false;
}
nbNames++;
}
if ( nbNames != names.size() )
{
return false;
}
}
if ( schemaName == null )
{
if ( that.schemaName != null )
{
return false;
}
}
else
{
if ( !schemaName.equalsIgnoreCase( that.schemaName ) )
{
return false;
}
}
if ( objectType != that.objectType )
{
return false;
}
if ( extensions != null )
{
if ( that.extensions == null )
{
return false;
}
else
{
for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
{
String key = entry.getKey();
if ( !that.extensions.containsKey( key ) )
{
return false;
}
List<String> thisValues = entry.getValue();
List<String> thatValues = that.extensions.get( key );
if ( thisValues != null )
{
if ( thatValues == null )
{
return false;
}
else
{
if ( thisValues.size() != thatValues.size() )
{
return false;
}
// TODO compare the values
}
}
else if ( thatValues != null )
{
return false;
}
}
}
}
else if ( that.extensions != null )
{
return false;
}
if ( this.isEnabled != that.isEnabled )
{
return false;
}
if ( this.isObsolete != that.isObsolete )
{
return false;
}
if ( this.description == null )
{
return that.description == null;
}
else
{
return this.description.equalsIgnoreCase( that.description );
}
}
/**
* Compare two oids, and return true if they are both null or equal.
*
* @param oid1 the first OID
* @param oid2 the second OID
* @return <code>true</code> if both OIDs are null or equal
*/
protected boolean compareOid( String oid1, String oid2 )
{
if ( oid1 == null )
{
return oid2 == null;
}
else
{
return oid1.equals( oid2 );
}
}
/**
* {@inheritDoc}
*/
@Override
public SchemaObject copy( SchemaObject original )
{
// copy the description
description = original.getDescription();
// copy the flags
isEnabled = original.isEnabled();
isObsolete = original.isObsolete();
// copy the names
names = new ArrayList<>();
for ( String name : original.getNames() )
{
names.add( name );
}
// copy the extensions
extensions = new HashMap<>();
for ( String key : original.getExtensions().keySet() )
{
List<String> extensionValues = original.getExtension( key );
List<String> cloneExtension = new ArrayList<>();
for ( String value : extensionValues )
{
cloneExtension.add( value );
}
extensions.put( key, cloneExtension );
}
// The SchemaName
schemaName = original.getSchemaName();
// The specification
specification = original.getSpecification();
return this;
}
/**
* Clear the current SchemaObject : remove all the references to other objects,
* and all the Maps.
*/
@Override
public void clear()
{
// Clear the extensions
for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
{
List<String> extensionList = entry.getValue();
extensionList.clear();
}
extensions.clear();
// Clear the names
names.clear();
computeHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public final void lock()
{
locked = true;
}
/**
* Unlock the Schema Object and make it modifiable again.
*/
public void unlock()
{
locked = false;
}
/**
* Compute the hashcode, and store it in the 'h' variable
*/
protected void computeHashCode()
{
int hash = 37;
// The OID
if ( oid != null )
{
hash += hash * 17 + oid.hashCode();
}
// The SchemaObject type
if ( objectType != null )
{
hash += hash * 17 + objectType.getValue();
}
// The Names, if any
if ( ( names != null ) && !names.isEmpty() )
{
int tempHash = 0;
for ( String name : names )
{
tempHash *= name.hashCode();
}
hash = hash * 17 + tempHash;
}
// The schemaName if any
if ( schemaName != null )
{
hash += hash * 17 + schemaName.hashCode();
}
hash += hash * 17 + ( isEnabled ? 1 : 0 );
// The description, if any
if ( description != null )
{
hash += hash * 17 + description.hashCode();
}
// The extensions, if any
// Because the extensions and their values are stored un-ordered
// we have to be careful when computing the hashcode so that it does
// not depend on the extensions/values order
int tempHash = 0;
for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
{
String key = entry.getKey();
int tempHash2 = key.hashCode();
List<String> values = entry.getValue();
if ( values != null )
{
int tempHash3 = 0;
for ( String value : values )
{
tempHash3 += value.hashCode();
}
tempHash += tempHash2 * tempHash3;
}
}
hash = hash * 17 + tempHash;
h = hash;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return h;
}
}