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