001 /*
002 * Copyright 2007-2012 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2012 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;
022
023
024
025 import java.util.Timer;
026 import java.util.concurrent.LinkedBlockingQueue;
027 import java.util.concurrent.TimeUnit;
028
029 import com.unboundid.asn1.ASN1Buffer;
030 import com.unboundid.asn1.ASN1Element;
031 import com.unboundid.asn1.ASN1OctetString;
032 import com.unboundid.ldap.protocol.LDAPMessage;
033 import com.unboundid.ldap.protocol.LDAPResponse;
034 import com.unboundid.ldap.protocol.ProtocolOp;
035 import com.unboundid.ldif.LDIFDeleteChangeRecord;
036 import com.unboundid.util.InternalUseOnly;
037 import com.unboundid.util.Mutable;
038 import com.unboundid.util.ThreadSafety;
039 import com.unboundid.util.ThreadSafetyLevel;
040
041 import static com.unboundid.ldap.sdk.LDAPMessages.*;
042 import static com.unboundid.util.Debug.*;
043 import static com.unboundid.util.StaticUtils.*;
044 import static com.unboundid.util.Validator.*;
045
046
047
048 /**
049 * This class implements the processing necessary to perform an LDAPv3 delete
050 * operation, which removes an entry from the directory. A delete request
051 * contains the DN of the entry to remove. It may also include a set of
052 * controls to send to the server.
053 * {@code DeleteRequest} objects are mutable and therefore can be altered and
054 * re-used for multiple requests. Note, however, that {@code DeleteRequest}
055 * objects are not threadsafe and therefore a single {@code DeleteRequest}
056 * object instance should not be used to process multiple requests at the same
057 * time.
058 * <BR><BR>
059 * <H2>Example</H2>
060 * The following example demonstrates the process for performing a delete
061 * operation:
062 * <PRE>
063 * DeleteRequest deleteRequest =
064 * new DeleteRequest("cn=entry to delete,dc=example,dc=com");
065 *
066 * try
067 * {
068 * LDAPResult deleteResult = connection.delete(deleteRequest);
069 *
070 * System.out.println("The entry was successfully deleted.");
071 * }
072 * catch (LDAPException le)
073 * {
074 * System.err.println("The delete operation failed.");
075 * }
076 * </PRE>
077 */
078 @Mutable()
079 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
080 public final class DeleteRequest
081 extends UpdatableLDAPRequest
082 implements ReadOnlyDeleteRequest, ResponseAcceptor, ProtocolOp
083 {
084 /**
085 * The serial version UID for this serializable class.
086 */
087 private static final long serialVersionUID = -6126029442850884239L;
088
089
090
091 // The message ID from the last LDAP message sent from this request.
092 private int messageID = -1;
093
094 // The queue that will be used to receive response messages from the server.
095 private final LinkedBlockingQueue<LDAPResponse> responseQueue =
096 new LinkedBlockingQueue<LDAPResponse>();
097
098 // The DN of the entry to delete.
099 private String dn;
100
101
102
103 /**
104 * Creates a new delete request with the provided DN.
105 *
106 * @param dn The DN of the entry to delete. It must not be {@code null}.
107 */
108 public DeleteRequest(final String dn)
109 {
110 super(null);
111
112 ensureNotNull(dn);
113
114 this.dn = dn;
115 }
116
117
118
119 /**
120 * Creates a new delete request with the provided DN.
121 *
122 * @param dn The DN of the entry to delete. It must not be
123 * {@code null}.
124 * @param controls The set of controls to include in the request.
125 */
126 public DeleteRequest(final String dn, final Control[] controls)
127 {
128 super(controls);
129
130 ensureNotNull(dn);
131
132 this.dn = dn;
133 }
134
135
136
137 /**
138 * Creates a new delete request with the provided DN.
139 *
140 * @param dn The DN of the entry to delete. It must not be {@code null}.
141 */
142 public DeleteRequest(final DN dn)
143 {
144 super(null);
145
146 ensureNotNull(dn);
147
148 this.dn = dn.toString();
149 }
150
151
152
153 /**
154 * Creates a new delete request with the provided DN.
155 *
156 * @param dn The DN of the entry to delete. It must not be
157 * {@code null}.
158 * @param controls The set of controls to include in the request.
159 */
160 public DeleteRequest(final DN dn, final Control[] controls)
161 {
162 super(controls);
163
164 ensureNotNull(dn);
165
166 this.dn = dn.toString();
167 }
168
169
170
171 /**
172 * {@inheritDoc}
173 */
174 public String getDN()
175 {
176 return dn;
177 }
178
179
180
181 /**
182 * Specifies the DN of the entry to delete.
183 *
184 * @param dn The DN of the entry to delete. It must not be {@code null}.
185 */
186 public void setDN(final String dn)
187 {
188 ensureNotNull(dn);
189
190 this.dn = dn;
191 }
192
193
194
195 /**
196 * Specifies the DN of the entry to delete.
197 *
198 * @param dn The DN of the entry to delete. It must not be {@code null}.
199 */
200 public void setDN(final DN dn)
201 {
202 ensureNotNull(dn);
203
204 this.dn = dn.toString();
205 }
206
207
208
209 /**
210 * {@inheritDoc}
211 */
212 public byte getProtocolOpType()
213 {
214 return LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST;
215 }
216
217
218
219 /**
220 * {@inheritDoc}
221 */
222 public void writeTo(final ASN1Buffer buffer)
223 {
224 buffer.addOctetString(LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, dn);
225 }
226
227
228
229 /**
230 * Encodes the delete request protocol op to an ASN.1 element.
231 *
232 * @return The ASN.1 element with the encoded delete request protocol op.
233 */
234 ASN1Element encodeProtocolOp()
235 {
236 return new ASN1OctetString(LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, dn);
237 }
238
239
240
241 /**
242 * Sends this delete request to the directory server over the provided
243 * connection and returns the associated response.
244 *
245 * @param connection The connection to use to communicate with the directory
246 * server.
247 * @param depth The current referral depth for this request. It should
248 * always be one for the initial request, and should only
249 * be incremented when following referrals.
250 *
251 * @return An LDAP result object that provides information about the result
252 * of the delete processing.
253 *
254 * @throws LDAPException If a problem occurs while sending the request or
255 * reading the response.
256 */
257 @Override()
258 protected LDAPResult process(final LDAPConnection connection, final int depth)
259 throws LDAPException
260 {
261 if (connection.synchronousMode())
262 {
263 return processSync(connection, depth);
264 }
265
266 final long requestTime = System.nanoTime();
267 processAsync(connection, null);
268
269 try
270 {
271 // Wait for and process the response.
272 final LDAPResponse response;
273 try
274 {
275 final long responseTimeout = getResponseTimeoutMillis(connection);
276 if (responseTimeout > 0)
277 {
278 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
279 }
280 else
281 {
282 response = responseQueue.take();
283 }
284 }
285 catch (InterruptedException ie)
286 {
287 debugException(ie);
288 throw new LDAPException(ResultCode.LOCAL_ERROR,
289 ERR_DELETE_INTERRUPTED.get(connection.getHostPort()), ie);
290 }
291
292 return handleResponse(connection, response, requestTime, depth);
293 }
294 finally
295 {
296 connection.deregisterResponseAcceptor(messageID);
297 }
298 }
299
300
301
302 /**
303 * Sends this delete request to the directory server over the provided
304 * connection and returns the message ID for the request.
305 *
306 * @param connection The connection to use to communicate with the
307 * directory server.
308 * @param resultListener The async result listener that is to be notified
309 * when the response is received. It may be
310 * {@code null} only if the result is to be processed
311 * by this class.
312 *
313 * @return The async request ID created for the operation, or {@code null} if
314 * the provided {@code resultListener} is {@code null} and the
315 * operation will not actually be processed asynchronously.
316 *
317 * @throws LDAPException If a problem occurs while sending the request.
318 */
319 AsyncRequestID processAsync(final LDAPConnection connection,
320 final AsyncResultListener resultListener)
321 throws LDAPException
322 {
323 // Create the LDAP message.
324 messageID = connection.nextMessageID();
325 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
326
327
328 // If the provided async result listener is {@code null}, then we'll use
329 // this class as the message acceptor. Otherwise, create an async helper
330 // and use it as the message acceptor.
331 final AsyncRequestID asyncRequestID;
332 if (resultListener == null)
333 {
334 asyncRequestID = null;
335 connection.registerResponseAcceptor(messageID, this);
336 }
337 else
338 {
339 final AsyncHelper helper = new AsyncHelper(connection,
340 OperationType.DELETE, messageID, resultListener,
341 getIntermediateResponseListener());
342 connection.registerResponseAcceptor(messageID, helper);
343 asyncRequestID = helper.getAsyncRequestID();
344
345 final long timeout = getResponseTimeoutMillis(connection);
346 if (timeout > 0L)
347 {
348 final Timer timer = connection.getTimer();
349 final AsyncTimeoutTimerTask timerTask =
350 new AsyncTimeoutTimerTask(helper);
351 timer.schedule(timerTask, timeout);
352 asyncRequestID.setTimerTask(timerTask);
353 }
354 }
355
356
357 // Send the request to the server.
358 try
359 {
360 debugLDAPRequest(this);
361 connection.getConnectionStatistics().incrementNumDeleteRequests();
362 connection.sendMessage(message);
363 return asyncRequestID;
364 }
365 catch (LDAPException le)
366 {
367 debugException(le);
368
369 connection.deregisterResponseAcceptor(messageID);
370 throw le;
371 }
372 }
373
374
375
376 /**
377 * Processes this delete operation in synchronous mode, in which the same
378 * thread will send the request and read the response.
379 *
380 * @param connection The connection to use to communicate with the directory
381 * server.
382 * @param depth The current referral depth for this request. It should
383 * always be one for the initial request, and should only
384 * be incremented when following referrals.
385 *
386 * @return An LDAP result object that provides information about the result
387 * of the delete processing.
388 *
389 * @throws LDAPException If a problem occurs while sending the request or
390 * reading the response.
391 */
392 private LDAPResult processSync(final LDAPConnection connection,
393 final int depth)
394 throws LDAPException
395 {
396 // Create the LDAP message.
397 messageID = connection.nextMessageID();
398 final LDAPMessage message =
399 new LDAPMessage(messageID, this, getControls());
400
401
402 // Set the appropriate timeout on the socket.
403 try
404 {
405 connection.getConnectionInternals().getSocket().setSoTimeout(
406 (int) getResponseTimeoutMillis(connection));
407 }
408 catch (Exception e)
409 {
410 debugException(e);
411 }
412
413
414 // Send the request to the server.
415 final long requestTime = System.nanoTime();
416 debugLDAPRequest(this);
417 connection.getConnectionStatistics().incrementNumDeleteRequests();
418 connection.sendMessage(message);
419
420 while (true)
421 {
422 final LDAPResponse response;
423 try
424 {
425 response = connection.readResponse(messageID);
426 }
427 catch (final LDAPException le)
428 {
429 debugException(le);
430
431 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
432 connection.getConnectionOptions().abandonOnTimeout())
433 {
434 connection.abandon(messageID);
435 }
436
437 throw le;
438 }
439
440 if (response instanceof IntermediateResponse)
441 {
442 final IntermediateResponseListener listener =
443 getIntermediateResponseListener();
444 if (listener != null)
445 {
446 listener.intermediateResponseReturned(
447 (IntermediateResponse) response);
448 }
449 }
450 else
451 {
452 return handleResponse(connection, response, requestTime, depth);
453 }
454 }
455 }
456
457
458
459 /**
460 * Performs the necessary processing for handling a response.
461 *
462 * @param connection The connection used to read the response.
463 * @param response The response to be processed.
464 * @param requestTime The time the request was sent to the server.
465 * @param depth The current referral depth for this request. It
466 * should always be one for the initial request, and
467 * should only be incremented when following referrals.
468 *
469 * @return The delete result.
470 *
471 * @throws LDAPException If a problem occurs.
472 */
473 private LDAPResult handleResponse(final LDAPConnection connection,
474 final LDAPResponse response,
475 final long requestTime, final int depth)
476 throws LDAPException
477 {
478 if (response == null)
479 {
480 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
481 if (connection.getConnectionOptions().abandonOnTimeout())
482 {
483 connection.abandon(messageID);
484 }
485
486 throw new LDAPException(ResultCode.TIMEOUT,
487 ERR_DELETE_CLIENT_TIMEOUT.get(waitTime, connection.getHostPort()));
488 }
489
490 connection.getConnectionStatistics().incrementNumDeleteResponses(
491 System.nanoTime() - requestTime);
492 if (response instanceof ConnectionClosedResponse)
493 {
494 // The connection was closed while waiting for the response.
495 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
496 final String message = ccr.getMessage();
497 if (message == null)
498 {
499 throw new LDAPException(ccr.getResultCode(),
500 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE.get(
501 connection.getHostPort(), toString()));
502 }
503 else
504 {
505 throw new LDAPException(ccr.getResultCode(),
506 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE_WITH_MESSAGE.get(
507 connection.getHostPort(), toString(), message));
508 }
509 }
510
511 final LDAPResult result = (LDAPResult) response;
512 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
513 followReferrals(connection))
514 {
515 if (depth >= connection.getConnectionOptions().getReferralHopLimit())
516 {
517 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
518 ERR_TOO_MANY_REFERRALS.get(),
519 result.getMatchedDN(), result.getReferralURLs(),
520 result.getResponseControls());
521 }
522
523 return followReferral(result, connection, depth);
524 }
525 else
526 {
527 return result;
528 }
529 }
530
531
532
533 /**
534 * Attempts to follow a referral to perform a delete operation in the target
535 * server.
536 *
537 * @param referralResult The LDAP result object containing information about
538 * the referral to follow.
539 * @param connection The connection on which the referral was received.
540 * @param depth The number of referrals followed in the course of
541 * processing this request.
542 *
543 * @return The result of attempting to process the delete operation by
544 * following the referral.
545 *
546 * @throws LDAPException If a problem occurs while attempting to establish
547 * the referral connection, sending the request, or
548 * reading the result.
549 */
550 private LDAPResult followReferral(final LDAPResult referralResult,
551 final LDAPConnection connection,
552 final int depth)
553 throws LDAPException
554 {
555 for (final String urlString : referralResult.getReferralURLs())
556 {
557 try
558 {
559 final LDAPURL referralURL = new LDAPURL(urlString);
560 final String host = referralURL.getHost();
561
562 if (host == null)
563 {
564 // We can't handle a referral in which there is no host.
565 continue;
566 }
567
568 final DeleteRequest deleteRequest;
569 if (referralURL.baseDNProvided())
570 {
571 deleteRequest = new DeleteRequest(referralURL.getBaseDN(),
572 getControls());
573 }
574 else
575 {
576 deleteRequest = this;
577 }
578
579 final LDAPConnection referralConn = connection.getReferralConnector().
580 getReferralConnection(referralURL, connection);
581 try
582 {
583 return deleteRequest.process(referralConn, depth+1);
584 }
585 finally
586 {
587 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
588 referralConn.close();
589 }
590 }
591 catch (LDAPException le)
592 {
593 debugException(le);
594 }
595 }
596
597 // If we've gotten here, then we could not follow any of the referral URLs,
598 // so we'll just return the original referral result.
599 return referralResult;
600 }
601
602
603
604 /**
605 * {@inheritDoc}
606 */
607 @InternalUseOnly()
608 public void responseReceived(final LDAPResponse response)
609 throws LDAPException
610 {
611 try
612 {
613 responseQueue.put(response);
614 }
615 catch (Exception e)
616 {
617 debugException(e);
618 throw new LDAPException(ResultCode.LOCAL_ERROR,
619 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
620 }
621 }
622
623
624
625 /**
626 * {@inheritDoc}
627 */
628 @Override()
629 public int getLastMessageID()
630 {
631 return messageID;
632 }
633
634
635
636 /**
637 * {@inheritDoc}
638 */
639 @Override()
640 public OperationType getOperationType()
641 {
642 return OperationType.DELETE;
643 }
644
645
646
647 /**
648 * {@inheritDoc}
649 */
650 public DeleteRequest duplicate()
651 {
652 return duplicate(getControls());
653 }
654
655
656
657 /**
658 * {@inheritDoc}
659 */
660 public DeleteRequest duplicate(final Control[] controls)
661 {
662 final DeleteRequest r = new DeleteRequest(dn, controls);
663
664 if (followReferralsInternal() != null)
665 {
666 r.setFollowReferrals(followReferralsInternal());
667 }
668
669 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
670
671 return r;
672 }
673
674
675
676 /**
677 * {@inheritDoc}
678 */
679 public LDIFDeleteChangeRecord toLDIFChangeRecord()
680 {
681 return new LDIFDeleteChangeRecord(this);
682 }
683
684
685
686 /**
687 * {@inheritDoc}
688 */
689 public String[] toLDIF()
690 {
691 return toLDIFChangeRecord().toLDIF();
692 }
693
694
695
696 /**
697 * {@inheritDoc}
698 */
699 public String toLDIFString()
700 {
701 return toLDIFChangeRecord().toLDIFString();
702 }
703
704
705
706 /**
707 * {@inheritDoc}
708 */
709 @Override()
710 public void toString(final StringBuilder buffer)
711 {
712 buffer.append("DeleteRequest(dn='");
713 buffer.append(dn);
714 buffer.append('\'');
715
716 final Control[] controls = getControls();
717 if (controls.length > 0)
718 {
719 buffer.append(", controls={");
720 for (int i=0; i < controls.length; i++)
721 {
722 if (i > 0)
723 {
724 buffer.append(", ");
725 }
726
727 buffer.append(controls[i]);
728 }
729 buffer.append('}');
730 }
731
732 buffer.append(')');
733 }
734 }