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