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