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.ToolMessages.*;
033import static org.opends.server.util.StaticUtils.*;
034
035import java.io.OutputStream;
036import java.io.PrintStream;
037import java.util.ArrayList;
038import java.util.List;
039import java.util.logging.Level;
040
041import org.forgerock.i18n.LocalizableMessage;
042import org.forgerock.opendj.config.server.ConfigException;
043import org.opends.server.admin.std.server.BackendCfg;
044import org.opends.server.api.Backend;
045import org.opends.server.api.Backend.BackendOperation;
046import org.opends.server.backends.VerifyConfig;
047import org.opends.server.core.CoreConfigManager;
048import org.opends.server.core.DirectoryServer;
049import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
050import org.opends.server.core.LockFileManager;
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;
064
065/**
066 * This program provides a utility to verify the contents of the indexes
067 * of a Directory Server backend.  This will be a process that is
068 * intended to run separate from Directory Server and not internally within the
069 * server process (e.g., via the tasks interface).
070 */
071public class VerifyIndex
072{
073
074  /**
075   * Processes the command-line arguments and invokes the verify process.
076   *
077   * @param  args  The command-line arguments provided to this program.
078   */
079  public static void main(String[] args)
080  {
081    int retCode = mainVerifyIndex(args, true, System.out, System.err);
082    if(retCode != 0)
083    {
084      System.exit(filterExitCode(retCode));
085    }
086  }
087
088  /**
089   * Processes the command-line arguments and invokes the verify process.
090   *
091   * @param  args              The command-line arguments provided to this
092   *                           program.
093   * @param  initializeServer  Indicates whether to initialize the server.
094   * @param  outStream         The output stream to use for standard output, or
095   *                           {@code null} if standard output is not needed.
096   * @param  errStream         The output stream to use for standard error, or
097   *                           {@code null} if standard error is not needed.
098   *
099   * @return The error code.
100   */
101  public static int mainVerifyIndex(String[] args, boolean initializeServer,
102                                    OutputStream outStream,
103                                    OutputStream errStream)
104  {
105    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
106    JDKLogging.enableConsoleLoggingForOpenDJ(Level.FINE);
107
108    // Define the command-line arguments that may be used with this program.
109    StringArgument  configClass             = null;
110    StringArgument  configFile              = null;
111    StringArgument  baseDNString            = null;
112    StringArgument  indexList               = null;
113    BooleanArgument cleanMode               = null;
114    BooleanArgument countErrors             = null;
115    BooleanArgument displayUsage            = null;
116
117
118    // Create the command-line argument parser for use with this program.
119    LocalizableMessage toolDescription = INFO_VERIFYINDEX_TOOL_DESCRIPTION.get();
120    ArgumentParser argParser =
121         new ArgumentParser("org.opends.server.tools.VerifyIndex",
122                            toolDescription, false);
123    argParser.setShortToolDescription(REF_SHORT_DESC_VERIFY_INDEX.get());
124    argParser.setVersionHandler(new DirectoryServerVersionHandler());
125
126    // Initialize all the command-line argument types and register them with the
127    // parser.
128    try
129    {
130      configClass =
131           new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
132                              OPTION_LONG_CONFIG_CLASS, true, false,
133                              true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
134                              ConfigFileHandler.class.getName(), null,
135                              INFO_DESCRIPTION_CONFIG_CLASS.get());
136      configClass.setHidden(true);
137      argParser.addArgument(configClass);
138
139
140      configFile =
141           new StringArgument("configfile", 'f', "configFile", true, false,
142                              true, INFO_CONFIGFILE_PLACEHOLDER.get(), null,
143                              null,
144                              INFO_DESCRIPTION_CONFIG_FILE.get());
145      configFile.setHidden(true);
146      argParser.addArgument(configFile);
147
148
149      baseDNString =
150           new StringArgument("basedn", OPTION_SHORT_BASEDN,
151                              OPTION_LONG_BASEDN, true, false, true,
152                              INFO_BASEDN_PLACEHOLDER.get(), null, null,
153                              INFO_VERIFYINDEX_DESCRIPTION_BASE_DN.get());
154      argParser.addArgument(baseDNString);
155
156
157      indexList =
158           new StringArgument("index", 'i', "index",
159                              false, true, true,
160                              INFO_INDEX_PLACEHOLDER.get(), null, null,
161                              INFO_VERIFYINDEX_DESCRIPTION_INDEX_NAME.get());
162      argParser.addArgument(indexList);
163
164      cleanMode =
165           new BooleanArgument("clean", 'c', "clean",
166                               INFO_VERIFYINDEX_DESCRIPTION_VERIFY_CLEAN.get());
167      argParser.addArgument(cleanMode);
168
169      countErrors =
170           new BooleanArgument("counterrors", null, "countErrors",
171                               INFO_VERIFYINDEX_DESCRIPTION_COUNT_ERRORS.get());
172      argParser.addArgument(countErrors);
173
174      displayUsage = CommonArguments.getShowUsage();
175      argParser.addArgument(displayUsage);
176      argParser.setUsageArgument(displayUsage);
177    }
178    catch (ArgumentException ae)
179    {
180      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
181      return 1;
182    }
183
184
185    // Parse the command-line arguments provided to this program.
186    try
187    {
188      argParser.parseArguments(args);
189    }
190    catch (ArgumentException ae)
191    {
192      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
193      return 1;
194    }
195
196
197    // If we should just display usage or version information,
198    // then print it and exit.
199    if (argParser.usageOrVersionDisplayed())
200    {
201      return 0;
202    }
203
204    if (cleanMode.isPresent() && indexList.getValues().size() != 1)
205    {
206      argParser.displayMessageAndUsageReference(err, ERR_VERIFYINDEX_VERIFY_CLEAN_REQUIRES_SINGLE_INDEX.get());
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      // Initialize the Directory Server core configuration.
274      try
275      {
276        CoreConfigManager coreConfigManager = new CoreConfigManager(directoryServer.getServerContext());
277        coreConfigManager.initializeCoreConfig();
278      }
279      catch (ConfigException | InitializationException e)
280      {
281        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(e.getMessage()));
282        return 1;
283      }
284      catch (Exception e)
285      {
286        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(getExceptionMessage(e)));
287        return 1;
288      }
289
290
291      // Initialize the Directory Server crypto manager.
292      try
293      {
294        directoryServer.initializeCryptoManager();
295      }
296      catch (ConfigException | InitializationException e)
297      {
298        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(e.getMessage()));
299        return 1;
300      }
301      catch (Exception e)
302      {
303        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(getExceptionMessage(e)));
304        return 1;
305      }
306    }
307
308    // Decode the base DN provided by the user.
309    DN verifyBaseDN ;
310    try
311    {
312      verifyBaseDN = DN.valueOf(baseDNString.getValue());
313    }
314    catch (DirectoryException de)
315    {
316      printWrappedText(err, ERR_CANNOT_DECODE_BASE_DN.get(baseDNString.getValue(), de.getMessageObject()));
317      return 1;
318    }
319    catch (Exception e)
320    {
321      printWrappedText(err, ERR_CANNOT_DECODE_BASE_DN.get(baseDNString.getValue(), getExceptionMessage(e)));
322      return 1;
323    }
324
325
326    // Get information about the backends defined in the server.  Iterate
327    // through them, finding the one backend to be verified.
328    ArrayList<Backend>     backendList = new ArrayList<>();
329    ArrayList<BackendCfg>  entryList   = new ArrayList<>();
330    ArrayList<List<DN>>    dnList      = new ArrayList<>();
331    BackendToolUtils.getBackends(backendList, entryList, dnList);
332
333    Backend<?> backend = null;
334    int numBackends = backendList.size();
335    for (int i=0; i < numBackends; i++)
336    {
337      Backend<?> b = backendList.get(i);
338      List<DN>    baseDNs = dnList.get(i);
339
340      if (baseDNs.contains(verifyBaseDN))
341      {
342        if (backend != null)
343        {
344          printWrappedText(err, ERR_MULTIPLE_BACKENDS_FOR_BASE.get(baseDNString.getValue()));
345          return 1;
346        }
347        backend = b;
348      }
349    }
350
351    if (backend == null)
352    {
353      printWrappedText(err, ERR_NO_BACKENDS_FOR_BASE.get(baseDNString.getValue()));
354      return 1;
355    }
356
357    if (!backend.supports(BackendOperation.INDEXING))
358    {
359      printWrappedText(err, ERR_BACKEND_NO_INDEXING_SUPPORT.get());
360      return 1;
361    }
362
363    // Initialize the verify configuration.
364    VerifyConfig verifyConfig = new VerifyConfig();
365    verifyConfig.setBaseDN(verifyBaseDN);
366    if (cleanMode.isPresent())
367    {
368      for (String s : indexList.getValues())
369      {
370        verifyConfig.addCleanIndex(s);
371      }
372    }
373    else
374    {
375      for (String s : indexList.getValues())
376      {
377        verifyConfig.addCompleteIndex(s);
378      }
379    }
380
381
382    // Acquire a shared lock for the backend.
383    try
384    {
385      String lockFile = LockFileManager.getBackendLockFileName(backend);
386      StringBuilder failureReason = new StringBuilder();
387      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
388      {
389        printWrappedText(err, ERR_VERIFYINDEX_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
390        return 1;
391      }
392    }
393    catch (Exception e)
394    {
395      printWrappedText(err, ERR_VERIFYINDEX_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
396      return 1;
397    }
398
399
400    try
401    {
402      // Launch the verify process.
403      final long errorCount = backend.verifyBackend(verifyConfig);
404      if (countErrors.isPresent())
405      {
406        if (errorCount > Integer.MAX_VALUE)
407        {
408          return Integer.MAX_VALUE;
409        }
410        return (int) errorCount;
411      }
412      return 0;
413    }
414    catch (InitializationException e)
415    {
416      printWrappedText(err, ERR_VERIFYINDEX_ERROR_DURING_VERIFY.get(e.getMessage()));
417      return 1;
418    }
419    catch (Exception e)
420    {
421      printWrappedText(err, ERR_VERIFYINDEX_ERROR_DURING_VERIFY.get(stackTraceToSingleLineString(e)));
422      return 1;
423    }
424    finally
425    {
426      // Release the shared lock on the backend.
427      try
428      {
429        String lockFile = LockFileManager.getBackendLockFileName(backend);
430        StringBuilder failureReason = new StringBuilder();
431        if (! LockFileManager.releaseLock(lockFile, failureReason))
432        {
433          printWrappedText(err, WARN_VERIFYINDEX_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), failureReason));
434        }
435      }
436      catch (Exception e)
437      {
438        printWrappedText(err,
439            WARN_VERIFYINDEX_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
440      }
441    }
442  }
443}