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    
029    
030    import java.util.ArrayList;
031    
032    import org.opends.server.types.*;
033    import org.opends.server.workflowelement.WorkflowElement;
034    
035    
036    /**
037     * This class implements a workflow node. A workflow node is used
038     * to build a tree of workflows (aka workflow topology). Each node
039     * may have a parent node and/or subordinate nodes. A node with no
040     * parent is a naming context.
041     *
042     * Each node in the workflow topology is linked to a WorkflowImpl
043     * which contains the real processing. The base DN of the workflow
044     * node is the base DN of the related WorkflowImpl.
045     *
046     * How the workflow topology is built?
047     * A workflow node is a subordinate of another workflow node when
048     * the base DN of the former workflow is an ancestor of the base DN
049     * of the latter workflow.
050     *
051     * A subtree search on a workflow node is performed on the node itself as
052     * well as on all the subordinate nodes.
053     */
054    public class WorkflowTopologyNode extends WorkflowTopology
055    {
056      // Parent node of the current workflow node.
057      private WorkflowTopologyNode parent = null;
058    
059    
060      // The list of subordinate nodes of the current workflow node.
061      private ArrayList<WorkflowTopologyNode> subordinates =
062        new ArrayList<WorkflowTopologyNode>();
063    
064    
065      /**
066       * Creates a new node for a workflow topology. The new node is initialized
067       * with a WorkflowImpl which contains the real processing. Optionally,
068       * the node may have tasks to be executed before and/or after the real
069       * processing. In the current implementation, such pre and post workflow
070       * elements are not used.
071       *
072       * @param workflowImpl          the real processing attached to the node
073       * @param preWorkflowElements   the list of tasks to be executed before
074       *                              the real processing
075       * @param postWorkflowElements  the list of tasks to be executed after
076       *                              the real processing
077       */
078      public WorkflowTopologyNode(
079          WorkflowImpl workflowImpl,
080          WorkflowElement[] preWorkflowElements,
081          WorkflowElement[] postWorkflowElements
082          )
083      {
084        super(workflowImpl);
085      }
086    
087    
088      /**
089       * Executes an operation on a set of data being identified by the
090       * workflow node base DN.
091       *
092       * @param operation the operation to execute
093       *
094       * @throws CanceledOperationException if this operation should
095       * be cancelled.
096       */
097      public void execute(Operation operation)
098          throws CanceledOperationException {
099        // Execute the operation
100        getWorkflowImpl().execute(operation);
101    
102        // For subtree search operation we need to go through the subordinate
103        // nodes.
104        if (operation.getOperationType() == OperationType.SEARCH)
105        {
106          executeSearchOnSubordinates((SearchOperation) operation);
107        }
108      }
109    
110    
111      /**
112       * Executes a search operation on the subordinate workflows.
113       *
114       * @param searchOp the search operation to execute
115       *
116       * @throws CanceledOperationException if this operation should
117       * be cancelled.
118       */
119      private void executeSearchOnSubordinates(SearchOperation searchOp)
120          throws CanceledOperationException {
121        // If the scope of the search is 'base' then it's useless to search
122        // in the subordinate workflows.
123        SearchScope originalScope = searchOp.getScope();
124        if (originalScope == SearchScope.BASE_OBJECT)
125        {
126          return;
127        }
128    
129        // Elaborate the new search scope before executing the search operation
130        // in the subordinate workflows.
131        SearchScope newScope = elaborateScopeForSearchInSubordinates(originalScope);
132        searchOp.setScope(newScope);
133    
134        // Let's search in the subordinate workflows.
135        WorkflowResultCode workflowResultCode = new WorkflowResultCode(
136            searchOp.getResultCode(), searchOp.getErrorMessage());
137        DN originalBaseDN = searchOp.getBaseDN();
138        for (WorkflowTopologyNode subordinate: getSubordinates())
139        {
140          // We have to change the operation request base DN to match the
141          // subordinate workflow base DN. Otherwise the workflow will
142          // return a no such entry result code as the operation request
143          // base DN is a superior of the subordinate workflow base DN.
144          DN subordinateDN = subordinate.getBaseDN();
145    
146          // If the new search scope is 'base' and the search base DN does not
147          // map the subordinate workflow then skip the subordinate workflow.
148          if ((newScope == SearchScope.BASE_OBJECT)
149              && !subordinateDN.getParent().equals(originalBaseDN))
150          {
151            continue;
152          }
153    
154          // If the request base DN is not a subordinate of the subordinate
155          // worklfow base DN then don't search in the subordinate workflow.
156          if (! originalBaseDN.isAncestorOf(subordinateDN))
157          {
158            continue;
159          }
160    
161          // Set the new request base DN and do execute the
162          // operation in the subordinate workflow.
163          searchOp.setBaseDN(subordinateDN);
164          subordinate.execute(searchOp);
165          boolean sendReferenceEntry =
166            workflowResultCode.elaborateGlobalResultCode(
167              searchOp.getResultCode(), searchOp.getErrorMessage());
168          if (sendReferenceEntry)
169          {
170            // TODO jdemendi - turn a referral result code into a reference entry
171            // and send the reference entry to the client application
172          }
173        }
174    
175        // Now we are done with the operation, let's restore the original
176        // base DN and search scope in the operation.
177        searchOp.setBaseDN(originalBaseDN);
178        searchOp.setScope(originalScope);
179    
180        // Update the operation result code and error message
181        searchOp.setResultCode(workflowResultCode.resultCode());
182        searchOp.setErrorMessage(workflowResultCode.errorMessage());
183      }
184    
185    
186      /**
187       * Sets the parent workflow.
188       *
189       * @param parent  the parent workflow of the current workflow
190       */
191      public void setParent(WorkflowTopologyNode parent)
192      {
193        this.parent = parent;
194      }
195    
196    
197      /**
198       * Gets the parent workflow.
199       *
200       * @return the parent workflow.
201       */
202      public WorkflowTopologyNode getParent()
203      {
204        return parent;
205      }
206    
207    
208      /**
209       * Indicates whether the root workflow element is encapsulating a private
210       * local backend or not.
211       *
212       * @return <code>true</code> if the root workflow element encapsulates
213       *         a private local backend
214       */
215      public boolean isPrivate()
216      {
217        return getWorkflowImpl().isPrivate();
218      }
219    
220    
221      /**
222       * Gets the base DN of the workflow that handles a given dn. The elected
223       * workflow may be the current workflow or one of its subordiante workflows.
224       *
225       * @param  dn  the DN for which we are looking a parent DN
226       * @return the base DN which is the parent of the <code>dn</code>,
227       *         <code>null</code> if no parent DN was found
228       */
229      public DN getParentBaseDN(DN dn)
230      {
231        if (dn == null)
232        {
233          return null;
234        }
235    
236        // parent base DN to return
237        DN parentBaseDN = null;
238    
239        // Is the dn a subordinate of the current base DN?
240        DN curBaseDN = getBaseDN();
241        if (curBaseDN != null)
242        {
243          if (dn.isDescendantOf(curBaseDN))
244          {
245            // The dn may be handled by the current workflow.
246            // Now we have to check whether the dn is handled by
247            // a subordinate.
248            for (WorkflowTopologyNode subordinate: getSubordinates())
249            {
250              parentBaseDN = subordinate.getParentBaseDN(dn);
251              if (parentBaseDN != null)
252              {
253                // the dn is handled by a subordinate
254                break;
255              }
256            }
257    
258            // If the dn is not handled by any subordinate, then it is
259            // handled by the current workflow.
260            if (parentBaseDN == null)
261            {
262              parentBaseDN = curBaseDN;
263            }
264          }
265        }
266    
267        return parentBaseDN;
268      }
269    
270    
271      /**
272       * Adds a workflow to the list of workflow subordinates without
273       * additional control.
274       *
275       * @param newWorkflow     the workflow to add to the subordinate list
276       * @param parentWorkflow  the parent workflow of the new workflow
277       */
278      private void addSubordinateNoCheck(
279          WorkflowTopologyNode newWorkflow,
280          WorkflowTopologyNode parentWorkflow
281          )
282      {
283        subordinates.add(newWorkflow);
284        newWorkflow.setParent(parentWorkflow);
285      }
286    
287    
288      /**
289       * Adds a workflow to the subordinate list of the current workflow.
290       * Before we can add the new workflow, we have to check whether
291       * the new workflow is a parent workflow of any of the current
292       * subordinates (if so, then we have to add the subordinate in the
293       * subordinate list of the new workflow).
294       *
295       * @param newWorkflow  the workflow to add in the subordinate list
296       */
297      private void addSubordinate(
298          WorkflowTopologyNode newWorkflow
299          )
300      {
301        // Dont try to add the workflow to itself.
302        if (newWorkflow == this)
303        {
304          return;
305        }
306    
307        // Check whether subordinates of current workflow should move to the
308        // new workflow subordinate list.
309        ArrayList<WorkflowTopologyNode> curSubordinateList =
310            new ArrayList<WorkflowTopologyNode>(getSubordinates());
311    
312        for (WorkflowTopologyNode curSubordinate: curSubordinateList)
313        {
314          DN newDN = newWorkflow.getBaseDN();
315          DN subordinateDN = curSubordinate.getBaseDN();
316    
317          // Dont try to add workflow when baseDNs are
318          // the same on both workflows.
319          if (newDN.equals(subordinateDN)) {
320            return;
321          }
322    
323          if (subordinateDN.isDescendantOf(newDN))
324          {
325            removeSubordinate(curSubordinate);
326            newWorkflow.addSubordinate(curSubordinate);
327          }
328        }
329    
330        // add the new workflow in the current workflow subordinate list
331        addSubordinateNoCheck(newWorkflow, this);
332      }
333    
334    
335      /**
336       * Remove a workflow from the subordinate list.
337       *
338       * @param subordinate  the subordinate to remove from the subordinate list
339       */
340      public void removeSubordinate(
341          WorkflowTopologyNode subordinate
342          )
343      {
344        subordinates.remove(subordinate);
345      }
346    
347    
348      /**
349       * Tries to insert a new workflow in the subordinate list of one of the
350       * current workflow subordinate, or in the current workflow subordinate list.
351       *
352       * @param newWorkflow  the new workflow to insert
353       *
354       * @return <code>true</code> if the new workflow has been inserted
355       *         in any subordinate list
356       */
357      public boolean insertSubordinate(
358          WorkflowTopologyNode newWorkflow
359          )
360      {
361        // don't try to insert the workflow in itself!
362        if (newWorkflow == this)
363        {
364          return false;
365        }
366    
367        // the returned status
368        boolean insertDone = false;
369    
370        DN parentBaseDN = getBaseDN();
371        DN newBaseDN    = newWorkflow.getBaseDN();
372    
373        // dont' try to insert workflows when baseDNs are the same on both
374        // workflows
375        if (parentBaseDN.equals(newBaseDN))
376        {
377          return false;
378        }
379    
380        // try to insert the new workflow
381        if (newBaseDN.isDescendantOf(parentBaseDN))
382        {
383          // the new workflow is a subordinate for this parent DN, let's
384          // insert the new workflow in the list of subordinates
385          for (WorkflowTopologyNode subordinate: getSubordinates())
386          {
387            insertDone = subordinate.insertSubordinate(newWorkflow);
388            if (insertDone)
389            {
390              // the newBaseDN is handled by a subordinate
391              break;
392            }
393          }
394    
395          // if the newBaseDN is not handled by a subordinate then the workflow
396          // is inserted it in the current workflow subordinate list
397          if (! insertDone)
398          {
399            addSubordinate(newWorkflow);
400            insertDone = true;
401          }
402        }
403    
404        return insertDone;
405      }
406    
407    
408      /**
409       * Removes the current workflow from the parent subordinate list
410       * and attach the workflow subordinates to the parent workflow.
411       *
412       * Example: the workflow to remove is w2
413       *
414       *        w1             w1
415       *        |             / \
416       *        w2     ==>   w3  w4
417       *       / \
418       *     w3   w4
419       *
420       * - Subordinate list of w1 is updated with w3 and w4.
421       * - Parent workflow of w3 and w4 is now w1.
422       */
423      public void remove()
424      {
425        // First of all, remove the workflow from the parent subordinate list
426        WorkflowTopologyNode parent = getParent();
427        if (parent != null)
428        {
429          parent.removeSubordinate(this);
430        }
431    
432        // Then set the parent of each subordinate and attach the subordinate to
433        // the parent.
434        for (WorkflowTopologyNode subordinate: getSubordinates())
435        {
436          subordinate.setParent(parent);
437          if (parent != null)
438          {
439            parent.addSubordinateNoCheck(subordinate, parent);
440          }
441        }
442      }
443    
444    
445      /**
446       * Gets the list of workflow subordinates.
447       *
448       * @return the list of workflow subordinates
449       */
450      public ArrayList<WorkflowTopologyNode> getSubordinates()
451      {
452        return subordinates;
453      }
454    
455    
456      /**
457       * Gets the highest workflow in the topology that can handle the requestDN.
458       * The highest workflow is either the current workflow or one of its
459       * subordinates.
460       *
461       * @param requestDN  The DN for which we search for a workflow
462       * @return the highest workflow that can handle the requestDN
463       *         <code>null</code> if none was found
464       */
465      public WorkflowTopologyNode getWorkflowCandidate(
466          DN requestDN
467          )
468      {
469        // the returned workflow
470        WorkflowTopologyNode workflowCandidate = null;
471    
472        // does the current workflow handle the request baseDN?
473        DN baseDN = getParentBaseDN(requestDN);
474        if (baseDN == null)
475        {
476          // the current workflow does not handle the requestDN,
477          // let's return null
478        }
479        else
480        {
481          // is there any subordinate that can handle the requestDN?
482          for (WorkflowTopologyNode subordinate: getSubordinates())
483          {
484            workflowCandidate = subordinate.getWorkflowCandidate(requestDN);
485            if (workflowCandidate != null)
486            {
487              break;
488            }
489          }
490    
491          // none of the subordinates can handle the requestDN, so the current
492          // workflow is the best root workflow candidate
493          if (workflowCandidate == null)
494          {
495            workflowCandidate = this;
496          }
497        }
498    
499        return workflowCandidate;
500      }
501    
502    
503      /**
504       * Dumps info from the current workflow for debug purpose.
505       *
506       * @param leftMargin  white spaces used to indent the traces
507       * @return a string buffer that contains trace information
508       */
509      public StringBuilder toString(String leftMargin)
510      {
511        StringBuilder sb = new StringBuilder();
512    
513        // display the baseDN
514        DN baseDN = getBaseDN();
515        String workflowID = this.getWorkflowImpl().getWorkflowId();
516        sb.append(leftMargin + "Workflow ID = " + workflowID + "\n");
517        sb.append(leftMargin + "         baseDN:[");
518        if (baseDN.isNullDN())
519        {
520          sb.append(" \"\"");
521        }
522        else
523        {
524          sb.append(" \"" + baseDN.toString() + "\"");
525        }
526        sb.append(" ]\n");
527    
528        // display the root workflow element
529        sb.append(leftMargin
530            + "         Root Workflow Element: "
531            + getWorkflowImpl().getRootWorkflowElement() + "\n");
532    
533        // display parent workflow
534        sb.append(leftMargin + "         Parent: " + getParent() + "\n");
535    
536        // dump each subordinate
537        sb.append(leftMargin + "         List of subordinates:\n");
538        ArrayList<WorkflowTopologyNode> subordinates = getSubordinates();
539        if (subordinates.isEmpty())
540        {
541          sb.append(leftMargin + "            NONE\n");
542        }
543        else
544        {
545          for (WorkflowTopologyNode subordinate: getSubordinates())
546          {
547            sb.append(subordinate.toString(leftMargin + "            "));
548          }
549        }
550    
551        return sb;
552      }
553    
554    }