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 2015 ForgeRock AS
025 */
026package org.opends.server.backends.pluggable;
027
028import java.util.ArrayList;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Set;
032
033import org.opends.server.types.DN;
034import org.opends.server.types.DirectoryException;
035import org.opends.server.types.LDIFImportConfig;
036
037/**
038 * Command that describes how a suffix should be imported. Gives the strategy to use and the data to
039 * drive the import operation of a single suffix.
040 */
041public class ImportSuffixCommand
042{
043  /** Strategy for importing a suffix. */
044  public static enum SuffixImportStrategy {
045    /**
046     * Create a {@link Suffix} specifying just the {@link EntryContainer} for the baseDN, no include or exclude
047     * branches are needed, normally used for append or clear backend modes.
048     */
049    APPEND_OR_REPLACE,
050    /** Do not create a {@link Suffix}. */
051    SKIP_SUFFIX,
052    /** Before creating a {@link Suffix}, clear the {@link EntryContainer} of the baseDN. */
053    CLEAR_SUFFIX,
054    /** Create a temporary {@link EntryContainer} to merge LDIF with original data. */
055    MERGE_DB_WITH_LDIF,
056    /**
057     * Create a {@link Suffix} specifying include and exclude branches and optionally a source {@link EntryContainer}.
058     */
059    INCLUDE_EXCLUDE_BRANCHES
060  }
061
062  private List<DN> includeBranches;
063  private List<DN> excludeBranches;
064  private SuffixImportStrategy strategy = SuffixImportStrategy.APPEND_OR_REPLACE;
065
066  List<DN> getIncludeBranches()
067  {
068    return includeBranches;
069  }
070
071  List<DN> getExcludeBranches()
072  {
073    return excludeBranches;
074  }
075
076  SuffixImportStrategy getSuffixImportStrategy()
077  {
078    return strategy;
079  }
080
081  ImportSuffixCommand(DN baseDN, LDIFImportConfig importCfg) throws DirectoryException
082  {
083    strategy = decideSuffixStrategy(baseDN, importCfg);
084  }
085
086  private SuffixImportStrategy decideSuffixStrategy(DN baseDN, LDIFImportConfig importCfg)
087      throws DirectoryException
088  {
089    if (importCfg.appendToExistingData() || importCfg.clearBackend())
090    {
091      return SuffixImportStrategy.APPEND_OR_REPLACE;
092    }
093    if (importCfg.getExcludeBranches().contains(baseDN))
094    {
095      // This entire base DN was explicitly excluded. Skip.
096      return SuffixImportStrategy.SKIP_SUFFIX;
097    }
098    excludeBranches = getDescendants(baseDN, importCfg.getExcludeBranches());
099    if (!importCfg.getIncludeBranches().isEmpty())
100    {
101      includeBranches = getDescendants(baseDN, importCfg.getIncludeBranches());
102      if (includeBranches.isEmpty())
103      {
104        // There are no branches in the explicitly defined include list under this base DN.
105        // Skip this base DN altogether.
106        return SuffixImportStrategy.SKIP_SUFFIX;
107      }
108
109      // Remove any overlapping include branches.
110      Iterator<DN> includeBranchIterator = includeBranches.iterator();
111      while (includeBranchIterator.hasNext())
112      {
113        DN includeDN = includeBranchIterator.next();
114        if (!isAnyNotEqualAndAncestorOf(includeBranches, includeDN))
115        {
116          includeBranchIterator.remove();
117        }
118      }
119
120      // Remove any exclude branches that are not are not under a include branch
121      // since they will be migrated as part of the existing entries
122      // outside of the include branches anyways.
123      Iterator<DN> excludeBranchIterator = excludeBranches.iterator();
124      while (excludeBranchIterator.hasNext())
125      {
126        DN excludeDN = excludeBranchIterator.next();
127        if (!isAnyAncestorOf(includeBranches, excludeDN))
128        {
129          excludeBranchIterator.remove();
130        }
131      }
132
133      if (excludeBranches.isEmpty() && includeBranches.size() == 1 && includeBranches.get(0).equals(baseDN))
134      {
135        // This entire base DN is explicitly included in the import with
136        // no exclude branches that we need to migrate.
137        // Just clear the entry container.
138        return SuffixImportStrategy.CLEAR_SUFFIX;
139      }
140      return SuffixImportStrategy.MERGE_DB_WITH_LDIF;
141    }
142    return SuffixImportStrategy.INCLUDE_EXCLUDE_BRANCHES;
143  }
144
145  private List<DN> getDescendants(DN baseDN, Set<DN> dns)
146  {
147    final List<DN> results = new ArrayList<>();
148    for (DN dn : dns)
149    {
150      if (baseDN.isAncestorOf(dn))
151      {
152        results.add(dn);
153      }
154    }
155    return results;
156  }
157
158  private boolean isAnyAncestorOf(List<DN> dns, DN childDN)
159  {
160    for (DN dn : dns)
161    {
162      if (dn.isAncestorOf(childDN))
163      {
164        return true;
165      }
166    }
167    return false;
168  }
169
170  private boolean isAnyNotEqualAndAncestorOf(List<DN> dns, DN childDN)
171  {
172    for (DN dn : dns)
173    {
174      if (!dn.equals(childDN) && dn.isAncestorOf(childDN))
175      {
176        return false;
177      }
178    }
179    return true;
180  }
181}