001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.replication.plugin;
018
019import java.util.ArrayList;
020import java.util.Hashtable;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.opendj.config.server.ConfigChangeResult;
028import org.forgerock.opendj.config.server.ConfigException;
029import org.forgerock.opendj.ldap.ByteString;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.util.Utils;
032import org.opends.server.admin.server.ConfigurationChangeListener;
033import org.opends.server.admin.server.ServerManagementContext;
034import org.opends.server.admin.std.server.FractionalLDIFImportPluginCfg;
035import org.opends.server.admin.std.server.PluginCfg;
036import org.opends.server.admin.std.server.ReplicationDomainCfg;
037import org.opends.server.admin.std.server.ReplicationSynchronizationProviderCfg;
038import org.opends.server.admin.std.server.RootCfg;
039import org.opends.server.api.plugin.DirectoryServerPlugin;
040import org.opends.server.api.plugin.PluginResult;
041import org.opends.server.api.plugin.PluginType;
042import org.opends.server.core.DirectoryServer;
043import org.opends.server.replication.plugin.LDAPReplicationDomain.FractionalConfig;
044import org.opends.server.types.Attribute;
045import org.opends.server.types.AttributeBuilder;
046import org.opends.server.types.Entry;
047import org.opends.server.types.LDIFImportConfig;
048
049import static org.opends.messages.ReplicationMessages.*;
050import static org.opends.server.replication.plugin.LDAPReplicationDomain.*;
051
052/**
053 * This class implements a Directory Server plugin that is used in fractional
054 * replication to initialize a just configured fractional domain (when an online
055 * full update occurs or offline/online ldif import).
056 * The following tasks are done:
057 * - check that the fractional configuration (if any) stored in the (incoming)
058 * root entry of the domain is compliant with the fractional configuration of
059 * the domain (if not make online update stop)
060 * - perform filtering according to fractional configuration of the domain
061 * - flush the fractional configuration of the domain in the root entry
062 *  (if no one already present)
063 */
064public final class FractionalLDIFImportPlugin
065  extends DirectoryServerPlugin<FractionalLDIFImportPluginCfg>
066  implements ConfigurationChangeListener<FractionalLDIFImportPluginCfg>
067{
068  /**
069   * Holds the fractional configuration and if available the replication domain
070   * matching this import session (they form the import fractional context).
071   * Domain is available if the server is online (import-ldif, online full
072   * update..) otherwise, this is an import-ldif with server off. The key is the
073   * ImportConfig object of the session which acts as a cookie for the whole
074   * session. This allows to potentially run man imports at the same time.
075   */
076  private final Map<LDIFImportConfig, ImportFractionalContext>
077    importSessionContexts = new Hashtable<>();
078
079  /**
080   * Holds an import session fractional context.
081   */
082  private static class ImportFractionalContext
083  {
084    /**
085     * Fractional configuration of the local domain (may be null if import on a
086     * not replicated domain).
087     */
088    private FractionalConfig fractionalConfig;
089    /** The local domain object (may stay null if server is offline). */
090    private LDAPReplicationDomain domain;
091
092    /**
093     * Constructor.
094     * @param fractionalConfig The fractional configuration.
095     * @param domain The replication domain.
096     */
097    public ImportFractionalContext(FractionalConfig fractionalConfig,
098      LDAPReplicationDomain domain)
099    {
100      this.fractionalConfig = fractionalConfig;
101      this.domain = domain;
102    }
103
104    /**
105     * Getter for the fractional configuration.
106     * @return the fractionalConfig
107     */
108    public FractionalConfig getFractionalConfig()
109    {
110      return fractionalConfig;
111    }
112
113    /**
114     * Getter for the domain..
115     * @return the domain
116     */
117    public LDAPReplicationDomain getDomain()
118    {
119      return domain;
120    }
121  }
122
123  /**
124   * Creates a new instance of this Directory Server plugin.  Every plugin must
125   * implement a default constructor (it is the only one that will be used to
126   * create plugins defined in the configuration), and every plugin constructor
127   * must call {@code super()} as its first element.
128   */
129  public FractionalLDIFImportPlugin()
130  {
131    super();
132  }
133
134  /** {@inheritDoc} */
135  @Override
136  public final void initializePlugin(Set<PluginType> pluginTypes,
137    FractionalLDIFImportPluginCfg configuration)
138    throws ConfigException
139  {
140    // Make sure that the plugin has been enabled for the appropriate types.
141    for (PluginType t : pluginTypes)
142    {
143      switch (t)
144      {
145        case LDIF_IMPORT:
146        case LDIF_IMPORT_END:
147          // This is acceptable.
148          break;
149
150        default:
151        throw new ConfigException(ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE.get(t));
152      }
153    }
154  }
155
156  /** {@inheritDoc} */
157  @Override
158  public final void finalizePlugin()
159  {
160    // Nothing to do
161  }
162
163  /**
164   * Attempts to retrieve the fractional configuration of the domain being
165   * imported.
166   * @param entry An imported entry of the imported domain
167   * @return The parsed fractional configuration for the domain matching the
168   * passed entry. Null if no configuration is found for the domain
169   * (not a replicated domain).
170   */
171  private static FractionalConfig getStaticReplicationDomainFractionalConfig(
172    Entry entry) throws Exception {
173
174    // Retrieve the configuration
175    ServerManagementContext context = ServerManagementContext.getInstance();
176    RootCfg root = context.getRootConfiguration();
177
178
179    ReplicationSynchronizationProviderCfg sync =
180      (ReplicationSynchronizationProviderCfg)
181      root.getSynchronizationProvider("Multimaster Synchronization");
182
183    String[] domainNames = sync.listReplicationDomains();
184    if (domainNames == null)
185    {
186      // No domain in replication
187      return null;
188    }
189
190    // Find the configuration for domain the entry is part of
191    ReplicationDomainCfg matchingReplicatedDomainCfg = null;
192    for (String domainName : domainNames)
193    {
194      ReplicationDomainCfg replicationDomainCfg =
195          sync.getReplicationDomain(domainName);
196      // Is the entry a sub entry of the replicated domain main entry ?
197      DN replicatedDn = replicationDomainCfg.getBaseDN();
198      DN entryDn = entry.getName();
199      if (entryDn.isSubordinateOrEqualTo(replicatedDn))
200      {
201        // Found the matching replicated domain configuration object
202        matchingReplicatedDomainCfg = replicationDomainCfg;
203        break;
204      }
205    }
206
207    if (matchingReplicatedDomainCfg == null)
208    {
209      // No matching replicated domain found
210      return null;
211    }
212
213    // Extract the fractional configuration from the domain configuration object
214    // and return it.
215    return FractionalConfig.toFractionalConfig(matchingReplicatedDomainCfg);
216  }
217
218  /** {@inheritDoc} */
219  @Override
220  public final void doLDIFImportEnd(LDIFImportConfig importConfig)
221  {
222    // Remove the cookie of this import session
223    synchronized(importSessionContexts)
224    {
225      importSessionContexts.remove(importConfig);
226    }
227  }
228
229  /**
230   * See class comment for what we achieve here...
231   * {@inheritDoc}
232   */
233  @Override
234  public final PluginResult.ImportLDIF doLDIFImport(
235    LDIFImportConfig importConfig, Entry entry)
236  {
237    /**
238     * try to get the import fractional context for this entry. If not found,
239     * create and initialize it. The mechanism here is done to take a lock only
240     * once for the whole import session (except the necessary lock of the
241     * doLDIFImportEnd method)
242     */
243    ImportFractionalContext importFractionalContext =
244      importSessionContexts.get(importConfig);
245
246    DN entryDn = entry.getName();
247    FractionalConfig localFractionalConfig = null;
248
249    // If no context, create it
250    if (importFractionalContext == null)
251    {
252      synchronized(importSessionContexts)
253      {
254        // Insure another thread was not creating the context at the same time
255        // (we would create it for the second time which is useless)
256        importFractionalContext = importSessionContexts.get(importConfig);
257        if (importFractionalContext == null)
258        {
259          /*
260           * Create context
261           */
262
263          /**
264           * Retrieve the replicated domain this entry belongs to. Try to
265           * retrieve replication domain instance first. If we are in an online
266           * server, we should get it (if we are treating an entry that belongs
267           * to a replicated domain), otherwise the domain is not replicated or
268           * we are in an offline server context (import-ldif command run with
269           * offline server) and we must retrieve the fractional configuration
270           * directly from the configuration management system.
271           */
272          LDAPReplicationDomain domain =
273            MultimasterReplication.findDomain(entryDn, null);
274
275          // Get the fractional configuration extracted from the local server
276          // configuration for the currently imported domain
277          if (domain == null)
278          {
279            // Server may be offline, attempt to find fractional configuration
280            // from config sub-system
281            try
282            {
283              localFractionalConfig =
284                getStaticReplicationDomainFractionalConfig(entry);
285            } catch (Exception ex)
286            {
287              return PluginResult.ImportLDIF.stopEntryProcessing(
288                  ERR_FRACTIONAL_COULD_NOT_RETRIEVE_CONFIG.get(entry));
289            }
290          } else
291          {
292            // Found a live domain, retrieve the fractional configuration from
293            // it.
294            localFractionalConfig = domain.getFractionalConfig();
295          }
296          // Create context and store it
297          importFractionalContext =
298            new ImportFractionalContext(localFractionalConfig, domain);
299          importSessionContexts.put(importConfig, importFractionalContext);
300        }
301      }
302    }
303
304    // Extract the fractional configuration from the context
305    localFractionalConfig = importFractionalContext.getFractionalConfig();
306    if (localFractionalConfig == null)
307    {
308      // Not part of a replicated domain : nothing to do
309      return PluginResult.ImportLDIF.continueEntryProcessing();
310    }
311
312    /**
313     * At this point, either the domain instance has been found and we  use its
314     * fractional configuration, or the server is offline and we use the parsed
315     * fractional configuration. We differentiate both cases testing if domain
316     * is null. We are also for sure handling an entry of a replicated suffix.
317     */
318
319    // Is the entry to handle the root entry of the domain ? If yes, analyze the
320    // fractional configuration in it and compare with local fractional
321    // configuration. Stop the import if some inconsistency is detected
322    DN replicatedDomainBaseDn = localFractionalConfig.getBaseDn();
323    if (replicatedDomainBaseDn.equals(entryDn))
324    {
325      // This is the root entry, try to read a fractional configuration from it
326      Attribute exclAttr = getAttribute(REPLICATION_FRACTIONAL_EXCLUDE, entry);
327      Iterator<ByteString> exclIt = null;
328      if (exclAttr != null)
329      {
330        exclIt = exclAttr.iterator();
331      }
332
333      Attribute inclAttr = getAttribute(REPLICATION_FRACTIONAL_INCLUDE, entry);
334      Iterator<ByteString> inclIt = null;
335      if (inclAttr != null)
336      {
337        inclIt = inclAttr.iterator();
338      }
339
340      // Compare backend and local fractional configuration
341      if (isFractionalConfigConsistent(localFractionalConfig, exclIt, inclIt))
342      {
343        // local and remote non/fractional config are equivalent :
344        // follow import, no need to go with filtering as remote backend
345        // should be ok
346        // let import finish
347        return PluginResult.ImportLDIF.continueEntryProcessing();
348      }
349
350      if (localFractionalConfig.isFractional())
351      {
352        // Local domain is fractional, remote domain has not same config
353        boolean remoteDomainHasSomeConfig =
354            isNotEmpty(exclAttr) || isNotEmpty(inclAttr);
355        if (remoteDomainHasSomeConfig)
356        {
357          LDAPReplicationDomain domain = importFractionalContext.getDomain();
358          if (domain != null)
359          {
360            // Local domain is fractional, remote domain has some config which
361            // is different : stop import (error will be logged when import is
362            // stopped)
363            domain.setImportErrorMessageId(IMPORT_ERROR_MESSAGE_BAD_REMOTE);
364            return PluginResult.ImportLDIF.stopEntryProcessing(null);
365          }
366
367          return PluginResult.ImportLDIF.stopEntryProcessing(
368              NOTE_ERR_LDIF_IMPORT_FRACTIONAL_BAD_DATA_SET.get(replicatedDomainBaseDn));
369        }
370
371        // Local domain is fractional but remote domain has no config :
372        // flush local config into root entry and follow import with filtering
373        flushFractionalConfigIntoEntry(localFractionalConfig, entry);
374      }
375      else
376      {
377        // Local domain is not fractional
378        LDAPReplicationDomain domain = importFractionalContext.getDomain();
379        if (domain != null)
380        {
381          // Local domain is not fractional but remote one is : stop import :
382          //local domain should be configured with the same config as remote one
383          domain.setImportErrorMessageId(
384              IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL);
385          return PluginResult.ImportLDIF.stopEntryProcessing(null);
386        }
387
388        return PluginResult.ImportLDIF.stopEntryProcessing(
389            NOTE_ERR_LDIF_IMPORT_FRACTIONAL_DATA_SET_IS_FRACTIONAL.get(replicatedDomainBaseDn));
390      }
391    }
392
393    // If we get here, local domain fractional configuration is enabled.
394    // Now filter for potential attributes to be removed.
395    LDAPReplicationDomain.fractionalRemoveAttributesFromEntry(
396      localFractionalConfig, entry.getName().rdn(),
397      entry.getObjectClasses(), entry.getUserAttributes(), true);
398
399    return PluginResult.ImportLDIF.continueEntryProcessing();
400  }
401
402  private boolean isNotEmpty(Attribute attr)
403  {
404    return attr != null && attr.size() > 0;
405  }
406
407  private Attribute getAttribute(String attributeName, Entry entry)
408  {
409    List<Attribute> attrs = entry.getAttribute(DirectoryServer.getAttributeType(attributeName));
410    return !attrs.isEmpty() ? attrs.get(0) : null;
411  }
412
413  /**
414   * Write the fractional configuration in the passed domain into the passed
415   * entry. WARNING: assumption is that no fractional attributes at all is
416   * already present in the passed entry. Also assumption is that domain
417   * fractional configuration is on.
418   *
419   * @param localFractionalConfig
420   *          The local domain fractional configuration
421   * @param entry
422   *          The entry to modify
423   */
424  private static void flushFractionalConfigIntoEntry(FractionalConfig
425    localFractionalConfig, Entry entry)
426  {
427    if (localFractionalConfig.isFractional()) // Paranoia check
428    {
429      // Get the fractional configuration of the domain
430      boolean fractionalExclusive =
431        localFractionalConfig.isFractionalExclusive();
432      Map<String, Set<String>> fractionalSpecificClassesAttributes =
433        localFractionalConfig.getFractionalSpecificClassesAttributes();
434      Set<String> fractionalAllClassesAttributes =
435        localFractionalConfig.getFractionalAllClassesAttributes();
436
437      // Create attribute builder for the right fractional mode
438      String fractAttribute = fractionalExclusive ?
439          REPLICATION_FRACTIONAL_EXCLUDE : REPLICATION_FRACTIONAL_INCLUDE;
440      AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute);
441      // Add attribute values for all classes
442      boolean somethingToFlush =
443          add(attrBuilder, "*", fractionalAllClassesAttributes);
444
445      // Add attribute values for specific classes
446      if (!fractionalSpecificClassesAttributes.isEmpty())
447      {
448        for (Map.Entry<String, Set<String>> specific
449            : fractionalSpecificClassesAttributes.entrySet())
450        {
451          if (add(attrBuilder, specific.getKey(), specific.getValue()))
452          {
453            somethingToFlush = true;
454          }
455        }
456      }
457
458      // Now flush attribute values into entry
459      if (somethingToFlush)
460      {
461        List<ByteString> duplicateValues = new ArrayList<>();
462        entry.addAttribute(attrBuilder.toAttribute(), duplicateValues);
463      }
464    }
465  }
466
467  private static boolean add(AttributeBuilder attrBuilder, String className,
468      Set<String> values)
469  {
470    if (!values.isEmpty())
471    {
472      attrBuilder.add(className + ":" + Utils.joinAsString(",", values));
473      return true;
474    }
475    return false;
476  }
477
478  /** {@inheritDoc} */
479  @Override
480  public boolean isConfigurationAcceptable(PluginCfg configuration,
481    List<LocalizableMessage> unacceptableReasons)
482  {
483    return true;
484  }
485
486  /** {@inheritDoc} */
487  @Override
488  public boolean isConfigurationChangeAcceptable(
489    FractionalLDIFImportPluginCfg configuration,
490    List<LocalizableMessage> unacceptableReasons)
491  {
492    return true;
493  }
494
495  /** {@inheritDoc} */
496  @Override
497  public ConfigChangeResult applyConfigurationChange(
498    FractionalLDIFImportPluginCfg configuration)
499  {
500    return new ConfigChangeResult();
501  }
502}