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.CommonArguments.*; 021import static com.forgerock.opendj.cli.Utils.*; 022 023import static org.opends.messages.ToolMessages.*; 024import static org.opends.server.config.ConfigConstants.*; 025import static org.opends.server.util.StaticUtils.*; 026 027import java.io.OutputStream; 028import java.io.PrintStream; 029import java.util.Iterator; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.TreeMap; 035import java.util.TreeSet; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.opendj.adapter.server3x.Converters; 039import org.forgerock.opendj.config.server.ConfigException; 040import org.forgerock.opendj.ldap.ByteString; 041import org.forgerock.opendj.ldap.DN; 042import org.opends.server.config.ConfigurationHandler; 043import org.opends.server.core.DirectoryServer; 044import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 045import org.opends.server.loggers.JDKLogging; 046import org.opends.server.types.Attribute; 047import org.opends.server.types.Entry; 048import org.opends.server.types.InitializationException; 049import org.opends.server.types.NullOutputStream; 050import org.opends.server.util.BuildVersion; 051 052import com.forgerock.opendj.cli.ArgumentException; 053import com.forgerock.opendj.cli.ArgumentParser; 054import com.forgerock.opendj.cli.BooleanArgument; 055import com.forgerock.opendj.cli.StringArgument; 056import com.forgerock.opendj.cli.TableBuilder; 057import com.forgerock.opendj.cli.TextTablePrinter; 058 059/** 060 * This program provides a utility that may be used to list the backends in the 061 * server, as well as to determine which backend holds a given entry. 062 */ 063public class ListBackends 064{ 065 /** 066 * Parses the provided command-line arguments and uses that information to 067 * list the backend information. 068 * 069 * @param args The command-line arguments provided to this program. 070 */ 071 public static void main(String[] args) 072 { 073 int retCode = listBackends(args, true, System.out, System.err); 074 075 if(retCode != 0) 076 { 077 System.exit(filterExitCode(retCode)); 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 086 * program. 087 * @param initializeServer Indicates whether to initialize the server. 088 * @param outStream The output stream to use for standard output, or 089 * <CODE>null</CODE> if standard output is not 090 * needed. 091 * @param errStream The output stream to use for standard error, or 092 * <CODE>null</CODE> if standard error is not 093 * needed. 094 * 095 * @return A return code indicating whether the processing was successful. 096 */ 097 public static int listBackends(String[] args, boolean initializeServer, 098 OutputStream outStream, OutputStream errStream) 099 { 100 PrintStream out = NullOutputStream.wrapOrNullStream(outStream); 101 PrintStream err = NullOutputStream.wrapOrNullStream(errStream); 102 JDKLogging.disableLogging(); 103 104 // Define the command-line arguments that may be used with this program. 105 BooleanArgument displayUsage = null; 106 StringArgument backendID = null; 107 StringArgument baseDN = null; 108 StringArgument configFile = null; 109 110 // Create the command-line argument parser for use with this program. 111 LocalizableMessage toolDescription = INFO_LISTBACKENDS_TOOL_DESCRIPTION.get(); 112 ArgumentParser argParser = 113 new ArgumentParser("org.opends.server.tools.ListBackends", 114 toolDescription, false); 115 argParser.setShortToolDescription(REF_SHORT_DESC_LIST_BACKENDS.get()); 116 argParser.setVersionHandler(new DirectoryServerVersionHandler()); 117 118 // Initialize all the command-line argument types and register them with the parser. 119 try 120 { 121 configFile = 122 StringArgument.builder("configFile") 123 .shortIdentifier('f') 124 .description(INFO_DESCRIPTION_CONFIG_FILE.get()) 125 .hidden() 126 .required() 127 .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get()) 128 .buildAndAddToParser(argParser); 129 backendID = 130 StringArgument.builder("backendID") 131 .shortIdentifier('n') 132 .description(INFO_LISTBACKENDS_DESCRIPTION_BACKEND_ID.get()) 133 .multiValued() 134 .valuePlaceholder(INFO_BACKENDNAME_PLACEHOLDER.get()) 135 .buildAndAddToParser(argParser); 136 baseDN = 137 StringArgument.builder(OPTION_LONG_BASEDN) 138 .shortIdentifier(OPTION_SHORT_BASEDN) 139 .description(INFO_LISTBACKENDS_DESCRIPTION_BASE_DN.get()) 140 .multiValued() 141 .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get()) 142 .buildAndAddToParser(argParser); 143 144 displayUsage = showUsageArgument(); 145 argParser.addArgument(displayUsage); 146 argParser.setUsageArgument(displayUsage, out); 147 } 148 catch (ArgumentException ae) 149 { 150 printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 151 return 1; 152 } 153 154 // Parse the command-line arguments provided to this program. 155 try 156 { 157 argParser.parseArguments(args); 158 } 159 catch (ArgumentException ae) 160 { 161 argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 162 return 1; 163 } 164 165 // If we should just display usage or version information, 166 // then it's already been done so just return. 167 if (argParser.usageOrVersionDisplayed()) 168 { 169 return 0; 170 } 171 172 // Make sure that the user did not provide both the backend ID and base DN 173 // arguments. 174 if (backendID.isPresent() && baseDN.isPresent()) 175 { 176 printWrappedText(err, conflictingArgsErrorMessage(backendID, baseDN)); 177 return 1; 178 } 179 180 // Checks the version - if upgrade required, the tool is unusable 181 try 182 { 183 BuildVersion.checkVersionMismatch(); 184 } 185 catch (InitializationException e) 186 { 187 printWrappedText(err, e.getMessage()); 188 return 1; 189 } 190 191 if (initializeServer) 192 { 193 try 194 { 195 new DirectoryServer.InitializationBuilder(configFile.getValue()) 196 .initialize(); 197 } 198 catch (Exception e) 199 { 200 printWrappedText(err, e.getLocalizedMessage()); 201 return 1; 202 } 203 } 204 205 // Retrieve a list of the backends defined in the server. 206 Map<String, Set<DN>> backends; 207 try 208 { 209 backends = getBackends(); 210 } 211 catch (ConfigException ce) 212 { 213 printWrappedText(err, ERR_LISTBACKENDS_CANNOT_GET_BACKENDS.get(ce.getMessage())); 214 return 1; 215 } 216 catch (Exception e) 217 { 218 printWrappedText(err, ERR_LISTBACKENDS_CANNOT_GET_BACKENDS.get(getExceptionMessage(e))); 219 return 1; 220 } 221 222 // See what action we need to take based on the arguments provided. If the 223 // backend ID argument was present, then list the base DNs for that backend. 224 // If the base DN argument was present, then list the backend for that base 225 // DN. If no arguments were provided, then list all backends and base DNs. 226 boolean invalidDn = false; 227 if (baseDN.isPresent()) 228 { 229 // Create a map from the base DNs of the backends to the corresponding backend ID. 230 Map<DN, String> baseToIDMap = new TreeMap<>(); 231 for (String id : backends.keySet()) 232 { 233 for (DN dn : backends.get(id)) 234 { 235 baseToIDMap.put(dn, id); 236 } 237 } 238 239 // Iterate through the base DN values specified by the user. Determine 240 // the backend for that entry, and whether the provided DN is a base DN 241 // for that backend. 242 for (String dnStr : baseDN.getValues()) 243 { 244 DN dn; 245 try 246 { 247 dn = DN.valueOf(dnStr); 248 } 249 catch (Exception e) 250 { 251 printWrappedText(err, ERR_LISTBACKENDS_INVALID_DN.get(dnStr, getExceptionMessage(e))); 252 return 1; 253 } 254 255 String id = baseToIDMap.get(dn); 256 if (id == null) 257 { 258 err.println(INFO_LISTBACKENDS_NOT_BASE_DN.get(dn)); 259 260 DN parentDN = dn.parent(); 261 while (true) 262 { 263 if (parentDN == null) 264 { 265 err.println(INFO_LISTBACKENDS_NO_BACKEND_FOR_DN.get(dn)); 266 invalidDn = true; 267 break; 268 } 269 else 270 { 271 id = baseToIDMap.get(parentDN); 272 if (id != null) 273 { 274 out.println(INFO_LISTBACKENDS_DN_BELOW_BASE.get(dn, parentDN, id)); 275 break; 276 } 277 } 278 279 parentDN = parentDN.parent(); 280 } 281 } 282 else 283 { 284 out.println(INFO_LISTBACKENDS_BASE_FOR_ID.get(dn, id)); 285 } 286 } 287 } 288 else 289 { 290 List<String> backendIDs = backendID.isPresent() ? backendID.getValues() : new LinkedList<>(backends.keySet()); 291 292 // Figure out the length of the longest backend ID and base DN defined in 293 // the server. We'll use that information to try to align the output. 294 LocalizableMessage backendIDLabel = INFO_LISTBACKENDS_LABEL_BACKEND_ID.get(); 295 LocalizableMessage baseDNLabel = INFO_LISTBACKENDS_LABEL_BASE_DN.get(); 296 int backendIDLength = 10; 297 int baseDNLength = 7; 298 299 Iterator<String> it = backendIDs.iterator(); 300 while (it.hasNext()) 301 { 302 String id = it.next(); 303 Set<DN> baseDNs = backends.get(id); 304 if (baseDNs == null) 305 { 306 printWrappedText(err, ERR_LISTBACKENDS_NO_SUCH_BACKEND.get(id)); 307 it.remove(); 308 } 309 else 310 { 311 backendIDLength = Math.max(id.length(), backendIDLength); 312 for (DN dn : baseDNs) 313 { 314 baseDNLength = Math.max(dn.toString().length(), baseDNLength); 315 } 316 } 317 } 318 319 if (backendIDs.isEmpty()) 320 { 321 printWrappedText(err, ERR_LISTBACKENDS_NO_VALID_BACKENDS.get()); 322 return 1; 323 } 324 325 TableBuilder table = new TableBuilder(); 326 LocalizableMessage[] headers = {backendIDLabel, baseDNLabel}; 327 for (LocalizableMessage header : headers) 328 { 329 table.appendHeading(header); 330 } 331 for (String id : backendIDs) 332 { 333 table.startRow(); 334 table.appendCell(id); 335 StringBuilder buf = new StringBuilder(); 336 337 Set<DN> baseDNs = backends.get(id); 338 boolean isFirst = true; 339 for (DN dn : baseDNs) 340 { 341 if (!isFirst) 342 { 343 buf.append(","); 344 } 345 else 346 { 347 isFirst = false; 348 } 349 if (dn.size() > 1) 350 { 351 buf.append("\"").append(dn).append("\""); 352 } 353 else 354 { 355 buf.append(dn); 356 } 357 } 358 table.appendCell(buf.toString()); 359 } 360 TextTablePrinter printer = new TextTablePrinter(out); 361 printer.setColumnSeparator(LIST_TABLE_SEPARATOR); 362 table.print(printer); 363 } 364 365 // If we've gotten here, then everything completed successfully. 366 return invalidDn ? 1 : 0 ; 367 } 368 369 /** 370 * Retrieves information about the backends configured in the Directory Server 371 * mapped between the backend ID to the set of base DNs for that backend. 372 * 373 * @return Information about the backends configured in the Directory Server. 374 * 375 * @throws ConfigException If a problem occurs while reading the server 376 * configuration. 377 */ 378 private static Map<String, Set<DN>> getBackends() throws ConfigException 379 { 380 // Get the base entry for all backend configuration. 381 DN backendBaseDN = null; 382 try 383 { 384 backendBaseDN = DN.valueOf(DN_BACKEND_BASE); 385 } 386 catch (Exception e) 387 { 388 LocalizableMessage message = ERR_CANNOT_DECODE_BACKEND_BASE_DN.get( 389 DN_BACKEND_BASE, getExceptionMessage(e)); 390 throw new ConfigException(message, e); 391 } 392 393 // Iterate through the immediate children, attempting to parse them as backends. 394 Map<String, Set<DN>> backendMap = new TreeMap<>(); 395 ConfigurationHandler configHandler = DirectoryServer.getConfigurationHandler(); 396 for (DN childrenDn : configHandler.getChildren(backendBaseDN)) 397 { 398 Entry configEntry = Converters.to(configHandler.getEntry(childrenDn)); 399 // Get the backend ID attribute from the entry. If there isn't one, then 400 // skip the entry. 401 String backendID = null; 402 try 403 { 404 backendID = BackendToolUtils.getStringSingleValuedAttribute(configEntry, ATTR_BACKEND_ID); 405 if (backendID == null) 406 { 407 continue; 408 } 409 } 410 catch (Exception e) 411 { 412 LocalizableMessage message = ERR_CANNOT_DETERMINE_BACKEND_ID.get(configEntry.getName(), getExceptionMessage(e)); 413 throw new ConfigException(message, e); 414 } 415 416 // Get the base DN attribute from the entry. If there isn't one, then 417 // just skip this entry. 418 Set<DN> baseDNs = new TreeSet<>(); 419 try 420 { 421 List<Attribute> attributes = configEntry.getAttribute(ATTR_BACKEND_BASE_DN); 422 if (!attributes.isEmpty()) 423 { 424 Attribute attribute = attributes.get(0); 425 for (ByteString byteString : attribute) 426 { 427 baseDNs.add(DN.valueOf(byteString.toString())); 428 } 429 } 430 } 431 catch (Exception e) 432 { 433 LocalizableMessage message = ERR_CANNOT_DETERMINE_BASES_FOR_BACKEND.get( 434 configEntry.getName(), getExceptionMessage(e)); 435 throw new ConfigException(message, e); 436 } 437 438 backendMap.put(backendID, baseDNs); 439 } 440 441 return backendMap; 442 } 443}