opendj-sdk/opendj3-server-dev/ivy.xml
@@ -57,9 +57,7 @@ <dependency org="org.forgerock.opendj" name="opendj-server3x-adapter" rev="&opendj.sdk.version;"> <exclude module="opendj-server" /> </dependency> <dependency org="org.forgerock.opendj" name="opendj-server" rev="&opendj.sdk.version;"> <exclude module="opendj-config" /> </dependency> <dependency org="org.forgerock.opendj" name="opendj-server" rev="&opendj.sdk.version;" /> <dependency org="org.glassfish.grizzly" name="grizzly-http-servlet" rev="&grizzly.version;"> <exclude module="javax.servlet-api" /> </dependency> opendj-sdk/opendj3-server-dev/resource/config/config.ldif
@@ -69,6 +69,21 @@ ds-cfg-allowed-task: org.opends.server.tasks.ShutdownTask ds-cfg-allowed-task: org.opends.server.tasks.PurgeConflictsHistoricalTask dn: cn=Schema Providers,cn=config objectClass: top objectClass: ds-cfg-branch cn: Schema Providers dn: cn=Core Schema,cn=Schema Providers,cn=config objectClass: top objectClass: ds-cfg-schema-provider objectClass: ds-cfg-core-schema cn: Core Schema ds-cfg-java-class: org.opends.server.schema.CoreSchemaProvider ds-cfg-enabled: true ds-cfg-strip-syntax-min-upper-bound-attribute-type-description: false ds-cfg-strict-format-country-string: false dn: cn=Access Control Handler,cn=config objectClass: top objectClass: ds-cfg-access-control-handler opendj-sdk/opendj3-server-dev/resource/schema/02-config.ldif
@@ -3749,6 +3749,34 @@ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.136 NAME 'ds-cfg-disabled-syntax' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.137 NAME 'ds-cfg-disabled-matching-rule' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.138 NAME 'ds-cfg-strict-format-country-string' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.139 NAME 'ds-cfg-allow-zero-length-values-directory-string' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.140 NAME 'ds-cfg-strip-syntax-min-upper-bound-attribute-type-description' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -5684,3 +5712,21 @@ STRUCTURAL MAY ds-cfg-pbkdf2-iterations X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.19 NAME 'ds-cfg-schema-provider' SUP top STRUCTURAL MUST ( cn $ ds-cfg-enabled $ ds-cfg-java-class ) X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.20 NAME 'ds-cfg-core-schema' SUP ds-cfg-schema-provider STRUCTURAL MAY ( ds-cfg-disabled-syntax $ ds-cfg-disabled-matching-rule $ ds-cfg-strict-format-country-string $ ds-cfg-allow-zero-length-values-directory-string $ ds-cfg-strip-syntax-min-upper-bound-attribute-type-description ) X-ORIGIN 'OpenDJ Directory Server' ) opendj-sdk/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/CoreSchemaConfiguration.xml
New file @@ -0,0 +1,187 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! 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 ForgeRock AS. ! --> <adm:managed-object name="core-schema" plural-name="core-schemas" package="org.opends.server.admin.std" extends="schema-provider" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> <adm:user-friendly-name /> define the core schema elements to load. </adm:synopsis> <adm:description> Core schema provider configuration. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-core-schema</ldap:name> <ldap:superior>ds-cfg-schema-provider</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.schema.CoreSchemaProvider </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="disabled-matching-rule" multi-valued="true"> <adm:synopsis> The set of disabled matching rules. </adm:synopsis> <adm:description> Matching rules must be specified using the syntax: OID, or use the default value 'NONE' to specify no value. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>NONE</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>^([0-9.]+\\d|NONE)$</adm:regex> <adm:usage>OID</adm:usage> <adm:synopsis> The OID of the disabled matching rule. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-disabled-matching-rule</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="disabled-syntax" multi-valued="true"> <adm:synopsis> The set of disabled syntaxes. </adm:synopsis> <adm:description> Syntaxes must be specified using the syntax: OID, or use the default value 'NONE' to specify no value. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>NONE</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>^([0-9.]+\\d|NONE)$</adm:regex> <adm:usage>OID</adm:usage> <adm:synopsis> The OID of the disabled syntax, or NONE </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-disabled-syntax</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="strip-syntax-min-upper-bound-attribute-type-description" advanced="true"> <adm:synopsis> Indicates whether the suggested minimum upper bound appended to an attribute's syntax OID in it's schema definition Attribute Type Description is stripped off. </adm:synopsis> <adm:description> When retrieving the server's schema, some APIs (JNDI) fail in their syntax lookup methods, because they do not parse this value correctly. This configuration option allows the server to be configured to provide schema definitions these APIs can parse correctly. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>false</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-strip-syntax-min-upper-bound-attribute-type-description</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="strict-format-country-string" advanced="true"> <adm:synopsis> Indicates whether or not country code values are required to strictly comply with the standard definition for this syntax. </adm:synopsis> <adm:description> When set to false, country codes will not be validated and, as a result any string containing 2 characters will be acceptable. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>true</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-strict-format-country-string</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="allow-zero-length-values-directory-string" advanced="true"> <adm:synopsis> Indicates whether zero-length (that is, an empty string) values are allowed for directory string. </adm:synopsis> <adm:description> This is technically not allowed by the revised LDAPv3 specification, but some environments may require it for backward compatibility with servers that do allow it. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>false</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-allow-zero-length-values-directory-string</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opendj-sdk/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
@@ -23,7 +23,7 @@ ! ! ! Copyright 2007-2010 Sun Microsystems, Inc. ! Portions Copyright 2011 ForgeRock AS ! Portions Copyright 2011-2014 ForgeRock AS ! --> <adm:root-managed-object xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap" @@ -40,6 +40,17 @@ <ldap:rdn-sequence>cn=config</ldap:rdn-sequence> </adm:profile> </adm:relation> <adm:relation name="schema-provider"> <adm:one-to-many /> <adm:profile name="ldap"> <ldap:rdn-sequence>cn=Schema Providers,cn=config</ldap:rdn-sequence> </adm:profile> <adm:profile name="cli"> <cli:relation> <cli:default-property name="enabled" /> </cli:relation> </adm:profile> </adm:relation> <adm:relation name="connection-handler"> <adm:one-to-many /> <adm:profile name="ldap"> opendj-sdk/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/SchemaProviderConfiguration.xml
New file @@ -0,0 +1,78 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! 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 ForgeRock AS. ! --> <adm:managed-object name="schema-provider" plural-name="schema-providers" package="org.opends.server.admin.std" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> <adm:user-friendly-plural-name /> define the schema elements to load. </adm:synopsis> <adm:description> Schema provider configuration. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-schema-provider</ldap:name> <ldap:superior>top</ldap:superior> </ldap:object-class> </adm:profile> <adm:property name="enabled" mandatory="true"> <adm:synopsis> Indicates whether the <adm:user-friendly-name /> is enabled for use. </adm:synopsis> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-enabled</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="java-class" mandatory="true"> <adm:synopsis> Specifies the fully-qualified name of the Java class that provides the <adm:user-friendly-name /> implementation. </adm:synopsis> <adm:syntax> <adm:java-class> <adm:instance-of> org.opends.server.schema.SchemaProvider </adm:instance-of> </adm:java-class> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-java-class</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opendj-sdk/opendj3-server-dev/src/admin/messages/CoreSchemaCfgDefn.properties
New file @@ -0,0 +1,18 @@ user-friendly-name=Core Schema user-friendly-plural-name=Core Schemas synopsis=Core Schema define the core schema elements to load. description=Core schema provider configuration. property.allow-zero-length-values-directory-string.synopsis=Indicates whether zero-length (that is, an empty string) values are allowed for directory string. property.allow-zero-length-values-directory-string.description=This is technically not allowed by the revised LDAPv3 specification, but some environments may require it for backward compatibility with servers that do allow it. property.disabled-matching-rule.synopsis=The set of disabled matching rules. property.disabled-matching-rule.description=Matching rules must be specified using the syntax: OID, or use the default value 'NONE' to specify no value. property.disabled-matching-rule.syntax.string.pattern.synopsis=The OID of the disabled matching rule. property.disabled-syntax.synopsis=The set of disabled syntaxes. property.disabled-syntax.description=Syntaxes must be specified using the syntax: OID, or use the default value 'NONE' to specify no value. property.disabled-syntax.syntax.string.pattern.synopsis=The OID of the disabled syntax, or NONE property.enabled.synopsis=Indicates whether the Core Schema is enabled for use. property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the Core Schema implementation. property.strict-format-country-string.synopsis=Indicates whether or not country code values are required to strictly comply with the standard definition for this syntax. property.strict-format-country-string.description=When set to false, country codes will not be validated and, as a result any string containing 2 characters will be acceptable. property.strip-syntax-min-upper-bound-attribute-type-description.synopsis=Indicates whether the suggested minimum upper bound appended to an attribute's syntax OID in it's schema definition Attribute Type Description is stripped off. property.strip-syntax-min-upper-bound-attribute-type-description.description=When retrieving the server's schema, some APIs (JNDI) fail in their syntax lookup methods, because they do not parse this value correctly. This configuration option allows the server to be configured to provide schema definitions these APIs can parse correctly. opendj-sdk/opendj3-server-dev/src/admin/messages/RootCfgDefn.properties
@@ -104,6 +104,10 @@ relation.sasl-mechanism-handler.user-friendly-plural-name=SASL Mechanism Handlers relation.sasl-mechanism-handler.synopsis=The SASL mechanism handler configuration entry is the parent for all SASL mechanism handlers defined in the OpenDJ directory server. relation.sasl-mechanism-handler.description=SASL mechanism handlers are responsible for authenticating users during the course of processing a SASL (Simple Authentication and Security Layer, as defined in RFC 4422) bind. relation.schema-provider.user-friendly-name=Schema Provider relation.schema-provider.user-friendly-plural-name=Schema Providers relation.schema-provider.synopsis=Schema Providers define the schema elements to load. relation.schema-provider.description=Schema provider configuration. relation.synchronization-provider.user-friendly-name=Synchronization Provider relation.synchronization-provider.user-friendly-plural-name=Synchronization Providers relation.synchronization-provider.synopsis=Synchronization Providers are responsible for handling synchronization of the directory server data with other OpenDJ instances or other data repositories. opendj-sdk/opendj3-server-dev/src/admin/messages/SchemaProviderCfgDefn.properties
New file @@ -0,0 +1,6 @@ user-friendly-name=Schema Provider user-friendly-plural-name=Schema Providers synopsis=Schema Providers define the schema elements to load. description=Schema provider configuration. property.enabled.synopsis=Indicates whether the Schema Provider is enabled for use. property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the Schema Provider implementation. opendj-sdk/opendj3-server-dev/src/server/org/opends/server/core/ConfigurationBootstrapper.java
New file @@ -0,0 +1,72 @@ /* * 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 ForgeRock AS. */ package org.opends.server.core; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.ConfigurationFramework; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.ServerManagementContext; import org.opends.server.types.InitializationException; /** * Bootstrap the server configuration. */ public class ConfigurationBootstrapper { /** * Bootstrap the server configuration. * <p> * The returned server management context is fully initialized with * all configuration objects valued from configuration file. * * @param serverContext * The server context. * @return the server management context * @throws InitializationException * If an error occurs during bootstrapping. */ public static ServerManagementContext bootstrap(ServerContext serverContext) throws InitializationException { final ConfigurationFramework configFramework = ConfigurationFramework.getInstance(); try { if (!configFramework.isInitialized()) { configFramework.initialize(); } } catch (ConfigException e) { // TODO : fix the message throw new InitializationException(LocalizableMessage.raw("Cannot initialize config framework"), e); } final ConfigurationHandler configurationHandler = new ConfigurationHandler(serverContext); configurationHandler.initialize(); return new ServerManagementContext(configurationHandler); } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/core/ConfigurationHandler.java
New file @@ -0,0 +1,957 @@ /* * 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 ForgeRock AS */ package org.opends.server.core; import static org.forgerock.util.Utils.*; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.config.ConfigConstants.*; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.adapter.server3x.Converters; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.spi.ConfigAddListener; import org.forgerock.opendj.config.server.spi.ConfigChangeListener; import org.forgerock.opendj.config.server.spi.ConfigDeleteListener; import org.forgerock.opendj.config.server.spi.ConfigurationRepository; import org.forgerock.opendj.ldap.CancelRequestListener; import org.forgerock.opendj.ldap.CancelledResultException; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.ErrorResultException; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.MemoryBackend; import org.forgerock.opendj.ldap.RequestContext; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.ResultHandler; import org.forgerock.opendj.ldap.SearchResultHandler; import org.forgerock.opendj.ldap.SearchScope; import org.forgerock.opendj.ldap.requests.Requests; import org.forgerock.opendj.ldap.responses.Result; import org.forgerock.opendj.ldap.responses.SearchResultEntry; import org.forgerock.opendj.ldap.responses.SearchResultReference; 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.util.Utils; import org.opends.server.types.DirectoryEnvironmentConfig; import org.opends.server.types.DirectoryException; import org.opends.server.types.InitializationException; /** * Responsible for managing configuration entries and listeners on these * entries. */ public class ConfigurationHandler implements ConfigurationRepository { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private static final String CONFIGURATION_FILE_NAME = "02-config.ldif"; private final ServerContext serverContext; /** The complete path to the configuration file to use. */ private File configFile; /** Indicates whether to start using the last known good configuration. */ private boolean useLastKnownGoodConfig; /** Backend containing the configuration entries. */ private MemoryBackend backend; /** The config root entry. */ private Entry rootEntry; /** The add/delete/change listeners on configuration entries. */ private final ConcurrentHashMap<DN, EntryListeners> listeners = new ConcurrentHashMap<DN, EntryListeners>(); /** Schema with configuration-related elements. */ private Schema configEnabledSchema; /** * Creates a new instance. * * @param serverContext * The server context. */ public ConfigurationHandler(final ServerContext serverContext) { this.serverContext = serverContext; } /** * Initialize the configuration. * * @throws InitializationException * If an error occurs during the initialization. */ public void initialize() throws InitializationException { final DirectoryEnvironmentConfig environment = serverContext.getEnvironment(); useLastKnownGoodConfig = environment.useLastKnownGoodConfiguration(); configFile = findConfigFileToUse(environment.getConfigFile()); configEnabledSchema = loadConfigEnabledSchema(); loadConfiguration(configFile, configEnabledSchema); } /** Holds add, change and delete listeners for a given configuration entry. */ private static class EntryListeners { /** The set of add listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigAddListener> addListeners = new CopyOnWriteArrayList<ConfigAddListener>(); /** The set of change listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners = new CopyOnWriteArrayList<ConfigChangeListener>(); /** The set of delete listeners that have been registered with this entry. */ private final CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners = new CopyOnWriteArrayList<ConfigDeleteListener>(); CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners() { return changeListeners; } void registerChangeListener(final ConfigChangeListener listener) { changeListeners.add(listener); } boolean deregisterChangeListener(final ConfigChangeListener listener) { return changeListeners.remove(listener); } CopyOnWriteArrayList<ConfigAddListener> getAddListeners() { return addListeners; } void registerAddListener(final ConfigAddListener listener) { addListeners.addIfAbsent(listener); } void deregisterAddListener(final ConfigAddListener listener) { addListeners.remove(listener); } CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners() { return deleteListeners; } void registerDeleteListener(final ConfigDeleteListener listener) { deleteListeners.addIfAbsent(listener); } void deregisterDeleteListener(final ConfigDeleteListener listener) { deleteListeners.remove(listener); } } /** Request context to be used when requesting the internal backend. */ private static final RequestContext UNCANCELLABLE_REQUEST_CONTEXT = new RequestContext() { /** {@inheritDoc} */ @Override public void removeCancelRequestListener(final CancelRequestListener listener) { // nothing to do } /** {@inheritDoc} */ @Override public int getMessageID() { return -1; } /** {@inheritDoc} */ @Override public void checkIfCancelled(final boolean signalTooLate) throws CancelledResultException { // nothing to do } /** {@inheritDoc} */ @Override public void addCancelRequestListener(final CancelRequestListener listener) { // nothing to do } }; /** Handler for search results. */ private static final class ConfigSearchResultHandler implements SearchResultHandler { private ErrorResultException resultError; private final Set<Entry> entries = new HashSet<Entry>(); ErrorResultException getResultError() { return resultError; } boolean hasCompletedSuccessfully() { return resultError == null; } Set<Entry> getEntries() { return entries; } /** {@inheritDoc} */ @Override public void handleResult(Result result) { // nothing to do } /** {@inheritDoc} */ @Override public void handleErrorResult(ErrorResultException error) { resultError = error; } /** {@inheritDoc} */ @Override public boolean handleReference(SearchResultReference reference) { throw new UnsupportedOperationException("Search references are not supported for configuration entries."); } /** {@inheritDoc} */ @Override public boolean handleEntry(SearchResultEntry entry) { entries.add(entry); return true; } } /** Handler for LDAP operations. */ private static final class ConfigResultHandler implements ResultHandler<Result> { private ErrorResultException resultError; ErrorResultException getResultError() { return resultError; } boolean hasCompletedSuccessfully() { return resultError == null; } /** {@inheritDoc} */ @Override public void handleResult(Result result) { // nothing to do } /** {@inheritDoc} */ @Override public void handleErrorResult(ErrorResultException error) { resultError = error; } } /** * Returns the configuration root entry. * * @return the root entry */ public Entry getRootEntry() { return rootEntry; } /** {@inheritDoc} */ /** {@inheritDoc} */ @Override public Entry getEntry(final DN dn) throws ConfigException { Entry entry = backend.get(dn); if (entry == null) { // TODO : fix message LocalizableMessage message = LocalizableMessage.raw("Unable to retrieve the configuration entry %s", dn); throw new ConfigException(message); } return entry; } /** {@inheritDoc} */ /** {@inheritDoc} */ @Override public boolean hasEntry(final DN dn) throws ConfigException { return backend.get(dn) != null; } /** {@inheritDoc} */ /** {@inheritDoc} */ @Override public Set<DN> getChildren(DN dn) throws ConfigException { final ConfigSearchResultHandler resultHandler = new ConfigSearchResultHandler(); backend.handleSearch( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newSearchRequest(dn, SearchScope.SINGLE_LEVEL, Filter.objectClassPresent()), null, resultHandler); if (resultHandler.hasCompletedSuccessfully()) { final Set<DN> children = new HashSet<DN>(); for (final Entry entry : resultHandler.getEntries()) { children.add(entry.getName()); } return children; } else { // TODO : fix message throw new ConfigException( LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", dn), resultHandler.getResultError()); } } /** * Retrieves the number of subordinates for the requested entry. * * @param entryDN * The distinguished name of the entry. * @param subtree * {@code true} to include all entries from the requested entry * to the lowest level in the tree or {@code false} to only * include the entries immediately below the requested entry. * @return The number of subordinate entries * @throws ConfigException * If a problem occurs while trying to retrieve the entry. */ public long numSubordinates(final DN entryDN, final boolean subtree) throws ConfigException { final ConfigSearchResultHandler resultHandler = new ConfigSearchResultHandler(); final SearchScope scope = subtree ? SearchScope.SUBORDINATES : SearchScope.SINGLE_LEVEL; backend.handleSearch( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newSearchRequest(entryDN, scope, Filter.objectClassPresent()), null, resultHandler); if (resultHandler.hasCompletedSuccessfully()) { return resultHandler.getEntries().size(); } else { // TODO : fix the message throw new ConfigException( LocalizableMessage.raw("Unable to retrieve children of configuration entry : %s", entryDN), resultHandler.getResultError()); } } /** * Add a configuration entry * <p> * The add is performed only if all Add listeners on the parent entry accept * the changes. Once the change is accepted, entry is effectively added and * all Add listeners are called again to apply the change resulting from this * new entry. * * @param entry * The configuration entry to add. * @throws DirectoryException * If an error occurs. */ public void addEntry(final Entry entry) throws DirectoryException { final DN entryDN = entry.getName(); if (backend.contains(entryDN)) { throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, ERR_CONFIG_FILE_ADD_ALREADY_EXISTS.get(entryDN)); } final DN parentDN = retrieveParentDN(entryDN); // Iterate through add listeners to make sure the new entry is acceptable. final List<ConfigAddListener> addListeners = getAddListeners(parentDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); for (final ConfigAddListener listener : addListeners) { if (!listener.configAddIsAcceptable(entry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); } } // Add the entry. final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleAdd(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newAddRequest(entry), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix the message : error when adding config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entryDN, parentDN, unacceptableReason)); } // Notify all the add listeners to apply the new configuration entry. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>(); for (final ConfigAddListener listener : addListeners) { final ConfigChangeResult result = listener.applyConfigurationAdd(entry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, entry.getName(), listener.getClass().getName(), "applyConfigurationAdd"); } if (resultCode != ResultCode.SUCCESS) { final String reasons = Utils.joinAsString(". ", messages); throw new DirectoryException(resultCode, ERR_CONFIG_FILE_ADD_APPLY_FAILED.get(reasons)); } } /** * Delete a configuration entry. * <p> * The delete is performed only if all Delete listeners on the parent entry * accept the changes. Once the change is accepted, entry is effectively * deleted and all Delete listeners are called again to apply the change * resulting from this deletion. * * @param dn * DN of entry to delete. * @throws DirectoryException * If a problem occurs. */ public void deleteEntry(final DN dn) throws DirectoryException { // Entry must exist. if (!backend.contains(dn)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_DELETE_NO_SUCH_ENTRY.get(dn), Converters.to(getMatchedDN(dn)), null); } // Entry must not have children. try { if (!getChildren(dn).isEmpty()) { throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn)); } } catch (ConfigException e) { // TODO : fix message = ERROR BACKEND CONFIG throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(dn), e); } // TODO : pass in the localizable message (2) final DN parentDN = retrieveParentDN(dn); // Iterate through delete listeners to make sure the deletion is acceptable. final List<ConfigDeleteListener> deleteListeners = getDeleteListeners(parentDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); final Entry entry = backend.get(dn); for (final ConfigDeleteListener listener : deleteListeners) { if (!listener.configDeleteIsAcceptable(entry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER.get(entry, parentDN, unacceptableReason)); } } // Delete the entry final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleDelete(UNCANCELLABLE_REQUEST_CONTEXT, Requests.newDeleteRequest(dn), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix message : error when deleting config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_REJECTED.get(dn, parentDN, unacceptableReason)); } // Notify all the delete listeners that the entry has been removed. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>(); for (final ConfigDeleteListener listener : deleteListeners) { final ConfigChangeResult result = listener.applyConfigurationDelete(entry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, dn, listener.getClass().getName(), "applyConfigurationDelete"); } if (resultCode != ResultCode.SUCCESS) { final String reasons = Utils.joinAsString(". ", messages); throw new DirectoryException(resultCode, ERR_CONFIG_FILE_DELETE_APPLY_FAILED.get(reasons)); } } /** * Replaces the old configuration entry with the new configuration entry * provided. * <p> * The replacement is performed only if all Change listeners on the entry * accept the changes. Once the change is accepted, entry is effectively * replaced and all Change listeners are called again to apply the change * resulting from the replacement. * * @param oldEntry * The original entry that is being replaced. * @param newEntry * The new entry to use in place of the existing entry with the same * DN. * @throws DirectoryException * If a problem occurs while trying to replace the entry. */ public void replaceEntry(final Entry oldEntry, final Entry newEntry) throws DirectoryException { final DN entryDN = oldEntry.getName(); if (!backend.contains(entryDN)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_MODIFY_NO_SUCH_ENTRY.get(oldEntry), Converters.to(getMatchedDN(entryDN)), null); } //TODO : add objectclass and attribute to the config schema in order to get this code run // if (!Entries.getStructuralObjectClass(oldEntry, configEnabledSchema) // .equals(Entries.getStructuralObjectClass(newEntry, configEnabledSchema))) // { // throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, // ERR_CONFIG_FILE_MODIFY_STRUCTURAL_CHANGE_NOT_ALLOWED.get(entryDN)); // } // Iterate through change listeners to make sure the change is acceptable. final List<ConfigChangeListener> changeListeners = getChangeListeners(entryDN); final LocalizableMessageBuilder unacceptableReason = new LocalizableMessageBuilder(); for (ConfigChangeListener listeners : changeListeners) { if (!listeners.configChangeIsAcceptable(newEntry, unacceptableReason)) { throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_MODIFY_REJECTED_BY_CHANGE_LISTENER.get(entryDN, unacceptableReason)); } } // Replace the old entry with new entry. final ConfigResultHandler resultHandler = new ConfigResultHandler(); backend.handleModify( UNCANCELLABLE_REQUEST_CONTEXT, Requests.newModifyRequest(oldEntry, newEntry), null, resultHandler); if (!resultHandler.hasCompletedSuccessfully()) { // TODO fix message : error when replacing config entry // use resultHandler.getResultError() to get the error throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_CONFIG_FILE_DELETE_REJECTED.get(entryDN, entryDN, unacceptableReason)); } // Notify all the change listeners of the update. ResultCode resultCode = ResultCode.SUCCESS; final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>(); for (final ConfigChangeListener listener : changeListeners) { final ConfigChangeResult result = listener.applyConfigurationChange(newEntry); if (result.getResultCode() != ResultCode.SUCCESS) { resultCode = resultCode == ResultCode.SUCCESS ? result.getResultCode() : resultCode; messages.addAll(result.getMessages()); } handleConfigChangeResult(result, entryDN, listener.getClass().getName(), "applyConfigurationChange"); } if (resultCode != ResultCode.SUCCESS) { throw new DirectoryException(resultCode, ERR_CONFIG_FILE_MODIFY_APPLY_FAILED.get(Utils.joinAsString(". ", messages))); } } /** {@inheritDoc} */ @Override public void registerAddListener(final DN dn, final ConfigAddListener listener) { getEntryListeners(dn).registerAddListener(listener); } /** {@inheritDoc} */ @Override public void registerDeleteListener(final DN dn, final ConfigDeleteListener listener) { getEntryListeners(dn).registerDeleteListener(listener); } /** {@inheritDoc} */ @Override public void registerChangeListener(final DN dn, final ConfigChangeListener listener) { getEntryListeners(dn).registerChangeListener(listener); } /** {@inheritDoc} */ @Override public void deregisterAddListener(final DN dn, final ConfigAddListener listener) { getEntryListeners(dn).deregisterAddListener(listener); } /** {@inheritDoc} */ @Override public void deregisterDeleteListener(final DN dn, final ConfigDeleteListener listener) { getEntryListeners(dn).deregisterDeleteListener(listener); } /** {@inheritDoc} */ @Override public boolean deregisterChangeListener(final DN dn, final ConfigChangeListener listener) { return getEntryListeners(dn).deregisterChangeListener(listener); } /** {@inheritDoc} */ @Override public List<ConfigAddListener> getAddListeners(final DN dn) { return getEntryListeners(dn).getAddListeners(); } /** {@inheritDoc} */ @Override public List<ConfigDeleteListener> getDeleteListeners(final DN dn) { return getEntryListeners(dn).getDeleteListeners(); } /** {@inheritDoc} */ @Override public List<ConfigChangeListener> getChangeListeners(final DN dn) { return getEntryListeners(dn).getChangeListeners(); } /** Load the configuration-enabled schema that will allow to read configuration file. */ private Schema loadConfigEnabledSchema() throws InitializationException { LDIFEntryReader reader = null; try { final File schemaDir = serverContext.getEnvironment().getSchemaDirectory(); reader = new LDIFEntryReader(new FileReader(new File(schemaDir, CONFIGURATION_FILE_NAME))); reader.setSchema(Schema.getDefaultSchema()); final Entry entry = reader.readEntry(); return new SchemaBuilder(Schema.getDefaultSchema()).addSchema(entry, false).toSchema(); } catch (Exception e) { // TODO : fix message throw new InitializationException(LocalizableMessage.raw("Unable to load config-enabled schema"), e); } finally { closeSilently(reader); } } /** * Read configuration entries from provided configuration file. * * @param configFile * LDIF file with configuration entries. * @param schema * Schema to validate entries when reading the config file. * @throws InitializationException * If an errors occurs. */ private void loadConfiguration(final File configFile, final Schema schema) throws InitializationException { EntryReader reader = null; try { reader = getLDIFReader(configFile, schema); backend = new MemoryBackend(schema, reader); } catch (IOException e) { throw new InitializationException( ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile.getAbsolutePath(), e.getCause()), e); } finally { closeSilently(reader); } // Check that root entry is the expected one rootEntry = backend.get(DN_CONFIG_ROOT); if (rootEntry == null) { // fix message : we didn't find the expected root in the file throw new InitializationException(ERR_CONFIG_FILE_INVALID_BASE_DN.get( configFile.getAbsolutePath(), "", DN_CONFIG_ROOT)); } } /** * Returns the LDIF reader on configuration entries. * <p> * It is the responsability of the caller to ensure that reader * is closed after usage. * * @param configFile * LDIF file containing the configuration entries. * @param schema * Schema to validate entries when reading the config file. * @return the LDIF reader * @throws InitializationException * If an error occurs. */ private EntryReader getLDIFReader(final File configFile, final Schema schema) throws InitializationException { LDIFEntryReader reader = null; try { reader = new LDIFEntryReader(new FileReader(configFile)); reader.setSchema(schema); } catch (Exception e) { throw new InitializationException( ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(configFile.getAbsolutePath(), e), e); } return reader; } /** * Returns the entry listeners attached to the provided DN. * <p> * If no listener exist for the provided DN, then a new set of empty listeners * is created and returned. * * @param dn * DN of a configuration entry. * @return the listeners attached to the corresponding configuration entry. */ private EntryListeners getEntryListeners(final DN dn) { EntryListeners entryListeners = listeners.get(dn); if (entryListeners == null) { entryListeners = new EntryListeners(); final EntryListeners previousListeners = listeners.putIfAbsent(dn, entryListeners); if (previousListeners != null) { entryListeners = previousListeners; } } return entryListeners; } /** * Returns the parent DN of the configuration entry corresponding to the * provided DN. * * @param entryDN * DN of entry to retrieve the parent from. * @return the parent DN * @throws DirectoryException * If entry has no parent or parent entry does not exist. */ private DN retrieveParentDN(final DN entryDN) throws DirectoryException { final DN parentDN = entryDN.parent(); // Entry must have a parent. if (parentDN == null) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_ADD_NO_PARENT_DN.get(entryDN)); } // Parent entry must exist. if (!backend.contains(parentDN)) { throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, ERR_CONFIG_FILE_ADD_NO_PARENT.get(entryDN, parentDN), Converters.to(getMatchedDN(parentDN)), null); } return parentDN; } /** * Returns the matched DN that is available in the configuration for the * provided DN. */ private DN getMatchedDN(final DN dn) { DN matchedDN = null; DN parentDN = dn.parent(); while (parentDN != null) { if (backend.contains(parentDN)) { matchedDN = parentDN; break; } parentDN = parentDN.parent(); } return matchedDN; } /** * Find the actual configuration file to use to load configuration, given the * standard config file. * * @param standardConfigFile * "Standard" configuration file provided. * @return the actual configuration file to use, which is either the standard * config file provided or the config file corresponding to the last * known good configuration * @throws InitializationException * If a problem occurs. */ private File findConfigFileToUse(final File standardConfigFile) throws InitializationException { File configFileToUse = null; if (useLastKnownGoodConfig) { configFileToUse = new File(standardConfigFile + ".startok"); if (! configFileToUse.exists()) { logger.warn(WARN_CONFIG_FILE_NO_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); useLastKnownGoodConfig = false; configFileToUse = standardConfigFile; } else { logger.info(NOTE_CONFIG_FILE_USING_STARTOK_FILE, configFileToUse.getAbsolutePath(), standardConfigFile); } } else { configFileToUse = standardConfigFile; } try { if (! configFileToUse.exists()) { throw new InitializationException(ERR_CONFIG_FILE_DOES_NOT_EXIST.get(configFileToUse.getAbsolutePath())); } } catch (Exception e) { throw new InitializationException( ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get(configFileToUse.getAbsolutePath(), e)); } return configFileToUse; } /** * Examines the provided result and logs a message if appropriate. If the * result code is anything other than {@code SUCCESS}, then it will log an * error message. If the operation was successful but admin action is * required, then it will log a warning message. If no action is required but * messages were generated, then it will log an informational message. * * @param result * The config change result object that * @param entryDN * The DN of the entry that was added, deleted, or modified. * @param className * The name of the class for the object that generated the provided * result. * @param methodName * The name of the method that generated the provided result. */ private void handleConfigChangeResult(ConfigChangeResult result, DN entryDN, String className, String methodName) { if (result == null) { logger.error(ERR_CONFIG_CHANGE_NO_RESULT, className, methodName, entryDN); return; } final ResultCode resultCode = result.getResultCode(); final boolean adminActionRequired = result.adminActionRequired(); final List<LocalizableMessage> messages = result.getMessages(); final String messageBuffer = Utils.joinAsString(" ", messages); if (resultCode != ResultCode.SUCCESS) { logger.error(ERR_CONFIG_CHANGE_RESULT_ERROR, className, methodName, entryDN, resultCode, adminActionRequired, messageBuffer); } else if (adminActionRequired) { logger.warn(WARN_CONFIG_CHANGE_RESULT_ACTION_REQUIRED, className, methodName, entryDN, messageBuffer); } else if (messageBuffer.length() > 0) { logger.debug(INFO_CONFIG_CHANGE_RESULT_MESSAGES, className, methodName, entryDN, messageBuffer); } } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/core/DirectoryServer.java
@@ -45,6 +45,7 @@ import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.schema.AttributeUsage; import org.forgerock.opendj.ldap.schema.ObjectClassType; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.forgerock.util.Reject; import org.forgerock.util.Utils; import org.opends.server.admin.AdministrationConnector; @@ -628,6 +629,10 @@ /** The schema for the Directory Server. */ private Schema schema; /** The schema for the Directory Server. */ // TODO : temporary field to be removed once old schema is completely removed private org.forgerock.opendj.ldap.schema.Schema schemaNG; /** The schema configuration manager for the Directory Server. */ private SchemaConfigManager schemaConfigManager; @@ -727,6 +732,9 @@ /** Temporary context object, to provide instance methods instead of static methods. */ private final DirectoryServerContext serverContext = new DirectoryServerContext(); /** Entry point for server configuration. */ private org.forgerock.opendj.config.server.ServerManagementContext serverManagementContext; /** * Creates a new instance of the Directory Server. This will allow only a * single instance of the server per JVM. @@ -775,9 +783,30 @@ /** {@inheritDoc} */ @Override public ServerManagementContext getServerManagementContext() public SchemaUpdater getSchemaUpdater() { return ServerManagementContext.getInstance(); return new SchemaUpdater() { @Override public boolean updateSchema(SchemaBuilder schemaBuilder) { schemaNG = schemaBuilder.toSchema(); return true; } @Override public SchemaBuilder getSchemaBuilder() { return new SchemaBuilder(schemaNG); } }; } /** {@inheritDoc} */ @Override public org.forgerock.opendj.config.server.ServerManagementContext getServerManagementContext() { return serverManagementContext; } } @@ -1142,7 +1171,42 @@ initializeConfiguration(); } /** * Initialize this server. * <p> * Initialization involves the following steps: * <ul> * <li>Configuration</li> * <li>Schema</li> * </ul> * @throws InitializationException */ private void initializeNG() throws InitializationException { serverManagementContext = ConfigurationBootstrapper.bootstrap(serverContext); initializeSchemaNG(); // TODO : config backend should be initialized later, with the other backends //ConfigBackend configBackend = new ConfigBackend(); //configBackend.initializeConfigBackend(serverContext, configurationHandler); } /** * Initialize the schema of this server. */ private void initializeSchemaNG() throws InitializationException { SchemaHandler schemaHandler = new SchemaHandler(); try { schemaHandler.initialize(serverContext); } catch (org.forgerock.opendj.config.server.ConfigException e) { // TODO : fix message throw new InitializationException(LocalizableMessage.raw("Cannot initialize schema handler"), e); } } /** * Instantiates the configuration handler and loads the Directory Server @@ -1157,7 +1221,6 @@ this.configClass = environmentConfig.getConfigClass(); this.configFile = environmentConfig.getConfigFile(); // Make sure that administration framework definition classes are loaded. ClassLoaderProvider provider = ClassLoaderProvider.getInstance(); if (! provider.isEnabled()) @@ -1165,7 +1228,6 @@ provider.enable(); } // Load and instantiate the configuration handler class. Class<ConfigHandler> handlerClass = configClass; try @@ -1182,6 +1244,8 @@ } // Perform the handler-specific initialization. try { @@ -1315,7 +1379,6 @@ // Initialize all the schema elements. initializeSchema(); // Initialize the plugin manager so that internal plugins can be // registered. pluginConfigManager.initializePluginConfigManager(); @@ -9055,9 +9118,6 @@ PrintStream serverOutStream; try { // We need to figure out where to put the file. See if the server root // is available as an environment variable and if so then use it. // Otherwise, try to figure it out from the location of the config file. File serverRoot = environmentConfig.getServerRoot(); if (serverRoot == null) { opendj-sdk/opendj3-server-dev/src/server/org/opends/server/core/SchemaHandler.java
New file @@ -0,0 +1,373 @@ /* * 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 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); } /** * 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<LocalizableMessage>(); 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<String>(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); } } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/core/ServerContext.java
@@ -25,7 +25,8 @@ */ package org.opends.server.core; import org.opends.server.admin.server.ServerManagementContext; import org.forgerock.opendj.config.server.ServerManagementContext; import org.opends.server.schema.SchemaUpdater; import org.opends.server.types.DirectoryEnvironmentConfig; import org.opends.server.types.Schema; @@ -57,6 +58,14 @@ public Schema getSchema(); /** * Returns the schema updater, which provides * a mean to update the server's current schema. * * @return the schema updater */ public SchemaUpdater getSchemaUpdater(); /** * Returns the environment of the server. * * @return the environment opendj-sdk/opendj3-server-dev/src/server/org/opends/server/schema/CoreSchemaProvider.java
New file @@ -0,0 +1,156 @@ /* * 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 ForgeRock AS. */ package org.opends.server.schema; import java.util.List; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigurationChangeListener; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.forgerock.opendj.server.config.server.CoreSchemaCfg; import org.opends.server.config.ConfigException; import org.opends.server.types.InitializationException; /** * Provides the core schema, which includes core matching rules and syntaxes. */ public class CoreSchemaProvider implements SchemaProvider<CoreSchemaCfg>, ConfigurationChangeListener<CoreSchemaCfg> { private static final String NONE_ELEMENT = "NONE"; /** The current configuration of core schema. */ private CoreSchemaCfg currentConfig; /** The current schema builder. */ private SchemaBuilder currentSchemaBuilder; /** Updater to notify schema update when configuration changes. */ private SchemaUpdater schemaUpdater; /** {@inheritDoc} */ @Override public void initialize(final CoreSchemaCfg configuration, final SchemaBuilder initialSchemaBuilder, final SchemaUpdater schemaUpdater) throws ConfigException, InitializationException { this.currentConfig = configuration; this.currentSchemaBuilder = initialSchemaBuilder; this.schemaUpdater = schemaUpdater; updateSchemaFromConfiguration(initialSchemaBuilder, configuration); currentConfig.addCoreSchemaChangeListener(this); } /** * Update the provided schema builder with the provided configuration. * * @param schemaBuilder * The schema builder to update. * @param configuration * The configuration to use for update. */ private void updateSchemaFromConfiguration(final SchemaBuilder schemaBuilder, final CoreSchemaCfg configuration) { schemaBuilder.allowZeroLengthDirectoryStrings(configuration.isAllowZeroLengthValuesDirectoryString()); // TODO : add the missing methods in schema builder for those properties // schemaBuilder.allowMalformedJPEGPhotos(configuration.) // schemaBuilder.allowMalformedNamesAndOptions(configuration.) // ... for (final String oid : configuration.getDisabledMatchingRule()) { if (oid.equals(NONE_ELEMENT)) { break; } schemaBuilder.removeMatchingRule(oid); } for (final String oid : configuration.getDisabledSyntax()) { if (oid.equals(NONE_ELEMENT)) { break; } schemaBuilder.removeSyntax(oid); } } /** {@inheritDoc} */ @Override public void finalizeProvider() { currentConfig.removeCoreSchemaChangeListener(this); } /** {@inheritDoc} */ @Override public boolean isConfigurationAcceptable(final CoreSchemaCfg configuration, final List<LocalizableMessage> unacceptableReasons) { // TODO : check that elements to disable are present in the schema ? return true; } /** {@inheritDoc} */ @Override public SchemaBuilder getSchema() { return currentSchemaBuilder; } /** {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable(final CoreSchemaCfg configuration, final List<LocalizableMessage> unacceptableReasons) { if (!configuration.isEnabled()) { // TODO : fix message unacceptableReasons.add(LocalizableMessage.raw("The core schema must always be enabled")); return false; } // TODO : check that elements to disable are present in the schema ? return true; } /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange(final CoreSchemaCfg configuration) { currentSchemaBuilder = schemaUpdater.getSchemaBuilder(); updateSchemaFromConfiguration(currentSchemaBuilder, configuration); final boolean isUpdated = schemaUpdater.updateSchema(currentSchemaBuilder); // TODO : fix result code + log an error in case of failure final ResultCode code = isUpdated ? ResultCode.SUCCESS : ResultCode.OTHER; return new ConfigChangeResult(code, false); } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/schema/SchemaProvider.java
New file @@ -0,0 +1,93 @@ /* * 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 ForgeRock AS. */ package org.opends.server.schema; import java.util.List; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.forgerock.opendj.server.config.server.SchemaProviderCfg; import org.opends.server.config.ConfigException; import org.opends.server.types.InitializationException; /** * Provides some schema elements to load at startup. * <p> * A schema provider must be able to update the provided {@code SchemaBuilder} * at initialization and to use the provided {@code SchemaUpdater} to update the * schema on further configuration changes. * * @param <T> * The type of provider configuration. */ public interface SchemaProvider<T extends SchemaProviderCfg> { /** * Initialize the schema provider from provided configuration and schema * builder. * * @param configuration * Configuration of the provider. * @param initialSchemaBuilder * Schema builder to update during initialization phase. * @param schemaUpdater * The updater to call when applying a configuration change. * @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. */ void initialize(T configuration, SchemaBuilder initialSchemaBuilder, SchemaUpdater schemaUpdater) throws ConfigException, InitializationException; /** * Finalize the provider. */ void finalizeProvider(); /** * Indicates whether the provided configuration is acceptable for this * provider. * * @param configuration * The provider configuration for which to make the determination. * @param unacceptableReasons * A list that may be used to hold the reasons that the provided * configuration is not acceptable. * @return {@code true} if the provided configuration is acceptable for this * provider, or {@code false} if not. */ boolean isConfigurationAcceptable(T configuration, List<LocalizableMessage> unacceptableReasons); /** * Returns the schema builder, as updated by this provider. * * @return the schema builder resulting from this provider. */ SchemaBuilder getSchema(); } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/schema/SchemaUpdater.java
New file @@ -0,0 +1,51 @@ /* * 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 ForgeRock AS. */ package org.opends.server.schema; import org.forgerock.opendj.ldap.schema.SchemaBuilder; /** * An interface to update a schema through a schema builder. */ public interface SchemaUpdater { /** * Returns a schema builder based on the current schema. * * @return a schema builder */ SchemaBuilder getSchemaBuilder(); /** * Update the current schema with the schema built from the * provided schema builder. * * @param schemaBuilder * Builder representing the updated schema. * @return {@code true} if the update succeeded, false otherwise */ boolean updateSchema(SchemaBuilder schemaBuilder); } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java
@@ -61,6 +61,8 @@ /** The set of properties for the environment config. */ private final Map<String, String> configProperties; private final boolean checkIfServerIsRunning; private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** @@ -69,7 +71,19 @@ */ public DirectoryEnvironmentConfig() { this(System.getProperties()); this(true); } /** * Creates a new directory environment configuration initialized from the * system properties defined in the JVM. * * @param checkIfServerIsRunning * If {@code true}, prevent any change when server is running. */ public DirectoryEnvironmentConfig(boolean checkIfServerIsRunning) { this(System.getProperties(), checkIfServerIsRunning); } @@ -81,9 +95,12 @@ * @param properties The properties to use when initializing this * environment configuration, or {@code null} * to use an empty set of properties. * @param checkIfServerIsRunning * If {@code true}, prevent any change when server is running. */ public DirectoryEnvironmentConfig(Properties properties) public DirectoryEnvironmentConfig(Properties properties, boolean checkIfServerIsRunning) { this.checkIfServerIsRunning = checkIfServerIsRunning; configProperties = new HashMap<String,String>(); if (properties != null) { @@ -106,9 +123,12 @@ * @param properties The properties to use when initializing this * environment configuration, or {@code null} * to use an empty set of properties. * @param checkIfServerIsRunning * If {@code true}, prevent any change when server is running. */ public DirectoryEnvironmentConfig(Map<String,String> properties) public DirectoryEnvironmentConfig(Map<String,String> properties, boolean checkIfServerIsRunning) { this.checkIfServerIsRunning = checkIfServerIsRunning; if (properties == null) { configProperties = new HashMap<String,String>(); @@ -163,11 +183,7 @@ public String setProperty(String name, String value) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (value == null) { @@ -352,11 +368,7 @@ public File setServerRoot(File serverRoot) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (!serverRoot.exists() || !serverRoot.isDirectory()) { @@ -420,11 +432,7 @@ public File setInstanceRoot(File instanceRoot) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (!instanceRoot.exists() || !instanceRoot.isDirectory()) { @@ -493,11 +501,7 @@ public File setConfigFile(File configFile) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (!configFile.exists() || !configFile.isFile()) { @@ -563,11 +567,7 @@ public Class setConfigClass(Class configClass) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (!ConfigHandler.class.isAssignableFrom(configClass)) { @@ -696,11 +696,7 @@ boolean maintainConfigArchive) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); String oldMaintainStr = setProperty(PROPERTY_MAINTAIN_CONFIG_ARCHIVE, @@ -768,11 +764,7 @@ public int setMaxConfigArchiveSize(int maxConfigArchiveSize) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (maxConfigArchiveSize < 0) { @@ -864,11 +856,7 @@ public File setSchemaDirectory(File schemaDirectory) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (!schemaDirectory.exists() || !schemaDirectory.isDirectory()) { @@ -936,11 +924,7 @@ public File setLockDirectory(File lockDirectory) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (lockDirectory.exists()) { @@ -1049,11 +1033,7 @@ private boolean setBooleanProperty(String propertyName, boolean newValue) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); final String oldValue = setProperty(propertyName, String.valueOf(newValue)); return "true".equalsIgnoreCase(oldValue); @@ -1195,11 +1175,7 @@ public int setLockManagerConcurrencyLevel(int concurrencyLevel) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (concurrencyLevel <= 0) { @@ -1271,11 +1247,7 @@ public boolean setLockManagerFairOrdering(boolean fairOrdering) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); String fairOrderingStr = setProperty(PROPERTY_LOCK_MANAGER_FAIR_ORDERING, @@ -1297,6 +1269,16 @@ } } /** Throws an exception if server is running and it is not allowed. */ private void checkServerIsRunning() throws InitializationException { if (checkIfServerIsRunning && DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } } /** * Retrieves the initial table size for the server lock table. This * can be used to ensure that the lock table has the appropriate @@ -1348,11 +1330,7 @@ public int setLockManagerTableSize(int lockTableSize) throws InitializationException { if (DirectoryServer.isRunning()) { throw new InitializationException( ERR_DIRCFG_SERVER_ALREADY_RUNNING.get()); } checkServerIsRunning(); if (lockTableSize <= 0) { opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/resource/config-small.ldif
New file @@ -0,0 +1,86 @@ # 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 2006-2010 Sun Microsystems, Inc. # Portions Copyright 2010-2014 ForgeRock AS. # Portions Copyright 2012-2014 Manuel Gaupp # # # This file contains the primary Directory Server configuration. It must not # be directly edited while the server is online. The server configuration # should only be managed using the administration utilities provided with the # Directory Server. dn: cn=config objectClass: top objectClass: ds-cfg-root-config cn: config ds-cfg-check-schema: true ds-cfg-add-missing-rdn-attributes: true ds-cfg-allow-attribute-name-exceptions: false ds-cfg-invalid-attribute-syntax-behavior: reject ds-cfg-single-structural-objectclass-behavior: reject ds-cfg-notify-abandoned-operations: false ds-cfg-proxied-authorization-identity-mapper: cn=Exact Match,cn=Identity Mappers,cn=config ds-cfg-size-limit: 1000 ds-cfg-time-limit: 60 seconds ds-cfg-lookthrough-limit: 5000 ds-cfg-writability-mode: enabled ds-cfg-bind-with-dn-requires-password: true ds-cfg-reject-unauthenticated-requests: false ds-cfg-default-password-policy: cn=Default Password Policy,cn=Password Policies,cn=config ds-cfg-return-bind-error-messages: false ds-cfg-idle-time-limit: 0 seconds ds-cfg-save-config-on-successful-startup: true ds-cfg-etime-resolution: milliseconds ds-cfg-entry-cache-preload: false ds-cfg-max-allowed-client-connections: 0 ds-cfg-max-psearches: -1 ds-cfg-allowed-task: org.opends.server.tasks.AddSchemaFileTask ds-cfg-allowed-task: org.opends.server.tasks.BackupTask ds-cfg-allowed-task: org.opends.server.tasks.DisconnectClientTask ds-cfg-allowed-task: org.opends.server.tasks.EnterLockdownModeTask ds-cfg-allowed-task: org.opends.server.tasks.ExportTask ds-cfg-allowed-task: org.opends.server.tasks.ImportTask ds-cfg-allowed-task: org.opends.server.tasks.InitializeTargetTask ds-cfg-allowed-task: org.opends.server.tasks.InitializeTask ds-cfg-allowed-task: org.opends.server.tasks.SetGenerationIdTask ds-cfg-allowed-task: org.opends.server.tasks.LeaveLockdownModeTask ds-cfg-allowed-task: org.opends.server.tasks.RebuildTask ds-cfg-allowed-task: org.opends.server.tasks.RestoreTask ds-cfg-allowed-task: org.opends.server.tasks.ShutdownTask ds-cfg-allowed-task: org.opends.server.tasks.PurgeConflictsHistoricalTask dn: cn=Schema Providers,cn=config objectClass: top objectClass: ds-cfg-branch cn: Schema Providers dn: cn=Core Schema,cn=Schema Providers,cn=config objectClass: top objectClass: ds-cfg-schema-provider objectClass: ds-cfg-core-schema ds-cfg-java-class: org.opends.server.schema.CoreSchemaProvider ds-cfg-enabled: true ds-cfg-disabled-matching-rule: NONE ds-cfg-disabled-syntax: NONE ds-cfg-strip-syntax-min-upper-bound-attribute-type-description: false ds-cfg-strict-format-country-string: false opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/ConfigurationMock.java
New file @@ -0,0 +1,246 @@ /* * 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 2013-2014 ForgeRock AS. */ package org.opends.server; import static org.mockito.Mockito.mock; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.Collection; import java.util.SortedSet; import java.util.TreeSet; import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider; import org.forgerock.opendj.config.AliasDefaultBehaviorProvider; import org.forgerock.opendj.config.Configuration; import org.forgerock.opendj.config.DefaultBehaviorProvider; import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor; import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider; import org.forgerock.opendj.config.ManagedObjectDefinition; import org.forgerock.opendj.config.PropertyDefinition; import org.forgerock.opendj.config.PropertyOption; import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider; import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider; import org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues; import org.mockito.invocation.InvocationOnMock; /** * Provides Mockito mocks for Configuration objects with default values * corresponding to those defined in xml configuration files. * <p> * These mocks can be used like any other mocks, e.g, you can define stubs using * {@code when} method or verify calls using {@code verify} method. * <p> * Example: * * <pre> * LDAPConnectionHandlerCfg mockCfg = mockCfg(LDAPConnectionHandlerCfg.class); * assertThat(mockCfg.getMaxRequestSize()).isEqualTo(5 * 1000 * 1000); * </pre> */ public final class ConfigurationMock { private static final ConfigAnswer CONFIG_ANSWER = new ConfigAnswer(); /** * Returns a mock for the provided configuration class. * <p> * If a setting has a default value, the mock automatically returns the * default value when the getter is called on the setting. * <p> * It is possible to override this default behavior with the usual methods * calls with Mockito (e.g, {@code when} method). * * @param <T> * The type of configuration. * @param configClass * The configuration class. * @return a mock */ public static <T extends Configuration> T mockCfg(Class<T> configClass) { return mock(configClass, CONFIG_ANSWER); } /** * A stubbed answer for Configuration objects, allowing to return default * value for settings when available. */ private static class ConfigAnswer extends ReturnsEmptyValues { private static final long serialVersionUID = 1L; /** {@inheritDoc} */ @Override public Object answer(InvocationOnMock invocation) { try { String definitionClassName = toDefinitionClassName(invocation.getMethod().getDeclaringClass().getName()); Class<?> definitionClass = Class.forName(definitionClassName); ManagedObjectDefinition<?, ?> definition = (ManagedObjectDefinition<?, ?>) definitionClass.getMethod("getInstance").invoke(null); String invokedMethodName = invocation.getMethod().getName(); if (!isGetterMethod(invokedMethodName)) { return answerFromDefaultMockitoBehavior(invocation); } Method getPropertyDefMethod = getPropertyDefinitionMethod(definitionClass, invokedMethodName); Class<?> propertyReturnType = getPropertyReturnType(getPropertyDefMethod); Object defaultValue = getDefaultValue(definition, getPropertyDefMethod, propertyReturnType); if (defaultValue == null) { return answerFromDefaultMockitoBehavior(invocation); } return defaultValue; } catch (Exception e) { return answerFromDefaultMockitoBehavior(invocation); } } private Object answerFromDefaultMockitoBehavior(InvocationOnMock invocation) { return super.answer(invocation); } private boolean isGetterMethod(String invokedMethodName) { return invokedMethodName.startsWith("get") || invokedMethodName.startsWith("is"); } private Method getPropertyDefinitionMethod(Class<?> definitionClass, String invokedMethodName) throws SecurityException, NoSuchMethodException { // Methods for boolean starts with "is" in Cfg class but with "get" in CfgDefn class. return definitionClass.getMethod(invokedMethodName.replaceAll("^is", "get") + "PropertyDefinition"); } /** * Returns the type of values returned by the property. */ private Class<?> getPropertyReturnType(Method getPropertyDefMethod) { Class<?> returnClass = getPropertyDefMethod.getReturnType(); return ((ParameterizedType) returnClass.getGenericSuperclass()) .getActualTypeArguments()[0].getClass(); } /** * Retrieve class name of definition from class name of configuration. * <p> * Convert class name "[package].server.FooCfg" to "[package].meta.FooCfgDef" */ private String toDefinitionClassName(String configClassName) { int finalDot = configClassName.lastIndexOf('.'); return configClassName.substring(0, finalDot - 6) + "meta." + configClassName.substring(finalDot + 1) + "Defn"; } /** * Returns the default value corresponding to the provided property * definition getter method from the provided managed object definition. * * @param <T> * The data type of values provided by the property * definition. * @param definition * The definition of the managed object. * @param getPropertyDefMethod * The method to retrieve the property definition from the * definition. * @param propertyReturnClass * The class of values provided by the property definition. * @return the default value of property definition, or * {@code null} if there is no default value. * @throws Exception * If an error occurs. */ @SuppressWarnings("unchecked") private <T> Object getDefaultValue(ManagedObjectDefinition<?, ?> definition, Method getPropertyDefMethod, Class<T> propertyReturnClass) throws Exception { PropertyDefinition<T> propertyDefinition = (PropertyDefinition<T>) getPropertyDefMethod.invoke(definition); DefaultBehaviorProvider<T> defaultBehaviorProvider = (DefaultBehaviorProvider<T>) propertyDefinition .getClass().getMethod("getDefaultBehaviorProvider").invoke(propertyDefinition); MockProviderVisitor<T> visitor = new MockProviderVisitor<T>(propertyDefinition); Collection<T> values = defaultBehaviorProvider.accept(visitor, null); if (values == null) { // No default behavior defined return null; } else if (propertyDefinition.hasOption(PropertyOption.MULTI_VALUED)) { return values; } else { // Single value returned return values.iterator().next(); } } } /** Visitor used to retrieve the default value. */ private static class MockProviderVisitor<T> implements DefaultBehaviorProviderVisitor<T, Collection<T>, Void> { /** The property definition used to decode the values. */ private PropertyDefinition<T> propertyDef; MockProviderVisitor(PropertyDefinition<T> propertyDef) { this.propertyDef = propertyDef; } /** {@inheritDoc} */ @Override public Collection<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> provider, Void p) { // not handled return null; } /** {@inheritDoc} */ @Override public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> provider, Void p) { // not handled return null; } /** * Returns the default value for the property as a collection. */ @Override public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> provider, Void p) { SortedSet<T> values = new TreeSet<T>(); for (String stringValue : provider.getDefaultValues()) { values.add(propertyDef.decodeValue(stringValue)); } return values; } /** {@inheritDoc} */ @Override public Collection<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d, Void p) { // not handled return null; } /** {@inheritDoc} */ @Override public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d, Void p) { // not handled return null; } } } opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/ServerContextBuilder.java
New file @@ -0,0 +1,103 @@ /* * 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 ForgeRock AS. */ package org.opends.server; import static org.mockito.Mockito.*; import java.io.File; import org.forgerock.opendj.config.server.ServerManagementContext; import org.opends.server.core.ConfigurationBootstrapper; import org.opends.server.core.ServerContext; import org.opends.server.schema.SchemaUpdater; import org.opends.server.types.DirectoryEnvironmentConfig; import org.opends.server.types.InitializationException; /** * A server context builder to be used in tests to minimize arrange phase. */ @SuppressWarnings("javadoc") public class ServerContextBuilder { private final ServerContext serverContext; private final DirectoryEnvironmentConfig env; public static ServerContextBuilder aServerContext() { return new ServerContextBuilder(); } public ServerContextBuilder() { serverContext = mock(ServerContext.class); env = new DirectoryEnvironmentConfig(false); when(serverContext.getEnvironment()).thenReturn(env); } public ServerContext build() { return serverContext; } public ServerContextBuilder setSchemaDirectory(File path) throws InitializationException { env.setSchemaDirectory(path); return this; } public ServerContextBuilder setConfigFile(File path) throws InitializationException { env.setConfigFile(path); return this; } public ServerContextBuilder setSchemaUpdater(SchemaUpdater updater) { when(serverContext.getSchemaUpdater()).thenReturn(updater); return this; } /** * Ensure that configuration is fully bootstrapped. Only use when necessary as * it will impact test performance. */ public ServerContextBuilder withConfigurationBootstrapped() throws InitializationException { if (serverContext.getSchemaUpdater() == null) { throw new RuntimeException("You must set a non-null schema updater to bootstrap configuration."); } final ServerManagementContext serverManagementContext = ConfigurationBootstrapper.bootstrap(serverContext); when(serverContext.getServerManagementContext()).thenReturn( serverManagementContext); return this; } } opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/ConfigurationHandlerTestCase.java
New file @@ -0,0 +1,395 @@ /* * 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 ForgeRock AS. */ package org.opends.server.core; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import static org.opends.server.ServerContextBuilder.*; import static org.testng.Assert.*; import java.io.File; import java.util.Arrays; import java.util.Set; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.spi.ConfigAddListener; import org.forgerock.opendj.config.server.spi.ConfigChangeListener; import org.forgerock.opendj.config.server.spi.ConfigDeleteListener; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.LinkedHashMapEntry; import org.forgerock.opendj.ldap.ResultCode; import org.opends.server.TestCaseUtils; import org.opends.server.config.ConfigConstants; import org.opends.server.types.DirectoryException; import org.opends.server.types.InitializationException; import org.testng.annotations.Test; @SuppressWarnings("javadoc") public class ConfigurationHandlerTestCase extends CoreTestCase { private static final DN DN_CONFIG = DN.valueOf(ConfigConstants.DN_CONFIG_ROOT); private static final DN DN_SCHEMA_PROVIDERS = DN.valueOf("cn=Schema Providers,cn=config"); private static final DN DN_CORE_SCHEMA = DN.valueOf("cn=Core Schema,cn=Schema Providers,cn=config"); /** Returns the configuration handler fully initialized from configuration file. */ private ConfigurationHandler getConfigurationHandler() throws InitializationException { final ServerContext context = aServerContext(). setSchemaDirectory(new File(TestCaseUtils.getBuildRoot(), "resource/schema")). setConfigFile(TestCaseUtils.getTestResource("config-small.ldif")). build(); final ConfigurationHandler configHandler = new ConfigurationHandler(context); configHandler.initialize(); return configHandler; } @Test public void testInitializeConfiguration() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); assertTrue(configHandler.hasEntry(DN_CONFIG)); } @Test public void testGetEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); Entry entry = configHandler.getEntry(DN_SCHEMA_PROVIDERS); assertTrue(entry.containsAttribute("objectclass", "top", "ds-cfg-branch")); } @Test public void testGetChildren() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); Set<DN> dns = configHandler.getChildren(DN_SCHEMA_PROVIDERS); assertTrue(dns.contains(DN_CORE_SCHEMA)); } @Test public void testNumSubordinates() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); long numSubordinates = configHandler.numSubordinates(DN_SCHEMA_PROVIDERS, false); assertEquals(numSubordinates, 1); numSubordinates = configHandler.numSubordinates(DN_CONFIG, true); assertEquals(numSubordinates, 2); } @Test public void testRegisterChangeListener() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigChangeListener listener1 = mock(ConfigChangeListener.class); ConfigChangeListener listener2 = mock(ConfigChangeListener.class); configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener1); configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener2); assertEquals(configHandler.getChangeListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2)); } @Test public void testRegisterDeregisterChangeListener() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigChangeListener listener1 = mock(ConfigChangeListener.class); ConfigChangeListener listener2 = mock(ConfigChangeListener.class); configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener1); configHandler.registerChangeListener(DN_SCHEMA_PROVIDERS, listener2); configHandler.deregisterChangeListener(DN_SCHEMA_PROVIDERS, listener1); assertEquals(configHandler.getChangeListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener2)); } @Test public void testRegisterAddListener() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigAddListener listener1 = mock(ConfigAddListener.class); ConfigAddListener listener2 = mock(ConfigAddListener.class); configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener1); configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener2); assertEquals(configHandler.getAddListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2)); } @Test public void testRegisterDeleteListener() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigDeleteListener listener1 = mock(ConfigDeleteListener.class); ConfigDeleteListener listener2 = mock(ConfigDeleteListener.class); configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener1); configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener2); assertEquals(configHandler.getDeleteListeners(DN_SCHEMA_PROVIDERS), Arrays.asList(listener1 ,listener2)); } @Test public void testAddEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); String dn = "cn=New schema provider,cn=Schema Providers,cn=config"; configHandler.addEntry(new LinkedHashMapEntry(dn)); assertTrue(configHandler.hasEntry(DN.valueOf(dn))); } @Test(expectedExceptions=DirectoryException.class) public void testAddEntryExistingEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.addEntry(new LinkedHashMapEntry(DN_CORE_SCHEMA)); } // TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. @Test(enabled=false, expectedExceptions=DirectoryException.class) public void testAddEntryParentUnknown() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.addEntry(new LinkedHashMapEntry("cn=Core Schema,cn=Schema Providers,cn=Providers,cn=config")); } @Test(expectedExceptions=DirectoryException.class) public void testAddEntryNoParent() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.addEntry(new LinkedHashMapEntry(DN.rootDN())); } @Test public void testAddListenerWithAddEntry() throws Exception { String dn = "cn=New schema provider,cn=Schema Providers,cn=config"; ConfigurationHandler configHandler = getConfigurationHandler(); ConfigAddListener listener = mock(ConfigAddListener.class); configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener); when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationAdd(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.SUCCESS, false)); configHandler.addEntry(new LinkedHashMapEntry(dn)); // ensure apply is called for listener verify(listener).applyConfigurationAdd(configHandler.getEntry(DN.valueOf(dn))); } @Test(expectedExceptions=DirectoryException.class) public void testAddListenerWithAddEntryWhenConfigNotAcceptable() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigAddListener listener = mock(ConfigAddListener.class); configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener); when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(false); configHandler.addEntry(new LinkedHashMapEntry("cn=New schema provider,cn=Schema Providers,cn=config")); } @Test(expectedExceptions=DirectoryException.class) public void testAddListenerWithAddEntryWhenFailureApplyingConfig() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigAddListener listener = mock(ConfigAddListener.class); configHandler.registerAddListener(DN_SCHEMA_PROVIDERS, listener); when(listener.configAddIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationAdd(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.OTHER, false)); configHandler.addEntry(new LinkedHashMapEntry("cn=New schema provider,cn=Schema Providers,cn=config")); } @Test public void testDeleteEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.deleteEntry(DN_CORE_SCHEMA); assertFalse(configHandler.hasEntry(DN_CORE_SCHEMA)); } // TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. @Test(enabled=false, expectedExceptions=DirectoryException.class) public void testDeleteEntryUnexistingEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.deleteEntry(DN.valueOf("cn=Unexisting provider,cn=Schema Providers,cn=config")); } @Test(enabled=false, expectedExceptions=DirectoryException.class) public void testDeleteEntryWithChildren() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.deleteEntry(DN_SCHEMA_PROVIDERS); } // TODO : disabled because fail when converting to server DN. Re-enable once migrated to SDK DN. @Test(enabled=false, expectedExceptions=DirectoryException.class) public void testDeleteEntryUnknownParent() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); configHandler.deleteEntry(DN.valueOf("cn=Core Schema,cn=Schema Providers,cn=Providers,cn=config")); } @Test public void testDeleteListenerWithDeleteEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigDeleteListener listener = mock(ConfigDeleteListener.class); configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener); Entry entryToDelete = configHandler.getEntry(DN_CORE_SCHEMA); when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationDelete(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.SUCCESS, false)); configHandler.deleteEntry(DN_CORE_SCHEMA); // ensure apply is called for listener verify(listener).applyConfigurationDelete(entryToDelete); } @Test(expectedExceptions=DirectoryException.class) public void testDeleteListenerWithDeleteEntryWhenConfigNotAcceptable() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigDeleteListener listener = mock(ConfigDeleteListener.class); configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener); when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(false); configHandler.deleteEntry(DN_CORE_SCHEMA); } @Test(expectedExceptions=DirectoryException.class) public void testDeleteListenerWithDeleteEntryWhenFailureApplyingConfig() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigDeleteListener listener = mock(ConfigDeleteListener.class); configHandler.registerDeleteListener(DN_SCHEMA_PROVIDERS, listener); when(listener.configDeleteIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationDelete(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.OTHER, false)); configHandler.deleteEntry(DN_CORE_SCHEMA); } @Test public void testReplaceEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); String dn = DN_CORE_SCHEMA.toString(); configHandler.replaceEntry( new LinkedHashMapEntry("dn: " + dn, "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: true"), new LinkedHashMapEntry("dn: " + dn, "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: false")); assertTrue(configHandler.hasEntry(DN_CORE_SCHEMA)); assertEquals(configHandler.getEntry(DN_CORE_SCHEMA).getAttribute("ds-cfg-enabled").firstValueAsString(), "false"); } @Test public void testChangeListenerWithReplaceEntry() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigChangeListener listener = mock(ConfigChangeListener.class); configHandler.registerChangeListener(DN_CORE_SCHEMA, listener); when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationChange(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.SUCCESS, false)); Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA); configHandler.replaceEntry(oldEntry, new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA.toString(), "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: false")); // ensure apply is called for listener verify(listener).applyConfigurationChange(configHandler.getEntry(DN_CORE_SCHEMA)); } @Test(expectedExceptions=DirectoryException.class) public void testChangeListenerWithReplaceEntryWhenConfigNotAcceptable() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigChangeListener listener = mock(ConfigChangeListener.class); configHandler.registerChangeListener(DN_CORE_SCHEMA, listener); when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(false); Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA); configHandler.replaceEntry(oldEntry, new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA.toString(), "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: false")); } @Test(expectedExceptions=DirectoryException.class) public void testChangeListenerWithReplaceEntryWhenFailureApplyingConfig() throws Exception { ConfigurationHandler configHandler = getConfigurationHandler(); ConfigChangeListener listener = mock(ConfigChangeListener.class); configHandler.registerChangeListener(DN_CORE_SCHEMA, listener); when(listener.configChangeIsAcceptable(any(Entry.class), any(LocalizableMessageBuilder.class))). thenReturn(true); when(listener.applyConfigurationChange(any(Entry.class))). thenReturn(new ConfigChangeResult(ResultCode.OTHER, false)); Entry oldEntry = configHandler.getEntry(DN_CORE_SCHEMA); configHandler.replaceEntry(oldEntry, new LinkedHashMapEntry("dn: " + DN_CORE_SCHEMA.toString(), "objectclass: ds-cfg-schema-provider", "ds-cfg-enabled: false")); } } opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/core/SchemaHandlerTestCase.java
New file @@ -0,0 +1,96 @@ /* * 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 ForgeRock AS. */ package org.opends.server.core; import static org.assertj.core.api.Assertions.*; import static org.opends.server.ServerContextBuilder.*; import java.io.File; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.opends.server.TestCaseUtils; import org.opends.server.schema.SchemaUpdater; import org.opends.server.types.InitializationException; import org.testng.annotations.Test; @SuppressWarnings("javadoc") public class SchemaHandlerTestCase extends CoreTestCase { private static final String DIRECTORY_STRING_SYNTAX_OID = "1.3.6.1.4.1.1466.115.121.1.15"; @Test public void testSchemaInitialization() throws Exception { MockSchemaUpdater schemaUpdater = new MockSchemaUpdater(); initializeSchemaHandler(schemaUpdater); assertThat(schemaUpdater.getSchemaBuilder()).isNotNull(); Schema schema = schemaUpdater.getSchemaBuilder().toSchema(); assertThat(schema.getMatchingRules()).isNotEmpty(); // some matching rules defined schema.getSyntax(DIRECTORY_STRING_SYNTAX_OID); schema.getAttributeType("javaClassName"); // from file 03-rfc2713 schema.getAttributeType("nisNetIdUser"); // from file 5-solaris.ldif schema.getObjectClass("changeLogEntry"); // from file 03-changelog.ldif } private void initializeSchemaHandler(SchemaUpdater updater) throws InitializationException, ConfigException { final ServerContext serverContext = aServerContext(). setSchemaDirectory(new File(TestCaseUtils.getBuildRoot(), "resource/schema")). setConfigFile(TestCaseUtils.getTestResource("config-small.ldif")). setSchemaUpdater(updater). withConfigurationBootstrapped(). build(); SchemaHandler schemaHandler = new SchemaHandler(); schemaHandler.initialize(serverContext); } private static final class MockSchemaUpdater implements SchemaUpdater { private SchemaBuilder schemaBuilder; @Override public boolean updateSchema(SchemaBuilder schemaBuilder) { this.schemaBuilder = schemaBuilder; return true; } @Override public SchemaBuilder getSchemaBuilder() { return schemaBuilder; } } } opendj-sdk/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CoreSchemaProviderTestCase.java
New file @@ -0,0 +1,94 @@ /* * 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 ForgeRock AS. */ package org.opends.server.schema; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import static org.opends.server.TestCaseUtils.*; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.forgerock.opendj.server.config.server.CoreSchemaCfg; import org.opends.server.ConfigurationMock; import org.opends.server.core.CoreTestCase; import org.testng.annotations.Test; @SuppressWarnings("javadoc") public class CoreSchemaProviderTestCase extends CoreTestCase { private static final String DIRECTORY_STRING_SYNTAX_OID = "1.3.6.1.4.1.1466.115.121.1.15"; private static final String GENERALIZED_TIME_MATCHING_RULE_OID = "2.5.13.27"; @Test public void testInitializeWithoutError() throws Exception { CoreSchemaCfg coreSchemaCfg = ConfigurationMock.mockCfg(CoreSchemaCfg.class); CoreSchemaProvider provider = new CoreSchemaProvider(); provider.initialize(coreSchemaCfg, new SchemaBuilder(), mock(SchemaUpdater.class)); verify(coreSchemaCfg).addCoreSchemaChangeListener(provider); } @Test public void testEnableZeroLengthDirectoryStrings() throws Exception { CoreSchemaCfg coreSchemaCfg = ConfigurationMock.mockCfg(CoreSchemaCfg.class); when(coreSchemaCfg.isAllowZeroLengthValuesDirectoryString()).thenReturn(true); SchemaBuilder schemaBuilder = new SchemaBuilder(); CoreSchemaProvider provider = new CoreSchemaProvider(); provider.initialize(coreSchemaCfg, schemaBuilder, mock(SchemaUpdater.class)); assertThat(schemaBuilder.toSchema().allowZeroLengthDirectoryStrings()).isTrue(); } @Test public void testDisableSyntax() throws Exception { CoreSchemaCfg coreSchemaCfg = ConfigurationMock.mockCfg(CoreSchemaCfg.class); when(coreSchemaCfg.getDisabledSyntax()).thenReturn(newSortedSet(DIRECTORY_STRING_SYNTAX_OID)); SchemaBuilder schemaBuilder = new SchemaBuilder(Schema.getCoreSchema()); CoreSchemaProvider provider = new CoreSchemaProvider(); provider.initialize(coreSchemaCfg, schemaBuilder, mock(SchemaUpdater.class)); assertThat(schemaBuilder.toSchema().hasSyntax(DIRECTORY_STRING_SYNTAX_OID)).isFalse(); } @Test public void testDisableMatchingRule() throws Exception { CoreSchemaCfg coreSchemaCfg = ConfigurationMock.mockCfg(CoreSchemaCfg.class); when(coreSchemaCfg.getDisabledMatchingRule()).thenReturn(newSortedSet(GENERALIZED_TIME_MATCHING_RULE_OID)); SchemaBuilder schemaBuilder = new SchemaBuilder(Schema.getCoreSchema()); CoreSchemaProvider provider = new CoreSchemaProvider(); provider.initialize(coreSchemaCfg, schemaBuilder, mock(SchemaUpdater.class)); assertThat(schemaBuilder.toSchema().hasMatchingRule(DIRECTORY_STRING_SYNTAX_OID)).isFalse(); } }