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