/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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. * Portions Copyright 2014-2015 ForgeRock AS */ package org.opends.server.tools.tasks; import static org.opends.messages.ToolMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.forgerock.opendj.ldap.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.forgerock.i18n.LocalizableMessage; import org.opends.server.backends.task.FailedDependencyAction; import org.opends.server.backends.task.TaskState; import org.opends.server.config.ConfigConstants; import org.forgerock.opendj.ldap.DecodeException; 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.forgerock.opendj.ldap.ByteString; import org.opends.server.types.Control; import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; import org.opends.server.types.Entry; import org.opends.server.types.LDAPException; import org.forgerock.opendj.ldap.ModificationType; import org.opends.server.types.RawAttribute; import org.opends.server.types.RawModification; import org.opends.server.types.SearchResultEntry; import org.forgerock.opendj.ldap.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 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 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 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) { return information.getRecurringDateTime() != null; } /** * 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 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(); } } 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 attributes = new ArrayList(); ArrayList ocValues = new ArrayList(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 taskIDValues = new ArrayList(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 classValues = new ArrayList(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 startDateValues = new ArrayList(1); startDateValues.add(ByteString.valueOf(startTimeString)); attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME, startDateValues)); } if (scheduleRecurring) { ArrayList recurringPatternValues = new ArrayList(1); recurringPatternValues.add(ByteString.valueOf( information.getRecurringDateTime())); attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_SCHEDULE, recurringPatternValues)); } // add dependency IDs List dependencyIds = information.getDependencyIds(); if (dependencyIds != null && dependencyIds.size() > 0) { ArrayList dependencyIdValues = new ArrayList(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 fdaValues = new ArrayList(1); fdaValues.add(ByteString.valueOf(fda.name())); attributes.add(new LDAPAttribute(ATTR_TASK_FAILED_DEPENDENCY_ACTION, fdaValues)); } // add completion notification email addresses List compNotifEmailAddresss = information.getNotifyUponCompletionEmailAddresses(); if (compNotifEmailAddresss != null && compNotifEmailAddresss.size() > 0) { ArrayList compNotifEmailAddrValues = new ArrayList(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 errNotifEmailAddresss = information.getNotifyUponErrorEmailAddresses(); if (errNotifEmailAddresss != null && errNotifEmailAddresss.size() > 0) { ArrayList errNotifEmailAddrValues = new ArrayList(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 DecodeException 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, DecodeException, TaskClientException { LDAPReader reader = connection.getLDAPReader(); LDAPWriter writer = connection.getLDAPWriter(); ArrayList controls = new ArrayList(); ArrayList 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 DecodeException if there is a problem with the encoding */ public synchronized List getTaskEntries() throws LDAPException, IOException, DecodeException { List entries = new ArrayList(); writeSearch(new SearchRequestProtocolOp( ByteString.valueOf(ConfigConstants.DN_TASK_ROOT), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(objectclass=ds-task)"), new LinkedHashSet())); 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 taskEntries = new ArrayList(entries.size()); for (Entry entry : entries) { taskEntries.add(new TaskEntry(entry)); } return Collections.unmodifiableList(taskEntries); } /** * Gets the entry of the task whose ID is id 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 DecodeException 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, DecodeException, TaskClientException { Entry entry = null; writeSearch(new SearchRequestProtocolOp( ByteString.valueOf(ConfigConstants.DN_TASK_ROOT), SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.NEVER, Integer.MAX_VALUE, Integer.MAX_VALUE, false, LDAPFilter.decode("(" + ATTR_TASK_ID + "=" + id + ")"), new LinkedHashSet())); LDAPReader reader = connection.getLDAPReader(); byte opType; do { LDAPMessage responseMessage = reader.readMessage(); if (responseMessage == null) { LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); throw new LDAPException(UNAVAILABLE.intValue(), 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 DecodeException 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, DecodeException, 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 mods = new ArrayList(); ArrayList values = new ArrayList(); 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) { LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); throw new LDAPException(UNAVAILABLE.intValue(), 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(); LocalizableMessage 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) { LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); throw new LDAPException(UNAVAILABLE.intValue(), 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(); LocalizableMessage 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()); // Send the request to the server and read the response. writer.writeMessage(requestMessage); } }