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