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-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.tasks;
018
019import static org.opends.messages.TaskMessages.*;
020import static org.opends.messages.ToolMessages.*;
021import static org.opends.server.config.ConfigConstants.*;
022import static org.opends.server.core.DirectoryServer.*;
023import static org.opends.server.util.StaticUtils.*;
024
025import java.util.ArrayList;
026import java.util.List;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.LocalizedIllegalArgumentException;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.opends.messages.TaskMessages;
033import org.opends.server.api.Backend;
034import org.opends.server.api.Backend.BackendOperation;
035import org.opends.server.api.ClientConnection;
036import org.opends.server.backends.RebuildConfig;
037import org.opends.server.backends.RebuildConfig.RebuildMode;
038import org.opends.server.backends.task.Task;
039import org.opends.server.backends.task.TaskState;
040import org.opends.server.core.DirectoryServer;
041import org.opends.server.core.LockFileManager;
042import org.opends.server.types.Attribute;
043import org.forgerock.opendj.ldap.schema.AttributeType;
044import org.forgerock.opendj.ldap.DN;
045import org.opends.server.types.DirectoryException;
046import org.opends.server.types.Entry;
047import org.opends.server.types.InitializationException;
048import org.opends.server.types.Operation;
049import org.opends.server.types.Privilege;
050
051/**
052 * This class provides an implementation of a Directory Server task that can be
053 * used to rebuild indexes in a backend.
054 */
055public class RebuildTask extends Task
056{
057  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
058
059  private String baseDN;
060  private ArrayList<String> indexes;
061  private String tmpDirectory;
062  private RebuildMode rebuildMode = RebuildMode.USER_DEFINED;
063  private boolean isClearDegradedState;
064
065  /** {@inheritDoc} */
066  @Override
067  public LocalizableMessage getDisplayName()
068  {
069    return TaskMessages.INFO_TASK_REBUILD_NAME.get();
070  }
071
072  /** {@inheritDoc} */
073  @Override
074  public void initializeTask() throws DirectoryException
075  {
076    // If the client connection is available, then make sure the associated
077    // client has the INDEX_REBUILD privilege.
078
079    Operation operation = getOperation();
080    if (operation != null)
081    {
082      ClientConnection clientConnection = operation.getClientConnection();
083      if (!clientConnection.hasPrivilege(Privilege.LDIF_IMPORT, operation))
084      {
085        LocalizableMessage message = ERR_TASK_INDEXREBUILD_INSUFFICIENT_PRIVILEGES.get();
086        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
087            message);
088      }
089    }
090
091    Entry taskEntry = getTaskEntry();
092
093    baseDN = asString(taskEntry, ATTR_REBUILD_BASE_DN);
094    tmpDirectory = asString(taskEntry, ATTR_REBUILD_TMP_DIRECTORY);
095    final String val = asString(taskEntry, ATTR_REBUILD_INDEX_CLEARDEGRADEDSTATE);
096    isClearDegradedState = Boolean.parseBoolean(val);
097
098    AttributeType typeIndex = getSchema().getAttributeType(ATTR_REBUILD_INDEX);
099    List<Attribute> attrList = taskEntry.getAttribute(typeIndex);
100    indexes = TaskUtils.getMultiValueString(attrList);
101
102    rebuildMode = getRebuildMode(indexes);
103    if (rebuildMode != RebuildMode.USER_DEFINED)
104    {
105      if (indexes.size() != 1)
106      {
107        LocalizableMessage msg = ERR_TASK_INDEXREBUILD_ALL_ERROR.get();
108        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg);
109      }
110      indexes.clear();
111    }
112  }
113
114  private String asString(Entry taskEntry, String attrName)
115  {
116    final AttributeType attrType = getSchema().getAttributeType(attrName);
117    final List<Attribute> attrList = taskEntry.getAttribute(attrType);
118    return TaskUtils.getSingleValueString(attrList);
119  }
120
121  private RebuildMode getRebuildMode(List<String> indexList)
122  {
123    for (String s : indexList)
124    {
125      if (REBUILD_ALL.equalsIgnoreCase(s))
126      {
127        return RebuildMode.ALL;
128      }
129      else if (REBUILD_DEGRADED.equalsIgnoreCase(s))
130      {
131        return RebuildMode.DEGRADED;
132      }
133    }
134    return RebuildMode.USER_DEFINED;
135  }
136
137  @Override
138  protected TaskState runTask()
139  {
140    RebuildConfig rebuildConfig = new RebuildConfig();
141
142    try
143    {
144      rebuildConfig.setBaseDN(DN.valueOf(baseDN));
145    }
146    catch (LocalizedIllegalArgumentException e)
147    {
148      logger.error(ERR_CANNOT_DECODE_BASE_DN, baseDN, e.getMessageObject());
149      return TaskState.STOPPED_BY_ERROR;
150    }
151
152    for (final String index : indexes)
153    {
154      rebuildConfig.addRebuildIndex(index);
155    }
156
157    // The degraded state is set(if present in args)
158    // during the initialization.
159    rebuildConfig.isClearDegradedState(isClearDegradedState);
160    boolean isBackendNeedToBeEnabled = false;
161
162    if (tmpDirectory == null)
163    {
164      tmpDirectory = "import-tmp";
165    }
166    rebuildConfig.setTmpDirectory(tmpDirectory);
167    rebuildConfig.setRebuildMode(rebuildMode);
168
169    final Backend<?> backend = DirectoryServer.getBackendWithBaseDN(rebuildConfig.getBaseDN());
170    if (backend == null)
171    {
172      logger.error(ERR_NO_BACKENDS_FOR_BASE, baseDN);
173      return TaskState.STOPPED_BY_ERROR;
174    }
175    if (!backend.supports(BackendOperation.INDEXING))
176    {
177      logger.error(ERR_REBUILDINDEX_WRONG_BACKEND_TYPE);
178      return TaskState.STOPPED_BY_ERROR;
179    }
180
181    // If we are rebuilding one or more system indexes, we have
182    // to acquire exclusive lock. Shared lock in 'cleardegradedstate' mode.
183    String lockFile = LockFileManager.getBackendLockFileName(backend);
184    StringBuilder failureReason = new StringBuilder();
185
186    // Disable the backend
187    // Except in 'cleardegradedstate' mode we don't need to disable it.
188    if (!isClearDegradedState)
189    {
190      try
191      {
192        TaskUtils.disableBackend(backend.getBackendID());
193      }
194      catch (DirectoryException e)
195      {
196        logger.traceException(e);
197
198        logger.error(e.getMessageObject());
199        return TaskState.STOPPED_BY_ERROR;
200      }
201
202      try
203      {
204        if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason))
205        {
206          logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend.getBackendID(), failureReason);
207          return TaskState.STOPPED_BY_ERROR;
208        }
209      }
210      catch (Exception e)
211      {
212        logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend
213                .getBackendID(), getExceptionMessage(e));
214        return TaskState.STOPPED_BY_ERROR;
215      }
216    }
217    else
218    {
219      // We just need a shared lock on the backend for this part.
220      try
221      {
222        if (!LockFileManager.acquireSharedLock(lockFile, failureReason))
223        {
224          logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend.getBackendID(), failureReason);
225          return TaskState.STOPPED_BY_ERROR;
226        }
227      }
228      catch (Exception e)
229      {
230        logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend
231                .getBackendID(), getExceptionMessage(e));
232        return TaskState.STOPPED_BY_ERROR;
233      }
234    }
235
236    TaskState returnCode = TaskState.COMPLETED_SUCCESSFULLY;
237
238    // Launch the rebuild process.
239    try
240    {
241      backend.rebuildBackend(rebuildConfig, DirectoryServer.getInstance().getServerContext());
242    }
243    catch (InitializationException e)
244    {
245      // This exception catches all 'index not found'
246      // The backend needs to be re-enabled at the end of the process.
247      LocalizableMessage message =
248          ERR_REBUILDINDEX_ERROR_DURING_REBUILD.get(getExceptionMessage(e));
249      logger.traceException(e);
250      logger.error(message);
251      isBackendNeedToBeEnabled = true;
252      returnCode = TaskState.STOPPED_BY_ERROR;
253    }
254    catch (Exception e)
255    {
256      logger.traceException(e);
257
258      logger.error(ERR_REBUILDINDEX_ERROR_DURING_REBUILD, getExceptionMessage(e));
259      returnCode = TaskState.STOPPED_BY_ERROR;
260    }
261    finally
262    {
263      // Release the lock on the backend.
264      try
265      {
266        lockFile = LockFileManager.getBackendLockFileName(backend);
267        failureReason = new StringBuilder();
268        if (!LockFileManager.releaseLock(lockFile, failureReason))
269        {
270          logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason);
271          returnCode = TaskState.COMPLETED_WITH_ERRORS;
272        }
273      }
274      catch (Throwable t)
275      {
276        logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(),
277                getExceptionMessage(t));
278        returnCode = TaskState.COMPLETED_WITH_ERRORS;
279      }
280    }
281
282    // The backend must be enabled only if the task is successful
283    // for prevent potential risks of database corruption.
284    if ((returnCode == TaskState.COMPLETED_SUCCESSFULLY || isBackendNeedToBeEnabled)
285        && !isClearDegradedState)
286    {
287      // Enable the backend.
288      try
289      {
290        TaskUtils.enableBackend(backend.getBackendID());
291      }
292      catch (DirectoryException e)
293      {
294        logger.traceException(e);
295
296        logger.error(e.getMessageObject());
297        returnCode = TaskState.STOPPED_BY_ERROR;
298      }
299    }
300
301    return returnCode;
302  }
303}