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.backends.task;
028    
029    
030    
031    import java.io.File;
032    import java.net.InetAddress;
033    import java.util.ArrayList;
034    import java.util.HashSet;
035    import java.util.Iterator;
036    import java.util.List;
037    import java.util.concurrent.locks.Lock;
038    
039    import org.opends.messages.Message;
040    import org.opends.server.admin.Configuration;
041    import org.opends.server.admin.server.ConfigurationChangeListener;
042    import org.opends.server.admin.std.server.TaskBackendCfg;
043    import org.opends.server.api.Backend;
044    import org.opends.server.config.ConfigException;
045    import org.opends.server.config.ConfigEntry;
046    import org.opends.server.core.AddOperation;
047    import org.opends.server.core.DeleteOperation;
048    import org.opends.server.core.DirectoryServer;
049    import org.opends.server.core.ModifyOperation;
050    import org.opends.server.core.ModifyDNOperation;
051    import org.opends.server.core.SearchOperation;
052    import org.opends.server.loggers.debug.DebugTracer;
053    import org.opends.server.types.Attribute;
054    import org.opends.server.types.AttributeType;
055    import org.opends.server.types.AttributeValue;
056    import org.opends.server.types.BackupConfig;
057    import org.opends.server.types.BackupDirectory;
058    import org.opends.server.types.CanceledOperationException;
059    import org.opends.server.types.ConditionResult;
060    import org.opends.server.types.ConfigChangeResult;
061    import org.opends.server.types.DebugLogLevel;
062    import org.opends.server.types.DirectoryException;
063    import org.opends.server.types.DN;
064    import org.opends.server.types.Entry;
065    import org.opends.server.types.IndexType;
066    import org.opends.server.types.InitializationException;
067    import org.opends.server.types.LDIFExportConfig;
068    import org.opends.server.types.LDIFImportConfig;
069    import org.opends.server.types.LDIFImportResult;
070    import org.opends.server.types.LockManager;
071    import org.opends.server.types.Modification;
072    import org.opends.server.types.ModificationType;
073    import org.opends.server.types.RestoreConfig;
074    import org.opends.server.types.ResultCode;
075    import org.opends.server.types.SearchFilter;
076    import org.opends.server.types.SearchScope;
077    import org.opends.server.util.Validator;
078    
079    import static org.opends.messages.BackendMessages.*;
080    import static org.opends.server.config.ConfigConstants.*;
081    import static org.opends.server.loggers.debug.DebugLogger.*;
082    import static org.opends.server.util.StaticUtils.*;
083    
084    
085    
086    /**
087     * This class provides an implementation of a Directory Server backend that may
088     * be used to execute various kinds of administrative tasks on a one-time or
089     * recurring basis.
090     */
091    public class TaskBackend
092           extends Backend
093           implements ConfigurationChangeListener<TaskBackendCfg>
094    {
095      /**
096       * The tracer object for the debug logger.
097       */
098      private static final DebugTracer TRACER = getTracer();
099    
100    
101    
102      // The current configuration state.
103      private TaskBackendCfg currentConfig;
104    
105      // The DN of the configuration entry for this backend.
106      private DN configEntryDN;
107    
108      // The DN of the entry that will serve as the parent for all recurring task
109      // entries.
110      private DN recurringTaskParentDN;
111    
112      // The DN of the entry that will serve as the parent for all scheduled task
113      // entries.
114      private DN scheduledTaskParentDN;
115    
116      // The DN of the entry that will serve as the root for all task entries.
117      private DN taskRootDN;
118    
119      // The set of base DNs defined for this backend.
120      private DN[] baseDNs;
121    
122      // The set of supported controls for this backend.
123      private HashSet<String> supportedControls;
124    
125      // The set of supported features for this backend.
126      private HashSet<String> supportedFeatures;
127    
128      // The length of time in seconds after a task is completed that it should be
129      // removed from the set of scheduled tasks.
130      private long retentionTime;
131    
132      // The e-mail address to use for the sender from notification messages.
133      private String notificationSenderAddress;
134    
135      // The path to the task backing file.
136      private String taskBackingFile;
137    
138      // The task scheduler that will be responsible for actually invoking scheduled
139      // tasks.
140      private TaskScheduler taskScheduler;
141    
142    
143    
144      /**
145       * Creates a new backend with the provided information.  All backend
146       * implementations must implement a default constructor that use
147       * <CODE>super()</CODE> to invoke this constructor.
148       */
149      public TaskBackend()
150      {
151        super();
152    
153        // Perform all initialization in initializeBackend.
154      }
155    
156    
157    
158      /**
159       * {@inheritDoc}
160       */
161      @Override()
162      public void configureBackend(Configuration config)
163             throws ConfigException
164      {
165        Validator.ensureNotNull(config);
166        Validator.ensureTrue(config instanceof TaskBackendCfg);
167    
168        TaskBackendCfg cfg = (TaskBackendCfg)config;
169    
170        DN[] baseDNs = new DN[cfg.getBaseDN().size()];
171        cfg.getBaseDN().toArray(baseDNs);
172    
173        ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn());
174    
175        configEntryDN = configEntry.getDN();
176    
177    
178        // Make sure that the provided set of base DNs contains exactly one value.
179        // We will only allow one base for task entries.
180        if ((baseDNs == null) || (baseDNs.length == 0))
181        {
182          Message message = ERR_TASKBE_NO_BASE_DNS.get();
183          throw new ConfigException(message);
184        }
185        else if (baseDNs.length > 1)
186        {
187          Message message = ERR_TASKBE_MULTIPLE_BASE_DNS.get();
188          throw new ConfigException(message);
189        }
190        else
191        {
192          this.baseDNs = baseDNs;
193    
194          taskRootDN = baseDNs[0];
195    
196          String recurringTaskBaseString = RECURRING_TASK_BASE_RDN + "," +
197                                           taskRootDN.toString();
198          try
199          {
200            recurringTaskParentDN = DN.decode(recurringTaskBaseString);
201          }
202          catch (Exception e)
203          {
204            if (debugEnabled())
205            {
206              TRACER.debugCaught(DebugLogLevel.ERROR, e);
207            }
208    
209            // This should never happen.
210            Message message = ERR_TASKBE_CANNOT_DECODE_RECURRING_TASK_BASE_DN.get(
211                String.valueOf(recurringTaskBaseString), getExceptionMessage(e));
212            throw new ConfigException(message, e);
213          }
214    
215          String scheduledTaskBaseString = SCHEDULED_TASK_BASE_RDN + "," +
216                                           taskRootDN.toString();
217          try
218          {
219            scheduledTaskParentDN = DN.decode(scheduledTaskBaseString);
220          }
221          catch (Exception e)
222          {
223            if (debugEnabled())
224            {
225              TRACER.debugCaught(DebugLogLevel.ERROR, e);
226            }
227    
228            // This should never happen.
229            Message message = ERR_TASKBE_CANNOT_DECODE_SCHEDULED_TASK_BASE_DN.get(
230                String.valueOf(scheduledTaskBaseString), getExceptionMessage(e));
231            throw new ConfigException(message, e);
232          }
233        }
234    
235    
236        // Get the retention time that will be used to determine how long task
237        // information stays around once the associated task is completed.
238        retentionTime = cfg.getTaskRetentionTime();
239    
240    
241        // Get the notification sender address.
242        notificationSenderAddress = cfg.getNotificationSenderAddress();
243        if (notificationSenderAddress == null)
244        {
245          try
246          {
247            notificationSenderAddress = "opends-task-notification@" +
248                 InetAddress.getLocalHost().getCanonicalHostName();
249          }
250          catch (Exception e)
251          {
252            notificationSenderAddress = "opends-task-notification@opends.org";
253          }
254        }
255    
256    
257        // Get the path to the task data backing file.
258        taskBackingFile = cfg.getTaskBackingFile();
259    
260        // Define an empty sets for the supported controls and features.
261        supportedControls = new HashSet<String>(0);
262        supportedFeatures = new HashSet<String>(0);
263    
264        currentConfig = cfg;
265      }
266    
267    
268    
269      /**
270       * {@inheritDoc}
271       */
272      @Override()
273      public void initializeBackend()
274             throws ConfigException, InitializationException
275      {
276        // Create the scheduler and initialize it from the backing file.
277        taskScheduler = new TaskScheduler(this);
278        taskScheduler.start();
279    
280    
281        // Register with the Directory Server as a configurable component.
282        currentConfig.addTaskChangeListener(this);
283    
284    
285        // Register the task base as a private suffix.
286        try
287        {
288          DirectoryServer.registerBaseDN(taskRootDN, this, true);
289        }
290        catch (Exception e)
291        {
292          if (debugEnabled())
293          {
294            TRACER.debugCaught(DebugLogLevel.ERROR, e);
295          }
296    
297          Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
298              taskRootDN.toString(), getExceptionMessage(e));
299          throw new InitializationException(message, e);
300        }
301      }
302    
303    
304    
305      /**
306       * {@inheritDoc}
307       */
308      @Override()
309      public void finalizeBackend()
310      {
311        currentConfig.removeTaskChangeListener(this);
312    
313    
314        try
315        {
316          taskScheduler.stopScheduler();
317        }
318        catch (Exception e)
319        {
320          if (debugEnabled())
321          {
322            TRACER.debugCaught(DebugLogLevel.ERROR, e);
323          }
324        }
325    
326        try
327        {
328    
329          Message message = INFO_TASKBE_INTERRUPTED_BY_SHUTDOWN.get();
330    
331          taskScheduler.interruptRunningTasks(TaskState.STOPPED_BY_SHUTDOWN,
332                                              message, true);
333        }
334        catch (Exception e)
335        {
336          if (debugEnabled())
337          {
338            TRACER.debugCaught(DebugLogLevel.ERROR, e);
339          }
340        }
341    
342        try
343        {
344          DirectoryServer.deregisterBaseDN(taskRootDN);
345        }
346        catch (Exception e)
347        {
348          if (debugEnabled())
349          {
350            TRACER.debugCaught(DebugLogLevel.ERROR, e);
351          }
352        }
353      }
354    
355    
356    
357      /**
358       * {@inheritDoc}
359       */
360      @Override()
361      public DN[] getBaseDNs()
362      {
363        return baseDNs;
364      }
365    
366    
367    
368      /**
369       * {@inheritDoc}
370       */
371      @Override()
372      public long getEntryCount()
373      {
374        if (taskScheduler != null)
375        {
376          return taskScheduler.getEntryCount();
377        }
378    
379        return -1;
380      }
381    
382    
383    
384      /**
385       * {@inheritDoc}
386       */
387      @Override()
388      public boolean isLocal()
389      {
390        // For the purposes of this method, this is a local backend.
391        return true;
392      }
393    
394    
395    
396      /**
397       * {@inheritDoc}
398       */
399      @Override()
400      public boolean isIndexed(AttributeType attributeType, IndexType indexType)
401      {
402        // All searches in this backend will always be considered indexed.
403        return true;
404      }
405    
406    
407    
408      /**
409       * {@inheritDoc}
410       */
411      @Override()
412      public ConditionResult hasSubordinates(DN entryDN)
413             throws DirectoryException
414      {
415        long ret = numSubordinates(entryDN, false);
416        if(ret < 0)
417        {
418          return ConditionResult.UNDEFINED;
419        }
420        else if(ret == 0)
421        {
422          return ConditionResult.FALSE;
423        }
424        else
425        {
426          return ConditionResult.TRUE;
427        }
428      }
429    
430    
431    
432      /**
433       * {@inheritDoc}
434       */
435      @Override()
436      public long numSubordinates(DN entryDN, boolean subtree)
437          throws DirectoryException
438      {
439        if (entryDN == null)
440        {
441          return -1;
442        }
443    
444        if (entryDN.equals(taskRootDN))
445        {
446          // scheduled and recurring parents.
447          if(!subtree)
448          {
449            return 2;
450          }
451          else
452          {
453            return taskScheduler.getScheduledTaskCount() +
454                taskScheduler.getRecurringTaskCount() + 2;
455          }
456        }
457        else if (entryDN.equals(scheduledTaskParentDN))
458        {
459          return taskScheduler.getScheduledTaskCount();
460        }
461        else if (entryDN.equals(recurringTaskParentDN))
462        {
463          return taskScheduler.getRecurringTaskCount();
464        }
465    
466        DN parentDN = entryDN.getParentDNInSuffix();
467        if (parentDN == null)
468        {
469          return -1;
470        }
471    
472        if (parentDN.equals(scheduledTaskParentDN) &&
473            taskScheduler.getScheduledTask(entryDN) != null)
474        {
475          return 0;
476        }
477        else if (parentDN.equals(recurringTaskParentDN) &&
478            taskScheduler.getRecurringTask(entryDN) != null)
479        {
480          return 0;
481        }
482        else
483        {
484          return -1;
485        }
486      }
487    
488    
489    
490      /**
491       * {@inheritDoc}
492       */
493      @Override()
494      public Entry getEntry(DN entryDN)
495             throws DirectoryException
496      {
497        if (entryDN == null)
498        {
499          return null;
500        }
501    
502        if (entryDN.equals(taskRootDN))
503        {
504          return taskScheduler.getTaskRootEntry();
505        }
506        else if (entryDN.equals(scheduledTaskParentDN))
507        {
508          return taskScheduler.getScheduledTaskParentEntry();
509        }
510        else if (entryDN.equals(recurringTaskParentDN))
511        {
512          return taskScheduler.getRecurringTaskParentEntry();
513        }
514    
515        DN parentDN = entryDN.getParentDNInSuffix();
516        if (parentDN == null)
517        {
518          return null;
519        }
520    
521        if (parentDN.equals(scheduledTaskParentDN))
522        {
523          return taskScheduler.getScheduledTaskEntry(entryDN);
524        }
525        else if (parentDN.equals(recurringTaskParentDN))
526        {
527          return taskScheduler.getRecurringTaskEntry(entryDN);
528        }
529        else
530        {
531          // If we've gotten here then this is not an entry that should exist in the
532          // task backend.
533          return null;
534        }
535      }
536    
537    
538    
539      /**
540       * {@inheritDoc}
541       */
542      @Override()
543      public void addEntry(Entry entry, AddOperation addOperation)
544             throws DirectoryException
545      {
546        Entry e = entry.duplicate(false);
547    
548        // Get the DN for the entry and then get its parent.
549        DN entryDN = e.getDN();
550        DN parentDN = entryDN.getParentDNInSuffix();
551    
552        if (parentDN == null)
553        {
554          Message message = ERR_TASKBE_ADD_DISALLOWED_DN.
555              get(String.valueOf(scheduledTaskParentDN),
556                  String.valueOf(recurringTaskParentDN));
557          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
558        }
559    
560        // If the parent DN is equal to the parent for scheduled tasks, then try to
561        // treat the provided entry like a scheduled task.
562        if (parentDN.equals(scheduledTaskParentDN))
563        {
564          Task task = taskScheduler.entryToScheduledTask(e, addOperation);
565          taskScheduler.scheduleTask(task, true);
566          return;
567        }
568    
569        // If the parent DN is equal to the parent for recurring tasks, then try to
570        // treat the provided entry like a recurring task.
571        if (parentDN.equals(recurringTaskParentDN))
572        {
573          RecurringTask recurringTask = taskScheduler.entryToRecurringTask(e);
574          taskScheduler.addRecurringTask(recurringTask, true);
575          return;
576        }
577    
578        // We won't allow the entry to be added.
579        Message message = ERR_TASKBE_ADD_DISALLOWED_DN.
580            get(String.valueOf(scheduledTaskParentDN),
581                String.valueOf(recurringTaskParentDN));
582        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
583      }
584    
585    
586    
587      /**
588       * {@inheritDoc}
589       */
590      @Override()
591      public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
592             throws DirectoryException
593      {
594        // Get the parent for the provided entry DN.  It must be either the
595        // scheduled or recurring task parent DN.
596        DN parentDN = entryDN.getParentDNInSuffix();
597        if (parentDN == null)
598        {
599          Message message =
600              ERR_TASKBE_DELETE_INVALID_ENTRY.get(String.valueOf(entryDN));
601          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
602        }
603        else if (parentDN.equals(scheduledTaskParentDN))
604        {
605          // It's a scheduled task.  Make sure that it exists.
606          Task t = taskScheduler.getScheduledTask(entryDN);
607          if (t == null)
608          {
609            Message message =
610                ERR_TASKBE_DELETE_NO_SUCH_TASK.get(String.valueOf(entryDN));
611            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
612          }
613    
614    
615          // Look at the state of the task.  We will allow pending and completed
616          // tasks to be removed, but not running tasks.
617          TaskState state = t.getTaskState();
618          if (TaskState.isPending(state))
619          {
620            taskScheduler.removePendingTask(t.getTaskID());
621          }
622          else if (TaskState.isDone(t.getTaskState()))
623          {
624            taskScheduler.removeCompletedTask(t.getTaskID());
625          }
626          else
627          {
628            Message message =
629                ERR_TASKBE_DELETE_RUNNING.get(String.valueOf(entryDN));
630            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
631          }
632        }
633        else if (parentDN.equals(recurringTaskParentDN))
634        {
635          // It's a recurring task.  Make sure that it exists.
636          RecurringTask rt = taskScheduler.getRecurringTask(entryDN);
637          if (rt == null)
638          {
639            Message message = ERR_TASKBE_DELETE_NO_SUCH_RECURRING_TASK.get(
640                String.valueOf(entryDN));
641            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
642          }
643    
644    
645          // Try to remove the recurring task.  This will fail if there are any
646          // associated iterations pending or running.
647          taskScheduler.removeRecurringTask(rt.getRecurringTaskID());
648        }
649        else
650        {
651          Message message =
652              ERR_TASKBE_DELETE_INVALID_ENTRY.get(String.valueOf(entryDN));
653          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
654        }
655      }
656    
657    
658    
659      /**
660       * {@inheritDoc}
661       */
662      @Override()
663      public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
664             throws DirectoryException
665      {
666        DN entryDN = entry.getDN();
667    
668        Lock entryLock = null;
669        if (! taskScheduler.holdsSchedulerLock())
670        {
671          for (int i=0; i < 3; i++)
672          {
673            entryLock = LockManager.lockWrite(entryDN);
674            if (entryLock != null)
675            {
676              break;
677            }
678          }
679    
680          if (entryLock == null)
681          {
682            throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
683                                         ERR_TASKBE_MODIFY_CANNOT_LOCK_ENTRY.get(
684                                              String.valueOf(entryDN)));
685          }
686        }
687    
688        try
689        {
690          // Get the parent for the provided entry DN.  It must be either the
691          // scheduled or recurring task parent DN.
692          DN parentDN = entryDN.getParentDNInSuffix();
693          if (parentDN == null)
694          {
695            Message message =
696                ERR_TASKBE_MODIFY_INVALID_ENTRY.get(String.valueOf(entryDN));
697            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
698          }
699          else if (parentDN.equals(scheduledTaskParentDN))
700          {
701            // It's a scheduled task.  Make sure that it exists.
702            Task t = taskScheduler.getScheduledTask(entryDN);
703            if (t == null)
704            {
705              Message message =
706                  ERR_TASKBE_MODIFY_NO_SUCH_TASK.get(String.valueOf(entryDN));
707              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
708            }
709    
710    
711            // Look at the state of the task.  We will allow anything to be altered
712            // for a pending task.  For a running task, we will only allow the state
713            // to be altered in order to cancel it.  We will not allow any
714            // modifications for completed tasks.
715            TaskState state = t.getTaskState();
716            if (TaskState.isPending(state))
717            {
718              Task newTask =
719                        taskScheduler.entryToScheduledTask(entry, modifyOperation);
720              taskScheduler.removePendingTask(t.getTaskID());
721              taskScheduler.scheduleTask(newTask, true);
722              return;
723            }
724            else if (TaskState.isRunning(state))
725            {
726              // If the task is running, we will only allow it to be cancelled.
727              // This will only be allowed using the replace modification type on
728              // the ds-task-state attribute if the value starts with "cancel" or
729              // "stop".  In that case, we'll cancel the task.
730              boolean acceptable = true;
731              for (Modification m : modifyOperation.getModifications())
732              {
733                if (m.isInternal())
734                {
735                  continue;
736                }
737    
738                if (m.getModificationType() != ModificationType.REPLACE)
739                {
740                  acceptable = false;
741                  break;
742                }
743    
744                Attribute a = m.getAttribute();
745                AttributeType at = a.getAttributeType();
746                if (! at.hasName(ATTR_TASK_STATE))
747                {
748                  acceptable = false;
749                  break;
750                }
751    
752                Iterator<AttributeValue> iterator = a.getValues().iterator();
753                if (! iterator.hasNext())
754                {
755                  acceptable = false;
756                  break;
757                }
758    
759                AttributeValue v = iterator.next();
760                String valueString = toLowerCase(v.getStringValue());
761                if (! (valueString.startsWith("cancel") ||
762                       valueString.startsWith("stop")))
763                {
764                  acceptable = false;
765                  break;
766                }
767    
768                if (iterator.hasNext())
769                {
770                  acceptable = false;
771                  break;
772                }
773              }
774    
775              if (acceptable)
776              {
777                Message message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get();
778                t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message);
779                return;
780              }
781              else
782              {
783                Message message =
784                     ERR_TASKBE_MODIFY_RUNNING.get(String.valueOf(entryDN));
785                throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
786                                             message);
787              }
788            }
789            else
790            {
791              Message message =
792                  ERR_TASKBE_MODIFY_COMPLETED.get(String.valueOf(entryDN));
793              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
794                                           message);
795            }
796          }
797          else if (parentDN.equals(recurringTaskParentDN))
798          {
799            // We don't currently support altering recurring tasks.
800            Message message =
801                ERR_TASKBE_MODIFY_RECURRING.get(String.valueOf(entryDN));
802            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
803          }
804          else
805          {
806            Message message =
807                ERR_TASKBE_MODIFY_INVALID_ENTRY.get(String.valueOf(entryDN));
808            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
809          }
810        }
811        finally
812        {
813          if (entryLock != null)
814          {
815            LockManager.unlock(entryDN, entryLock);
816          }
817        }
818      }
819    
820    
821    
822      /**
823       * {@inheritDoc}
824       */
825      @Override()
826      public void renameEntry(DN currentDN, Entry entry,
827                                       ModifyDNOperation modifyDNOperation)
828             throws DirectoryException
829      {
830        Message message = ERR_TASKBE_MODIFY_DN_NOT_SUPPORTED.get();
831        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
832      }
833    
834    
835    
836      /**
837       * {@inheritDoc}
838       */
839      @Override()
840      public void search(SearchOperation searchOperation)
841             throws DirectoryException, CanceledOperationException {
842        // Look at the base DN and scope for the search operation to decide which
843        // entries we need to look at.
844        boolean searchRoot            = false;
845        boolean searchScheduledParent = false;
846        boolean searchScheduledTasks  = false;
847        boolean searchRecurringParent = false;
848        boolean searchRecurringTasks  = false;
849    
850        DN           baseDN       = searchOperation.getBaseDN();
851        SearchScope  searchScope  = searchOperation.getScope();
852        SearchFilter searchFilter = searchOperation.getFilter();
853    
854        if (baseDN.equals(taskRootDN))
855        {
856          switch (searchScope)
857          {
858            case BASE_OBJECT:
859              searchRoot = true;
860              break;
861            case SINGLE_LEVEL:
862              searchScheduledParent = true;
863              searchRecurringParent = true;
864              break;
865            case WHOLE_SUBTREE:
866              searchRoot            = true;
867              searchScheduledParent = true;
868              searchRecurringParent = true;
869              searchScheduledTasks  = true;
870              searchRecurringTasks  = true;
871              break;
872            case SUBORDINATE_SUBTREE:
873              searchScheduledParent = true;
874              searchRecurringParent = true;
875              searchScheduledTasks  = true;
876              searchRecurringTasks  = true;
877              break;
878          }
879        }
880        else if (baseDN.equals(scheduledTaskParentDN))
881        {
882          switch (searchScope)
883          {
884            case BASE_OBJECT:
885              searchScheduledParent = true;
886              break;
887            case SINGLE_LEVEL:
888              searchScheduledTasks = true;
889              break;
890            case WHOLE_SUBTREE:
891              searchScheduledParent = true;
892              searchScheduledTasks  = true;
893              break;
894            case SUBORDINATE_SUBTREE:
895              searchScheduledTasks  = true;
896              break;
897          }
898        }
899        else if (baseDN.equals(recurringTaskParentDN))
900        {
901          switch (searchScope)
902          {
903            case BASE_OBJECT:
904              searchRecurringParent = true;
905              break;
906            case SINGLE_LEVEL:
907              searchRecurringTasks = true;
908              break;
909            case WHOLE_SUBTREE:
910              searchRecurringParent = true;
911              searchRecurringTasks  = true;
912              break;
913            case SUBORDINATE_SUBTREE:
914              searchRecurringTasks  = true;
915              break;
916          }
917        }
918        else
919        {
920          DN parentDN = baseDN.getParentDNInSuffix();
921          if (parentDN == null)
922          {
923            Message message =
924                ERR_TASKBE_SEARCH_INVALID_BASE.get(String.valueOf(baseDN));
925            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
926          }
927          else if (parentDN.equals(scheduledTaskParentDN))
928          {
929            Lock lock = taskScheduler.readLockEntry(baseDN);
930    
931            try
932            {
933              Entry e = taskScheduler.getScheduledTaskEntry(baseDN);
934              if (e == null)
935              {
936                Message message =
937                    ERR_TASKBE_SEARCH_NO_SUCH_TASK.get(String.valueOf(baseDN));
938                throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
939                                             scheduledTaskParentDN, null);
940              }
941    
942              if (((searchScope == SearchScope.BASE_OBJECT) ||
943                   (searchScope == SearchScope.WHOLE_SUBTREE)) &&
944                  searchFilter.matchesEntry(e))
945              {
946                searchOperation.returnEntry(e, null);
947              }
948    
949              return;
950            }
951            finally
952            {
953              taskScheduler.unlockEntry(baseDN, lock);
954            }
955          }
956          else if (parentDN.equals(recurringTaskParentDN))
957          {
958            Lock lock = taskScheduler.readLockEntry(baseDN);
959    
960            try
961            {
962              Entry e = taskScheduler.getRecurringTaskEntry(baseDN);
963              if (e == null)
964              {
965                Message message = ERR_TASKBE_SEARCH_NO_SUCH_RECURRING_TASK.get(
966                    String.valueOf(baseDN));
967                throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
968                                             recurringTaskParentDN, null);
969              }
970    
971              if (((searchScope == SearchScope.BASE_OBJECT) ||
972                   (searchScope == SearchScope.WHOLE_SUBTREE)) &&
973                  searchFilter.matchesEntry(e))
974              {
975                searchOperation.returnEntry(e, null);
976              }
977    
978              return;
979            }
980            finally
981            {
982              taskScheduler.unlockEntry(baseDN, lock);
983            }
984          }
985          else
986          {
987            Message message =
988                ERR_TASKBE_SEARCH_INVALID_BASE.get(String.valueOf(baseDN));
989            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
990          }
991        }
992    
993    
994        if (searchRoot)
995        {
996          Entry e = taskScheduler.getTaskRootEntry();
997          if (searchFilter.matchesEntry(e))
998          {
999            if (! searchOperation.returnEntry(e, null))
1000            {
1001              return;
1002            }
1003          }
1004        }
1005    
1006    
1007        if (searchScheduledParent)
1008        {
1009          Entry e = taskScheduler.getScheduledTaskParentEntry();
1010          if (searchFilter.matchesEntry(e))
1011          {
1012            if (! searchOperation.returnEntry(e, null))
1013            {
1014              return;
1015            }
1016          }
1017        }
1018    
1019    
1020        if (searchScheduledTasks)
1021        {
1022          if (! taskScheduler.searchScheduledTasks(searchOperation))
1023          {
1024            return;
1025          }
1026        }
1027    
1028    
1029        if (searchRecurringParent)
1030        {
1031          Entry e = taskScheduler.getRecurringTaskParentEntry();
1032          if (searchFilter.matchesEntry(e))
1033          {
1034            if (! searchOperation.returnEntry(e, null))
1035            {
1036              return;
1037            }
1038          }
1039        }
1040    
1041    
1042        if (searchRecurringTasks)
1043        {
1044          if (! taskScheduler.searchRecurringTasks(searchOperation))
1045          {
1046            return;
1047          }
1048        }
1049      }
1050    
1051    
1052    
1053      /**
1054       * {@inheritDoc}
1055       */
1056      @Override()
1057      public HashSet<String> getSupportedControls()
1058      {
1059        return supportedControls;
1060      }
1061    
1062    
1063    
1064      /**
1065       * {@inheritDoc}
1066       */
1067      @Override()
1068      public HashSet<String> getSupportedFeatures()
1069      {
1070        return supportedFeatures;
1071      }
1072    
1073    
1074    
1075      /**
1076       * {@inheritDoc}
1077       */
1078      @Override()
1079      public boolean supportsLDIFExport()
1080      {
1081        // LDIF exports are supported.
1082        return true;
1083      }
1084    
1085    
1086    
1087      /**
1088       * {@inheritDoc}
1089       */
1090      @Override()
1091      public void exportLDIF(LDIFExportConfig exportConfig)
1092             throws DirectoryException
1093      {
1094        // FIXME -- Implement support for exporting to LDIF.
1095      }
1096    
1097    
1098    
1099      /**
1100       * {@inheritDoc}
1101       */
1102      @Override()
1103      public boolean supportsLDIFImport()
1104      {
1105        // This backend does not support LDIF imports.
1106        return false;
1107      }
1108    
1109    
1110    
1111      /**
1112       * {@inheritDoc}
1113       */
1114      @Override()
1115      public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
1116             throws DirectoryException
1117      {
1118        // This backend does not support LDIF imports.
1119        Message message = ERR_TASKBE_IMPORT_NOT_SUPPORTED.get();
1120        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1121      }
1122    
1123    
1124    
1125      /**
1126       * {@inheritDoc}
1127       */
1128      @Override()
1129      public boolean supportsBackup()
1130      {
1131        // This backend does provide a backup/restore mechanism.
1132        return true;
1133      }
1134    
1135    
1136    
1137      /**
1138       * {@inheritDoc}
1139       */
1140      @Override()
1141      public boolean supportsBackup(BackupConfig backupConfig,
1142                                    StringBuilder unsupportedReason)
1143      {
1144        // This backend does provide a backup/restore mechanism.
1145        return true;
1146      }
1147    
1148    
1149    
1150      /**
1151       * {@inheritDoc}
1152       */
1153      @Override()
1154      public void createBackup(BackupConfig backupConfig)
1155             throws DirectoryException
1156      {
1157        // NYI -- Create the backup.
1158      }
1159    
1160    
1161    
1162      /**
1163       * {@inheritDoc}
1164       */
1165      @Override()
1166      public void removeBackup(BackupDirectory backupDirectory,
1167                               String backupID)
1168             throws DirectoryException
1169      {
1170        // NYI -- Remove the backup.
1171      }
1172    
1173    
1174    
1175      /**
1176       * {@inheritDoc}
1177       */
1178      @Override()
1179      public boolean supportsRestore()
1180      {
1181        // This backend does provide a backup/restore mechanism.
1182        return true;
1183      }
1184    
1185    
1186    
1187      /**
1188       * {@inheritDoc}
1189       */
1190      @Override()
1191      public void restoreBackup(RestoreConfig restoreConfig)
1192             throws DirectoryException
1193      {
1194        // NYI -- Restore the backup.
1195      }
1196    
1197    
1198    
1199      /**
1200       * {@inheritDoc}
1201       */
1202      @Override()
1203      public boolean isConfigurationAcceptable(Configuration configuration,
1204                                               List<Message> unacceptableReasons)
1205      {
1206        TaskBackendCfg config = (TaskBackendCfg) configuration;
1207        return isConfigAcceptable(config, unacceptableReasons, null);
1208      }
1209    
1210    
1211    
1212      /**
1213       * {@inheritDoc}
1214       */
1215      public boolean isConfigurationChangeAcceptable(TaskBackendCfg configEntry,
1216                                                List<Message> unacceptableReasons)
1217      {
1218        return isConfigAcceptable(configEntry, unacceptableReasons,
1219                                  taskBackingFile);
1220      }
1221    
1222    
1223    
1224      /**
1225       * Indicates whether the provided configuration is acceptable for this task
1226       * backend.
1227       *
1228       * @param  config               The configuration for which to make the
1229       *                              determination.
1230       * @param  unacceptableReasons  A list into which the unacceptable reasons
1231       *                              should be placed.
1232       * @param  taskBackingFile      The currently-configured task backing file, or
1233       *                              {@code null} if it should not be taken into
1234       *                              account.
1235       *
1236       * @return  {@code true} if the configuration is acceptable, or {@code false}
1237       *          if not.
1238       */
1239      private static boolean isConfigAcceptable(TaskBackendCfg config,
1240                                                List<Message> unacceptableReasons,
1241                                                String taskBackingFile)
1242      {
1243        boolean configIsAcceptable = true;
1244    
1245    
1246        try
1247        {
1248          String tmpBackingFile = config.getTaskBackingFile();
1249          if ((taskBackingFile == null) ||
1250              (! taskBackingFile.equals(tmpBackingFile)))
1251          {
1252            File f = getFileForPath(tmpBackingFile);
1253            if (f.exists())
1254            {
1255              // This is only a problem if it's different from the active one.
1256              if (taskBackingFile != null)
1257              {
1258                unacceptableReasons.add(
1259                        ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile));
1260                configIsAcceptable = false;
1261              }
1262            }
1263            else
1264            {
1265              File p = f.getParentFile();
1266              if (p == null)
1267              {
1268                unacceptableReasons.add(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get(
1269                        tmpBackingFile));
1270                configIsAcceptable = false;
1271              }
1272              else if (! p.exists())
1273              {
1274    
1275                unacceptableReasons.add(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get(
1276                        p.getPath(),
1277                        tmpBackingFile));
1278                configIsAcceptable = false;
1279              }
1280              else if (! p.isDirectory())
1281              {
1282                unacceptableReasons.add(
1283                        ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get(
1284                                p.getPath(),
1285                                tmpBackingFile));
1286                configIsAcceptable = false;
1287              }
1288            }
1289          }
1290        }
1291        catch (Exception e)
1292        {
1293          if (debugEnabled())
1294          {
1295            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1296          }
1297    
1298          unacceptableReasons.add(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get(
1299                  getExceptionMessage(e)));
1300    
1301          configIsAcceptable = false;
1302        }
1303    
1304        return configIsAcceptable;
1305      }
1306    
1307    
1308    
1309      /**
1310       * {@inheritDoc}
1311       */
1312      public ConfigChangeResult applyConfigurationChange(TaskBackendCfg configEntry)
1313      {
1314        ResultCode         resultCode          = ResultCode.SUCCESS;
1315        boolean            adminActionRequired = false;
1316        ArrayList<Message> messages            = new ArrayList<Message>();
1317    
1318    
1319        String tmpBackingFile = taskBackingFile;
1320        try
1321        {
1322          {
1323            tmpBackingFile = configEntry.getTaskBackingFile();
1324            if (! taskBackingFile.equals(tmpBackingFile))
1325            {
1326              File f = getFileForPath(tmpBackingFile);
1327              if (f.exists())
1328              {
1329    
1330                messages.add(ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile));
1331                resultCode = ResultCode.CONSTRAINT_VIOLATION;
1332              }
1333              else
1334              {
1335                File p = f.getParentFile();
1336                if (p == null)
1337                {
1338    
1339                  messages.add(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get(
1340                          tmpBackingFile));
1341                  resultCode = ResultCode.CONSTRAINT_VIOLATION;
1342                }
1343                else if (! p.exists())
1344                {
1345    
1346                  messages.add(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get(
1347                          String.valueOf(p), tmpBackingFile));
1348                  resultCode = ResultCode.CONSTRAINT_VIOLATION;
1349                }
1350                else if (! p.isDirectory())
1351                {
1352    
1353                  messages.add(ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get(
1354                          String.valueOf(p), tmpBackingFile));
1355                  resultCode = ResultCode.CONSTRAINT_VIOLATION;
1356                }
1357              }
1358            }
1359          }
1360        }
1361        catch (Exception e)
1362        {
1363          if (debugEnabled())
1364          {
1365            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1366          }
1367    
1368          messages.add(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get(
1369                  getExceptionMessage(e)));
1370    
1371          resultCode = DirectoryServer.getServerErrorResultCode();
1372        }
1373    
1374    
1375        long tmpRetentionTime = configEntry.getTaskRetentionTime();
1376    
1377    
1378        if (resultCode == ResultCode.SUCCESS)
1379        {
1380          // Everything looks OK, so apply the changes.
1381          if (retentionTime != tmpRetentionTime)
1382          {
1383            retentionTime = tmpRetentionTime;
1384    
1385            messages.add(INFO_TASKBE_UPDATED_RETENTION_TIME.get(retentionTime));
1386          }
1387    
1388    
1389          if (! taskBackingFile.equals(tmpBackingFile))
1390          {
1391            taskBackingFile = tmpBackingFile;
1392            taskScheduler.writeState();
1393    
1394            messages.add(INFO_TASKBE_UPDATED_BACKING_FILE.get(taskBackingFile));
1395          }
1396        }
1397    
1398    
1399        String tmpNotificationAddress = configEntry.getNotificationSenderAddress();
1400        if (tmpNotificationAddress == null)
1401        {
1402          try
1403          {
1404            tmpNotificationAddress = "opends-task-notification@" +
1405                 InetAddress.getLocalHost().getCanonicalHostName();
1406          }
1407          catch (Exception e)
1408          {
1409            tmpNotificationAddress = "opends-task-notification@opends.org";
1410          }
1411        }
1412        notificationSenderAddress = tmpNotificationAddress;
1413    
1414    
1415        currentConfig = configEntry;
1416        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1417      }
1418    
1419    
1420    
1421      /**
1422       * Retrieves the DN of the configuration entry for this task backend.
1423       *
1424       * @return  The DN of the configuration entry for this task backend.
1425       */
1426      public DN getConfigEntryDN()
1427      {
1428        return configEntryDN;
1429      }
1430    
1431    
1432    
1433      /**
1434       * Retrieves the path to the backing file that will hold the scheduled and
1435       * recurring task definitions.
1436       *
1437       * @return  The path to the backing file that will hold the scheduled and
1438       *          recurring task definitions.
1439       */
1440      public String getTaskBackingFile()
1441      {
1442        File f = getFileForPath(taskBackingFile);
1443        return f.getPath();
1444      }
1445    
1446    
1447    
1448      /**
1449       * Retrieves the sender address that should be used for e-mail notifications
1450       * of task completion.
1451       *
1452       * @return  The sender address that should be used for e-mail notifications of
1453       *          task completion.
1454       */
1455      public String getNotificationSenderAddress()
1456      {
1457        return notificationSenderAddress;
1458      }
1459    
1460    
1461    
1462      /**
1463       * Retrieves the length of time in seconds that information for a task should
1464       * be retained after processing on it has completed.
1465       *
1466       * @return  The length of time in seconds that information for a task should
1467       *          be retained after processing on it has completed.
1468       */
1469      public long getRetentionTime()
1470      {
1471        return retentionTime;
1472      }
1473    
1474    
1475    
1476      /**
1477       * Retrieves the DN of the entry that is the root for all task information in
1478       * the Directory Server.
1479       *
1480       * @return  The DN of the entry that is the root for all task information in
1481       *          the Directory Server.
1482       */
1483      public DN getTaskRootDN()
1484      {
1485        return taskRootDN;
1486      }
1487    
1488    
1489    
1490      /**
1491       * Retrieves the DN of the entry that is the immediate parent for all
1492       * recurring task information in the Directory Server.
1493       *
1494       * @return  The DN of the entry that is the immediate parent for all recurring
1495       *          task information in the Directory Server.
1496       */
1497      public DN getRecurringTasksParentDN()
1498      {
1499        return recurringTaskParentDN;
1500      }
1501    
1502    
1503    
1504      /**
1505       * Retrieves the DN of the entry that is the immediate parent for all
1506       * scheduled task information in the Directory Server.
1507       *
1508       * @return  The DN of the entry that is the immediate parent for all scheduled
1509       *          task information in the Directory Server.
1510       */
1511      public DN getScheduledTasksParentDN()
1512      {
1513        return scheduledTaskParentDN;
1514      }
1515    
1516    
1517    
1518      /**
1519       * Retrieves the scheduled task for the entry with the provided DN.
1520       *
1521       * @param  taskEntryDN  The DN of the entry for the task to retrieve.
1522       *
1523       * @return  The requested task, or {@code null} if there is no task with the
1524       *          specified entry DN.
1525       */
1526      public Task getScheduledTask(DN taskEntryDN)
1527      {
1528        return taskScheduler.getScheduledTask(taskEntryDN);
1529      }
1530    
1531    
1532    
1533      /**
1534       * Retrieves the recurring task for the entry with the provided DN.
1535       *
1536       * @param  taskEntryDN  The DN of the entry for the recurring task to
1537       *                      retrieve.
1538       *
1539       * @return  The requested recurring task, or {@code null} if there is no task
1540       *          with the specified entry DN.
1541       */
1542      public RecurringTask getRecurringTask(DN taskEntryDN)
1543      {
1544        return taskScheduler.getRecurringTask(taskEntryDN);
1545      }
1546    
1547    
1548    
1549      /**
1550       * {@inheritDoc}
1551       */
1552      public void preloadEntryCache() throws UnsupportedOperationException {
1553        throw new UnsupportedOperationException("Operation not supported.");
1554      }
1555    }
1556