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}