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