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