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