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