From 5ee33e1763d2574f769a8a278806e882694feb00 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 09 Sep 2016 09:45:23 +0000
Subject: [PATCH] OPENDJ-3089 Update AddSchemaFileTask class to replace server Schema usage by SchemaHandler

---
 opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java      |  122 ++++++++++++++++++++++++++++-
 opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java |   63 +++++++++++----
 opendj-server-legacy/src/messages/org/opends/messages/task.properties             |    2 
 3 files changed, 164 insertions(+), 23 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
index cd1eb96..e72e7af 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
@@ -17,8 +17,6 @@
 
 import static org.opends.messages.ConfigMessages.ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY;
 import static org.opends.messages.ConfigMessages.ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR;
-
-import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
 import static org.opends.messages.SchemaMessages.NOTE_SCHEMA_IMPORT_FAILED;
 import static org.opends.server.util.SchemaUtils.getElementSchemaFile;
 import static org.forgerock.util.Utils.closeSilently;
@@ -51,13 +49,17 @@
 import org.opends.server.schema.SubtreeSpecificationSyntax;
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.adapter.server3x.Converters;
 import org.forgerock.opendj.config.ClassPropertyDefinition;
 import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Entry;
+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.ConflictingSchemaElementException;
+import org.forgerock.opendj.ldap.schema.CoreSchema;
 import org.forgerock.opendj.ldap.schema.DITContentRule;
 import org.forgerock.opendj.ldap.schema.DITStructureRule;
 import org.forgerock.opendj.ldap.schema.MatchingRule;
@@ -81,11 +83,14 @@
 import org.opends.server.types.Attribute;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
+import org.opends.server.types.Modification;
 import org.opends.server.types.SchemaWriter;
 import org.opends.server.util.ActivateOnceSDKSchemaIsUsed;
 import org.opends.server.util.SchemaUtils;
 import org.opends.server.util.StaticUtils;
 
+import com.sun.corba.se.spi.ior.WriteContents;
+
 /**
  * Responsible for loading the server schema.
  * <p>
@@ -107,8 +112,13 @@
   private static final String CONFIG_SCHEMA_ELEMENTS_FILE = "02-config.ldif";
   private static final String CORE_SCHEMA_ELEMENTS_FILE = "00-core.ldif";
 
-  private static final AttributeType attributeTypesType = getAttributeTypesAttributeType();
-  private static final AttributeType objectClassesType = getObjectClassesAttributeType();
+  private static final AttributeType attributeTypesType = CoreSchema.getAttributeTypesAttributeType();
+  private static final AttributeType objectClassesType = CoreSchema.getObjectClassesAttributeType();
+  private static final AttributeType ditStructureRulesType = CoreSchema.getDITStructureRulesAttributeType();
+  private static final AttributeType ditContentRulesType = CoreSchema.getDITContentRulesAttributeType();
+  private static final AttributeType ldapSyntaxesType = CoreSchema.getLDAPSyntaxesAttributeType();
+  private static final AttributeType matchingRuleUsesType = CoreSchema.getMatchingRuleUseAttributeType();
+  private static final AttributeType nameFormsType = CoreSchema.getNameFormsAttributeType();
 
   private ServerContext serverContext;
 
@@ -335,6 +345,29 @@
   }
 
   /**
+   * Replaces the schema with the provided schema and update the concatened schema files.
+   *
+   * @param newSchema
+   *            The new schema to use
+   * @throws DirectoryException
+   *            If an error occurs during update of schema or schema files
+   */
+  public void updateSchemaAndConcatenatedSchemaFiles(Schema newSchema) throws DirectoryException
+  {
+    exclusiveLock.lock();
+    try
+    {
+      switchSchema(newSchema);
+      SchemaWriter.writeConcatenatedSchema();
+      youngestModificationTime = System.currentTimeMillis();
+    }
+    finally
+    {
+      exclusiveLock.unlock();
+    }
+  }
+
+  /**
    * Updates the schema option  if the new value differs from the old value.
    *
    * @param <T> the schema option's type
@@ -657,7 +690,7 @@
 
     for (String schemaFile : schemaFileNames)
     {
-      loadSchemaFile(new File(schemaDirectory, schemaFile), schemaBuilder, Schema.getDefaultSchema());
+      loadSchemaFile(new File(schemaDirectory, schemaFile), schemaBuilder, Schema.getDefaultSchema(), false);
     }
   }
 
@@ -749,6 +782,75 @@
   }
 
   /**
+   * Loads the contents of the provided schema file into the provided schema builder.
+   *
+   * @param schemaFile
+   *            The schema file to load.
+   * @param schemaBuilder
+   *            The schema builder to update.
+   * @param readSchema
+   *            The schema used to read the schema file.
+   * @throws InitializationException
+   *            If a problem occurs when reading the schema file.
+   * @throws ConfigException
+   *            If a problem occurs when updating the schema builder.
+   */
+  public void loadSchemaFileIntoSchemaBuilder(final File schemaFile, final SchemaBuilder schemaBuilder,
+      final Schema readSchema) throws InitializationException, ConfigException
+  {
+    loadSchemaFile(schemaFile, schemaBuilder, readSchema, true);
+  }
+
+  /**
+   * Loads the contents of the provided schema file into the provided schema builder and returns the list
+   * of modifications.
+   *
+   * @param schemaFile
+   *          The schema file to load.
+   * @param schemaBuilder
+   *            The schema builder to update.
+   * @param readSchema
+   *            The schema used to read the schema file.
+   * @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 List<Modification> loadSchemaFileIntoSchemaBuilderAndReturnModifications(final File schemaFile,
+      final SchemaBuilder schemaBuilder, final Schema readSchema) throws InitializationException, ConfigException
+  {
+    final Entry entry = loadSchemaFile(schemaFile, schemaBuilder, readSchema, true);
+    if (entry != null)
+    {
+      return createAddModifications(entry,
+          ldapSyntaxesType,
+          attributeTypesType,
+          objectClassesType,
+          nameFormsType,
+          ditContentRulesType,
+          ditStructureRulesType,
+          matchingRuleUsesType);
+    }
+    return Collections.emptyList();
+  }
+
+  private List<Modification> createAddModifications(Entry entry, AttributeType... attrTypes)
+  {
+    List<Modification> mods = new ArrayList<>(entry.getAttributeCount());
+    for (AttributeType attrType : attrTypes)
+    {
+      for (org.forgerock.opendj.ldap.Attribute a : entry.getAllAttributes(AttributeDescription.create(attrType)))
+      {
+        mods.add(new Modification(ModificationType.ADD, Converters.toAttribute(a)));
+      }
+    }
+    return mods;
+  }
+
+  /**
    * Add the schema from the provided schema file to the provided schema
    * builder.
    *
@@ -759,10 +861,16 @@
    *          be loaded.
    * @param readSchema
    *          The schema used to read the file.
+   * @param failOnError
+   *          Indicates whether an exception must be thrown if an error occurs
+   * @return the schema entry that has been read from the schema file
    * @throws InitializationException
    *           If a problem occurs while initializing the schema elements.
+   * @throws ConfigException
+   *           If a problem occurs when updating the schema builder.
    */
