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