/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
*/
package org.opends.server.core;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.adapter.server3x.Converters;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.opends.server.types.Attribute;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.Modification;
import org.opends.server.types.Schema;
import org.opends.server.types.Schema.SchemaUpdater;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.StaticUtils;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class defines a utility that will be used to manage the interaction with
* the Directory Server schema. It will be used to initially load all of the
* matching rules and attribute syntaxes that have been defined in the
* configuration, and will then read the actual schema definitions. At present,
* only attribute types and objectclasses are supported in the schema config
* files. Other components like DIT content rules, DIT structure rules, name
* forms, and matching rule use definitions will be ignored.
*/
public class SchemaConfigManager
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The schema that has been parsed from the server configuration. */
private Schema schema;
private final ServerContext serverContext;
/**
* Creates a new instance of this schema config manager.
*
* @param serverContext
* The server context.
*/
public SchemaConfigManager(ServerContext serverContext)
{
this.serverContext = serverContext;
try
{
// the manager will build the schema from scratch, but we need to start from core schema for SDK schema
schema = new Schema(org.forgerock.opendj.ldap.schema.Schema.getCoreSchema());
}
catch (DirectoryException unexpected)
{
// the core schema should not have any warning
throw new RuntimeException(unexpected);
}
}
/**
* Retrieves the path to the directory containing the server schema files.
*
* @return The path to the directory containing the server schema files.
*/
public static String getSchemaDirectoryPath()
{
File schemaDir =
DirectoryServer.getEnvironmentConfig().getSchemaDirectory();
if (schemaDir != null) {
return schemaDir.getAbsolutePath();
}
return null;
}
/**
* Retrieves a reference to the schema information that has been read from the
* server configuration. Note that this information will not be complete
* until the initializeMatchingRules,
* initializeAttributeSyntaxes, and
* initializeAttributeTypesAndObjectClasses methods have been
* called.
*
* @return A reference to the schema information that has been read from the
* server configuration.
*/
public Schema getSchema()
{
return schema;
}
/**
* Initializes all the matching rules defined in the Directory Server
* configuration. This should only be called at Directory Server startup.
*
* @throws ConfigException If a configuration problem causes the matching
* rule initialization process to fail.
*
* @throws InitializationException If a problem occurs while initializing
* the matching rules that is not related to
* the server configuration.
*/
public void initializeMatchingRules()
throws ConfigException, InitializationException
{
MatchingRuleConfigManager matchingRuleConfigManager = new MatchingRuleConfigManager(serverContext);
matchingRuleConfigManager.initializeMatchingRules();
}
/**
* Initializes all the attribute syntaxes defined in the Directory Server
* configuration. This should only be called at Directory Server startup.
*
* @throws ConfigException If a configuration problem causes the syntax
* initialization process to fail.
*
* @throws InitializationException If a problem occurs while initializing
* the syntaxes that is not related to the
* server configuration.
*/
public void initializeAttributeSyntaxes()
throws ConfigException, InitializationException
{
AttributeSyntaxConfigManager syntaxConfigManager =
new AttributeSyntaxConfigManager(serverContext);
syntaxConfigManager.initializeAttributeSyntaxes();
}
/** Filter implementation that accepts only ldif files. */
public static class SchemaFileFilter implements FilenameFilter
{
@Override
public boolean accept(File directory, String filename)
{
return filename.endsWith(".ldif");
}
}
/**
* Initializes all the attribute type, object class, name form, DIT content
* rule, DIT structure rule, and matching rule use definitions by reading the
* server schema files. These files will be located in a single directory and
* will be processed in lexicographic order. However, to make the order
* easier to understand, they may be prefixed with a two digit number (with a
* leading zero if necessary) so that they will be read in numeric order.
* This should only be called at Directory Server startup.
*
* @throws ConfigException If a configuration problem causes the schema
* element initialization to fail.
*
* @throws InitializationException If a problem occurs while initializing
* the schema elements that is not related
* to the server configuration.
*/
public void initializeSchemaFromFiles()
throws ConfigException, InitializationException
{
// Construct the path to the directory that should contain the schema files
// and make sure that it exists and is a directory. Get a list of the files
// in that directory sorted in alphabetic order.
String schemaInstanceDirPath = getSchemaDirectoryPath();
File schemaInstanceDir = null;
try
{
if (schemaInstanceDirPath != null)
{
schemaInstanceDir = new File(schemaInstanceDirPath);
}
} catch (Exception e)
{
schemaInstanceDir = null;
}
long oldestModificationTime = -1L;
long youngestModificationTime = -1L;
String[] fileNames;
try
{
if (schemaInstanceDir == null || ! schemaInstanceDir.exists())
{
LocalizableMessage message =
ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(schemaInstanceDirPath);
throw new InitializationException(message);
}
if (! schemaInstanceDir.isDirectory())
{
LocalizableMessage message =
ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY.get(schemaInstanceDirPath);
throw new InitializationException(message);
}
FilenameFilter filter = new SchemaFileFilter();
File[] schemaInstanceDirFiles =
schemaInstanceDir.listFiles(filter);
int fileNumber = schemaInstanceDirFiles.length ;
ArrayList fileList = new ArrayList<>(fileNumber);
for (File f : schemaInstanceDirFiles)
{
if (f.isFile())
{
fileList.add(f.getName());
}
long modificationTime = f.lastModified();
if (oldestModificationTime <= 0L ||
modificationTime < oldestModificationTime)
{
oldestModificationTime = modificationTime;
}
if (youngestModificationTime <= 0 ||
modificationTime > youngestModificationTime)
{
youngestModificationTime = modificationTime;
}
}
fileNames = new String[fileList.size()];
fileList.toArray(fileNames);
Arrays.sort(fileNames);
}
catch (InitializationException ie)
{
logger.traceException(ie);
throw ie;
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = ERR_CONFIG_SCHEMA_CANNOT_LIST_FILES.get(
schemaInstanceDirPath, getExceptionMessage(e));
throw new InitializationException(message, e);
}
// If the oldest and youngest modification timestamps didn't get set for
// some reason, then set them to the current time.
if (oldestModificationTime <= 0)
{
oldestModificationTime = System.currentTimeMillis();
}
if (youngestModificationTime <= 0)
{
youngestModificationTime = oldestModificationTime;
}
schema.setOldestModificationTime(oldestModificationTime);
schema.setYoungestModificationTime(youngestModificationTime);
// Iterate through the schema files and read them as an LDIF file containing
// a single entry. Then get the attributeTypes and objectClasses attributes
// from that entry and parse them to initialize the server schema.
for (String schemaFile : fileNames)
{
loadSchemaFile(schema, schemaFile, false);
}
}
/**
* Loads the contents of the specified schema file into the provided schema.
*
* @param schema The schema in which the contents of the schema file are
* to be loaded.
* @param schemaFile The name of the schema file to be loaded into the
* provided schema.
* @throws ConfigException If a configuration problem causes the schema
* element initialization to fail.
* @throws InitializationException If a problem occurs while initializing
* the schema elements that is not related
* to the server configuration.
*/
public static void loadSchemaFile(Schema schema, String schemaFile)
throws ConfigException, InitializationException
{
loadSchemaFile(schema, schemaFile, true);
}
/**
* Loads the contents of the specified schema file into the provided schema and returns the list
* of modifications.
*
* @param schema
* The schema in which the contents of the schema file are to be loaded.
* @param schemaFile
* The name of the schema file to be loaded into the provided schema.
* @return A list of the modifications that could be performed in order to obtain the contents of
* the file.
* @throws ConfigException
* If a configuration problem causes the schema element initialization to fail.
* @throws InitializationException
* If a problem occurs while initializing the schema elements that is not related to the
* server configuration.
*/
public static List loadSchemaFileReturnModifications(Schema schema, String schemaFile)
throws ConfigException, InitializationException
{
final Entry entry = loadSchemaFile(schema, schemaFile, true);
if (entry != null)
{
return createAddModifications(entry,
CoreSchema.getLDAPSyntaxesAttributeType(),
CoreSchema.getAttributeTypesAttributeType(),
CoreSchema.getObjectClassesAttributeType(),
CoreSchema.getNameFormsAttributeType(),
CoreSchema.getDITContentRulesAttributeType(),
CoreSchema.getDITStructureRulesAttributeType(),
CoreSchema.getMatchingRuleUseAttributeType());
}
return Collections.emptyList();
}
/**
* Loads the contents of the specified schema file into the provided schema.
*
* @param schema The schema in which the contents of the schema file
* are to be loaded.
* @param schemaFile The name of the schema file to be loaded into the
* provided schema.
* @param failOnError If {@code true}, indicates that this method should
* throw an exception if certain kinds of errors occur.
* If {@code false}, indicates that this method should
* log an error message and return without an exception.
* This should only be {@code false} when called from
* {@code initializeSchemaFromFiles}.
* @return the schema entry that has been read from the schema file
* @throws ConfigException If a configuration problem causes the schema
* element initialization to fail.
* @throws InitializationException If a problem occurs while initializing
* the schema elements that is not related
* to the server configuration.
*/
private static Entry loadSchemaFile(Schema schema, String schemaFile, boolean failOnError)
throws ConfigException, InitializationException
{
final Entry entry = readSchemaEntryFromFile(schemaFile, failOnError);
if (entry != null)
{
updateSchemaWithEntry(schema, schemaFile, failOnError, entry);
}
return entry;
}
private static void updateSchemaWithEntry(Schema schema, String schemaFile, boolean failOnError, final Entry entry)
throws ConfigException
{
final org.forgerock.opendj.ldap.Entry schemaEntry = Converters.from(entry);
try
{
updateSchema(schema, schemaEntry, false);
}
catch (DirectoryException e)
{
if (e.getResultCode().equals(ResultCode.CONSTRAINT_VIOLATION))
{
// Register it with the schema. We will allow duplicates, with the
// later definition overriding any earlier definition, but we want
// to trap them and log a warning.
logger.warn(WARN_CONFIG_CONFLICTING_DEFINITIONS_IN_SCHEMA_FILE, schemaFile, e.getMessageObject());
try
{
updateSchema(schema, schemaEntry, true);
}
catch (DirectoryException e2)
{
// This should never happen
logger.traceException(e2);
}
}
else
{
reportError(failOnError, e,
WARN_CONFIG_SCHEMA_CANNOT_PARSE_DEFINITIONS_IN_SCHEMA_FILE.get(schemaFile, e.getMessageObject()));
}
}
}
private static Entry readSchemaEntryFromFile(String schemaFile, boolean failOnError)
throws ConfigException, InitializationException
{
// Create an LDIF reader to use when reading the files.
String schemaDirPath = getSchemaDirectoryPath();
File f = new File(schemaDirPath, schemaFile);
LDIFReader reader;
try
{
reader = new LDIFReader(new LDIFImportConfig(f.getAbsolutePath()));
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_OPEN_FILE.get(
schemaFile, schemaDirPath, getExceptionMessage(e));
if (failOnError)
{
throw new ConfigException(message);
}
logger.error(message);
return null;
}
// Read the LDIF entry from the file and close the file.
final Entry entry;
try
{
entry = reader.readEntry(false);
if (entry == null)
{
// The file was empty -- skip it.
reader.close();
return null;
}
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get(
schemaFile, schemaDirPath, getExceptionMessage(e));
if (failOnError)
{
throw new InitializationException(message, e);
}
logger.error(message);
StaticUtils.close(reader);
return null;
}
// If there are any more entries in the file, then print a warning message.
try
{
Entry e = reader.readEntry(false);
if (e != null)
{
logger.warn(WARN_CONFIG_SCHEMA_MULTIPLE_ENTRIES_IN_FILE, schemaFile, schemaDirPath);
}
}
catch (Exception e)
{
logger.traceException(e);
logger.warn(WARN_CONFIG_SCHEMA_UNPARSEABLE_EXTRA_DATA_IN_FILE, schemaFile, schemaDirPath, getExceptionMessage(e));
}
finally
{
StaticUtils.close(reader);
}
return entry;
}
private static void updateSchema(Schema schema, final org.forgerock.opendj.ldap.Entry schemaEntry,
final boolean overwrite) throws DirectoryException
{
schema.updateSchema(new SchemaUpdater()
{
@Override
public org.forgerock.opendj.ldap.schema.Schema update(SchemaBuilder builder)
{
return builder.addSchema(schemaEntry, overwrite).toSchema();
}
});
}
private static List createAddModifications(Entry entry, AttributeType... attrTypes)
{
int nbMods = entry.getUserAttributes().size() + entry.getOperationalAttributes().size();
List mods = new ArrayList<>(nbMods);
for (AttributeType attrType : attrTypes)
{
for (Attribute a : entry.getAttribute(attrType))
{
mods.add(new Modification(ModificationType.ADD, a));
}
}
return mods;
}
private static void reportError(boolean failOnError, Exception e,
LocalizableMessage message) throws ConfigException
{
if (failOnError)
{
throw new ConfigException(message, e);
}
logger.error(message);
}
/**
* This method checks if a given attribute is an attribute that
* is used by the definition of the schema.
*
* @param attribute The attribute to be checked.
* @return true if the attribute is part of the schema definition,
* false if the attribute is not part of the schema
* definition.
*/
public static boolean isSchemaAttribute(Attribute attribute)
{
String attributeOid = attribute.getAttributeDescription().getAttributeType().getOID();
return attributeOid.equals("2.5.21.1") ||
attributeOid.equals("2.5.21.2") ||
attributeOid.equals("2.5.21.4") ||
attributeOid.equals("2.5.21.5") ||
attributeOid.equals("2.5.21.6") ||
attributeOid.equals("2.5.21.7") ||
attributeOid.equals("2.5.21.8") ||
attributeOid.equals("2.5.4.3") ||
attributeOid.equals("1.3.6.1.4.1.1466.101.120.16") ||
attributeOid.equals("cn-oid") ||
attributeOid.equals("attributetypes-oid") ||
attributeOid.equals("objectclasses-oid") ||
attributeOid.equals("matchingrules-oid") ||
attributeOid.equals("matchingruleuse-oid") ||
attributeOid.equals("nameformdescription-oid") ||
attributeOid.equals("ditcontentrules-oid") ||
attributeOid.equals("ditstructurerules-oid") ||
attributeOid.equals("ldapsyntaxes-oid");
}
}