001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk.extensions;
022    
023    
024    
025    import com.unboundid.asn1.ASN1Element;
026    import com.unboundid.asn1.ASN1Integer;
027    import com.unboundid.asn1.ASN1OctetString;
028    import com.unboundid.asn1.ASN1Sequence;
029    import com.unboundid.ldap.sdk.AsyncRequestID;
030    import com.unboundid.ldap.sdk.Control;
031    import com.unboundid.ldap.sdk.ExtendedRequest;
032    import com.unboundid.ldap.sdk.ExtendedResult;
033    import com.unboundid.ldap.sdk.LDAPConnection;
034    import com.unboundid.ldap.sdk.LDAPException;
035    import com.unboundid.ldap.sdk.ResultCode;
036    import com.unboundid.util.NotMutable;
037    import com.unboundid.util.ThreadSafety;
038    import com.unboundid.util.ThreadSafetyLevel;
039    
040    import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
041    import 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)
112    public 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    }