/*
* 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 2014-2015 ForgeRock AS
*/
package org.opends.server.core;
import static org.forgerock.util.Utils.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.ClassPropertyDefinition;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.forgerock.opendj.ldif.EntryReader;
import org.forgerock.opendj.ldif.LDIFEntryReader;
import org.forgerock.opendj.server.config.meta.SchemaProviderCfgDefn;
import org.forgerock.opendj.server.config.server.RootCfg;
import org.forgerock.opendj.server.config.server.SchemaProviderCfg;
import org.forgerock.util.Utils;
import org.opends.server.schema.SchemaProvider;
import org.opends.server.schema.SchemaUpdater;
import org.opends.server.types.InitializationException;
/**
* Responsible for loading the server schema.
* <p>
* The schema is loaded in three steps :
* <ul>
* <li>Start from the core schema.</li>
* <li>Load schema elements from the schema providers defined in configuration.</li>
* <li>Load all schema files located in the schema directory.</li>
* </ul>
*/
public final class SchemaHandler
{
private static final String CORE_SCHEMA_PROVIDER_NAME = "Core Schema";
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private ServerContext serverContext;
private long oldestModificationTime = -1L;
private long youngestModificationTime = -1L;
/**
* Creates a new instance.
*/
public SchemaHandler()
{
// no implementation.
}
/**
* Initialize this schema handler.
*
* @param serverContext
* The server context.
* @throws ConfigException
* If a configuration problem arises in the process of performing
* the initialization.
* @throws InitializationException
* If a problem that is not configuration-related occurs during
* initialization.
*/
public void initialize(final ServerContext serverContext) throws InitializationException, ConfigException
{
this.serverContext = serverContext;
final RootCfg rootConfiguration = serverContext.getServerManagementContext().getRootConfiguration();
final SchemaUpdater schemaUpdater = serverContext.getSchemaUpdater();
// Start from the core schema (TODO: or start with empty schema and add core schema in core schema provider ?)
final SchemaBuilder schemaBuilder = new SchemaBuilder(Schema.getCoreSchema());
// Take providers into account.
loadSchemaFromProviders(rootConfiguration, schemaBuilder, schemaUpdater);
// Take schema files into account (TODO : or load files using provider mechanism ?)
completeSchemaFromFiles(schemaBuilder);
schemaUpdater.updateSchema(schemaBuilder.toSchema());
}
/**
* Load the schema from provided root configuration.
*
* @param rootConfiguration
* The root to retrieve schema provider configurations.
* @param schemaBuilder
* The schema builder that providers should update.
* @param schemaUpdater
* The updater that providers should use when applying a
* configuration change.
*/
private void loadSchemaFromProviders(final RootCfg rootConfiguration, final SchemaBuilder schemaBuilder,
final SchemaUpdater schemaUpdater) throws ConfigException, InitializationException {
for (final String name : rootConfiguration.listSchemaProviders())
{
final SchemaProviderCfg config = rootConfiguration.getSchemaProvider(name);
if (config.isEnabled())
{
loadSchemaProvider(config.getJavaClass(), config, schemaBuilder, schemaUpdater, true);
}
else if (name.equals(CORE_SCHEMA_PROVIDER_NAME)) {
// TODO : use correct message ERR_CORE_SCHEMA_NOT_ENABLED
LocalizableMessage message = LocalizableMessage.raw("Core Schema can't be disabled");
throw new ConfigException(message);
}
}
}
/**
* Load the schema provider from the provided class name.
* <p>
* If {@code} initialize} is {@code true}, then the provider is initialized,
* and the provided schema builder is updated with schema elements fropm the
* provider.
*/
private <T extends SchemaProviderCfg> SchemaProvider<T> loadSchemaProvider(final String className,
final T config, final SchemaBuilder schemaBuilder, final SchemaUpdater schemaUpdater, final boolean initialize)
throws InitializationException
{
try
{
final ClassPropertyDefinition propertyDef = SchemaProviderCfgDefn.getInstance().getJavaClassPropertyDefinition();
final Class<? extends SchemaProvider> providerClass = propertyDef.loadClass(className, SchemaProvider.class);
final SchemaProvider<T> provider = providerClass.newInstance();
if (initialize) {
provider.initialize(config, schemaBuilder, schemaUpdater);
}
else {
final List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
final boolean isAcceptable = provider.isConfigurationAcceptable(config, unacceptableReasons);
if (!isAcceptable)
{
final String reasons = Utils.joinAsString(". ", unacceptableReasons);
// TODO : fix message, eg CONFIG SCHEMA PROVIDER CONFIG NOT ACCEPTABLE
throw new InitializationException(ERR_CONFIG_ALERTHANDLER_CONFIG_NOT_ACCEPTABLE.get(config.dn(), reasons));
}
}
return provider;
}
catch (Exception e)
{
// TODO : fix message
throw new InitializationException(ERR_CONFIG_SCHEMA_SYNTAX_CANNOT_INITIALIZE.
get(className, config.dn(), stackTraceToSingleLineString(e)), e);
}
}
/**
* Retrieves the path to the directory containing the server schema files.
*
* @return The path to the directory containing the server schema files.
*/
private File getSchemaDirectoryPath() throws InitializationException
{
final File dir = serverContext.getEnvironment().getSchemaDirectory();
if (dir == null)
{
throw new InitializationException(ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(null));
}
if (!dir.exists())
{
throw new InitializationException(ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(dir.getPath()));
}
if (!dir.isDirectory())
{
throw new InitializationException(ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY.get(dir.getPath()));
}
return dir;
}
/** Returns the LDIF reader on provided LDIF file. The caller must ensure the reader is closed. */
private EntryReader getLDIFReader(final File ldifFile, final Schema schema)
throws InitializationException
{
try
{
final LDIFEntryReader reader = new LDIFEntryReader(new FileReader(ldifFile));
reader.setSchema(schema);
return reader;
}
catch (Exception e)
{
// TODO : fix message
throw new InitializationException(ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(ldifFile.getAbsolutePath(), e), e);
}
}
/**
* Complete the schema with schema files.
*
* @param schemaBuilder
* The schema builder to update with the content of the schema files.
* @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 void completeSchemaFromFiles(final SchemaBuilder schemaBuilder)
throws ConfigException, InitializationException
{
final File schemaDirectory = getSchemaDirectoryPath();
for (String schemaFile : getSchemaFileNames(schemaDirectory))
{
loadSchemaFile(schemaFile, schemaBuilder, Schema.getDefaultSchema());
}
}
/** Returns the list of names of schema files contained in the provided directory. */
private List<String> getSchemaFileNames(final File schemaDirectory) throws InitializationException {
try
{
final File[] schemaFiles = schemaDirectory.listFiles(new SchemaFileFilter());
final List<String> schemaFileNames = new ArrayList<>(schemaFiles.length);
for (final File f : schemaFiles)
{
if (f.isFile())
{
schemaFileNames.add(f.getName());
}
final long modificationTime = f.lastModified();
if (oldestModificationTime <= 0L
|| modificationTime < oldestModificationTime)
{
oldestModificationTime = modificationTime;
}
if (youngestModificationTime <= 0
|| modificationTime > youngestModificationTime)
{
youngestModificationTime = modificationTime;
}
}
// If the oldest and youngest modification timestamps didn't get set
// then set them to the current time.
if (oldestModificationTime <= 0)
{
oldestModificationTime = System.currentTimeMillis();
}
if (youngestModificationTime <= 0)
{
youngestModificationTime = oldestModificationTime;
}
Collections.sort(schemaFileNames);
return schemaFileNames;
}
catch (Exception e)
{
throw new InitializationException(ERR_CONFIG_SCHEMA_CANNOT_LIST_FILES
.get(schemaDirectory, getExceptionMessage(e)), e);
}
}
/** Returns the schema entry from the provided reader. */
private Entry readSchemaEntry(final EntryReader reader, final File schemaFile) throws InitializationException {
try
{
Entry entry = null;
if (reader.hasNext())
{
entry = reader.readEntry();
if (reader.hasNext())
{
// TODO : fix message
logger.warn(WARN_CONFIG_SCHEMA_MULTIPLE_ENTRIES_IN_FILE, schemaFile, "");
}
return entry;
}
else
{
// TODO : fix message - should be SCHEMA NO LDIF ENTRY
throw new InitializationException(WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get(
schemaFile, "", ""));
}
}
catch (IOException e)
{
// TODO : fix message
throw new InitializationException(WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get(
schemaFile, "", getExceptionMessage(e)), e);
}
finally
{
closeSilently(reader);
}
}
/**
* Add the schema from the provided schema file to the provided schema
* builder.
*
* @param schemaFileName
* The name of the schema file to be loaded
* @param schemaBuilder
* The schema builder in which the contents of the schema file are to
* be loaded.
* @param readSchema
* The schema used to read the file.
* @throws InitializationException
* If a problem occurs while initializing the schema elements.
*/
private void loadSchemaFile(final String schemaFileName, final SchemaBuilder schemaBuilder, final Schema readSchema)
throws InitializationException
{
EntryReader reader = null;
try
{
File schemaFile = new File(getSchemaDirectoryPath(), schemaFileName);
reader = getLDIFReader(schemaFile, readSchema);
final Entry entry = readSchemaEntry(reader, schemaFile);
// TODO : there is no more file information attached to schema elements - we should add support for this
// in order to be able to redirect schema elements in the correct file when doing backups
schemaBuilder.addSchema(entry, true);
}
finally {
Utils.closeSilently(reader);
}
}
/** A file filter implementation that accepts only LDIF files. */
private static class SchemaFileFilter implements FilenameFilter
{
private static final String LDIF_SUFFIX = ".ldif";
@Override
public boolean accept(File directory, String filename)
{
return filename.endsWith(LDIF_SUFFIX);
}
}
}