-  private void loadSchemaFile(final File schemaFile, final SchemaBuilder schemaBuilder, final Schema readSchema)
+  private Entry loadSchemaFile(final File schemaFile, final SchemaBuilder schemaBuilder, final Schema readSchema,
+      boolean failOnError)
          throws InitializationException, ConfigException
   {
     EntryReader reader = null;
@@ -772,9 +880,9 @@
       final Entry entry = readSchemaEntry(reader, schemaFile);
       if (entry != null)
       {
-        boolean failOnError = true;
         updateSchemaBuilderWithEntry(schemaBuilder, entry, schemaFile.getName(), failOnError);
       }
+      return entry;
     }
     finally {
       Utils.closeSilently(reader);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
index d70a4c4..3f1ad23 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
@@ -27,13 +27,16 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.Schema;
+import org.forgerock.opendj.ldap.schema.SchemaBuilder;
 import org.forgerock.opendj.server.config.server.SynchronizationProviderCfg;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.SynchronizationProvider;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskState;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.SchemaConfigManager;
+import org.opends.server.core.SchemaHandler;
+import org.opends.server.core.ServerContext;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeBuilder;
 import org.opends.server.types.DirectoryException;
@@ -43,8 +46,6 @@
 import org.opends.server.types.Modification;
 import org.opends.server.types.Operation;
 import org.opends.server.types.Privilege;
-import org.opends.server.types.Schema;
-import org.opends.server.types.SchemaWriter;
 import org.opends.server.util.SchemaUtils;
 
 import static org.opends.messages.TaskMessages.*;
@@ -89,7 +90,8 @@
 
     // Get the attribute that specifies which schema file(s) to add.
     Entry taskEntry = getTaskEntry();
-    AttributeType attrType = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_ADDSCHEMAFILE_FILENAME);
+    ServerContext serverContext = getServerContext();
+    AttributeType attrType = serverContext.getSchema().getAttributeType(ATTR_TASK_ADDSCHEMAFILE_FILENAME);
     List<Attribute> attrList = taskEntry.getAllAttributes(attrType);
     if (attrList.isEmpty())
     {
@@ -100,7 +102,7 @@
 
     // Get the name(s) of the schema files to add and make sure they exist in
     // the schema directory.
-    String schemaInstanceDirectory = DirectoryServer.getEnvironmentConfig().getSchemaDirectory().getPath();
+    File schemaInstanceDirectory = getSchemaDirectory();
     filesToAdd = new TreeSet<>();
     for (Attribute a : attrList)
     {
@@ -133,17 +135,20 @@
       }
     }
 
