/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
*/
package org.opends.server.tools.tasks;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.types.ResultCode.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.messages.Message;
import org.opends.server.backends.task.FailedDependencyAction;
import org.opends.server.backends.task.TaskState;
import org.opends.server.config.ConfigConstants;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.ldap.AddRequestProtocolOp;
import org.opends.server.protocols.ldap.AddResponseProtocolOp;
import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
import org.opends.server.protocols.ldap.DeleteResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPConstants;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.ByteString;
import org.opends.server.types.Control;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.Entry;
import org.opends.server.types.LDAPException;
import org.opends.server.types.ModificationType;
import org.opends.server.types.RawAttribute;
import org.opends.server.types.RawModification;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import org.opends.server.util.StaticUtils;
/**
* Helper class for interacting with the task backend on behalf of utilities
* that are capable of being scheduled.
*/
public class TaskClient {
/**
* Connection through which task scheduling will take place.
*/
protected LDAPConnection connection;
/**
* Keeps track of message IDs.
*/
private final AtomicInteger nextMessageID = new AtomicInteger(0);
/**
* Creates a new TaskClient for interacting with the task backend remotely.
* @param conn for accessing the task backend
*/
public TaskClient(LDAPConnection conn) {
this.connection = conn;
}
/**
* Returns the ID of the task entry for a given list of task attributes.
* @param taskAttributes the task attributes.
* @return the ID of the task entry for a given list of task attributes.
*/
public static String getTaskID(List<RawAttribute> taskAttributes)
{
String taskID = null;
RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID,
taskAttributes);
if (recurringIDAttr != null) {
taskID = recurringIDAttr.getValues().get(0).toString();
} else {
RawAttribute taskIDAttr = getAttribute(ATTR_TASK_ID,
taskAttributes);
taskID = taskIDAttr.getValues().get(0).toString();
}
return taskID;
}
private static RawAttribute getAttribute(String attrName,
List<RawAttribute> taskAttributes)
{
for (RawAttribute attr : taskAttributes)
{
if (attr.getAttributeType().equalsIgnoreCase(attrName))
{
return attr;
}
}
return null;
}
/**
* Returns the DN of the task entry for a given list of task attributes.
* @param taskAttributes the task attributes.
* @return the DN of the task entry for a given list of task attributes.
*/
public static String getTaskDN(List<RawAttribute> taskAttributes)
{
String entryDN = null;
String taskID = getTaskID(taskAttributes);
RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID,
taskAttributes);
if (recurringIDAttr != null) {
entryDN = ATTR_RECURRING_TASK_ID + "=" +
taskID + "," + RECURRING_TASK_BASE_RDN + "," + DN_TASK_ROOT;
} else {
entryDN = ATTR_TASK_ID + "=" + taskID + "," +
SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT;
}
return entryDN;
}
private static boolean isScheduleRecurring(
TaskScheduleInformation information)
{
boolean scheduleRecurring = false;
if (information.getRecurringDateTime() != null) {
scheduleRecurring = true;
}
return scheduleRecurring;
}
/**
* This is a commodity method that returns the common attributes (those
* related to scheduling) of a task entry for a given
* {@link TaskScheduleInformation} object.
* @param information the scheduling information.
* @return the schedule attributes of the task entry.
*/
public static ArrayList<RawAttribute> getTaskAttributes(
TaskScheduleInformation information)
{
String taskID = null;
boolean scheduleRecurring = isScheduleRecurring(information);
if (scheduleRecurring) {
taskID = information.getTaskId();
if ((taskID == null) || taskID.length() == 0) {
taskID = information.getTaskClass().getSimpleName() +
"-" + UUID.randomUUID().toString();
}
} else {
// Use a formatted time/date for the ID so that is remotely useful
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
taskID = df.format(new Date());
}
ArrayList<RawAttribute> attributes = new ArrayList<RawAttribute>();
ArrayList<ByteString> ocValues = new ArrayList<ByteString>(3);
ocValues.add(ByteString.valueOf("top"));
ocValues.add(ByteString.valueOf(ConfigConstants.OC_TASK));
if (scheduleRecurring) {
ocValues.add(ByteString.valueOf(ConfigConstants.OC_RECURRING_TASK));
}
ocValues.add(ByteString.valueOf(information.getTaskObjectclass()));
attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, ocValues));
ArrayList<ByteString> taskIDValues = new ArrayList<ByteString>(1);
taskIDValues.add(ByteString.valueOf(taskID));
if (scheduleRecurring) {
attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_ID, taskIDValues));
}
attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskIDValues));
ArrayList<ByteString> classValues = new ArrayList<ByteString>(1);
classValues.add(ByteString.valueOf(information.getTaskClass().getName()));
attributes.add(new LDAPAttribute(ATTR_TASK_CLASS, classValues));
// add the start time if necessary
Date startDate = information.getStartDateTime();
if (startDate != null) {
String startTimeString = StaticUtils.formatDateTimeString(startDate);
ArrayList<ByteString> startDateValues =
new ArrayList<ByteString>(1);
startDateValues.add(ByteString.valueOf(startTimeString));
attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME,
startDateValues));
}
if (scheduleRecurring) {
ArrayList<ByteString> recurringPatternValues =
new ArrayList<ByteString>(1);
recurringPatternValues.add(ByteString.valueOf(
information.getRecurringDateTime()));
attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_SCHEDULE,
recurringPatternValues));
}
// add dependency IDs
List<String> dependencyIds = information.getDependencyIds();
if (dependencyIds != null && dependencyIds.size() > 0) {
ArrayList<ByteString> dependencyIdValues =
new ArrayList<ByteString>(dependencyIds.size());
for (String dependencyId : dependencyIds) {
dependencyIdValues.add(ByteString.valueOf(dependencyId));
}
attributes.add(new LDAPAttribute(ATTR_TASK_DEPENDENCY_IDS,
dependencyIdValues));
// add the dependency action
FailedDependencyAction fda = information.getFailedDependencyAction();
if (fda == null) {
fda = FailedDependencyAction.defaultValue();
}
ArrayList<ByteString> fdaValues =
new ArrayList<ByteString>(1);
fdaValues.add(ByteString.valueOf(fda.name()));
attributes.add(new LDAPAttribute(ATTR_TASK_FAILED_DEPENDENCY_ACTION,
fdaValues));
}
// add completion notification email addresses
List<String> compNotifEmailAddresss =
information.getNotifyUponCompletionEmailAddresses();
if (compNotifEmailAddresss != null && compNotifEmailAddresss.size() > 0) {
ArrayList<ByteString> compNotifEmailAddrValues =
new ArrayList<ByteString>(compNotifEmailAddresss.size());
for (String emailAddr : compNotifEmailAddresss) {
compNotifEmailAddrValues.add(ByteString.valueOf(emailAddr));
}
attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_COMPLETION,
compNotifEmailAddrValues));
}
// add error notification email addresses
List<String> errNotifEmailAddresss =
information.getNotifyUponErrorEmailAddresses();
if (errNotifEmailAddresss != null && errNotifEmailAddresss.size() > 0) {
ArrayList<ByteString> errNotifEmailAddrValues =
new ArrayList<ByteString>(errNotifEmailAddresss.size());
for (String emailAddr : errNotifEmailAddresss) {
errNotifEmailAddrValues.add(ByteString.valueOf(emailAddr));
}
attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_ERROR,
errNotifEmailAddrValues));
}
information.addTaskAttributes(attributes);
return attributes;
}
/**
* Schedule a task for execution by writing an entry to the task backend.
*
* @param information to be scheduled
* @return String task ID assigned the new task
* @throws IOException if there is a stream communication problem
* @throws LDAPException if there is a problem getting information
* out to the directory
* @throws ASN1Exception if there is a problem with the encoding
* @throws TaskClientException if there is a problem with the task entry
*/
public synchronized TaskEntry schedule(TaskScheduleInformation information)
throws LDAPException, IOException, ASN1Exception, TaskClientException
{
LDAPReader reader = connection.getLDAPReader();
LDAPWriter writer = connection.getLDAPWriter();
ArrayList<Control> controls = new ArrayList<Control>();
ArrayList<RawAttribute> attributes = getTaskAttributes(information);
ByteString entryDN = ByteString.valueOf(getTaskDN(attributes));
AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN,
attributes);
LDAPMessage requestMessage =
new LDAPMessage(nextMessageID.getAndIncrement(), addRequest, controls);
// Send the request to the server and read the response.
LDAPMessage responseMessage;
writer.writeMessage(requestMessage);
responseMessage = reader.readMessage();
if (responseMessage == null)
{
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get());
}
if (responseMessage.getProtocolOpType() !=
LDAPConstants.OP_TYPE_ADD_RESPONSE)
{
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get(
responseMessage.getProtocolOpName()));
}
AddResponseProtocolOp addResponse =
responseMessage.getAddResponseProtocolOp();
if (addResponse.getResultCode() != 0) {
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
addResponse.getErrorMessage());
}
return getTaskEntry(getTaskID(attributes));
}
/**
* Gets all the ds-task entries from the task root.
*
* @return list of entries from the task root
* @throws IOException if there is a stream communication problem
* @throws LDAPException if there is a problem getting information
* out to the directory
* @throws ASN1Exception if there is a problem with the encoding
*/
public synchronized List<TaskEntry> getTaskEntries()
throws LDAPException, IOException, ASN1Exception {
List<Entry> entries = new ArrayList<Entry>();
writeSearch(new SearchRequestProtocolOp(
ByteString.valueOf(ConfigConstants.DN_TASK_ROOT),
SearchScope.WHOLE_SUBTREE,
DereferencePolicy.NEVER_DEREF_ALIASES,
Integer.MAX_VALUE,
Integer.MAX_VALUE,
false,
LDAPFilter.decode("(objectclass=ds-task)"),
new LinkedHashSet<String>()));
LDAPReader reader = connection.getLDAPReader();
byte opType;
do {
LDAPMessage responseMessage = reader.readMessage();
if (responseMessage == null) {
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get());
} else {
opType = responseMessage.getProtocolOpType();
if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) {
SearchResultEntryProtocolOp searchEntryOp =
responseMessage.getSearchResultEntryProtocolOp();
SearchResultEntry entry = searchEntryOp.toSearchResultEntry();
entries.add(entry);
}
}
}
while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
List<TaskEntry> taskEntries = new ArrayList<TaskEntry>(entries.size());
for (Entry entry : entries) {
taskEntries.add(new TaskEntry(entry));
}
return Collections.unmodifiableList(taskEntries);
}
/**
* Gets the entry of the task whose ID is <code>id</code> from the directory.
*
* @param id of the entry to retrieve
* @return Entry for the task
* @throws IOException if there is a stream communication problem
* @throws LDAPException if there is a problem getting information
* out to the directory
* @throws ASN1Exception if there is a problem with the encoding
* @throws TaskClientException if there is no task with the requested id
*/
public synchronized TaskEntry getTaskEntry(String id)
throws LDAPException, IOException, ASN1Exception, TaskClientException
{
Entry entry = null;
writeSearch(new SearchRequestProtocolOp(
ByteString.valueOf(ConfigConstants.DN_TASK_ROOT),
SearchScope.WHOLE_SUBTREE,
DereferencePolicy.NEVER_DEREF_ALIASES,
Integer.MAX_VALUE,
Integer.MAX_VALUE,
false,
LDAPFilter.decode("(" + ATTR_TASK_ID + "=" + id + ")"),
new LinkedHashSet<String>()));
LDAPReader reader = connection.getLDAPReader();
byte opType;
do {
LDAPMessage responseMessage = reader.readMessage();
if (responseMessage == null) {
Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get();
throw new LDAPException(UNAVAILABLE.getIntValue(), message);
} else {
opType = responseMessage.getProtocolOpType();
if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) {
SearchResultEntryProtocolOp searchEntryOp =
responseMessage.getSearchResultEntryProtocolOp();
entry = searchEntryOp.toSearchResultEntry();
}
}
}
while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
if (entry == null) {
throw new TaskClientException(ERR_TASK_CLIENT_UNKNOWN_TASK.get(id));
}
return new TaskEntry(entry);
}
/**
* Changes that the state of the task in the backend to a canceled state.
*
* @param id if the task to cancel
* @throws IOException if there is a stream communication problem
* @throws LDAPException if there is a problem getting information
* out to the directory
* @throws ASN1Exception if there is a problem with the encoding
* @throws TaskClientException if there is no task with the requested id
*/
public synchronized void cancelTask(String id)
throws TaskClientException, IOException, ASN1Exception, LDAPException
{
LDAPReader reader = connection.getLDAPReader();
LDAPWriter writer = connection.getLDAPWriter();
TaskEntry entry = getTaskEntry(id);
TaskState state = entry.getTaskState();
if (state != null) {
if (!TaskState.isDone(state)) {
ByteString dn = ByteString.valueOf(entry.getDN().toString());
ArrayList<RawModification> mods = new ArrayList<RawModification>();
ArrayList<ByteString> values = new ArrayList<ByteString>();
String newState;
if (TaskState.isPending(state)) {
newState = TaskState.CANCELED_BEFORE_STARTING.name();
} else {
newState = TaskState.STOPPED_BY_ADMINISTRATOR.name();
}
values.add(ByteString.valueOf(newState));
LDAPAttribute attr = new LDAPAttribute(ATTR_TASK_STATE, values);
mods.add(new LDAPModification(ModificationType.REPLACE, attr));
ModifyRequestProtocolOp modRequest =
new ModifyRequestProtocolOp(dn, mods);
LDAPMessage requestMessage =
new LDAPMessage(nextMessageID.getAndIncrement(), modRequest, null);
writer.writeMessage(requestMessage);
LDAPMessage responseMessage = reader.readMessage();
if (responseMessage == null) {
Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get();
throw new LDAPException(UNAVAILABLE.getIntValue(), message);
}
if (responseMessage.getProtocolOpType() !=
LDAPConstants.OP_TYPE_MODIFY_RESPONSE)
{
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get(
responseMessage.getProtocolOpName()));
}
ModifyResponseProtocolOp modResponse =
responseMessage.getModifyResponseProtocolOp();
Message errorMessage = modResponse.getErrorMessage();
if (errorMessage != null) {
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
errorMessage);
}
} else if (TaskState.isRecurring(state)) {
ByteString dn = ByteString.valueOf(entry.getDN().toString());
DeleteRequestProtocolOp deleteRequest =
new DeleteRequestProtocolOp(dn);
LDAPMessage requestMessage = new LDAPMessage(
nextMessageID.getAndIncrement(), deleteRequest, null);
writer.writeMessage(requestMessage);
LDAPMessage responseMessage = reader.readMessage();
if (responseMessage == null) {
Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get();
throw new LDAPException(UNAVAILABLE.getIntValue(), message);
}
if (responseMessage.getProtocolOpType() !=
LDAPConstants.OP_TYPE_DELETE_RESPONSE)
{
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get(
responseMessage.getProtocolOpName()));
}
DeleteResponseProtocolOp deleteResponse =
responseMessage.getDeleteResponseProtocolOp();
Message errorMessage = deleteResponse.getErrorMessage();
if (errorMessage != null) {
throw new LDAPException(
LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
errorMessage);
}
} else {
throw new TaskClientException(
ERR_TASK_CLIENT_UNCANCELABLE_TASK.get(id));
}
} else {
throw new TaskClientException(
ERR_TASK_CLIENT_TASK_STATE_UNKNOWN.get(id));
}
}
/**
* Writes a search to the directory writer.
* @param searchRequest to write
* @throws IOException if there is a stream communication problem
*/
private void writeSearch(SearchRequestProtocolOp searchRequest)
throws IOException {
LDAPWriter writer = connection.getLDAPWriter();
LDAPMessage requestMessage = new LDAPMessage(
nextMessageID.getAndIncrement(),
searchRequest,
new ArrayList<Control>());
// Send the request to the server and read the response.
writer.writeMessage(requestMessage);
}
}