001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.extensions;
022
023
024
025import com.unboundid.asn1.ASN1Element;
026import com.unboundid.asn1.ASN1Integer;
027import com.unboundid.asn1.ASN1OctetString;
028import com.unboundid.asn1.ASN1Sequence;
029import com.unboundid.ldap.sdk.AsyncRequestID;
030import com.unboundid.ldap.sdk.Control;
031import com.unboundid.ldap.sdk.ExtendedRequest;
032import com.unboundid.ldap.sdk.ExtendedResult;
033import com.unboundid.ldap.sdk.LDAPConnection;
034import com.unboundid.ldap.sdk.LDAPException;
035import com.unboundid.ldap.sdk.ResultCode;
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
041import static com.unboundid.util.Debug.*;
042
043
044
045/**
046 * This class provides an implementation of the LDAP cancel extended request as
047 * defined in <A HREF="http://www.ietf.org/rfc/rfc3909.txt">RFC 3909</A>.  It
048 * may be used to request that the server interrupt processing on another
049 * operation in progress on the same connection.  It behaves much like the
050 * abandon operation, with the exception that both the cancel request and the
051 * operation that is canceled will receive responses, whereas an abandon request
052 * never returns a response, and the operation that is abandoned will also not
053 * receive a response if the abandon is successful.
054 * <BR><BR>
055 * <H2>Example</H2>
056 * The following example initiates an asynchronous modify operation and then
057 * attempts to cancel it:
058 * <PRE>
059 * Modification mod = new Modification(ModificationType.REPLACE,
060 *      "description", "This is the new description.");
061 * ModifyRequest modifyRequest =
062 *      new ModifyRequest("dc=example,dc=com", mod);
063 *
064 * AsyncRequestID asyncRequestID =
065 *      connection.asyncModify(modifyRequest, myAsyncResultListener);
066 *
067 * // Assume that we've waited a reasonable amount of time but the modify
068 * // hasn't completed yet so we'll try to cancel it.
069 *
070 * ExtendedResult cancelResult;
071 * try
072 * {
073 *   cancelResult = connection.processExtendedOperation(
074 *        new CancelExtendedRequest(asyncRequestID));
075 *   // This doesn't necessarily mean that the operation was successful, since
076 *   // some kinds of extended operations (like cancel) return non-success
077 *   // results under normal conditions.
078 * }
079 * catch (LDAPException le)
080 * {
081 *   // For an extended operation, this generally means that a problem was
082 *   // encountered while trying to send the request or read the result.
083 *   cancelResult = new ExtendedResult(le);
084 * }
085 *
086 * switch (cancelResult.getResultCode().intValue())
087 * {
088 *   case ResultCode.CANCELED_INT_VALUE:
089 *     // The modify operation was successfully canceled.
090 *     break;
091 *   case ResultCode.CANNOT_CANCEL_INT_VALUE:
092 *     // This indicates that the server isn't capable of canceling that
093 *     // type of operation.  This probably won't happen for  this kind of
094 *     // modify operation, but it could happen for other kinds of operations.
095 *     break;
096 *   case ResultCode.TOO_LATE_INT_VALUE:
097 *     // This indicates that the cancel request was received too late and the
098 *     // server is intending to process the operation.
099 *     break;
100 *   case ResultCode.NO_SUCH_OPERATION_INT_VALUE:
101 *     // This indicates that the server doesn't know anything about the
102 *     // operation, most likely because it has already completed.
103 *     break;
104 *   default:
105 *     // This suggests that the operation failed for some other reason.
106 *     break;
107 * }
108 * </PRE>
109 */
110@NotMutable()
111@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
112public final class CancelExtendedRequest
113       extends ExtendedRequest
114{
115  /**
116   * The OID (1.3.6.1.1.8) for the cancel extended request.
117   */
118  public static final String CANCEL_REQUEST_OID = "1.3.6.1.1.8";
119
120
121
122  /**
123   * The serial version UID for this serializable class.
124   */
125  private static final long serialVersionUID = -7170687636394194183L;
126
127
128
129  // The message ID of the request to cancel.
130  private final int targetMessageID;
131
132
133
134  /**
135   * Creates a new cancel extended request that will cancel the request with the
136   * specified async request ID.
137   *
138   * @param  requestID  The async request ID of the request to cancel.  It must
139   *                    not be {@code null}.
140   */
141  public CancelExtendedRequest(final AsyncRequestID requestID)
142  {
143    this(requestID.getMessageID(), null);
144  }
145
146
147
148  /**
149   * Creates a new cancel extended request that will cancel the request with the
150   * specified message ID.
151   *
152   * @param  targetMessageID  The message ID of the request to cancel.
153   */
154  public CancelExtendedRequest(final int targetMessageID)
155  {
156    this(targetMessageID, null);
157  }
158
159
160
161  /**
162   * Creates a new cancel extended request that will cancel the request with the
163   * specified request ID.
164   *
165   * @param  requestID  The async request ID of the request to cancel.  It must
166   *                    not be {@code null}.
167   * @param  controls   The set of controls to include in the request.
168   */
169  public CancelExtendedRequest(final AsyncRequestID requestID,
170                               final Control[] controls)
171  {
172    this(requestID.getMessageID(), controls);
173  }
174
175
176
177  /**
178   * Creates a new cancel extended request that will cancel the request with the
179   * specified message ID.
180   *
181   * @param  targetMessageID  The message ID of the request to cancel.
182   * @param  controls         The set of controls to include in the request.
183   */
184  public CancelExtendedRequest(final int targetMessageID,
185                               final Control[] controls)
186  {
187    super(CANCEL_REQUEST_OID, encodeValue(targetMessageID), controls);
188
189    this.targetMessageID = targetMessageID;
190  }
191
192
193
194  /**
195   * Creates a new cancel extended request from the provided generic extended
196   * request.
197   *
198   * @param  extendedRequest  The generic extended request to use to create this
199   *                          cancel extended request.
200   *
201   * @throws  LDAPException  If a problem occurs while decoding the request.
202   */
203  public CancelExtendedRequest(final ExtendedRequest extendedRequest)
204         throws LDAPException
205  {
206    super(extendedRequest);
207
208    final ASN1OctetString value = extendedRequest.getValue();
209    if (value == null)
210    {
211      throw new LDAPException(ResultCode.DECODING_ERROR,
212                              ERR_CANCEL_REQUEST_NO_VALUE.get());
213    }
214
215    try
216    {
217      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
218      final ASN1Element[] elements =
219           ASN1Sequence.decodeAsSequence(valueElement).elements();
220      targetMessageID = ASN1Integer.decodeAsInteger(elements[0]).intValue();
221    }
222    catch (Exception e)
223    {
224      debugException(e);
225      throw new LDAPException(ResultCode.DECODING_ERROR,
226                              ERR_CANCEL_REQUEST_CANNOT_DECODE.get(e), e);
227    }
228  }
229
230
231
232  /**
233   * Generates a properly-encoded request value for this cancel extended
234   * request.
235   *
236   * @param  targetMessageID  The message ID of the request to cancel.
237   *
238   * @return  An ASN.1 octet string containing the encoded request value.
239   */
240  private static ASN1OctetString encodeValue(final int targetMessageID)
241  {
242    final ASN1Element[] sequenceValues =
243    {
244      new ASN1Integer(targetMessageID)
245    };
246
247    return new ASN1OctetString(new ASN1Sequence(sequenceValues).encode());
248  }
249
250
251
252  /**
253   * {@inheritDoc}
254   */
255  @Override()
256  protected ExtendedResult process(final LDAPConnection connection,
257                                   final int depth)
258            throws LDAPException
259  {
260    if (connection.synchronousMode())
261    {
262      throw new LDAPException(ResultCode.NOT_SUPPORTED,
263           ERR_CANCEL_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
264    }
265
266    return super.process(connection, depth);
267  }
268
269
270
271  /**
272   * Retrieves the message ID of the request to cancel.
273   *
274   * @return  The message ID of the request to cancel.
275   */
276  public int getTargetMessageID()
277  {
278    return targetMessageID;
279  }
280
281
282
283  /**
284   * {@inheritDoc}
285   */
286  @Override()
287  public CancelExtendedRequest duplicate()
288  {
289    return duplicate(getControls());
290  }
291
292
293
294  /**
295   * {@inheritDoc}
296   */
297  @Override()
298  public CancelExtendedRequest duplicate(final Control[] controls)
299  {
300    final CancelExtendedRequest cancelRequest =
301         new CancelExtendedRequest(targetMessageID, controls);
302    cancelRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
303    return cancelRequest;
304  }
305
306
307
308  /**
309   * {@inheritDoc}
310   */
311  @Override()
312  public String getExtendedRequestName()
313  {
314    return INFO_EXTENDED_REQUEST_NAME_CANCEL.get();
315  }
316
317
318
319  /**
320   * {@inheritDoc}
321   */
322  @Override()
323  public void toString(final StringBuilder buffer)
324  {
325    buffer.append("CancelExtendedRequest(targetMessageID=");
326    buffer.append(targetMessageID);
327
328    final Control[] controls = getControls();
329    if (controls.length > 0)
330    {
331      buffer.append(", controls={");
332      for (int i=0; i < controls.length; i++)
333      {
334        if (i > 0)
335        {
336          buffer.append(", ");
337        }
338
339        buffer.append(controls[i]);
340      }
341      buffer.append('}');
342    }
343
344    buffer.append(')');
345  }
346}