FractionalLDIFImportPlugin.java revision ea1068c292e9b341af6d6b563cd8988a96be20a9
/*
* 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 2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.replication.plugin;
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.util.Utils;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.*;
import org.opends.server.api.plugin.DirectoryServerPlugin;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.api.plugin.PluginType;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.plugin.LDAPReplicationDomain.*;
import org.opends.server.types.*;
import org.forgerock.opendj.ldap.ByteString;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.replication.plugin.LDAPReplicationDomain.*;
/**
* This class implements a Directory Server plugin that is used in fractional
* replication to initialize a just configured fractional domain (when an online
* full update occurs or offline/online ldif import).
* The following tasks are done:
* - check that the fractional configuration (if any) stored in the (incoming)
* root entry of the domain is compliant with the fractional configuration of
* the domain (if not make online update stop)
* - perform filtering according to fractional configuration of the domain
* - flush the fractional configuration of the domain in the root entry
* (if no one already present)
*/
public final class FractionalLDIFImportPlugin
extends DirectoryServerPlugin<FractionalLDIFImportPluginCfg>
implements ConfigurationChangeListener<FractionalLDIFImportPluginCfg>
{
/**
* Holds the fractional configuration and if available the replication domain
* matching this import session (they form the import fractional context).
* Domain is available if the server is online (import-ldif, online full
* update..) otherwise, this is an import-ldif with server off. The key is the
* ImportConfig object of the session which acts as a cookie for the whole
* session. This allows to potentially run man imports at the same time.
*/
private final Map<LDIFImportConfig, ImportFractionalContext>
importSessionContexts = new Hashtable<LDIFImportConfig,
ImportFractionalContext>();
/**
* Holds an import session fractional context.
*/
private static class ImportFractionalContext
{
/**
* Fractional configuration of the local domain (may be null if import on a
* not replicated domain).
*/
private FractionalConfig fractionalConfig;
/** The local domain object (may stay null if server is offline). */
private LDAPReplicationDomain domain;
/**
* Constructor.
* @param fractionalConfig The fractional configuration.
* @param domain The replication domain.
*/
public ImportFractionalContext(FractionalConfig fractionalConfig,
LDAPReplicationDomain domain)
{
this.fractionalConfig = fractionalConfig;
this.domain = domain;
}
/**
* Getter for the fractional configuration.
* @return the fractionalConfig
*/
public FractionalConfig getFractionalConfig()
{
return fractionalConfig;
}
/**
* Getter for the domain..
* @return the domain
*/
public LDAPReplicationDomain getDomain()
{
return domain;
}
}
/**
* Creates a new instance of this Directory Server plugin. Every plugin must
* implement a default constructor (it is the only one that will be used to
* create plugins defined in the configuration), and every plugin constructor
* must call {@code super()} as its first element.
*/
public FractionalLDIFImportPlugin()
{
super();
}
/** {@inheritDoc} */
@Override
public final void initializePlugin(Set<PluginType> pluginTypes,
FractionalLDIFImportPluginCfg configuration)
throws ConfigException
{
// Make sure that the plugin has been enabled for the appropriate types.
for (PluginType t : pluginTypes)
{
switch (t)
{
case LDIF_IMPORT:
case LDIF_IMPORT_END:
// This is acceptable.
break;
default:
throw new ConfigException(ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE.get(t));
}
}
}
/** {@inheritDoc} */
@Override
public final void finalizePlugin()
{
// Nothing to do
}
/**
* Attempts to retrieve the fractional configuration of the domain being
* imported.
* @param entry An imported entry of the imported domain
* @return The parsed fractional configuration for the domain matching the
* passed entry. Null if no configuration is found for the domain
* (not a replicated domain).
*/
private static FractionalConfig getStaticReplicationDomainFractionalConfig(
Entry entry) throws Exception {
// Retrieve the configuration
ServerManagementContext context = ServerManagementContext.getInstance();
RootCfg root = context.getRootConfiguration();
ReplicationSynchronizationProviderCfg sync =
(ReplicationSynchronizationProviderCfg)
root.getSynchronizationProvider("Multimaster Synchronization");
String[] domainNames = sync.listReplicationDomains();
if (domainNames == null)
{
// No domain in replication
return null;
}
// Find the configuration for domain the entry is part of
ReplicationDomainCfg matchingReplicatedDomainCfg = null;
for (String domainName : domainNames)
{
ReplicationDomainCfg replicationDomainCfg =
sync.getReplicationDomain(domainName);
// Is the entry a sub entry of the replicated domain main entry ?
DN replicatedDn = replicationDomainCfg.getBaseDN();
DN entryDn = entry.getName();
if (entryDn.isDescendantOf(replicatedDn))
{
// Found the matching replicated domain configuration object
matchingReplicatedDomainCfg = replicationDomainCfg;
break;
}
}
if (matchingReplicatedDomainCfg == null)
{
// No matching replicated domain found
return null;
}
// Extract the fractional configuration from the domain configuration object
// and return it.
return FractionalConfig.toFractionalConfig(matchingReplicatedDomainCfg);
}
/** {@inheritDoc} */
@Override
public final void doLDIFImportEnd(LDIFImportConfig importConfig)
{
// Remove the cookie of this import session
synchronized(importSessionContexts)
{
importSessionContexts.remove(importConfig);
}
}
/**
* See class comment for what we achieve here...
* {@inheritDoc}
*/
@Override
public final PluginResult.ImportLDIF doLDIFImport(
LDIFImportConfig importConfig, Entry entry)
{
/**
* try to get the import fractional context for this entry. If not found,
* create and initialize it. The mechanism here is done to take a lock only
* once for the whole import session (except the necessary lock of the
* doLDIFImportEnd method)
*/
ImportFractionalContext importFractionalContext =
importSessionContexts.get(importConfig);
DN entryDn = entry.getName();
FractionalConfig localFractionalConfig = null;
// If no context, create it
if (importFractionalContext == null)
{
synchronized(importSessionContexts)
{
// Insure another thread was not creating the context at the same time
// (we would create it for the second time which is useless)
importFractionalContext = importSessionContexts.get(importConfig);
if (importFractionalContext == null)
{
/*
* Create context
*/
/**
* Retrieve the replicated domain this entry belongs to. Try to
* retrieve replication domain instance first. If we are in an online
* server, we should get it (if we are treating an entry that belongs
* to a replicated domain), otherwise the domain is not replicated or
* we are in an offline server context (import-ldif command run with
* offline server) and we must retrieve the fractional configuration
* directly from the configuration management system.
*/
LDAPReplicationDomain domain =
MultimasterReplication.findDomain(entryDn, null);
// Get the fractional configuration extracted from the local server
// configuration for the currently imported domain
if (domain == null)
{
// Server may be offline, attempt to find fractional configuration
// from config sub-system
try
{
localFractionalConfig =
getStaticReplicationDomainFractionalConfig(entry);
} catch (Exception ex)
{
return PluginResult.ImportLDIF.stopEntryProcessing(
ERR_FRACTIONAL_COULD_NOT_RETRIEVE_CONFIG.get(entry));
}
} else
{
// Found a live domain, retrieve the fractional configuration from
// it.
localFractionalConfig = domain.getFractionalConfig();
}
// Create context and store it
importFractionalContext =
new ImportFractionalContext(localFractionalConfig, domain);
importSessionContexts.put(importConfig, importFractionalContext);
}
}
}
// Extract the fractional configuration from the context
localFractionalConfig = importFractionalContext.getFractionalConfig();
if (localFractionalConfig == null)
{
// Not part of a replicated domain : nothing to do
return PluginResult.ImportLDIF.continueEntryProcessing();
}
/**
* At this point, either the domain instance has been found and we use its
* fractional configuration, or the server is offline and we use the parsed
* fractional configuration. We differentiate both cases testing if domain
* is null. We are also for sure handling an entry of a replicated suffix.
*/
// Is the entry to handle the root entry of the domain ? If yes, analyze the
// fractional configuration in it and compare with local fractional
// configuration. Stop the import if some inconsistency is detected
DN replicatedDomainBaseDn = localFractionalConfig.getBaseDn();
if (replicatedDomainBaseDn.equals(entryDn))
{
// This is the root entry, try to read a fractional configuration from it
Attribute exclAttr = getAttribute(REPLICATION_FRACTIONAL_EXCLUDE, entry);
Iterator<String> exclIt = null;
if (exclAttr != null)
{
exclIt = new AttributeValueStringIterator(exclAttr.iterator());
}
Attribute inclAttr = getAttribute(REPLICATION_FRACTIONAL_INCLUDE, entry);
Iterator<String> inclIt = null;
if (inclAttr != null)
{
inclIt = new AttributeValueStringIterator(inclAttr.iterator());
}
// Compare backend and local fractional configuration
if (isFractionalConfigConsistent(localFractionalConfig, exclIt, inclIt))
{
// local and remote non/fractional config are equivalent :
// follow import, no need to go with filtering as remote backend
// should be ok
// let import finish
return PluginResult.ImportLDIF.continueEntryProcessing();
}
if (localFractionalConfig.isFractional())
{
// Local domain is fractional, remote domain has not same config
boolean remoteDomainHasSomeConfig =
isNotEmpty(exclAttr) || isNotEmpty(inclAttr);
if (remoteDomainHasSomeConfig)
{
LDAPReplicationDomain domain = importFractionalContext.getDomain();
if (domain != null)
{
// Local domain is fractional, remote domain has some config which
// is different : stop import (error will be logged when import is
// stopped)
domain.setImportErrorMessageId(IMPORT_ERROR_MESSAGE_BAD_REMOTE);
return PluginResult.ImportLDIF.stopEntryProcessing(null);
}
return PluginResult.ImportLDIF.stopEntryProcessing(
NOTE_ERR_LDIF_IMPORT_FRACTIONAL_BAD_DATA_SET.get(replicatedDomainBaseDn));
}
// Local domain is fractional but remote domain has no config :
// flush local config into root entry and follow import with filtering
flushFractionalConfigIntoEntry(localFractionalConfig, entry);
}
else
{
// Local domain is not fractional
LDAPReplicationDomain domain = importFractionalContext.getDomain();
if (domain != null)
{
// Local domain is not fractional but remote one is : stop import :
//local domain should be configured with the same config as remote one
domain.setImportErrorMessageId(
IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL);
return PluginResult.ImportLDIF.stopEntryProcessing(null);
}
return PluginResult.ImportLDIF.stopEntryProcessing(
NOTE_ERR_LDIF_IMPORT_FRACTIONAL_DATA_SET_IS_FRACTIONAL.get(replicatedDomainBaseDn));
}
}
// If we get here, local domain fractional configuration is enabled.
// Now filter for potential attributes to be removed.
LDAPReplicationDomain.fractionalRemoveAttributesFromEntry(
localFractionalConfig, entry.getName().rdn(),
entry.getObjectClasses(), entry.getUserAttributes(), true);
return PluginResult.ImportLDIF.continueEntryProcessing();
}
private boolean isNotEmpty(Attribute attr)
{
return attr != null && attr.size() > 0;
}
private Attribute getAttribute(String attributeName, Entry entry)
{
AttributeType attrType = DirectoryServer.getAttributeType(attributeName);
List<Attribute> inclAttrs = entry.getAttribute(attrType);
if (inclAttrs != null)
{
return inclAttrs.get(0);
}
return null;
}
/**
* Write the fractional configuration in the passed domain into the passed
* entry. WARNING: assumption is that no fractional attributes at all is
* already present in the passed entry. Also assumption is that domain
* fractional configuration is on.
*
* @param localFractionalConfig
* The local domain fractional configuration
* @param entry
* The entry to modify
*/
private static void flushFractionalConfigIntoEntry(FractionalConfig
localFractionalConfig, Entry entry)
{
if (localFractionalConfig.isFractional()) // Paranoia check
{
// Get the fractional configuration of the domain
boolean fractionalExclusive =
localFractionalConfig.isFractionalExclusive();
Map<String, Set<String>> fractionalSpecificClassesAttributes =
localFractionalConfig.getFractionalSpecificClassesAttributes();
Set<String> fractionalAllClassesAttributes =
localFractionalConfig.getFractionalAllClassesAttributes();
// Create attribute builder for the right fractional mode
String fractAttribute = fractionalExclusive ?
REPLICATION_FRACTIONAL_EXCLUDE : REPLICATION_FRACTIONAL_INCLUDE;
AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute);
// Add attribute values for all classes
boolean somethingToFlush =
add(attrBuilder, "*", fractionalAllClassesAttributes);
// Add attribute values for specific classes
if (fractionalSpecificClassesAttributes.size() > 0)
{
for (Map.Entry<String, Set<String>> specific
: fractionalSpecificClassesAttributes.entrySet())
{
if (add(attrBuilder, specific.getKey(), specific.getValue()))
{
somethingToFlush = true;
}
}
}
// Now flush attribute values into entry
if (somethingToFlush)
{
List<ByteString> duplicateValues = new ArrayList<ByteString>();
entry.addAttribute(attrBuilder.toAttribute(), duplicateValues);
}
}
}
private static boolean add(AttributeBuilder attrBuilder, String className,
Set<String> values)
{
if (values.size() > 0)
{
attrBuilder.add(className + ":" + Utils.joinAsString(",", values));
return true;
}
return false;
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationAcceptable(PluginCfg configuration,
List<LocalizableMessage> unacceptableReasons)
{
return true;
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationChangeAcceptable(
FractionalLDIFImportPluginCfg configuration,
List<LocalizableMessage> unacceptableReasons)
{
return true;
}
/** {@inheritDoc} */
@Override
public ConfigChangeResult applyConfigurationChange(
FractionalLDIFImportPluginCfg configuration)
{
return new ConfigChangeResult();
}
}