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 static org.opends.messages.CoreMessages.*;
030 import static org.opends.server.util.Validator.ensureNotNull;
031
032 import java.util.TreeMap;
033 import java.util.Collection;
034
035 import org.opends.server.types.DN;
036 import org.opends.server.types.DirectoryException;
037 import org.opends.server.types.ResultCode;
038 import org.opends.server.workflowelement.WorkflowElement;
039
040
041 /**
042 * This class defines the network group. A network group is used to categorize
043 * client connections. A network group is defined by a set of criteria, a
044 * set of policies and a set of workflow nodes. A client connection belongs to
045 * a network group whenever it satisfies all the network group criteria. As
046 * soon as a client connection belongs to a network group, it has to comply
047 * with all the network group policies. Any cleared client operation can be
048 * routed to one the network group workflow nodes.
049 */
050 public class NetworkGroup
051 {
052 // Workflow nodes registered with the current network group.
053 // Keys are workflowIDs.
054 private TreeMap<String, WorkflowTopologyNode> registeredWorkflowNodes =
055 new TreeMap<String, WorkflowTopologyNode>();
056
057
058 // A lock to protect concurrent access to the registered Workflow nodes.
059 private Object registeredWorkflowNodesLock = new Object();
060
061
062 // The workflow node for the rootDSE entry. The RootDSE workflow node
063 // is not stored in the list of registered workflow nodes.
064 private RootDseWorkflowTopology rootDSEWorkflowNode = null;
065
066
067 // List of naming contexts handled by the network group.
068 private NetworkGroupNamingContexts namingContexts =
069 new NetworkGroupNamingContexts();
070
071
072 // The default network group (singleton).
073 // The default network group has no criterion, no policy, and gives
074 // access to all the workflows. The purpose of the default network
075 // group is to allow new clients to perform a first operation before
076 // they can be attached to a specific network group.
077 private static NetworkGroup defaultNetworkGroup =
078 new NetworkGroup ("default");
079
080
081 // The list of all network groups that are registered with the server.
082 // The defaultNetworkGroup is not in the list of registered network groups.
083 private static TreeMap<String, NetworkGroup> registeredNetworkGroups =
084 new TreeMap<String, NetworkGroup>();
085
086
087 // A lock to protect concurrent access to the registeredNetworkGroups.
088 private static Object registeredNetworkGroupsLock = new Object();
089
090
091 // The network group internal identifier.
092 private String networkGroupID = null;
093
094
095
096 /**
097 * Creates a new instance of the network group.
098 *
099 * @param networkGroupID the network group internal identifier
100 */
101 public NetworkGroup(
102 String networkGroupID
103 )
104 {
105 this.networkGroupID = networkGroupID;
106 }
107
108
109 /**
110 * Performs any finalization that might be required when this
111 * network group is unloaded. No action is taken in the
112 * default implementation.
113 */
114 public void finalizeNetworkGroup()
115 {
116 // No action is required by default.
117 }
118
119
120 /**
121 * Registers the current network group (this) with the server.
122 *
123 * @throws DirectoryException If the network group ID for the provided
124 * network group conflicts with the network
125 * group ID of an existing network group.
126 */
127 public void register()
128 throws DirectoryException
129 {
130 ensureNotNull(networkGroupID);
131
132 synchronized (registeredNetworkGroupsLock)
133 {
134 // The network group must not be already registered
135 if (registeredNetworkGroups.containsKey(networkGroupID))
136 {
137 Message message = ERR_REGISTER_NETWORK_GROUP_ALREADY_EXISTS.get(
138 networkGroupID);
139 throw new DirectoryException(
140 ResultCode.UNWILLING_TO_PERFORM, message);
141 }
142
143 TreeMap<String, NetworkGroup> newRegisteredNetworkGroups =
144 new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
145 newRegisteredNetworkGroups.put(networkGroupID, this);
146 registeredNetworkGroups = newRegisteredNetworkGroups;
147 }
148 }
149
150
151 /**
152 * Deregisters the current network group (this) with the server.
153 */
154 public void deregister()
155 {
156 synchronized (registeredNetworkGroupsLock)
157 {
158 TreeMap<String, NetworkGroup> networkGroups =
159 new TreeMap<String, NetworkGroup>(registeredNetworkGroups);
160 networkGroups.remove(networkGroupID);
161 registeredNetworkGroups = networkGroups;
162 }
163 }
164
165
166 /**
167 * Registers a workflow with the network group.
168 *
169 * @param workflow the workflow to register
170 *
171 * @throws DirectoryException If the workflow ID for the provided
172 * workflow conflicts with the workflow
173 * ID of an existing workflow.
174 */
175 public void registerWorkflow(
176 WorkflowImpl workflow
177 ) throws DirectoryException
178 {
179 // The workflow is rgistered with no pre/post workflow element.
180 registerWorkflow(workflow, null, null);
181 }
182
183
184 /**
185 * Registers a workflow with the network group and the workflow may have
186 * pre and post workflow element.
187 *
188 * @param workflow the workflow to register
189 * @param preWorkflowElements the tasks to execute before the workflow
190 * @param postWorkflowElements the tasks to execute after the workflow
191 *
192 * @throws DirectoryException If the workflow ID for the provided
193 * workflow conflicts with the workflow
194 * ID of an existing workflow.
195 */
196 private void registerWorkflow(
197 WorkflowImpl workflow,
198 WorkflowElement[] preWorkflowElements,
199 WorkflowElement[] postWorkflowElements
200 ) throws DirectoryException
201 {
202 // Is it the rootDSE workflow?
203 DN baseDN = workflow.getBaseDN();
204 if (baseDN.isNullDN())
205 {
206 // NOTE - The rootDSE workflow is stored with the registeredWorkflows.
207 rootDSEWorkflowNode =
208 new RootDseWorkflowTopology(workflow, namingContexts);
209 }
210 else
211 {
212 // This workflow is not the rootDSE workflow. Try to insert it in the
213 // workflow topology.
214 WorkflowTopologyNode workflowNode = new WorkflowTopologyNode(
215 workflow, preWorkflowElements, postWorkflowElements);
216
217 // Register the workflow node with the network group. If the workflow
218 // ID is already existing then an exception is raised.
219 registerWorkflowNode(workflowNode);
220
221 // Now add the workflow in the workflow topology...
222 for (WorkflowTopologyNode curNode: registeredWorkflowNodes.values())
223 {
224 // Try to insert the new workflow under an existing workflow...
225 if (curNode.insertSubordinate(workflowNode))
226 {
227 // new workflow has been inserted in the topology
228 continue;
229 }
230
231 // ... or try to insert the existing workflow below the new
232 // workflow
233 if (workflowNode.insertSubordinate(curNode))
234 {
235 // new workflow has been inserted in the topology
236 continue;
237 }
238 }
239
240 // Rebuild the list of naming context handled by the network group
241 rebuildNamingContextList();
242 }
243 }
244
245
246 /**
247 * Deregisters a workflow with the network group. The workflow to
248 * deregister is identified by its baseDN.
249 *
250 * @param baseDN the baseDN of the workflow to deregister, may be null
251 *
252 * @return the deregistered workflow
253 */
254 public Workflow deregisterWorkflow(
255 DN baseDN
256 )
257 {
258 Workflow workflow = null;
259
260 if (baseDN == null)
261 {
262 return workflow;
263 }
264
265 if (baseDN.isNullDN())
266 {
267 // deregister the rootDSE
268 deregisterWorkflow(rootDSEWorkflowNode);
269 workflow = rootDSEWorkflowNode.getWorkflowImpl();
270 }
271 else
272 {
273 // deregister a workflow node
274 synchronized (registeredWorkflowNodesLock)
275 {
276 for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
277 {
278 DN curDN = node.getBaseDN();
279 if (curDN.equals(baseDN))
280 {
281 // Call deregisterWorkflow() instead of deregisterWorkflowNode()
282 // because we want the naming context list to be updated as well.
283 deregisterWorkflow(node);
284 workflow = node.getWorkflowImpl();
285
286 // Only one workflow can match the baseDN, so we can break
287 // the loop here.
288 break;
289 }
290 }
291 }
292 }
293
294 return workflow;
295 }
296
297
298 /**
299 * Deregisters a workflow with the network group. The workflow to
300 * deregister is identified by its workflow ID.
301 *
302 * @param workflowID the workflow identifier of the workflow to deregister
303 */
304 public void deregisterWorkflow(
305 String workflowID
306 )
307 {
308 String rootDSEWorkflowID = null;
309 if (rootDSEWorkflowNode != null)
310 {
311 rootDSEWorkflowID = rootDSEWorkflowNode.getWorkflowImpl().getWorkflowId();
312 }
313
314 if (workflowID.equalsIgnoreCase(rootDSEWorkflowID))
315 {
316 // deregister the rootDSE
317 deregisterWorkflow(rootDSEWorkflowNode);
318 }
319 else
320 {
321 // deregister a workflow node
322 synchronized (registeredWorkflowNodesLock)
323 {
324 for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
325 {
326 String curID = node.getWorkflowImpl().getWorkflowId();
327 if (curID.equals(workflowID))
328 {
329 // Call deregisterWorkflow() instead of deregisterWorkflowNode()
330 // because we want the naming context list to be updated as well.
331 deregisterWorkflow(node);
332
333 // Only one workflow can match the baseDN, so we can break
334 // the loop here.
335 break;
336 }
337 }
338 }
339 }
340 }
341
342
343 /**
344 * Deregisters a workflow node with the network group.
345 *
346 * @param workflow the workflow node to deregister
347 */
348 private void deregisterWorkflow(Workflow workflow)
349 {
350 // true as soon as the workflow has been deregistered
351 boolean deregistered = false;
352
353 // Is it the rootDSE workflow?
354 if (workflow == rootDSEWorkflowNode)
355 {
356 rootDSEWorkflowNode = null;
357 deregistered = true;
358 }
359 else
360 {
361 // Deregister the workflow with the network group.
362 WorkflowTopologyNode workflowNode = (WorkflowTopologyNode) workflow;
363 deregisterWorkflowNode(workflowNode);
364 deregistered = true;
365
366 // The workflow to deregister is not the root DSE workflow.
367 // Remove it from the workflow topology.
368 workflowNode.remove();
369
370 // Rebuild the list of naming context handled by the network group
371 rebuildNamingContextList();
372 }
373
374 // If the workflow has been deregistered then deregister it with
375 // the default network group as well
376 if (deregistered && (this != defaultNetworkGroup))
377 {
378 defaultNetworkGroup.deregisterWorkflow(workflow);
379 }
380 }
381
382
383 /**
384 * Registers a workflow node with the network group.
385 *
386 * @param workflowNode the workflow node to register
387 *
388 * @throws DirectoryException If the workflow node ID for the provided
389 * workflow node conflicts with the workflow
390 * node ID of an existing workflow node.
391 */
392 private void registerWorkflowNode(
393 WorkflowTopologyNode workflowNode
394 ) throws DirectoryException
395 {
396 String workflowID = workflowNode.getWorkflowImpl().getWorkflowId();
397 ensureNotNull(workflowID);
398
399 synchronized (registeredWorkflowNodesLock)
400 {
401 // The workflow must not be already registered
402 if (registeredWorkflowNodes.containsKey(workflowID))
403 {
404 Message message = ERR_REGISTER_WORKFLOW_NODE_ALREADY_EXISTS.get(
405 workflowID, networkGroupID);
406 throw new DirectoryException(
407 ResultCode.UNWILLING_TO_PERFORM, message);
408 }
409
410 TreeMap<String, WorkflowTopologyNode> newRegisteredWorkflowNodes =
411 new TreeMap<String, WorkflowTopologyNode>(registeredWorkflowNodes);
412 newRegisteredWorkflowNodes.put(workflowID, workflowNode);
413 registeredWorkflowNodes = newRegisteredWorkflowNodes;
414 }
415 }
416
417
418 /**
419 * Deregisters the current worklow (this) with the server.
420 *
421 * @param workflowNode the workflow node to deregister
422 */
423 private void deregisterWorkflowNode(
424 WorkflowTopologyNode workflowNode
425 )
426 {
427 synchronized (registeredWorkflowNodesLock)
428 {
429 TreeMap<String, WorkflowTopologyNode> newWorkflowNodes =
430 new TreeMap<String, WorkflowTopologyNode>(registeredWorkflowNodes);
431 newWorkflowNodes.remove(workflowNode.getWorkflowImpl().getWorkflowId());
432 registeredWorkflowNodes = newWorkflowNodes;
433 }
434 }
435
436
437 /**
438 * Gets the highest workflow in the topology that can handle the baseDN.
439 *
440 * @param baseDN the base DN of the request
441 * @return the highest workflow in the topology that can handle the base DN,
442 * <code>null</code> if none was found
443 */
444 public Workflow getWorkflowCandidate(
445 DN baseDN
446 )
447 {
448 // the top workflow to return
449 Workflow workflowCandidate = null;
450
451 // get the list of workflow candidates
452 if (baseDN.isNullDN())
453 {
454 // The rootDSE workflow is the candidate.
455 workflowCandidate = rootDSEWorkflowNode;
456 }
457 else
458 {
459 // Search the highest workflow in the topology that can handle
460 // the baseDN.
461 for (WorkflowTopologyNode curWorkflow: namingContexts.getNamingContexts())
462 {
463 workflowCandidate = curWorkflow.getWorkflowCandidate (baseDN);
464 if (workflowCandidate != null)
465 {
466 break;
467 }
468 }
469 }
470
471 return workflowCandidate;
472 }
473
474
475 /**
476 * Returns the default network group. The default network group is always
477 * defined and has no criterion, no policy and provide full access to
478 * all the registered workflows.
479 *
480 * @return the default network group
481 */
482 public static NetworkGroup getDefaultNetworkGroup()
483 {
484 return defaultNetworkGroup;
485 }
486
487
488 /**
489 * Rebuilds the list of naming contexts handled by the network group.
490 * This operation should be performed whenever a workflow topology
491 * has been updated (workflow registration or de-registration).
492 */
493 private void rebuildNamingContextList()
494 {
495 // reset lists of naming contexts
496 namingContexts.resetLists();
497
498 // a registered workflow with no parent is a naming context
499 for (WorkflowTopologyNode workflowNode: registeredWorkflowNodes.values())
500 {
501 WorkflowTopologyNode parent = workflowNode.getParent();
502 if (parent == null)
503 {
504 namingContexts.addNamingContext (workflowNode);
505 }
506 }
507 }
508
509
510 /**
511 * Returns the list of naming contexts handled by the network group.
512 *
513 * @return the list of naming contexts
514 */
515 public NetworkGroupNamingContexts getNamingContexts()
516 {
517 return namingContexts;
518 }
519
520
521 /**
522 * Dumps info from the current network group for debug purpose.
523 *
524 * @param leftMargin white spaces used to indent traces
525 * @return a string buffer that contains trace information
526 */
527 public StringBuilder toString(String leftMargin)
528 {
529 StringBuilder sb = new StringBuilder();
530 String newMargin = leftMargin + " ";
531
532 sb.append (leftMargin + "Networkgroup (" + networkGroupID+ "\n");
533 sb.append (leftMargin + "List of registered workflows:\n");
534 for (WorkflowTopologyNode node: registeredWorkflowNodes.values())
535 {
536 sb.append (node.toString (newMargin));
537 }
538
539 namingContexts.toString (leftMargin);
540
541 sb.append (leftMargin + "rootDSEWorkflow:\n");
542 if (rootDSEWorkflowNode == null)
543 {
544 sb.append (newMargin + "null\n");
545 }
546 else
547 {
548 sb.append (rootDSEWorkflowNode.toString (newMargin));
549 }
550
551 return sb;
552 }
553
554
555 /**
556 * Deregisters all network groups that have been registered. This should be
557 * called when the server is shutting down.
558 */
559 public static void deregisterAllOnShutdown()
560 {
561 synchronized (registeredNetworkGroupsLock)
562 {
563 // Invalidate all NetworkGroups so they cannot accidentally be used
564 // after a restart.
565 Collection<NetworkGroup> networkGroups = registeredNetworkGroups.values();
566 for (NetworkGroup networkGroup: networkGroups)
567 {
568 networkGroup.invalidate();
569 }
570 defaultNetworkGroup.invalidate();
571
572 registeredNetworkGroups = new TreeMap<String,NetworkGroup>();
573 defaultNetworkGroup = new NetworkGroup ("default");
574 }
575 }
576
577 /**
578 * We've seen parts of the server hold references to a NetworkGroup
579 * during an in-core server restart. To help detect when this happens,
580 * we null out the member variables, so we will fail fast with an NPE if an
581 * invalidate NetworkGroup is used.
582 */
583 private void invalidate()
584 {
585 namingContexts = null;
586 networkGroupID = null;
587 rootDSEWorkflowNode = null;
588 registeredWorkflowNodes = null;
589 }
590
591
592 /**
593 * Provides the list of network group registered with the server.
594 *
595 * @return the list of registered network groups
596 */
597 public static Collection<NetworkGroup> getRegisteredNetworkGroups()
598 {
599 return registeredNetworkGroups.values();
600 }
601
602
603 /**
604 * Resets the configuration of all the registered network groups.
605 */
606 public static void resetConfig()
607 {
608 // Reset the default network group
609 defaultNetworkGroup.reset();
610
611 // Reset all the registered network group
612 synchronized (registeredNetworkGroupsLock)
613 {
614 registeredNetworkGroups = new TreeMap<String, NetworkGroup>();
615 }
616 }
617
618
619 /**
620 * Resets the configuration of the current network group.
621 */
622 public void reset()
623 {
624 synchronized (registeredWorkflowNodesLock)
625 {
626 registeredWorkflowNodes = new TreeMap<String, WorkflowTopologyNode>();
627 rootDSEWorkflowNode = null;
628 namingContexts = new NetworkGroupNamingContexts();
629 }
630 }
631 }