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