001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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     *   CancelExtendedRequest cancelRequest =
071     *        new CancelExtendedRequest(asyncRequestID);
072     *
073     *   // NOTE:  The processExtendedOperation method will only throw an exception
074     *   // if a problem occurs while trying to send the request or read the
075     *   // response.  It will not throw an exception because of a non-success
076     *   // response.  That's good for us in this case because the cancel result
077     *   // should never be "SUCCESS".
078     *   ExtendedResult cancelResult =
079     *        connection.processExtendedOperation(cancelRequest);
080     *   switch (cancelResult.getResultCode())
081     *   {
082     *     case ResultCode.CANCELED:
083     *       System.out.println("The operation was successfully canceled.");
084     *       break;
085     *     case ResultCode.NO_SUCH_OPERATION:
086     *       System.out.println("The server didn't know anything about the " +
087     *                          "operation.  Maybe it's already completed.");
088     *       break;
089     *     case ResultCode.TOO_LATE:
090     *       System.out.println("It was too late in the operation processing " +
091     *                          "to cancel the operation.");
092     *       break;
093     *     case ResultCode.CANNOT_CANCEL:
094     *       System.out.println("The target operation is not one that could be " +
095     *                          "canceled.");
096     *       break;
097     *     default:
098     *       System.err.println("An error occurred while processing the cancel " +
099     *                          "request.");
100     *       break;
101     *   }
102     * </PRE>
103     */
104    @NotMutable()
105    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
106    public final class CancelExtendedRequest
107           extends ExtendedRequest
108    {
109      /**
110       * The OID (1.3.6.1.1.8) for the cancel extended request.
111       */
112      public static final String CANCEL_REQUEST_OID = "1.3.6.1.1.8";
113    
114    
115    
116      /**
117       * The serial version UID for this serializable class.
118       */
119      private static final long serialVersionUID = -7170687636394194183L;
120    
121    
122    
123      // The message ID of the request to cancel.
124      private final int targetMessageID;
125    
126    
127    
128      /**
129       * Creates a new cancel extended request that will cancel the request with the
130       * specified async request ID.
131       *
132       * @param  requestID  The async request ID of the request to cancel.  It must
133       *                    not be {@code null}.
134       */
135      public CancelExtendedRequest(final AsyncRequestID requestID)
136      {
137        this(requestID.getMessageID(), null);
138      }
139    
140    
141    
142      /**
143       * Creates a new cancel extended request that will cancel the request with the
144       * specified message ID.
145       *
146       * @param  targetMessageID  The message ID of the request to cancel.
147       */
148      public CancelExtendedRequest(final int targetMessageID)
149      {
150        this(targetMessageID, null);
151      }
152    
153    
154    
155      /**
156       * Creates a new cancel extended request that will cancel the request with the
157       * specified request ID.
158       *
159       * @param  requestID  The async request ID of the request to cancel.  It must
160       *                    not be {@code null}.
161       * @param  controls   The set of controls to include in the request.
162       */
163      public CancelExtendedRequest(final AsyncRequestID requestID,
164                                   final Control[] controls)
165      {
166        this(requestID.getMessageID(), controls);
167      }
168    
169    
170    
171      /**
172       * Creates a new cancel extended request that will cancel the request with the
173       * specified message ID.
174       *
175       * @param  targetMessageID  The message ID of the request to cancel.
176       * @param  controls         The set of controls to include in the request.
177       */
178      public CancelExtendedRequest(final int targetMessageID,
179                                   final Control[] controls)
180      {
181        super(CANCEL_REQUEST_OID, encodeValue(targetMessageID), controls);
182    
183        this.targetMessageID = targetMessageID;
184      }
185    
186    
187    
188      /**
189       * Creates a new cancel extended request from the provided generic extended
190       * request.
191       *
192       * @param  extendedRequest  The generic extended request to use to create this
193       *                          cancel extended request.
194       *
195       * @throws  LDAPException  If a problem occurs while decoding the request.
196       */
197      public CancelExtendedRequest(final ExtendedRequest extendedRequest)
198             throws LDAPException
199      {
200        super(extendedRequest);
201    
202        final ASN1OctetString value = extendedRequest.getValue();
203        if (value == null)
204        {
205          throw new LDAPException(ResultCode.DECODING_ERROR,
206                                  ERR_CANCEL_REQUEST_NO_VALUE.get());
207        }
208    
209        try
210        {
211          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
212          final ASN1Element[] elements =
213               ASN1Sequence.decodeAsSequence(valueElement).elements();
214          targetMessageID = ASN1Integer.decodeAsInteger(elements[0]).intValue();
215        }
216        catch (Exception e)
217        {
218          debugException(e);
219          throw new LDAPException(ResultCode.DECODING_ERROR,
220                                  ERR_CANCEL_REQUEST_CANNOT_DECODE.get(e), e);
221        }
222      }
223    
224    
225    
226      /**
227       * Generates a properly-encoded request value for this cancel extended
228       * request.
229       *
230       * @param  targetMessageID  The message ID of the request to cancel.
231       *
232       * @return  An ASN.1 octet string containing the encoded request value.
233       */
234      private static ASN1OctetString encodeValue(final int targetMessageID)
235      {
236        final ASN1Element[] sequenceValues =
237        {
238          new ASN1Integer(targetMessageID)
239        };
240    
241        return new ASN1OctetString(new ASN1Sequence(sequenceValues).encode());
242      }
243    
244    
245    
246      /**
247       * {@inheritDoc}
248       */
249      @Override()
250      protected ExtendedResult process(final LDAPConnection connection,
251                                       final int depth)
252                throws LDAPException
253      {
254        if (connection.synchronousMode())
255        {
256          throw new LDAPException(ResultCode.NOT_SUPPORTED,
257               ERR_CANCEL_NOT_SUPPORTED_IN_SYNCHRONOUS_MODE.get());
258        }
259    
260        return super.process(connection, depth);
261      }
262    
263    
264    
265      /**
266       * Retrieves the message ID of the request to cancel.
267       *
268       * @return  The message ID of the request to cancel.
269       */
270      public int getTargetMessageID()
271      {
272        return targetMessageID;
273      }
274    
275    
276    
277      /**
278       * {@inheritDoc}
279       */
280      @Override()
281      public CancelExtendedRequest duplicate()
282      {
283        return duplicate(getControls());
284      }
285    
286    
287    
288      /**
289       * {@inheritDoc}
290       */
291      @Override()
292      public CancelExtendedRequest duplicate(final Control[] controls)
293      {
294        final CancelExtendedRequest cancelRequest =
295             new CancelExtendedRequest(targetMessageID, controls);
296        cancelRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
297        return cancelRequest;
298      }
299    
300    
301    
302      /**
303       * {@inheritDoc}
304       */
305      @Override()
306      public String getExtendedRequestName()
307      {
308        return INFO_EXTENDED_REQUEST_NAME_CANCEL.get();
309      }
310    
311    
312    
313      /**
314       * {@inheritDoc}
315       */
316      @Override()
317      public void toString(final StringBuilder buffer)
318      {
319        buffer.append("CancelExtendedRequest(targetMessageID=");
320        buffer.append(targetMessageID);
321    
322        final Control[] controls = getControls();
323        if (controls.length > 0)
324        {
325          buffer.append(", controls={");
326          for (int i=0; i < controls.length; i++)
327          {
328            if (i > 0)
329            {
330              buffer.append(", ");
331            }
332    
333            buffer.append(controls[i]);
334          }
335          buffer.append('}');
336        }
337    
338        buffer.append(')');
339      }
340    }