001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2007-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    import org.opends.messages.MessageBuilder;
030    
031    
032    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ENTRY_DN;
033    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_ERROR_MESSAGE;
034    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_MATCHED_DN;
035    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_PROCESSING_TIME;
036    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_REFERRAL_URLS;
037    import static org.opends.server.core.CoreConstants.LOG_ELEMENT_RESULT_CODE;
038    import static org.opends.server.loggers.AccessLogger.logDeleteRequest;
039    import static org.opends.server.loggers.AccessLogger.logDeleteResponse;
040    import static org.opends.server.loggers.ErrorLogger.logError;
041    import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
042    import static org.opends.messages.CoreMessages.*;
043    import static org.opends.server.util.StaticUtils.getExceptionMessage;
044    
045    import java.util.ArrayList;
046    import java.util.Iterator;
047    import java.util.List;
048    
049    import org.opends.server.api.ClientConnection;
050    import org.opends.server.api.plugin.PluginResult;
051    import org.opends.server.loggers.debug.DebugLogger;
052    import org.opends.server.loggers.debug.DebugTracer;
053    import org.opends.server.protocols.asn1.ASN1OctetString;
054    import org.opends.server.types.*;
055    import org.opends.server.types.operation.PostResponseDeleteOperation;
056    import org.opends.server.types.operation.PreParseDeleteOperation;
057    import org.opends.server.workflowelement.localbackend.*;
058    
059    
060    
061    /**
062     * This class defines an operation that may be used to remove an entry from the
063     * Directory Server.
064     */
065    public class DeleteOperationBasis
066           extends AbstractOperation
067           implements PreParseDeleteOperation,
068                      DeleteOperation,
069                      PostResponseDeleteOperation
070    {
071      /**
072       * The tracer object for the debug logger.
073       */
074      private static final DebugTracer TRACER = DebugLogger.getTracer();
075    
076      // The raw, unprocessed entry DN as included in the client request.
077      private ByteString rawEntryDN;
078    
079      // The DN of the entry for the delete operation.
080      private DN entryDN;
081    
082      // The proxied authorization target DN for this operation.
083      private DN proxiedAuthorizationDN;
084    
085      // The set of response controls for this delete operation.
086      private List<Control> responseControls;
087    
088      // The change number that has been assigned to this operation.
089      private long changeNumber;
090    
091    
092      /**
093       * Creates a new delete operation with the provided information.
094       *
095       * @param  clientConnection  The client connection with which this operation
096       *                           is associated.
097       * @param  operationID       The operation ID for this operation.
098       * @param  messageID         The message ID of the request with which this
099       *                           operation is associated.
100       * @param  requestControls   The set of controls included in the request.
101       * @param  rawEntryDN        The raw, unprocessed DN of the entry to delete,
102       *                           as included in the client request.
103       */
104      public DeleteOperationBasis(ClientConnection clientConnection,
105                             long operationID,
106                             int messageID, List<Control> requestControls,
107                             ByteString rawEntryDN)
108      {
109        super(clientConnection, operationID, messageID, requestControls);
110    
111    
112        this.rawEntryDN = rawEntryDN;
113    
114        entryDN          = null;
115        responseControls = new ArrayList<Control>();
116        cancelRequest    = null;
117        changeNumber     = -1;
118      }
119    
120    
121    
122      /**
123       * Creates a new delete operation with the provided information.
124       *
125       * @param  clientConnection  The client connection with which this operation
126       *                           is associated.
127       * @param  operationID       The operation ID for this operation.
128       * @param  messageID         The message ID of the request with which this
129       *                           operation is associated.
130       * @param  requestControls   The set of controls included in the request.
131       * @param  entryDN           The entry DN for this delete operation.
132       */
133      public DeleteOperationBasis(ClientConnection clientConnection,
134                             long operationID,
135                             int messageID, List<Control> requestControls,
136                             DN entryDN)
137      {
138        super(clientConnection, operationID, messageID, requestControls);
139    
140    
141        this.entryDN = entryDN;
142    
143        rawEntryDN       = new ASN1OctetString(entryDN.toString());
144        responseControls = new ArrayList<Control>();
145        cancelRequest    = null;
146        changeNumber     = -1;
147      }
148    
149      /**
150       * {@inheritDoc}
151       */
152      public final ByteString getRawEntryDN()
153      {
154        return rawEntryDN;
155      }
156    
157      /**
158       * {@inheritDoc}
159       */
160      public final void setRawEntryDN(ByteString rawEntryDN)
161      {
162        this.rawEntryDN = rawEntryDN;
163    
164        entryDN = null;
165      }
166    
167      /**
168       * {@inheritDoc}
169       */
170      public final DN getEntryDN()
171      {
172        try
173        {
174          if (entryDN == null)
175          {
176            entryDN = DN.decode(rawEntryDN);
177          }
178        }
179        catch (DirectoryException de)
180        {
181          if (debugEnabled())
182          {
183            TRACER.debugCaught(DebugLogLevel.ERROR, de);
184          }
185    
186          setResultCode(de.getResultCode());
187          appendErrorMessage(de.getMessageObject());
188          setMatchedDN(de.getMatchedDN());
189          setReferralURLs(de.getReferralURLs());
190        }
191    
192        return entryDN;
193      }
194    
195      /**
196       * {@inheritDoc}
197       */
198      public final long getChangeNumber()
199      {
200        return changeNumber;
201      }
202    
203      /**
204       * {@inheritDoc}
205       */
206      public final void setChangeNumber(long changeNumber)
207      {
208        this.changeNumber = changeNumber;
209      }
210    
211      /**
212       * {@inheritDoc}
213       */
214      @Override()
215      public final OperationType getOperationType()
216      {
217        // Note that no debugging will be done in this method because it is a likely
218        // candidate for being called by the logging subsystem.
219    
220        return OperationType.DELETE;
221      }
222    
223    
224      /**
225       * {@inheritDoc}
226       */
227      @Override()
228      public final String[][] getRequestLogElements()
229      {
230        // Note that no debugging will be done in this method because it is a likely
231        // candidate for being called by the logging subsystem.
232    
233        return new String[][]
234        {
235          new String[] { LOG_ELEMENT_ENTRY_DN, String.valueOf(rawEntryDN) }
236        };
237      }
238    
239      /**
240       * {@inheritDoc}
241       */
242      @Override()
243      public final String[][] getResponseLogElements()
244      {
245        // Note that no debugging will be done in this method because it is a likely
246        // candidate for being called by the logging subsystem.
247    
248        String resultCode = String.valueOf(getResultCode().getIntValue());
249    
250        String errorMessage;
251        MessageBuilder errorMessageBuffer = getErrorMessage();
252        if (errorMessageBuffer == null)
253        {
254          errorMessage = null;
255        }
256        else
257        {
258          errorMessage = errorMessageBuffer.toString();
259        }
260    
261        String matchedDNStr;
262        DN matchedDN = getMatchedDN();
263        if (matchedDN == null)
264        {
265          matchedDNStr = null;
266        }
267        else
268        {
269          matchedDNStr = matchedDN.toString();
270        }
271    
272        String referrals;
273        List<String> referralURLs = getReferralURLs();
274        if ((referralURLs == null) || referralURLs.isEmpty())
275        {
276          referrals = null;
277        }
278        else
279        {
280          StringBuilder buffer = new StringBuilder();
281          Iterator<String> iterator = referralURLs.iterator();
282          buffer.append(iterator.next());
283    
284          while (iterator.hasNext())
285          {
286            buffer.append(", ");
287            buffer.append(iterator.next());
288          }
289    
290          referrals = buffer.toString();
291        }
292    
293        String processingTime =
294             String.valueOf(getProcessingTime());
295    
296        return new String[][]
297        {
298          new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
299          new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
300          new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
301          new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
302          new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
303        };
304      }
305    
306      /**
307       * {@inheritDoc}
308       */
309      public DN getProxiedAuthorizationDN()
310      {
311        return proxiedAuthorizationDN;
312      }
313    
314      /**
315       * {@inheritDoc}
316       */
317      @Override()
318      public final List<Control> getResponseControls()
319      {
320        return responseControls;
321      }
322    
323      /**
324       * {@inheritDoc}
325       */
326      @Override()
327      public final void addResponseControl(Control control)
328      {
329        responseControls.add(control);
330      }
331    
332      /**
333       * {@inheritDoc}
334       */
335      @Override()
336      public final void removeResponseControl(Control control)
337      {
338        responseControls.remove(control);
339      }
340    
341      /**
342       * {@inheritDoc}
343       */
344      @Override()
345      public final void toString(StringBuilder buffer)
346      {
347        buffer.append("DeleteOperation(connID=");
348        buffer.append(clientConnection.getConnectionID());
349        buffer.append(", opID=");
350        buffer.append(operationID);
351        buffer.append(", dn=");
352        buffer.append(rawEntryDN);
353        buffer.append(")");
354      }
355      /**
356       * {@inheritDoc}
357       */
358      public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
359      {
360        this.proxiedAuthorizationDN = proxiedAuthorizationDN;
361      }
362    
363      /**
364       * {@inheritDoc}
365       */
366      public final void run()
367      {
368        setResultCode(ResultCode.UNDEFINED);
369    
370        // Start the processing timer.
371        setProcessingStartTime();
372    
373        // Log the delete request message.
374        logDeleteRequest(this);
375    
376        // Get the plugin config manager that will be used for invoking plugins.
377        PluginConfigManager pluginConfigManager =
378            DirectoryServer.getPluginConfigManager();
379    
380        // This flag is set to true as soon as a workflow has been executed.
381        boolean workflowExecuted = false;
382    
383        try
384        {
385          // Invoke the pre-parse delete plugins.
386          PluginResult.PreParse preParseResult =
387              pluginConfigManager.invokePreParseDeletePlugins(this);
388          if(!preParseResult.continueProcessing())
389          {
390            setResultCode(preParseResult.getResultCode());
391            appendErrorMessage(preParseResult.getErrorMessage());
392            setMatchedDN(preParseResult.getMatchedDN());
393            setReferralURLs(preParseResult.getReferralURLs());
394            return;
395          }
396    
397    
398          // Check for a request to cancel this operation.
399          checkIfCanceled(false);
400    
401    
402          // Process the entry DN to convert it from its raw form as provided by the
403          // client to the form required for the rest of the delete processing.
404          DN entryDN = getEntryDN();
405          if (entryDN == null){
406            return;
407          }
408    
409    
410          // Retrieve the network group attached to the client connection
411          // and get a workflow to process the operation.
412          NetworkGroup ng = getClientConnection().getNetworkGroup();
413          Workflow workflow = ng.getWorkflowCandidate(entryDN);
414          if (workflow == null)
415          {
416            // We have found no workflow for the requested base DN, just return
417            // a no such entry result code and stop the processing.
418            updateOperationErrMsgAndResCode();
419            return;
420          }
421          workflow.execute(this);
422          workflowExecuted = true;
423    
424        }
425        catch(CanceledOperationException coe)
426        {
427          if (debugEnabled())
428          {
429            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
430          }
431    
432          setResultCode(ResultCode.CANCELED);
433          cancelResult = new CancelResult(ResultCode.CANCELED, null);
434    
435          appendErrorMessage(coe.getCancelRequest().getCancelReason());
436        }
437        finally
438        {
439          // Stop the processing timer.
440          setProcessingStopTime();
441    
442          if(cancelRequest == null || cancelResult == null ||
443              cancelResult.getResultCode() != ResultCode.CANCELED ||
444              cancelRequest.notifyOriginalRequestor() ||
445              DirectoryServer.notifyAbandonedOperations())
446          {
447            clientConnection.sendResponse(this);
448          }
449    
450    
451          // Log the delete response.
452          logDeleteResponse(this);
453    
454          // Notifies any persistent searches that might be registered with the
455          // server.
456          notifyPersistentSearches(workflowExecuted);
457    
458          // Invoke the post-response delete plugins.
459          invokePostResponsePlugins(workflowExecuted);
460    
461          // If no cancel result, set it
462          if(cancelResult == null)
463          {
464            cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
465          }
466        }
467      }
468    
469    
470      /**
471       * Invokes the post response plugins. If a workflow has been executed
472       * then invoke the post response plugins provided by the workflow
473       * elements of the worklfow, otherwise invoke the post reponse plugins
474       * that have been registered with the current operation.
475       *
476       * @param workflowExecuted <code>true</code> if a workflow has been
477       *                         executed
478       */
479      private void invokePostResponsePlugins(boolean workflowExecuted)
480      {
481        // Get the plugin config manager that will be used for invoking plugins.
482        PluginConfigManager pluginConfigManager =
483          DirectoryServer.getPluginConfigManager();
484    
485        // Invoke the post response plugins
486        if (workflowExecuted)
487        {
488          // Invoke the post response plugins that have been registered by
489          // the workflow elements
490          List localOperations =
491            (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
492    
493          if (localOperations != null)
494          {
495            for (Object localOp : localOperations)
496            {
497              LocalBackendDeleteOperation localOperation =
498                (LocalBackendDeleteOperation)localOp;
499              pluginConfigManager.invokePostResponseDeletePlugins(localOperation);
500            }
501          }
502        }
503        else
504        {
505          // Invoke the post response plugins that have been registered with
506          // the current operation
507          pluginConfigManager.invokePostResponseDeletePlugins(this);
508        }
509      }
510    
511    
512      /**
513       * Notifies any persistent searches that might be registered with the server.
514       * If no workflow has been executed then don't notify persistent searches.
515       *
516       * @param workflowExecuted <code>true</code> if a workflow has been
517       *                         executed
518       */
519      private void notifyPersistentSearches(boolean workflowExecuted)
520      {
521        if (! workflowExecuted)
522        {
523          return;
524        }
525    
526        List localOperations =
527          (List)getAttachment(Operation.LOCALBACKENDOPERATIONS);
528    
529        if (localOperations != null)
530        {
531          for (Object localOp : localOperations)
532          {
533            LocalBackendDeleteOperation localOperation =
534              (LocalBackendDeleteOperation)localOp;
535            // Notify any persistent searches that might be registered with the
536            // server.
537            if (getResultCode() == ResultCode.SUCCESS)
538            {
539              for (PersistentSearch persistentSearch :
540                DirectoryServer.getPersistentSearches())
541              {
542                try
543                {
544                  persistentSearch.processDelete(localOperation,
545                      localOperation.getEntryToDelete());
546                }
547                catch (Exception e)
548                {
549                  if (debugEnabled())
550                  {
551                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
552                  }
553    
554                  Message message = ERR_DELETE_ERROR_NOTIFYING_PERSISTENT_SEARCH.
555                      get(String.valueOf(persistentSearch), getExceptionMessage(e));
556                  logError(message);
557    
558                  DirectoryServer.deregisterPersistentSearch(persistentSearch);
559                }
560              }
561            }
562          }
563        }
564      }
565    
566    
567      /**
568       * Updates the error message and the result code of the operation.
569       *
570       * This method is called because no workflows were found to process
571       * the operation.
572       */
573      private void updateOperationErrMsgAndResCode()
574      {
575        setResultCode(ResultCode.NO_SUCH_OBJECT);
576        appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
577                String.valueOf(getEntryDN())));
578      }
579    
580    
581      /**
582       * {@inheritDoc}
583       *
584       * This method always returns null.
585       */
586      public Entry getEntryToDelete() {
587        // TODO Auto-generated method stub
588        return null;
589      }
590    
591    }
592