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 2014-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.io.File; 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031 032import org.forgerock.i18n.LocalizableMessage; 033import org.forgerock.i18n.slf4j.LocalizedLogger; 034import org.forgerock.opendj.ldap.DN; 035import org.forgerock.opendj.ldap.ResultCode; 036import org.forgerock.opendj.ldap.schema.AttributeType; 037import org.opends.messages.Severity; 038import org.opends.messages.TaskMessages; 039import org.opends.server.api.Backend; 040import org.opends.server.api.Backend.BackendOperation; 041import org.opends.server.api.ClientConnection; 042import org.opends.server.backends.task.Task; 043import org.opends.server.backends.task.TaskState; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.core.LockFileManager; 046import org.opends.server.types.Attribute; 047import org.opends.server.types.DirectoryException; 048import org.opends.server.types.Entry; 049import org.opends.server.types.ExistingFileBehavior; 050import org.opends.server.types.LDIFExportConfig; 051import org.opends.server.types.Operation; 052import org.opends.server.types.Privilege; 053import org.opends.server.types.SearchFilter; 054 055/** 056 * This class provides an implementation of a Directory Server task that can 057 * be used to export the contents of a Directory Server backend to an LDIF file. 058 */ 059public class ExportTask extends Task 060{ 061 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 062 063 /** Stores mapping between configuration attribute name and its label. */ 064 private static final Map<String, LocalizableMessage> argDisplayMap = new HashMap<>(); 065 static { 066 argDisplayMap.put(ATTR_TASK_EXPORT_LDIF_FILE, INFO_EXPORT_ARG_LDIF_FILE.get()); 067 argDisplayMap.put(ATTR_TASK_EXPORT_BACKEND_ID, INFO_EXPORT_ARG_BACKEND_ID.get()); 068 argDisplayMap.put(ATTR_TASK_EXPORT_APPEND_TO_LDIF, INFO_EXPORT_ARG_APPEND_TO_LDIF.get()); 069 argDisplayMap.put(ATTR_TASK_EXPORT_COMPRESS_LDIF, INFO_EXPORT_ARG_COMPRESS_LDIF.get()); 070 argDisplayMap.put(ATTR_TASK_EXPORT_ENCRYPT_LDIF, INFO_EXPORT_ARG_ENCRYPT_LDIF.get()); 071 argDisplayMap.put(ATTR_TASK_EXPORT_SIGN_HASH, INFO_EXPORT_ARG_SIGN_HASH.get()); 072 argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_ATTRIBUTE, INFO_EXPORT_ARG_INCL_ATTR.get()); 073 argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_ATTRIBUTE, INFO_EXPORT_ARG_EXCL_ATTR.get()); 074 argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_FILTER, INFO_EXPORT_ARG_INCL_FILTER.get()); 075 argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_FILTER, INFO_EXPORT_ARG_EXCL_FILTER.get()); 076 argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_BRANCH, INFO_EXPORT_ARG_INCL_BRANCH.get()); 077 argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_BRANCH, INFO_EXPORT_ARG_EXCL_BRANCH.get()); 078 argDisplayMap.put(ATTR_TASK_EXPORT_WRAP_COLUMN, INFO_EXPORT_ARG_WRAP_COLUMN.get()); 079 } 080 081 private String ldifFile; 082 private String backendID; 083 private int wrapColumn; 084 private boolean appendToLDIF; 085 private boolean compressLDIF; 086 private boolean encryptLDIF; 087 private boolean signHash; 088 private boolean includeOperationalAttributes; 089 private ArrayList<String> includeAttributeStrings; 090 private ArrayList<String> excludeAttributeStrings; 091 private ArrayList<String> includeFilterStrings; 092 private ArrayList<String> excludeFilterStrings; 093 private ArrayList<String> includeBranchStrings; 094 private ArrayList<String> excludeBranchStrings; 095 096 private LDIFExportConfig exportConfig; 097 098 @Override 099 public LocalizableMessage getDisplayName() { 100 return INFO_TASK_EXPORT_NAME.get(); 101 } 102 103 @Override 104 public LocalizableMessage getAttributeDisplayName(String name) { 105 return argDisplayMap.get(name); 106 } 107 108 @Override 109 public void initializeTask() throws DirectoryException 110 { 111 // If the client connection is available, then make sure the associated 112 // client has the LDIF_EXPORT privilege. 113 Operation operation = getOperation(); 114 if (operation != null) 115 { 116 ClientConnection clientConnection = operation.getClientConnection(); 117 if (! clientConnection.hasPrivilege(Privilege.LDIF_EXPORT, operation)) 118 { 119 LocalizableMessage message = ERR_TASK_LDIFEXPORT_INSUFFICIENT_PRIVILEGES.get(); 120 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 121 message); 122 } 123 } 124 125 Entry taskEntry = getTaskEntry(); 126 AttributeType typeWrapColumn = getSchema().getAttributeType(ATTR_TASK_EXPORT_WRAP_COLUMN); 127 128 ldifFile = toString(taskEntry, ATTR_TASK_EXPORT_LDIF_FILE); 129 File f = new File (ldifFile); 130 if (! f.isAbsolute()) 131 { 132 f = new File(DirectoryServer.getInstanceRoot(), ldifFile); 133 try 134 { 135 ldifFile = f.getCanonicalPath(); 136 } 137 catch (Exception ex) 138 { 139 ldifFile = f.getAbsolutePath(); 140 } 141 } 142 143 backendID = toString(taskEntry, ATTR_TASK_EXPORT_BACKEND_ID); 144 appendToLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_APPEND_TO_LDIF); 145 compressLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_COMPRESS_LDIF); 146 encryptLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_ENCRYPT_LDIF); 147 signHash = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_SIGN_HASH); 148 includeAttributeStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_ATTRIBUTE); 149 excludeAttributeStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_ATTRIBUTE); 150 includeFilterStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_FILTER); 151 excludeFilterStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_FILTER); 152 includeBranchStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_BRANCH); 153 excludeBranchStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_BRANCH); 154 155 List<Attribute> attrList = taskEntry.getAttribute(typeWrapColumn); 156 wrapColumn = TaskUtils.getSingleValueInteger(attrList, 0); 157 158 includeOperationalAttributes = toBoolean(taskEntry, true, ATTR_TASK_EXPORT_INCLUDE_OPERATIONAL_ATTRIBUTES); 159 } 160 161 private boolean toBoolean(Entry entry, boolean defaultValue, String attrName) 162 { 163 final AttributeType attrType = getSchema().getAttributeType(attrName); 164 final List<Attribute> attrs = entry.getAttribute(attrType); 165 return TaskUtils.getBoolean(attrs, defaultValue); 166 } 167 168 private ArrayList<String> toListOfString(Entry entry, String attrName) 169 { 170 final AttributeType attrType = getSchema().getAttributeType(attrName); 171 final List<Attribute> attrs = entry.getAttribute(attrType); 172 return TaskUtils.getMultiValueString(attrs); 173 } 174 175 private String toString(Entry entry, String attrName) 176 { 177 final AttributeType attrType = getSchema().getAttributeType(attrName); 178 final List<Attribute> attrs = entry.getAttribute(attrType); 179 return TaskUtils.getSingleValueString(attrs); 180 } 181 182 @Override 183 public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason) 184 { 185 if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) && 186 exportConfig != null) 187 { 188 addLogMessage(Severity.INFORMATION, TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get( 189 interruptReason)); 190 setTaskInterruptState(interruptState); 191 exportConfig.cancel(); 192 } 193 } 194 195 @Override 196 public boolean isInterruptable() { 197 return true; 198 } 199 200 @Override 201 protected TaskState runTask() 202 { 203 // See if there were any user-defined sets of include/exclude attributes or 204 // filters. If so, then process them. 205 HashSet<AttributeType> excludeAttributes = toAttributeTypes(excludeAttributeStrings); 206 HashSet<AttributeType> includeAttributes = toAttributeTypes(includeAttributeStrings); 207 208 ArrayList<SearchFilter> excludeFilters; 209 if (excludeFilterStrings == null) 210 { 211 excludeFilters = null; 212 } 213 else 214 { 215 excludeFilters = new ArrayList<>(); 216 for (String filterString : excludeFilterStrings) 217 { 218 try 219 { 220 excludeFilters.add(SearchFilter.createFilterFromString(filterString)); 221 } 222 catch (DirectoryException de) 223 { 224 logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, de.getMessageObject()); 225 return TaskState.STOPPED_BY_ERROR; 226 } 227 catch (Exception e) 228 { 229 logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, getExceptionMessage(e)); 230 return TaskState.STOPPED_BY_ERROR; 231 } 232 } 233 } 234 235 ArrayList<SearchFilter> includeFilters; 236 if (includeFilterStrings == null) 237 { 238 includeFilters = null; 239 } 240 else 241 { 242 includeFilters = new ArrayList<>(); 243 for (String filterString : includeFilterStrings) 244 { 245 try 246 { 247 includeFilters.add(SearchFilter.createFilterFromString(filterString)); 248 } 249 catch (DirectoryException de) 250 { 251 logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, de.getMessageObject()); 252 return TaskState.STOPPED_BY_ERROR; 253 } 254 catch (Exception e) 255 { 256 logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, getExceptionMessage(e)); 257 return TaskState.STOPPED_BY_ERROR; 258 } 259 } 260 } 261 262 // Get the backend into which the LDIF should be imported. 263 264 Backend<?> backend = DirectoryServer.getBackend(backendID); 265 266 if (backend == null) 267 { 268 logger.error(ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID, backendID); 269 return TaskState.STOPPED_BY_ERROR; 270 } 271 else if (!backend.supports(BackendOperation.LDIF_EXPORT)) 272 { 273 logger.error(ERR_LDIFEXPORT_CANNOT_EXPORT_BACKEND, backendID); 274 return TaskState.STOPPED_BY_ERROR; 275 } 276 277 ArrayList<DN> defaultIncludeBranches = new ArrayList<>(backend.getBaseDNs()); 278 279 ArrayList<DN> excludeBranches = new ArrayList<>(); 280 if (excludeBranchStrings != null) 281 { 282 for (String s : excludeBranchStrings) 283 { 284 DN excludeBranch; 285 try 286 { 287 excludeBranch = DN.valueOf(s); 288 } 289 catch (Exception e) 290 { 291 logger.error(ERR_LDIFEXPORT_CANNOT_DECODE_EXCLUDE_BASE, s, getExceptionMessage(e)); 292 return TaskState.STOPPED_BY_ERROR; 293 } 294 295 if (! excludeBranches.contains(excludeBranch)) 296 { 297 excludeBranches.add(excludeBranch); 298 } 299 } 300 } 301 302 ArrayList<DN> includeBranches; 303 if (!includeBranchStrings.isEmpty()) 304 { 305 includeBranches = new ArrayList<>(); 306 for (String s : includeBranchStrings) 307 { 308 DN includeBranch; 309 try 310 { 311 includeBranch = DN.valueOf(s); 312 } 313 catch (Exception e) 314 { 315 logger.error(ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE, s, getExceptionMessage(e)); 316 return TaskState.STOPPED_BY_ERROR; 317 } 318 319 if (! Backend.handlesEntry(includeBranch, defaultIncludeBranches, 320 excludeBranches)) 321 { 322 logger.error(ERR_LDIFEXPORT_INVALID_INCLUDE_BASE, s, backendID); 323 return TaskState.STOPPED_BY_ERROR; 324 } 325 326 includeBranches.add(includeBranch); 327 } 328 } 329 else 330 { 331 includeBranches = defaultIncludeBranches; 332 } 333 334 // Create the LDIF export configuration to use when reading the LDIF. 335 ExistingFileBehavior existingBehavior; 336 if (appendToLDIF) 337 { 338 existingBehavior = ExistingFileBehavior.APPEND; 339 } 340 else 341 { 342 existingBehavior = ExistingFileBehavior.OVERWRITE; 343 } 344 345 exportConfig = new LDIFExportConfig(ldifFile, existingBehavior); 346 exportConfig.setCompressData(compressLDIF); 347 exportConfig.setEncryptData(encryptLDIF); 348 exportConfig.setExcludeAttributes(excludeAttributes); 349 exportConfig.setExcludeBranches(excludeBranches); 350 exportConfig.setExcludeFilters(excludeFilters); 351 exportConfig.setIncludeAttributes(includeAttributes); 352 exportConfig.setIncludeBranches(includeBranches); 353 exportConfig.setIncludeFilters(includeFilters); 354 exportConfig.setSignHash(signHash); 355 exportConfig.setWrapColumn(wrapColumn); 356 exportConfig.setIncludeOperationalAttributes(includeOperationalAttributes); 357 358 // FIXME -- Should this be conditional? 359 exportConfig.setInvokeExportPlugins(true); 360 361 // Get the set of base DNs for the backend as an array. 362 DN[] baseDNs = new DN[defaultIncludeBranches.size()]; 363 defaultIncludeBranches.toArray(baseDNs); 364 365 // From here we must make sure we close the export config. 366 try 367 { 368 // Acquire a shared lock for the backend. 369 try 370 { 371 String lockFile = LockFileManager.getBackendLockFileName(backend); 372 StringBuilder failureReason = new StringBuilder(); 373 if (! LockFileManager.acquireSharedLock(lockFile, failureReason)) 374 { 375 logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason); 376 return TaskState.STOPPED_BY_ERROR; 377 } 378 } 379 catch (Exception e) 380 { 381 logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 382 return TaskState.STOPPED_BY_ERROR; 383 } 384 385 // From here we must make sure we release the shared backend lock. 386 try 387 { 388 // Launch the export. 389 try 390 { 391 DirectoryServer.notifyExportBeginning(backend, exportConfig); 392 addLogMessage(Severity.INFORMATION, INFO_LDIFEXPORT_PATH_TO_LDIF_FILE.get(ldifFile)); 393 backend.exportLDIF(exportConfig); 394 DirectoryServer.notifyExportEnded(backend, exportConfig, true); 395 } 396 catch (DirectoryException de) 397 { 398 DirectoryServer.notifyExportEnded(backend, exportConfig, false); 399 logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, de.getMessageObject()); 400 return TaskState.STOPPED_BY_ERROR; 401 } 402 catch (Exception e) 403 { 404 DirectoryServer.notifyExportEnded(backend, exportConfig, false); 405 logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, getExceptionMessage(e)); 406 return TaskState.STOPPED_BY_ERROR; 407 } 408 } 409 finally 410 { 411 // Release the shared lock on the backend. 412 try 413 { 414 String lockFile = LockFileManager.getBackendLockFileName(backend); 415 StringBuilder failureReason = new StringBuilder(); 416 if (! LockFileManager.releaseLock(lockFile, failureReason)) 417 { 418 logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason); 419 return TaskState.COMPLETED_WITH_ERRORS; 420 } 421 } 422 catch (Exception e) 423 { 424 logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 425 return TaskState.COMPLETED_WITH_ERRORS; 426 } 427 } 428 } 429 finally 430 { 431 // Clean up after the export by closing the export config. 432 exportConfig.close(); 433 } 434 435 // If the operation was cancelled delete the export file since 436 // if will be incomplete. 437 if (exportConfig.isCancelled()) 438 { 439 File f = new File(ldifFile); 440 if (f.exists()) 441 { 442 f.delete(); 443 } 444 } 445 446 // If we got here the task either completed successfully or was interrupted 447 return getFinalTaskState(); 448 } 449 450 private HashSet<AttributeType> toAttributeTypes(ArrayList<String> attributeStrings) 451 { 452 if (attributeStrings == null) 453 { 454 return null; 455 } 456 HashSet<AttributeType> attributes = new HashSet<>(); 457 for (String attrName : attributeStrings) 458 { 459 attributes.add(DirectoryServer.getSchema().getAttributeType(attrName)); 460 } 461 return attributes; 462 } 463}