-    // Create a new dummy schema and make sure that we can add the contents of
-    // all the schema files into it.  Even though this duplicates work we'll
-    // have to do later, it will be good to do it now as well so we can reject
+    // Make sure that we can add the contents of all the schema files into the schema.
+    // Even though this duplicates work we'll have to do later,
+    // it will be good to do it now as well so we can reject
     // the entry immediately which will fail the attempt by the client to add it
     // to the server, rather than having to check its status after the fact.
-    Schema schema = DirectoryServer.getSchema().duplicate();
+    final SchemaHandler schemaHandler = serverContext.getSchemaHandler();
+    final Schema currentSchema = schemaHandler.getSchema();
+    final SchemaBuilder schemaBuilder = new SchemaBuilder(currentSchema);
     for (String schemaFile : filesToAdd)
     {
       try
       {
-        SchemaConfigManager.loadSchemaFile(schema, schemaFile);
+        File file = new File(schemaInstanceDirectory, schemaFile);
+        schemaHandler.loadSchemaFileIntoSchemaBuilder(file, schemaBuilder, currentSchema);
       }
       catch (ConfigException | InitializationException e)
       {
@@ -155,6 +160,19 @@
     }
   }
 
+  private File getSchemaDirectory() throws DirectoryException
+  {
+    try
+    {
+      return getServerContext().getSchemaHandler().getSchemaDirectoryPath();
+    }
+    catch (InitializationException e)
+    {
+      logger.traceException(e);
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,  e.getMessageObject());
+    }
+  }
+
   @Override
   protected TaskState runTask()
   {
@@ -170,12 +188,16 @@
     try
     {
       LinkedList<Modification> mods = new LinkedList<>();
-      Schema schema = DirectoryServer.getSchema().duplicate();
+      final SchemaHandler schemaHandler = getServerContext().getSchemaHandler();
+      final Schema currentSchema = schemaHandler.getSchema();
+      final SchemaBuilder schemaBuilder = new SchemaBuilder(currentSchema);
       for (String schemaFile : filesToAdd)
       {
         try
         {
-          List<Modification> modList = SchemaConfigManager.loadSchemaFileReturnModifications(schema, schemaFile);
+          File file = new File(getSchemaDirectory(), schemaFile);
+          List<Modification> modList =
+              schemaHandler.loadSchemaFileIntoSchemaBuilderAndReturnModifications(file, schemaBuilder, currentSchema);
           for (Modification m : modList)
           {
             Attribute a = m.getAttribute();
@@ -188,7 +210,7 @@
             mods.add(new Modification(m.getModificationType(), builder.toAttribute()));
           }
         }
-        catch (ConfigException | InitializationException e)
+        catch (DirectoryException | ConfigException | InitializationException e)
         {
           logger.traceException(e);
           logger.error(ERR_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE, schemaFile, e.getMessage());
@@ -214,11 +236,20 @@
           }
         }
 
-        SchemaWriter.writeConcatenatedSchema();
+        final Schema newSchema = schemaBuilder.toSchema();
+        try
+        {
+          schemaHandler.updateSchemaAndConcatenatedSchemaFiles(newSchema);
+        }
+        catch (DirectoryException e)
+        {
+          // This is very unlikely to happen because each schema file was previously loaded without error
+          logger.traceException(e);
+          logger.error(ERR_TASK_ADDSCHEMAFILE_SCHEMA_VALIDATION_ERROR, e.getMessage());
+          return TaskState.STOPPED_BY_ERROR;
+        }
       }
 
-      schema.setYoungestModificationTime(System.currentTimeMillis());
-      DirectoryServer.setSchema(schema);
       return TaskState.COMPLETED_SUCCESSFULLY;
     }
     finally
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/task.properties b/opendj-server-legacy/src/messages/org/opends/messages/task.properties
index ff49037..e5a9d88 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/task.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/task.properties
@@ -193,3 +193,5 @@
   does not appear to be a replication server
 ERR_TASK_RESET_CHANGE_NUMBER_INVALID_114=Invalid change number (%d) specified, it must be greater than zero
 ERR_TASK_RESET_CHANGE_NUMBER_FAILED_115=Unable to reset the change number index: %s
+ERR_TASK_ADDSCHEMAFILE_SCHEMA_VALIDATION_ERROR_116=An error occurred while attempting to \
+ validate the schema after changes made by the add schema file task: %s
\ No newline at end of file

--
Gitblit v1.10.0