From 160491be597f55e22b5d5a11bd0c7c1c6a440104 Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Fri, 24 Jun 2016 13:54:17 +0000
Subject: [PATCH] OPENDJ-3157: Schema updates via LDAP can overwrite existing schema.

---
 opendj-server-legacy/src/main/java/org/opends/server/core/SchemaConfigManager.java |   53 ++++++++++++-
 opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java      |  166 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 203 insertions(+), 16 deletions(-)

diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
index a4203a3..fdfc804 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaBuilder.java
@@ -122,6 +122,73 @@
         return subschemaDN;
     }
 
+    /** Allows to perform modifications on element's builders before adding the result to this schema builder. */
+    public interface SchemaBuilderHook {
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddSyntax(Syntax.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddAttribute(AttributeType.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddObjectClass(ObjectClass.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddMatchingRuleUse(MatchingRuleUse.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddMatchingRule(MatchingRule.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddDitContentRule(DITContentRule.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddDitStructureRule(DITStructureRule.Builder builder);
+
+        /**
+         * Allow to modify the builder before its inclusion in schema.
+         *
+         * @param builder
+         *            Schema's element builder.
+         */
+        public void beforeAddNameForm(NameForm.Builder builder);
+    }
+
     private Map<Integer, DITStructureRule> id2StructureRules;
     private Map<String, List<AttributeType>> name2AttributeTypes;
     private Map<String, List<DITContentRule>> name2ContentRules;
@@ -167,7 +234,7 @@
      */
     public SchemaBuilder(final Entry entry) {
         preLazyInitBuilder(entry.getName().toString(), null);
-        addSchema(entry, true);
+        addSchema(entry, true, null);
     }
 
     /**
@@ -218,6 +285,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addAttributeType(final String definition, final boolean overwrite) {
+        return addAttributeType(definition, overwrite, null);
+    }
+
+    SchemaBuilder addAttributeType(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -371,6 +442,9 @@
             atBuilder.superiorType(superiorType)
                      .syntax(syntax);
 
+            if (hook != null) {
+                hook.beforeAddAttribute(atBuilder);
+            }
             return atBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg = ERR_ATTR_SYNTAX_ATTRTYPE_INVALID1.get(definition, e.getMessageObject());
@@ -397,6 +471,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addDITContentRule(final String definition, final boolean overwrite) {
+        return addDITContentRule(definition, overwrite, null);
+    }
+
+    SchemaBuilder addDITContentRule(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -478,6 +556,9 @@
                 }
             }
 
+            if (hook != null) {
+                hook.beforeAddDitContentRule(contentRuleBuilder);
+            }
             return contentRuleBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg = ERR_ATTR_SYNTAX_DCR_INVALID1.get(definition, e.getMessageObject());
@@ -504,6 +585,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addDITStructureRule(final String definition, final boolean overwrite) {
+        return addDITStructureRule(definition, overwrite, null);
+    }
+
+    SchemaBuilder addDITStructureRule(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -585,6 +670,9 @@
             }
             ruleBuilder.nameForm(nameForm);
 
+            if (hook != null) {
+                hook.beforeAddDitStructureRule(ruleBuilder);
+            }
             return ruleBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg = ERR_ATTR_SYNTAX_DSR_INVALID1.get(definition, e.getMessageObject());
@@ -637,6 +725,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addMatchingRule(final String definition, final boolean overwrite) {
+        return addMatchingRule(definition, overwrite, null);
+    }
+
+    SchemaBuilder addMatchingRule(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -720,6 +812,9 @@
             if (syntax == null) {
                 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition));
             }
+            if (hook != null) {
+                hook.beforeAddMatchingRule(matchingRuleBuilder);
+            }
             matchingRuleBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg =
@@ -748,6 +843,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addMatchingRuleUse(final String definition, final boolean overwrite) {
+        return addMatchingRuleUse(definition, overwrite, null);
+    }
+
+    SchemaBuilder addMatchingRuleUse(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -829,6 +928,9 @@
             }
             useBuilder.attributes(attributes);
 
+            if (hook != null) {
+                hook.beforeAddMatchingRuleUse(useBuilder);
+            }
             return useBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg = ERR_ATTR_SYNTAX_MRUSE_INVALID1.get(definition, e.getMessageObject());
@@ -929,6 +1031,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addNameForm(final String definition, final boolean overwrite) {
+        return addNameForm(definition, overwrite, null);
+    }
+
+    SchemaBuilder addNameForm(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -1027,6 +1133,9 @@
                 throw new LocalizedIllegalArgumentException(ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition));
             }
 
+            if (hook != null) {
+                hook.beforeAddNameForm(nameFormBuilder);
+            }
             nameFormBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg =
@@ -1246,6 +1355,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addObjectClass(final String definition, final boolean overwrite) {
+        return addObjectClass(definition, overwrite, null);
+    }
+
+    SchemaBuilder addObjectClass(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -1335,6 +1448,9 @@
                         ERR_ATTR_SYNTAX_OBJECTCLASS_ILLEGAL_TOKEN1.get(definition, tokenName));
                 }
             }
+            if (hook != null) {
+                hook.beforeAddObjectClass(ocBuilder);
+            }
 
             if (EXTENSIBLE_OBJECT_OBJECTCLASS_OID.equals(oid)) {
                 addObjectClass(newExtensibleObjectObjectClass(
@@ -1411,7 +1527,7 @@
         // The call to addSchema will perform copyOnWrite.
         final SearchRequest request = getReadSchemaSearchRequest(name);
         final Entry entry = connection.searchSingleEntry(request);
-        return addSchema(entry, overwrite);
+        return addSchema(entry, overwrite, null);
     }
 
     /**
@@ -1430,6 +1546,27 @@
      *             If {@code entry} was {@code null}.
      */
     public SchemaBuilder addSchema(final Entry entry, final boolean overwrite) {
+        return addSchema(entry, overwrite, null);
+    }
+
+    /**
+     * Adds all of the schema elements contained in the provided subschema
+     * subentry to this schema builder. Any problems encountered while parsing
+     * the entry can be retrieved using the returned schema's
+     * {@link Schema#getWarnings()} method.
+     *
+     * @param entry
+     *            The subschema subentry to be parsed.
+     * @param overwrite
+     *            {@code true} if existing schema elements with the same
+     *            conflicting OIDs should be overwritten.
+     * @param hook
+     *            Allows to perform modifications on element's builders before adding the result to this schema builder.
+     * @return A reference to this schema builder.
+     * @throws NullPointerException
+     *             If {@code entry} was {@code null}.
+     */
+    public SchemaBuilder addSchema(final Entry entry, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(entry);
 
         lazyInitBuilder();
@@ -1438,7 +1575,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addSyntax(def.toString(), overwrite);
+                    addSyntax(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1449,7 +1586,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addAttributeType(def.toString(), overwrite);
+                    addAttributeType(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1460,7 +1597,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addObjectClass(def.toString(), overwrite);
+                    addObjectClass(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1471,7 +1608,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addMatchingRuleUse(def.toString(), overwrite);
+                    addMatchingRuleUse(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1482,7 +1619,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addMatchingRule(def.toString(), overwrite);
+                    addMatchingRule(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1493,7 +1630,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addDITContentRule(def.toString(), overwrite);
+                    addDITContentRule(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1504,7 +1641,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addDITStructureRule(def.toString(), overwrite);
+                    addDITStructureRule(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1515,7 +1652,7 @@
         if (attr != null) {
             for (final ByteString def : attr) {
                 try {
-                    addNameForm(def.toString(), overwrite);
+                    addNameForm(def.toString(), overwrite, hook);
                 } catch (final LocalizedIllegalArgumentException e) {
                     warnings.add(e.getMessageObject());
                 }
@@ -1582,7 +1719,7 @@
                 new Function<SearchResultEntry, SchemaBuilder, LdapException>() {
                     @Override
                     public SchemaBuilder apply(SearchResultEntry result) throws LdapException {
-                        addSchema(result, overwrite);
+                        addSchema(result, overwrite, null);
                         return SchemaBuilder.this;
                     }
                 });
@@ -1718,6 +1855,10 @@
      *             If {@code definition} was {@code null}.
      */
     public SchemaBuilder addSyntax(final String definition, final boolean overwrite) {
+        return addSyntax(definition, overwrite, null);
+    }
+
+    SchemaBuilder addSyntax(final String definition, final boolean overwrite, SchemaBuilderHook hook) {
         Reject.ifNull(definition);
 
         lazyInitBuilder();
@@ -1784,6 +1925,9 @@
                 }
             }
 
+            if (hook != null) {
+                hook.beforeAddSyntax(syntaxBuilder);
+            }
             syntaxBuilder.addToSchema(overwrite);
         } catch (final DecodeException e) {
             final LocalizableMessage msg =
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaConfigManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaConfigManager.java
index c2fec3e..a47679c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaConfigManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/SchemaConfigManager.java
@@ -36,7 +36,16 @@
 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.DITContentRule;
+import org.forgerock.opendj.ldap.schema.DITStructureRule;
+import org.forgerock.opendj.ldap.schema.MatchingRule;
+import org.forgerock.opendj.ldap.schema.MatchingRuleUse;
+import org.forgerock.opendj.ldap.schema.NameForm;
+import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.SchemaBuilder;
+import org.forgerock.opendj.ldap.schema.SchemaBuilder.SchemaBuilderHook;
+import org.forgerock.opendj.ldap.schema.Syntax;
+import org.forgerock.opendj.ldap.schema.AttributeType.Builder;
 import org.forgerock.opendj.ldif.LDIFEntryReader;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.InitializationException;
@@ -47,6 +56,7 @@
 import static org.forgerock.opendj.ldap.schema.SchemaValidationPolicy.*;
 import static org.opends.messages.ConfigMessages.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.ServerConstants.SCHEMA_PROPERTY_FILENAME;
 
 /**
  * This class defines a utility that will be used to manage the interaction with
@@ -357,7 +367,7 @@
       // immediately overwrite these definitions which are already defined in the SDK core schema
       final boolean overwriteCoreSchemaDefinitions =
           CORE_SCHEMA_FILE.equals(schemaFile) || RFC_3112_SCHEMA_FILE.equals(schemaFile);
-      updateSchema(schema, schemaEntry, overwriteCoreSchemaDefinitions);
+      updateSchema(schema, schemaFile, schemaEntry, overwriteCoreSchemaDefinitions);
     }
     catch (DirectoryException e)
     {
@@ -369,7 +379,7 @@
         logger.warn(WARN_CONFIG_CONFLICTING_DEFINITIONS_IN_SCHEMA_FILE, schemaFile, e.getMessageObject());
         try
         {
-          updateSchema(schema, schemaEntry, true);
+          updateSchema(schema, schemaFile, schemaEntry, true);
         }
         catch (DirectoryException e2)
         {
@@ -439,15 +449,48 @@
     }
   }
 
-  private static void updateSchema(Schema schema, final Entry schemaEntry, final boolean overwrite)
-      throws DirectoryException
+    private static void updateSchema(Schema schema, final String schemaFile, final 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();
+        return builder.addSchema(schemaEntry, overwrite, new SchemaBuilderHook() {
+            @Override
+            public void beforeAddSyntax(Syntax.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddObjectClass(ObjectClass.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddNameForm(NameForm.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddMatchingRuleUse(MatchingRuleUse.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddMatchingRule(MatchingRule.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddDitStructureRule(DITStructureRule.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddDitContentRule(DITContentRule.Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+            @Override
+            public void beforeAddAttribute(Builder builder) {
+                builder.extraProperties(SCHEMA_PROPERTY_FILENAME, Collections.singletonList(schemaFile));
+            }
+        }).toSchema();
       }
     });
   }

--
Gitblit v1.10.0