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