/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2013 ForgeRock AS
*/
package org.opends.server.types;
import org.opends.messages.Message;
import static org.opends.messages.SchemaMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import static org.opends.server.util.Validator.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* An abstract base class for LDAP schema definitions which contain an
* OID, optional names, description, an obsolete flag, and an optional
* set of extra properties.
* <p>
* This class defines common properties and behaviour of the various
* types of schema definitions (e.g. object class definitions, and
* attribute type definitions).
* <p>
* Any methods which accesses the set of names associated with this
* definition, will retrieve the primary name as the first name,
* regardless of whether or not it was contained in the original set
* of <code>names</code> passed to the constructor.
* <p>
* Where ordered sets of names, 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.
* <p>
* Note that these schema elements are not completely immutable, as
* the set of extra properties for the schema element may be altered
* after the element is created. Among other things, this allows the
* associated schema file to be edited so that an element created over
* protocol may be associated with a particular schema file.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.VOLATILE,
mayInstantiate=false,
mayExtend=false,
mayInvoke=true)
public abstract class CommonSchemaElements {
// Indicates whether this definition is declared "obsolete".
private final boolean isObsolete;
// The hash code for this definition.
private final int hashCode;
// The set of additional name-value pairs associated with this
// definition.
private final Map<String, List<String>> extraProperties;
// The set of names for this definition, in a mapping between
// the all-lowercase form and the user-defined form.
private final Map<String, String> names;
// The description for this definition.
private final String description;
// The OID that may be used to reference this definition.
private final String oid;
// The primary name to use for this definition.
private final String primaryName;
// The lower case name for this definition.
private final String lowerName;
/**
* Creates a new definition with the provided information.
* <p>
* If no <code>primaryName</code> is specified, but a set of
* <code>names</code> is specified, then the first name retrieved
* from the set of <code>names</code> will be used as the primary
* name.
*
* @param primaryName
* The primary name for this definition, or
* <code>null</code> if there is no primary name.
* @param names
* The full set of names for this definition, or
* <code>null</code> if there are no names.
* @param oid
* The OID for this definition (must not be
* <code>null</code>).
* @param description
* The description for the definition, or <code>null</code>
* if there is no description.
* @param isObsolete
* Indicates whether this definition is declared
* "obsolete".
* @param extraProperties
* A set of extra properties for this definition, or
* <code>null</code> if there are no extra properties.
* @throws NullPointerException
* If the provided OID was <code>null</code>.
*/
protected CommonSchemaElements(String primaryName,
Collection<String> names, String oid, String description,
boolean isObsolete, Map<String, List<String>> extraProperties)
throws NullPointerException {
// Make sure mandatory parameters are specified.
if (oid == null) {
throw new NullPointerException(
"No oid specified in constructor");
}
this.oid = oid;
this.description = description;
this.isObsolete = isObsolete;
hashCode = oid.hashCode();
// Make sure we have a primary name if possible.
if (primaryName == null) {
if (names != null && !names.isEmpty()) {
this.primaryName = names.iterator().next();
} else {
this.primaryName = null;
}
} else {
this.primaryName = primaryName;
}
this.lowerName = toLowerCase(primaryName);
// Construct the normalized attribute name mapping.
if (names != null) {
this.names = new LinkedHashMap<String, String>(names.size());
// Make sure the primary name is first (never null).
this.names.put(lowerName, this.primaryName);
// Add the remaining names in the order specified.
for (String name : names) {
this.names.put(toLowerCase(name), name);
}
} else if (this.primaryName != null) {
this.names = Collections.singletonMap(lowerName,
this.primaryName);
} else {
this.names = Collections.emptyMap();
}
// FIXME: should really be a deep-copy.
if (extraProperties != null) {
this.extraProperties = new LinkedHashMap<String, List<String>>(
extraProperties);
} else {
this.extraProperties = Collections.emptyMap();
}
}
/**
* Check if the extra schema properties contain safe filenames.
*
* @param extraProperties
* The schema properties to check.
*
* @throws DirectoryException
* If a provided value was unsafe.
*/
public static void checkSafeProperties(Map <String,List<String>>
extraProperties)
throws DirectoryException
{
// Check that X-SCHEMA-FILE doesn't contain unsafe characters
List<String> filenames = extraProperties.get(SCHEMA_PROPERTY_FILENAME);
if (filenames != null && !filenames.isEmpty()) {
String filename = filenames.get(0);
if (filename.indexOf('/') != -1 || filename.indexOf('\\') != -1)
{
Message message = ERR_ATTR_SYNTAX_ILLEGAL_X_SCHEMA_FILE.get(filename);
throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
message);
}
}
}
/**
* Retrieves the primary name for this schema definition.
*
* @return The primary name for this schema definition, or
* <code>null</code> if there is no primary name.
*/
public final String getPrimaryName() {
return primaryName;
}
/**
* Retrieve the normalized primary name for this schema definition.
*
* @return Returns the normalized primary name for this attribute
* type, or <code>null</code> if there is no primary name.
*/
public final String getNormalizedPrimaryName() {
return lowerName;
}
/**
* Retrieves an iterable over the set of normalized names that may
* be used to reference this schema definition. The normalized form
* of an attribute name is defined as the user-defined name
* converted to lower-case.
*
* @return Returns an iterable over the set of normalized names that
* may be used to reference this schema definition.
*/
public final Iterable<String> getNormalizedNames() {
return names.keySet();
}
/**
* Retrieves an iterable over the set of user-defined names that may
* be used to reference this schema definition.
*
* @return Returns an iterable over the set of user-defined names
* that may be used to reference this schema definition.
*/
public final Iterable<String> getUserDefinedNames() {
return names.values();
}
/**
* Indicates whether this schema definition has the specified name.
*
* @param lowerName
* The lowercase name for which to make the determination.
* @return <code>true</code> if the specified name is assigned to
* this schema definition, or <code>false</code> if not.
*/
public final boolean hasName(String lowerName) {
return names.containsKey(lowerName);
}
/**
* Retrieves the OID for this schema definition.
*
* @return The OID for this schema definition.
*/
public final String getOID() {
return oid;
}
/**
* Retrieves 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 final String getNameOrOID() {
if (primaryName != null) {
return primaryName;
} else {
// Guaranteed not to be null.
return oid;
}
}
/**
* Retrieves the normalized primary name or OID for this schema
* definition. If it does not have any names, then the OID will be
* returned.
*
* @return The name or OID for this schema definition.
*/
public final String getNormalizedPrimaryNameOrOID() {
if (lowerName != null) {
return lowerName;
} else {
// Guaranteed not to be null.
return oid;
}
}
/**
* Indicates whether this schema definition has the specified name
* or OID.
*
* @param lowerValue
* The lowercase value for which to make the determination.
* @return <code>true</code> if the provided value matches the OID
* or one of the names assigned to this schema definition,
* or <code>false</code> if not.
*/
public final boolean hasNameOrOID(String lowerValue) {
if (names.containsKey(lowerValue)) {
return true;
}
return oid.equals(lowerValue);
}
/**
* Retrieves the name of the schema file that contains the
* definition for this schema definition.
*
* @return The name of the schema file that contains the definition
* for this schema definition, or <code>null</code> if it
* is not known or if it is not stored in any schema file.
*/
public final String getSchemaFile() {
List<String> values = extraProperties
.get(SCHEMA_PROPERTY_FILENAME);
if (values != null && !values.isEmpty()) {
return values.get(0);
}
return null;
}
/**
* Specifies the name of the schema file that contains the
* definition for this schema element. If a schema file is already
* defined in the set of extra properties, then it will be
* overwritten. If the provided schema file value is {@code null},
* then any existing schema file definition will be removed.
*
* @param schemaFile The name of the schema file that contains the
* definition for this schema element.
*/
public final void setSchemaFile(String schemaFile) {
setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
}
/**
* Retrieves the description for this schema definition.
*
* @return The description for this schema definition, or
* <code>null</code> if there is no description.
*/
public final String getDescription() {
return description;
}
/**
* Indicates whether this schema definition is declared "obsolete".
*
* @return <code>true</code> if this schema definition is declared
* "obsolete", or <code>false</code> if not.
*/
public final boolean isObsolete() {
return isObsolete;
}
/**
* Retrieves an iterable over the names of "extra" properties
* associated with this schema definition.
*
* @return Returns an iterable over the names of "extra" properties
* associated with this schema definition.
*/
public final Iterable<String> getExtraPropertyNames() {
return extraProperties.keySet();
}
/**
* Retrieves an iterable over the value(s) of the specified "extra"
* property for this schema definition.
*
* @param name
* The name of the "extra" property for which to retrieve
* the value(s).
* @return Returns an iterable over the value(s) of the specified
* "extra" property for this schema definition, or
* <code>null</code> if no such property is defined.
*/
public final Iterable<String> getExtraProperty(String name) {
return extraProperties.get(name);
}
/**
* Sets the value for an "extra" property for this schema element.
* If a property already exists with the specified name, then it
* will be overwritten. If the value is {@code null}, then any
* existing property with the given name will be removed.
*
* @param name The name for the "extra" property. It must not be
* {@code null}.
* @param value The value for the "extra" property. If it is
* {@code null}, then any existing definition will be
* removed.
*/
public final void setExtraProperty(String name, String value) {
ensureNotNull(name);
if (value == null)
{
extraProperties.remove(name);
}
else
{
LinkedList<String> values = new LinkedList<String>();
values.add(value);
extraProperties.put(name, values);
}
}
/**
* Sets the values for an "extra" property for this schema element.
* If a property already exists with the specified name, then it
* will be overwritten. If the set of values is {@code null} or
* empty, then any existing property with the given name will be
* removed.
*
* @param name The name for the "extra" property. It must not
* be {@code null}.
* @param values The set of values for the "extra" property. If
* it is {@code null} or empty, then any existing
* definition will be removed.
*/
public final void setExtraProperty(String name,
List<String> values) {
ensureNotNull(name);
if ((values == null) || values.isEmpty())
{
extraProperties.remove(name);
}
else
{
LinkedList<String> valuesCopy = new LinkedList<String>(values);
extraProperties.put(name, valuesCopy);
}
}
/**
* Indicates whether the provided object is equal to this attribute
* type. The object will be considered equal if it is an attribute
* type with the same OID as the current type.
*
* @param o
* The object for which to make the determination.
* @return <code>true</code> if the provided object is equal to
* this schema definition, or <code>false</code> if not.
*/
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof CommonSchemaElements) {
CommonSchemaElements other = (CommonSchemaElements) o;
return oid.equals(other.oid);
}
return false;
}
/**
* Retrieves the hash code for this schema definition. It will be
* based on the sum of the bytes of the OID.
*
* @return The hash code for this schema definition.
*/
public final int hashCode() {
return hashCode;
}
/**
* Retrieves 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.
*/
public final String toString() {
StringBuilder buffer = new StringBuilder();
toString(buffer, true);
return buffer.toString();
}
/**
* Appends a string representation of this schema definition in the
* form specified in RFC 2252 to the provided buffer.
*
* @param buffer
* The buffer to which the information should be appended.
* @param includeFileElement
* Indicates whether to include an "extra" property that
* specifies the path to the schema file from which this
* schema definition was loaded.
*/
public final void toString(StringBuilder buffer,
boolean includeFileElement) {
buffer.append("( ");
buffer.append(oid);
if (!names.isEmpty()) {
Iterator<String> iterator = names.values().iterator();
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");
}
// Delegate remaining string output to sub-class.
toStringContent(buffer);
if (!extraProperties.isEmpty()) {
for (Map.Entry<String, List<String>> e : extraProperties
.entrySet()) {
String property = e.getKey();
if (!includeFileElement
&& property.equals(SCHEMA_PROPERTY_FILENAME)) {
// Don't include the schema file if it was not requested.
continue;
}
List<String> valueList = e.getValue();
buffer.append(" ");
buffer.append(property);
if (valueList.size() == 1) {
buffer.append(" '");
buffer.append(valueList.get(0));
buffer.append("'");
} else {
buffer.append(" ( ");
for (String value : valueList) {
buffer.append("'");
buffer.append(value);
buffer.append("' ");
}
buffer.append(")");
}
}
}
buffer.append(" )");
}
/**
* Appends a string representation of this schema definition's
* non-generic properties to the provided buffer.
*
* @param buffer
* The buffer to which the information should be appended.
*/
protected abstract void toStringContent(StringBuilder buffer);
}