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 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019import static com.forgerock.opendj.cli.ArgumentConstants.*;
020import static com.forgerock.opendj.cli.Utils.*;
021import static com.forgerock.opendj.cli.CommonArguments.*;
022
023import static org.opends.messages.ConfigMessages.*;
024import static org.opends.messages.ToolMessages.*;
025import static org.opends.server.config.ConfigConstants.*;
026import static org.opends.server.util.StaticUtils.*;
027
028import java.io.OutputStream;
029import java.io.PrintStream;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.TreeMap;
034import java.util.TreeSet;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.forgerock.opendj.config.server.ConfigException;
038import org.forgerock.opendj.ldap.DN;
039import org.opends.server.config.ConfigEntry;
040import org.opends.server.config.DNConfigAttribute;
041import org.opends.server.config.StringConfigAttribute;
042import org.opends.server.core.DirectoryServer;
043import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
044import org.opends.server.extensions.ConfigFileHandler;
045import org.opends.server.loggers.JDKLogging;
046import org.opends.server.types.InitializationException;
047import org.opends.server.types.NullOutputStream;
048import org.opends.server.util.BuildVersion;
049
050import com.forgerock.opendj.cli.ArgumentException;
051import com.forgerock.opendj.cli.ArgumentParser;
052import com.forgerock.opendj.cli.BooleanArgument;
053import com.forgerock.opendj.cli.StringArgument;
054import com.forgerock.opendj.cli.TableBuilder;
055import com.forgerock.opendj.cli.TextTablePrinter;
056
057/**
058 * This program provides a utility that may be used to list the backends in the
059 * server, as well as to determine which backend holds a given entry.
060 */
061public class ListBackends
062{
063  /**
064   * Parses the provided command-line arguments and uses that information to
065   * list the backend information.
066   *
067   * @param  args  The command-line arguments provided to this program.
068   */
069  public static void main(String[] args)
070  {
071    int retCode = listBackends(args, true, System.out, System.err);
072
073    if(retCode != 0)
074    {
075      System.exit(filterExitCode(retCode));
076    }
077  }
078
079
080
081  /**
082   * Parses the provided command-line arguments and uses that information to
083   * list the backend information.
084   *
085   * @param  args  The command-line arguments provided to this program.
086   *
087   * @return  A return code indicating whether the processing was successful.
088   */
089  public static int listBackends(String[] args)
090  {
091    return listBackends(args, true, System.out, System.err);
092  }
093
094
095
096  /**
097   * Parses the provided command-line arguments and uses that information to
098   * list the backend information.
099   *
100   * @param  args              The command-line arguments provided to this
101   *                           program.
102   * @param  initializeServer  Indicates whether to initialize the server.
103   * @param  outStream         The output stream to use for standard output, or
104   *                           <CODE>null</CODE> if standard output is not
105   *                           needed.
106   * @param  errStream         The output stream to use for standard error, or
107   *                           <CODE>null</CODE> if standard error is not
108   *                           needed.
109   *
110   * @return  A return code indicating whether the processing was successful.
111   */
112  public static int listBackends(String[] args, boolean initializeServer,
113                                 OutputStream outStream, OutputStream errStream)
114  {
115    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
116    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
117    JDKLogging.disableLogging();
118
119    // Define the command-line arguments that may be used with this program.
120    BooleanArgument displayUsage = null;
121    StringArgument  backendID    = null;
122    StringArgument  baseDN       = null;
123    StringArgument  configClass  = null;
124    StringArgument  configFile   = null;
125
126
127    // Create the command-line argument parser for use with this program.
128    LocalizableMessage toolDescription = INFO_LISTBACKENDS_TOOL_DESCRIPTION.get();
129    ArgumentParser argParser =
130         new ArgumentParser("org.opends.server.tools.ListBackends",
131                            toolDescription, false);
132    argParser.setShortToolDescription(REF_SHORT_DESC_LIST_BACKENDS.get());
133    argParser.setVersionHandler(new DirectoryServerVersionHandler());
134
135    // Initialize all the command-line argument types and register them with the
136    // parser.
137    try
138    {
139      configClass =
140              StringArgument.builder(OPTION_LONG_CONFIG_CLASS)
141                      .shortIdentifier(OPTION_SHORT_CONFIG_CLASS)
142                      .description(INFO_DESCRIPTION_CONFIG_CLASS.get())
143                      .hidden()
144                      .required()
145                      .defaultValue(ConfigFileHandler.class.getName())
146                      .valuePlaceholder(INFO_CONFIGCLASS_PLACEHOLDER.get())
147                      .buildAndAddToParser(argParser);
148      configFile =
149              StringArgument.builder("configFile")
150                      .shortIdentifier('f')
151                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
152                      .hidden()
153                      .required()
154                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
155                      .buildAndAddToParser(argParser);
156      backendID =
157              StringArgument.builder("backendID")
158                      .shortIdentifier('n')
159                      .description(INFO_LISTBACKENDS_DESCRIPTION_BACKEND_ID.get())
160                      .multiValued()
161                      .valuePlaceholder(INFO_BACKENDNAME_PLACEHOLDER.get())
162                      .buildAndAddToParser(argParser);
163      baseDN =
164              StringArgument.builder(OPTION_LONG_BASEDN)
165                      .shortIdentifier(OPTION_SHORT_BASEDN)
166                      .description(INFO_LISTBACKENDS_DESCRIPTION_BASE_DN.get())
167                      .multiValued()
168                      .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get())
169                      .buildAndAddToParser(argParser);
170
171      displayUsage = showUsageArgument();
172      argParser.addArgument(displayUsage);
173      argParser.setUsageArgument(displayUsage, out);
174    }
175    catch (ArgumentException ae)
176    {
177      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
178      return 1;
179    }
180
181
182    // Parse the command-line arguments provided to this program.
183    try
184    {
185      argParser.parseArguments(args);
186    }
187    catch (ArgumentException ae)
188    {
189      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
190      return 1;
191    }
192
193
194    // If we should just display usage or version information,
195    // then it's already been done so just return.
196    if (argParser.usageOrVersionDisplayed())
197    {
198      return 0;
199    }
200
201
202    // Make sure that the user did not provide both the backend ID and base DN
203    // arguments.
204    if (backendID.isPresent() && baseDN.isPresent())
205    {
206      printWrappedText(err, conflictingArgsErrorMessage(backendID, baseDN));
207      return 1;
208    }
209
210    // Checks the version - if upgrade required, the tool is unusable
211    try
212    {
213      BuildVersion.checkVersionMismatch();
214    }
215    catch (InitializationException e)
216    {
217      printWrappedText(err, e.getMessage());
218      return 1;
219    }
220
221    // Perform the initial bootstrap of the Directory Server and process the
222    // configuration.
223    DirectoryServer directoryServer = DirectoryServer.getInstance();
224
225    if (initializeServer)
226    {
227      try
228      {
229        DirectoryServer.bootstrapClient();
230        DirectoryServer.initializeJMX();
231      }
232      catch (Exception e)
233      {
234        printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getExceptionMessage(e)));
235        return 1;
236      }
237
238      try
239      {
240        directoryServer.initializeConfiguration(configClass.getValue(),
241                                                configFile.getValue());
242      }
243      catch (InitializationException ie)
244      {
245        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage()));
246        return 1;
247      }
248      catch (Exception e)
249      {
250        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e)));
251        return 1;
252      }
253
254
255
256      // Initialize the Directory Server schema elements.
257      try
258      {
259        directoryServer.initializeSchema();
260      }
261      catch (ConfigException | InitializationException e)
262      {
263        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(e.getMessage()));
264        return 1;
265      }
266      catch (Exception e)
267      {
268        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e)));
269        return 1;
270      }
271    }
272
273
274    // Retrieve a list of the backends defined in the server.
275    TreeMap<String,TreeSet<DN>> backends;
276    try
277    {
278      backends = getBackends();
279    }
280    catch (ConfigException ce)
281    {
282      printWrappedText(err, ERR_LISTBACKENDS_CANNOT_GET_BACKENDS.get(ce.getMessage()));
283      return 1;
284    }
285    catch (Exception e)
286    {
287      printWrappedText(err, ERR_LISTBACKENDS_CANNOT_GET_BACKENDS.get(getExceptionMessage(e)));
288      return 1;
289    }
290
291
292    // See what action we need to take based on the arguments provided.  If the
293    // backend ID argument was present, then list the base DNs for that backend.
294    // If the base DN argument was present, then list the backend for that base
295    // DN.  If no arguments were provided, then list all backends and base DNs.
296    boolean invalidDn = false;
297    if (baseDN.isPresent())
298    {
299      // Create a map from the base DNs of the backends to the corresponding
300      // backend ID.
301      TreeMap<DN,String> baseToIDMap = new TreeMap<>();
302      for (String id : backends.keySet())
303      {
304        for (DN dn : backends.get(id))
305        {
306          baseToIDMap.put(dn, id);
307        }
308      }
309
310
311      // Iterate through the base DN values specified by the user.  Determine
312      // the backend for that entry, and whether the provided DN is a base DN
313      // for that backend.
314      for (String dnStr : baseDN.getValues())
315      {
316        DN dn;
317        try
318        {
319          dn = DN.valueOf(dnStr);
320        }
321        catch (Exception e)
322        {
323          printWrappedText(err, ERR_LISTBACKENDS_INVALID_DN.get(dnStr, getExceptionMessage(e)));
324          return 1;
325        }
326
327
328        String id = baseToIDMap.get(dn);
329        if (id == null)
330        {
331          err.println(INFO_LISTBACKENDS_NOT_BASE_DN.get(dn));
332
333          DN parentDN = dn.parent();
334          while (true)
335          {
336            if (parentDN == null)
337            {
338              err.println(INFO_LISTBACKENDS_NO_BACKEND_FOR_DN.get(dn));
339              invalidDn = true;
340              break;
341            }
342            else
343            {
344              id = baseToIDMap.get(parentDN);
345              if (id != null)
346              {
347                out.println(INFO_LISTBACKENDS_DN_BELOW_BASE.get(dn, parentDN, id));
348                break;
349              }
350            }
351
352            parentDN = parentDN.parent();
353          }
354        }
355        else
356        {
357          out.println(INFO_LISTBACKENDS_BASE_FOR_ID.get(dn, id));
358        }
359      }
360    }
361    else
362    {
363      List<String> backendIDs = backendID.isPresent() ? backendID.getValues() : new LinkedList<>(backends.keySet());
364
365      // Figure out the length of the longest backend ID and base DN defined in
366      // the server.  We'll use that information to try to align the output.
367      LocalizableMessage backendIDLabel = INFO_LISTBACKENDS_LABEL_BACKEND_ID.get();
368      LocalizableMessage baseDNLabel = INFO_LISTBACKENDS_LABEL_BASE_DN.get();
369      int    backendIDLength = 10;
370      int    baseDNLength    = 7;
371
372      Iterator<String> iterator = backendIDs.iterator();
373      while (iterator.hasNext())
374      {
375        String id = iterator.next();
376        TreeSet<DN> baseDNs = backends.get(id);
377        if (baseDNs == null)
378        {
379          printWrappedText(err, ERR_LISTBACKENDS_NO_SUCH_BACKEND.get(id));
380          iterator.remove();
381        }
382        else
383        {
384          backendIDLength = Math.max(id.length(), backendIDLength);
385          for (DN dn : baseDNs)
386          {
387            baseDNLength = Math.max(dn.toString().length(), baseDNLength);
388          }
389        }
390      }
391
392      if (backendIDs.isEmpty())
393      {
394        printWrappedText(err, ERR_LISTBACKENDS_NO_VALID_BACKENDS.get());
395        return 1;
396      }
397
398      TableBuilder table = new TableBuilder();
399      LocalizableMessage[] headers = {backendIDLabel, baseDNLabel};
400      for (LocalizableMessage header : headers)
401      {
402        table.appendHeading(header);
403      }
404      for (String id : backendIDs)
405      {
406        table.startRow();
407        table.appendCell(id);
408        StringBuilder buf = new StringBuilder();
409
410        TreeSet<DN> baseDNs = backends.get(id);
411        boolean isFirst = true;
412        for (DN dn : baseDNs)
413        {
414          if (!isFirst)
415          {
416            buf.append(",");
417          }
418          else
419          {
420            isFirst = false;
421          }
422          if (dn.size() > 1)
423          {
424            buf.append("\"").append(dn).append("\"");
425          }
426          else
427          {
428            buf.append(dn);
429          }
430        }
431        table.appendCell(buf.toString());
432      }
433      TextTablePrinter printer = new TextTablePrinter(out);
434      printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
435      table.print(printer);
436    }
437
438
439    // If we've gotten here, then everything completed successfully.
440    return invalidDn ? 1 : 0 ;
441  }
442
443
444
445  /**
446   * Retrieves information about the backends configured in the Directory Server
447   * mapped between the backend ID to the set of base DNs for that backend.
448   *
449   * @return  Information about the backends configured in the Directory Server.
450   *
451   * @throws  ConfigException  If a problem occurs while reading the server
452   *                           configuration.
453   */
454  private static TreeMap<String,TreeSet<DN>> getBackends()
455          throws ConfigException
456  {
457    // Get the base entry for all backend configuration.
458    DN backendBaseDN = null;
459    try
460    {
461      backendBaseDN = DN.valueOf(DN_BACKEND_BASE);
462    }
463    catch (Exception e)
464    {
465      LocalizableMessage message = ERR_CANNOT_DECODE_BACKEND_BASE_DN.get(
466          DN_BACKEND_BASE, getExceptionMessage(e));
467      throw new ConfigException(message, e);
468    }
469
470    ConfigEntry baseEntry = null;
471    try
472    {
473      baseEntry = DirectoryServer.getConfigEntry(backendBaseDN);
474    }
475    catch (ConfigException ce)
476    {
477      LocalizableMessage message = ERR_CANNOT_RETRIEVE_BACKEND_BASE_ENTRY.get(
478          DN_BACKEND_BASE, ce.getMessage());
479      throw new ConfigException(message, ce);
480    }
481    catch (Exception e)
482    {
483      LocalizableMessage message = ERR_CANNOT_RETRIEVE_BACKEND_BASE_ENTRY.get(
484          DN_BACKEND_BASE, getExceptionMessage(e));
485      throw new ConfigException(message, e);
486    }
487
488
489    // Iterate through the immediate children, attempting to parse them as backends.
490    TreeMap<String,TreeSet<DN>> backendMap = new TreeMap<>();
491    for (ConfigEntry configEntry : baseEntry.getChildren().values())
492    {
493      // Get the backend ID attribute from the entry.  If there isn't one, then
494      // skip the entry.
495      String backendID = null;
496      try
497      {
498        LocalizableMessage msg = INFO_CONFIG_BACKEND_ATTR_DESCRIPTION_BACKEND_ID.get();
499        StringConfigAttribute idStub =
500             new StringConfigAttribute(ATTR_BACKEND_ID, msg,
501                                       true, false, true);
502        StringConfigAttribute idAttr =
503             (StringConfigAttribute) configEntry.getConfigAttribute(idStub);
504        if (idAttr == null)
505        {
506          continue;
507        }
508        else
509        {
510          backendID = idAttr.activeValue();
511        }
512      }
513      catch (ConfigException ce)
514      {
515        LocalizableMessage message = ERR_CANNOT_DETERMINE_BACKEND_ID.get(configEntry.getDN(), ce.getMessage());
516        throw new ConfigException(message, ce);
517      }
518      catch (Exception e)
519      {
520        LocalizableMessage message = ERR_CANNOT_DETERMINE_BACKEND_ID.get(configEntry.getDN(), getExceptionMessage(e));
521        throw new ConfigException(message, e);
522      }
523
524
525      // Get the base DN attribute from the entry.  If there isn't one, then
526      // just skip this entry.
527      TreeSet<DN> baseDNs = new TreeSet<>();
528      try
529      {
530        LocalizableMessage msg = INFO_CONFIG_BACKEND_ATTR_DESCRIPTION_BASE_DNS.get();
531        DNConfigAttribute baseDNStub =
532             new DNConfigAttribute(ATTR_BACKEND_BASE_DN, msg,
533                                   true, true, true);
534        DNConfigAttribute baseDNAttr =
535             (DNConfigAttribute) configEntry.getConfigAttribute(baseDNStub);
536        if (baseDNAttr != null)
537        {
538          baseDNs.addAll(baseDNAttr.activeValues());
539        }
540      }
541      catch (Exception e)
542      {
543        LocalizableMessage message = ERR_CANNOT_DETERMINE_BASES_FOR_BACKEND.get(
544            configEntry.getDN(), getExceptionMessage(e));
545        throw new ConfigException(message, e);
546      }
547
548      backendMap.put(backendID, baseDNs);
549    }
550
551    return backendMap;
552  }
553}
554