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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.tools;
028    import org.opends.messages.Message;
029    
030    import static org.opends.server.util.StaticUtils.wrapText;
031    import org.opends.server.util.args.ArgumentException;
032    import org.opends.server.util.args.ArgumentParser;
033    import org.opends.server.util.args.BooleanArgument;
034    import org.opends.server.util.args.StringArgument;
035    import org.opends.server.extensions.ConfigFileHandler;
036    
037    import static org.opends.messages.ToolMessages.*;
038    import org.opends.server.config.ConfigException;
039    import static org.opends.server.loggers.ErrorLogger.logError;
040    import org.opends.server.loggers.TextWriter;
041    import org.opends.server.loggers.ErrorLogger;
042    import org.opends.server.loggers.TextErrorLogPublisher;
043    import org.opends.server.loggers.debug.TextDebugLogPublisher;
044    import org.opends.server.loggers.debug.DebugLogger;
045    import static org.opends.server.util.ServerConstants.*;
046    import static org.opends.server.util.StaticUtils.*;
047    import org.opends.server.core.DirectoryServer;
048    import org.opends.server.core.CoreConfigManager;
049    import org.opends.server.core.LockFileManager;
050    import org.opends.server.types.*;
051    import org.opends.server.api.Backend;
052    import org.opends.server.api.ErrorLogPublisher;
053    import org.opends.server.api.DebugLogPublisher;
054    import org.opends.server.backends.jeb.BackendImpl;
055    import org.opends.server.backends.jeb.RebuildConfig;
056    import org.opends.server.admin.std.server.BackendCfg;
057    
058    import java.io.OutputStream;
059    import java.io.PrintStream;
060    import java.util.ArrayList;
061    import java.util.List;
062    
063    
064    /**
065     * This program provides a utility to rebuild the contents of the indexes
066     * of a Directory Server backend.  This will be a process that is
067     * intended to run separate from Directory Server and not internally within the
068     * server process (e.g., via the tasks interface).
069     */
070    public class RebuildIndex
071    {
072    
073      /**
074       * Processes the command-line arguments and invokes the rebuild process.
075       *
076       * @param  args  The command-line arguments provided to this program.
077       */
078      public static void main(String[] args)
079      {
080        int retCode = mainRebuildIndex(args, true, System.out, System.err);
081    
082        if(retCode != 0)
083        {
084          System.exit(filterExitCode(retCode));
085        }
086      }
087    
088      /**
089       * Processes the command-line arguments and invokes the rebuild 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 mainRebuildIndex(String[] args, boolean initializeServer,
102                                         OutputStream outStream,
103                                         OutputStream errStream)
104      {
105        PrintStream out;
106        if (outStream == null)
107        {
108          out = NullOutputStream.printStream();
109        }
110        else
111        {
112          out = new PrintStream(outStream);
113        }
114    
115        PrintStream err;
116        if (errStream == null)
117        {
118          err = NullOutputStream.printStream();
119        }
120        else
121        {
122          err = new PrintStream(errStream);
123        }
124    
125        // Define the command-line arguments that may be used with this program.
126        StringArgument  configClass             = null;
127        StringArgument  configFile              = null;
128        StringArgument  baseDNString            = null;
129        StringArgument  indexList               = null;
130        BooleanArgument displayUsage            = null;
131    
132    
133        // Create the command-line argument parser for use with this program.
134        Message toolDescription = INFO_REBUILDINDEX_TOOL_DESCRIPTION.get();
135        ArgumentParser argParser =
136             new ArgumentParser("org.opends.server.tools.RebuildIndex",
137                                toolDescription, false);
138    
139    
140        // Initialize all the command-line argument types and register them with the
141        // parser.
142        try
143        {
144          configClass =
145               new StringArgument("configclass", 'C', "configClass", true, false,
146                                  true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
147                                  ConfigFileHandler.class.getName(), null,
148                                  INFO_DESCRIPTION_CONFIG_CLASS.get());
149          configClass.setHidden(true);
150          argParser.addArgument(configClass);
151    
152    
153          configFile =
154               new StringArgument("configfile", 'f', "configFile", true, false,
155                                  true, INFO_CONFIGFILE_PLACEHOLDER.get(), null,
156                                  null,
157                                  INFO_DESCRIPTION_CONFIG_FILE.get());
158          configFile.setHidden(true);
159          argParser.addArgument(configFile);
160    
161    
162          baseDNString =
163               new StringArgument("basedn", 'b', "baseDN", true, false, true,
164                                  INFO_BASEDN_PLACEHOLDER.get(), null, null,
165                                  INFO_REBUILDINDEX_DESCRIPTION_BASE_DN.get());
166          argParser.addArgument(baseDNString);
167    
168    
169          indexList =
170               new StringArgument("index", 'i', "index",
171                                  false, true, true,
172                                  INFO_INDEX_PLACEHOLDER.get(), null, null,
173                                  INFO_REBUILDINDEX_DESCRIPTION_INDEX_NAME.get());
174          argParser.addArgument(indexList);
175    
176    
177          displayUsage =
178               new BooleanArgument("help", 'H', "help",
179                                   INFO_DESCRIPTION_USAGE.get());
180          argParser.addArgument(displayUsage);
181          argParser.setUsageArgument(displayUsage);
182        }
183        catch (ArgumentException ae)
184        {
185          Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
186    
187          err.println(wrapText(message, MAX_LINE_WIDTH));
188          return 1;
189        }
190    
191    
192        // Parse the command-line arguments provided to this program.
193        try
194        {
195          argParser.parseArguments(args);
196        }
197        catch (ArgumentException ae)
198        {
199          Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
200    
201          err.println(wrapText(message, MAX_LINE_WIDTH));
202          err.println(argParser.getUsage());
203          return 1;
204        }
205    
206    
207        // If we should just display usage or version information,
208        // then print it and exit.
209        if (argParser.usageOrVersionDisplayed())
210        {
211          return 0;
212        }
213    
214    
215    
216    
217        // If no arguments were provided, then display usage information and exit.
218        int numArgs = args.length;
219        if (numArgs == 0)
220        {
221          out.println(argParser.getUsage());
222          return 1;
223        }
224    
225    
226        if (indexList.getValues().size() <= 0)
227        {
228          Message message = ERR_REBUILDINDEX_REQUIRES_AT_LEAST_ONE_INDEX.get();
229    
230          err.println(wrapText(message, MAX_LINE_WIDTH));
231          out.println(argParser.getUsage());
232          return 1;
233        }
234    
235        // Perform the initial bootstrap of the Directory Server and process the
236        // configuration.
237        DirectoryServer directoryServer = DirectoryServer.getInstance();
238    
239        if (initializeServer)
240        {
241          try
242          {
243            DirectoryServer.bootstrapClient();
244            DirectoryServer.initializeJMX();
245          }
246          catch (Exception e)
247          {
248            Message message = ERR_SERVER_BOOTSTRAP_ERROR.get(
249                    getExceptionMessage(e));
250            err.println(wrapText(message, MAX_LINE_WIDTH));
251            return 1;
252          }
253    
254          try
255          {
256            directoryServer.initializeConfiguration(configClass.getValue(),
257                                                    configFile.getValue());
258          }
259          catch (InitializationException ie)
260          {
261            Message message = ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage());
262            err.println(wrapText(message, MAX_LINE_WIDTH));
263            return 1;
264          }
265          catch (Exception e)
266          {
267            Message message = ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e));
268            err.println(wrapText(message, MAX_LINE_WIDTH));
269            return 1;
270          }
271    
272    
273    
274          // Initialize the Directory Server schema elements.
275          try
276          {
277            directoryServer.initializeSchema();
278          }
279          catch (ConfigException ce)
280          {
281            Message message = ERR_CANNOT_LOAD_SCHEMA.get(ce.getMessage());
282            err.println(wrapText(message, MAX_LINE_WIDTH));
283            return 1;
284          }
285          catch (InitializationException ie)
286          {
287            Message message = ERR_CANNOT_LOAD_SCHEMA.get(ie.getMessage());
288            err.println(wrapText(message, MAX_LINE_WIDTH));
289            return 1;
290          }
291          catch (Exception e)
292          {
293            Message message = ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e));
294            err.println(wrapText(message, MAX_LINE_WIDTH));
295            return 1;
296          }
297    
298    
299          // Initialize the Directory Server core configuration.
300          try
301          {
302            CoreConfigManager coreConfigManager = new CoreConfigManager();
303            coreConfigManager.initializeCoreConfig();
304          }
305          catch (ConfigException ce)
306          {
307            Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(
308                    ce.getMessage());
309            err.println(wrapText(message, MAX_LINE_WIDTH));
310            return 1;
311          }
312          catch (InitializationException ie)
313          {
314            Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(
315                    ie.getMessage());
316            err.println(wrapText(message, MAX_LINE_WIDTH));
317            return 1;
318          }
319          catch (Exception e)
320          {
321            Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(
322                    getExceptionMessage(e));
323            err.println(wrapText(message, MAX_LINE_WIDTH));
324            return 1;
325          }
326    
327    
328          // Initialize the Directory Server crypto manager.
329          try
330          {
331            directoryServer.initializeCryptoManager();
332          }
333          catch (ConfigException ce)
334          {
335            Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(
336                    ce.getMessage());
337            err.println(wrapText(message, MAX_LINE_WIDTH));
338            return 1;
339          }
340          catch (InitializationException ie)
341          {
342            Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(
343                    ie.getMessage());
344            err.println(wrapText(message, MAX_LINE_WIDTH));
345            return 1;
346          }
347          catch (Exception e)
348          {
349            Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(
350                    getExceptionMessage(e));
351            err.println(wrapText(message, MAX_LINE_WIDTH));
352            return 1;
353          }
354    
355    
356    
357          try
358          {
359            ErrorLogPublisher errorLogPublisher =
360                TextErrorLogPublisher.getStartupTextErrorPublisher(
361                new TextWriter.STREAM(out));
362            DebugLogPublisher debugLogPublisher =
363                TextDebugLogPublisher.getStartupTextDebugPublisher(
364                new TextWriter.STREAM(out));
365            ErrorLogger.addErrorLogPublisher(errorLogPublisher);
366            DebugLogger.addDebugLogPublisher(debugLogPublisher);
367          }
368          catch(Exception e)
369          {
370            err.println("Error installing the custom error logger: " +
371                        stackTraceToSingleLineString(e));
372          }
373        }
374    
375        // Decode the base DN provided by the user.
376        DN rebuildBaseDN;
377        try
378        {
379          rebuildBaseDN = DN.decode(baseDNString.getValue());
380        }
381        catch (DirectoryException de)
382        {
383          Message message = ERR_CANNOT_DECODE_BASE_DN.get(
384              baseDNString.getValue(), de.getMessageObject());
385          logError(message);
386          return 1;
387        }
388        catch (Exception e)
389        {
390          Message message = ERR_CANNOT_DECODE_BASE_DN.get(
391              baseDNString.getValue(), getExceptionMessage(e));
392          logError(message);
393          return 1;
394        }
395    
396        // Get information about the backends defined in the server.
397        Backend backend = null;
398        DN[]          baseDNArray;
399    
400        ArrayList<Backend>     backendList = new ArrayList<Backend>();
401        ArrayList<BackendCfg>  entryList   = new ArrayList<BackendCfg>();
402        ArrayList<List<DN>> dnList = new ArrayList<List<DN>>();
403        int code = BackendToolUtils.getBackends(backendList, entryList, dnList);
404    
405        int numBackends = backendList.size();
406        for (int i=0; i < numBackends; i++)
407        {
408          Backend     b       = backendList.get(i);
409          List<DN>    baseDNs = dnList.get(i);
410    
411          for (DN baseDN : baseDNs)
412          {
413            if (baseDN.equals(rebuildBaseDN))
414            {
415              if (backend == null)
416              {
417                backend         = b;
418                baseDNArray     = new DN[baseDNs.size()];
419                baseDNs.toArray(baseDNArray);
420              }
421              else
422              {
423                Message message =
424                    ERR_MULTIPLE_BACKENDS_FOR_BASE.get(baseDNString.getValue());
425                logError(message);
426                return 1;
427              }
428              break;
429            }
430          }
431        }
432    
433        if (backend == null)
434        {
435          Message message = ERR_NO_BACKENDS_FOR_BASE.get(baseDNString.getValue());
436          logError(message);
437          return 1;
438        }
439    
440        if (!(backend instanceof BackendImpl))
441        {
442          Message message = ERR_BACKEND_NO_INDEXING_SUPPORT.get();
443          logError(message);
444          return 1;
445        }
446    
447        // Initialize the rebuild configuration.
448        RebuildConfig rebuildConfig = new RebuildConfig();
449        rebuildConfig.setBaseDN(rebuildBaseDN);
450        for (String s : indexList.getValues())
451        {
452          rebuildConfig.addRebuildIndex(s);
453        }
454    
455        // Acquire an exclusive lock for the backend.
456        //TODO: Find a way to do this with the server online.
457        try
458        {
459          String lockFile = LockFileManager.getBackendLockFileName(backend);
460          StringBuilder failureReason = new StringBuilder();
461          if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason))
462          {
463            Message message = ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND.get(
464                backend.getBackendID(), String.valueOf(failureReason));
465            logError(message);
466            return 1;
467          }
468        }
469        catch (Exception e)
470        {
471          Message message = ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND.get(
472              backend.getBackendID(), getExceptionMessage(e));
473          logError(message);
474          return 1;
475        }
476    
477        // Launch the rebuild process.
478        int returnCode = 0;
479        try
480        {
481          BackendImpl jebBackend = (BackendImpl)backend;
482          jebBackend.rebuildBackend(rebuildConfig);
483        }
484        catch (Exception e)
485        {
486          Message message =
487              ERR_REBUILDINDEX_ERROR_DURING_REBUILD.get(getExceptionMessage(e));
488          logError(message);
489          returnCode = 1;
490        }
491        finally
492        {
493          // Release the shared lock on the backend.
494          try
495          {
496            String lockFile = LockFileManager.getBackendLockFileName(backend);
497            StringBuilder failureReason = new StringBuilder();
498            if (! LockFileManager.releaseLock(lockFile, failureReason))
499            {
500              Message message = WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND.get(
501                  backend.getBackendID(), String.valueOf(failureReason));
502              logError(message);
503            }
504          }
505          catch (Exception e)
506          {
507            Message message = WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND.get(
508                backend.getBackendID(), getExceptionMessage(e));
509            logError(message);
510          }
511        }
512    
513        return returnCode;
514      }
515    }