KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > core > config > node > AbstractNode


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.core.config.node;
19
20 import java.util.Collection JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Set JavaDoc;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import org.sape.carbon.core.config.InvalidConfigurationException;
32 import org.sape.carbon.core.config.node.event.NodeEventListener;
33 import org.sape.carbon.core.exception.ExceptionUtility;
34 import org.sape.carbon.core.exception.InvalidParameterException;
35
36 /**
37  * Abstract implementation of the <code>Node</code> interface. This
38  * implementation is designed to be extended with the logic specific to the
39  * backing data store placed in the <code>destroyBackingData</code> method.
40  *
41  * Copyright 2002 Sapient
42  * @since carbon 1.0
43  * @author Douglas Voet, February 2002
44  * @version $Revision: 1.28 $($Author: dvoet $ / $Date: 2003/10/17 14:40:55 $)
45  */

46 public abstract class AbstractNode implements Node {
47
48     /**
49      * Provides a handle to Apache-commons logger
50      */

51     private Log log = LogFactory.getLog(this.getClass());
52
53     /** this Node's name */
54     private final String JavaDoc name;
55
56     /** link to the Node's parent */
57     private final Node parent;
58
59     /** this node's removed status */
60     private boolean removed = false;
61     
62     /**
63      * This lock is used to ensure that nodes are not added or
64      * loaded concurrently. Any time a node is put into childNodes, this lock
65      * guarantees that the node is constructed once and only once.
66      * addOrLoadChildLock is usually used in conjunction with a lock on this.
67      * A lock on addOrLoadChildLock keeps other threads from loading or adding
68      * new nodes while a lock on this ensures that access to childNodes is
69      * thread-safe.
70      */

71     private final Object JavaDoc addOrLoadChildLock;
72     
73     /**
74      * This object is syncrhonized upon whenever this node's state is read
75      * or altered. The state of this node includes the removed flag and
76      * the childNodes map, but not children themselves. State does not
77      * include this node's name or parent as these references are final.
78      * State also does not include the nodeListeners set as it synchronizes
79      * itself.
80      */

81     private final Object JavaDoc readOrAlterNodeLock;
82
83     /**
84      * Map of child Nodes keyed by the Node's name (String). Final because
85      * the reference to the map should never change for this node.
86      *
87      * @link aggregation
88      * @associates <{Node}>
89      */

90     protected final Map JavaDoc childNodes = new HashMap JavaDoc();
91
92     /**
93      * Contains the list of node listeners for this node.
94      */

95     protected final Set JavaDoc nodeListeners =
96         Collections.synchronizedSet(new HashSet JavaDoc());
97
98     /**
99      * Constructor
100      *
101      * @param parent the node's parent
102      * @param name the node's name
103      *
104      * @throws InvalidParameterException if name is null
105      */

106     public AbstractNode(Node parent, String JavaDoc name) {
107         this(parent, name, new Object JavaDoc(), new Object JavaDoc());
108     }
109
110     /**
111      * Constructor
112      *
113      * @param parent the node's parent
114      * @param name the node's name
115      * @param readOrAlterNodeLock the lock object that will be synchronized
116      * upon for all operations that access or modify this node's state
117      *
118      * @throws InvalidParameterException if name is null or readOrAlterNodeLock
119      * is null
120      */

121     public AbstractNode(
122         Node parent,
123         String JavaDoc name,
124         Object JavaDoc readOrAlterNodeLock,
125         Object JavaDoc addOrLoadChildLock) {
126             
127         // parameter check
128
if (name == null) {
129             throw new InvalidParameterException(
130                 this.getClass(),
131                 "name parameter cannot be null");
132         }
133
134         if (readOrAlterNodeLock == null) {
135             throw new InvalidParameterException(
136                 this.getClass(),
137                 "readOrAlterNodeLock parameter cannot be null");
138         }
139
140         if (addOrLoadChildLock == null) {
141             throw new InvalidParameterException(
142                 this.getClass(),
143                 "addOrLoadChildLock parameter cannot be null");
144         }
145
146         if (addOrLoadChildLock == readOrAlterNodeLock) {
147             throw new InvalidParameterException(
148                 this.getClass(),
149                 "readOrAlterNodeLock and addOrLoadChildLock cannot be the same object");
150         }
151
152         this.parent = parent;
153         this.name = name;
154         this.readOrAlterNodeLock = readOrAlterNodeLock;
155         this.addOrLoadChildLock = addOrLoadChildLock;
156     }
157
158     /**
159      * @see Node#getName()
160      */

161     public String JavaDoc getName() {
162         return this.name;
163     }
164
165     /**
166      * @see Node#getParent()
167      */

168     public Node getParent() {
169         return this.parent;
170     }
171
172     /**
173      * This implementation is recursive.
174      * If the parent is not null, it returns the parent's absolute name
175      * followed by Node.DELIMITER and this node's name
176      *
177      * @see Node#getAbsoluteName()
178      */

179     public String JavaDoc getAbsoluteName() {
180
181         if (getParent() == null) {
182             return this.name;
183         } else {
184             return getParent().getAbsoluteName() + Node.DELIMITER + this.name;
185         }
186     }
187
188     /**
189      * @see Node#getAllowsChildren()
190      */

191     public boolean getAllowsChildren() {
192         return true;
193     }
194
195     /**
196      * @see Node#remove()
197      *
198      * synchronized to ensure no one is fetching or adding children while
199      * this method is removing them
200      */

201     public int remove() throws NodeRemovalException {
202         int removedNodes = 0;
203         
204         if (!isRemoved()) {
205             // remove all the children
206
// note that this is not synchronized because that would cause
207
// a cascade of locks down the config hierachy and open a deadlock
208
// vulnerability
209
Node[] children = fetchChildren();
210
211             for (int i = 0; i < children.length; i++) {
212                 removedNodes += children[i].remove();
213             }
214                 
215             synchronized (getReadOrAlterNodeLock()) {
216                 if (backingDataExists()) {
217                     // remove this node
218
destroyBackingData();
219                     removedNodes++;
220                 }
221                 this.childNodes.clear();
222                 setRemoved();
223             }
224         }
225
226         return removedNodes;
227     }
228
229     /**
230      * Returns the name of the node.
231      *
232      * @return the name of the node
233      */

234     public String JavaDoc toString() {
235         return getName();
236     }
237
238     /**
239      * @see Node#isRemoved()
240      */

241     public boolean isRemoved() {
242         synchronized (getReadOrAlterNodeLock()) {
243             return this.removed;
244         }
245     }
246
247     /**
248      * This implementation refreshes all children and toggles the isRemoved
249      * flag depending on the return value of the backingDataExists method.
250      *
251      * @see Node#refresh()
252      */

253     public void refresh() {
254         if (!isRemoved()) {
255             
256             // localChildNodesMap is a local copy of this.childNodes that is
257
// used so that children can be refreshed in an unsynchronized
258
// context
259
Map JavaDoc localChildNodesMap;
260             
261             synchronized (getReadOrAlterNodeLock()) {
262                 localChildNodesMap = new HashMap JavaDoc(this.childNodes);
263                 
264                 if (!backingDataExists()) {
265                     // backing data is not there anymore!
266
setRemoved();
267                 }
268     
269             }
270             removeRemovedChildren();
271
272             // tell all the children to refresh themselves
273
// note that if this node has been removed, refreshing all
274
// the children should cause them to be marked as removed
275
// as well
276
Iterator JavaDoc childIterator = localChildNodesMap.values().iterator();
277             while (childIterator.hasNext()) {
278                 ((Node) childIterator.next()).refresh();
279             }
280         }
281     }
282
283     /**
284      * synchronized to ensure no one is adding or removing children
285      * while childName is being fetched
286      *
287      * @see Node#fetchChild(String)
288      */

289     public Node fetchChild(String JavaDoc childName)
290         throws NodeNotFoundException {
291
292         if (isRemoved()) {
293             throw new NodeRemovedException(
294                 this.getClass(),
295                 this);
296         }
297
298         if (childName == null) {
299             throw new InvalidParameterException(
300                 this.getClass(),
301                 "childName cannot be null");
302         }
303
304         Node child;
305         synchronized (getReadOrAlterNodeLock()) {
306             child = (Node) this.childNodes.get(childName);
307         }
308         
309         // note that the call to child.isRemoved() requires a lock on child
310
// which means that we cannot call it while synchronized on this without
311
// openning a deadlock vulnerability
312
while (child != null && child.isRemoved()) {
313             synchronized (getReadOrAlterNodeLock()) {
314                 Node doubleCheckChild = (Node) this.childNodes.get(childName);
315                 if (child == doubleCheckChild) {
316                     // doubleCheckChild is the same child that we checked was
317
// removed, this means that no one else loaded it behind
318
// our back so we can go ahead and remove it and continue
319
// as if the child did not exist in the first place
320
this.childNodes.remove(childName);
321                     child = null;
322                 } else {
323                     // the doubleCheckChild is different than child so someone
324
// must have loaded it behind our back, so, continue with
325
// child = doubleCheckChild and recheck
326
child = doubleCheckChild;
327                 }
328             }
329             
330         }
331
332         if (child == null) {
333             // we must synchronize on an object other than this here because
334
// we must ensure that the child is loaded only once, but we
335
// cannot maintain a lock on this while loading the child because
336
// loading a child might require traversing the config hierachy
337
// which requires other locks and opens a deadlock vulnerability
338
synchronized (getAddOrLoadChildLock()) {
339                 synchronized (getReadOrAlterNodeLock()) {
340                     // need to check if childName exists in childNodes one more
341
// time because another thread could loaded childName
342
// before we synchronized on loadChildLock
343
child = (Node) this.childNodes.get(childName);
344                 }
345                 
346                 if (child == null) {
347                     // OK, the child still has not been loaded, we are safe to
348
// load it
349
child = loadChild(childName);
350                     
351                     // add it to childNodes
352
synchronized (getReadOrAlterNodeLock()) {
353                         this.childNodes.put(childName, child);
354                     }
355                 }
356             }
357         }
358
359         return child;
360     }
361
362     /**
363      * @see Node#fetchChildren()
364      */

365     public Node[] fetchChildren() {
366         if (isRemoved()) {
367             throw new NodeRemovedException(
368                 this.getClass(),
369                 this);
370         }
371
372         removeRemovedChildren();
373         Collection JavaDoc cachedChildNames;
374         synchronized (getReadOrAlterNodeLock()) {
375             cachedChildNames = this.childNodes.keySet();
376         }
377
378         // fetch non-cached children
379
Set JavaDoc childNamesToLoad = getAllChildNames();
380         childNamesToLoad.removeAll(cachedChildNames);
381         Iterator JavaDoc childNameIterator = childNamesToLoad.iterator();
382         while (childNameIterator.hasNext()) {
383             try {
384                 fetchChild((String JavaDoc) childNameIterator.next());
385             } catch (NodeNotFoundException nnfe) {
386                 // This would happen only if the impl of getAllChildNames()
387
// returns invalid data or if the node is invalid
388
if (log.isInfoEnabled()) {
389                     log.info("Caught NodeNotFoundException: "
390                         + ExceptionUtility.printStackTracesToString(nnfe));
391                 }
392                 // continue to load other children
393
} catch (InvalidConfigurationException ice) {
394                 // This would happen only if the node is invalid
395
if (log.isInfoEnabled()) {
396                     log.info("Caught InvalidConfigurationException: "
397                         + ExceptionUtility.printStackTracesToString(ice));
398                 }
399                 // continue to load other children
400
}
401         }
402
403         // create return array, but be sure not to return any removed nodes
404
Collection JavaDoc children = new HashSet JavaDoc(this.childNodes.values());
405         Iterator JavaDoc childrenIterator = children.iterator();
406
407         while (childrenIterator.hasNext()) {
408             Node child = (Node) childrenIterator.next();
409             if (child.isRemoved()) {
410                 childrenIterator.remove();
411             }
412         }
413
414         return (Node[]) children.toArray(new Node[children.size()]);
415     }
416
417     /**
418      * @see Folder#containsChild(String)
419      *
420      * synchronized to ensure no one is adding or removing children
421      * while childName is being checking to see if childName exists
422      */

423     public boolean containsChild(String JavaDoc childName) {
424         if (isRemoved()) {
425             throw new NodeRemovedException(
426                 this.getClass(),
427                 this);
428         }
429
430         removeRemovedChildren();
431         boolean containsChild;
432         synchronized (getReadOrAlterNodeLock()) {
433             containsChild = this.childNodes.containsKey(childName);
434         }
435         
436         if (!containsChild) {
437             containsChild = getAllChildNames().contains(childName);
438         }
439         
440         return containsChild;
441     }
442
443     /**
444      * @see org.sape.carbon.core.config.node.Node#addNodeListener(NodeEventListener)
445      */

446     public void addNodeListener(NodeEventListener listener) {
447         this.nodeListeners.add(listener);
448     }
449
450     /**
451      * This method is called by <code>remove</code> to destroy the data
452      * backing this node in the data source. Implementations of this method
453      * should remove all traces of the <code>Node</code>'s existence
454      *
455      * @throws NodeRemovalException indicates an error removing the node
456      */

457     protected abstract void destroyBackingData() throws NodeRemovalException;
458
459     /**
460      * Called by fetchChildren to get the names of all the children from
461      * the backing data store. If a name appears in the returned Set,
462      * calling fetchChild with the name can not result in a
463      * NodeNotFoundException being thrown.
464      *
465      * @return Set of <code>String</code>s, the names of all the children
466      */

467     protected abstract Set JavaDoc getAllChildNames();
468
469     /**
470      * Loads the child specified by nodeName from the backing data store.
471      * Called by fetchChild when the child has not yet been cached.
472      *
473      * @param nodeName the name of the node to load
474      * @return Node the loaded node
475      *
476      * @throws NodeNotFoundException if the backing data for the specifed
477      * node could not be found in the data store.
478      */

479     protected abstract Node loadChild(String JavaDoc nodeName)
480         throws NodeNotFoundException;
481
482     /**
483      * Method called from the refresh method to see if the backing data
484      * still exists. If it exists, the isRemoved flag is set to false, if
485      * it does not, the isRemoved flag is set to true.
486      * @return boolean true if the backing date exists, false otherwise
487      */

488     protected abstract boolean backingDataExists();
489
490     /**
491      * Removes all nodes from this.childNodes for which isRemoved returns true.
492      */

493     protected void removeRemovedChildren() {
494         Map JavaDoc childNodesLocalCopy;
495         synchronized (getReadOrAlterNodeLock()) {
496             childNodesLocalCopy = new HashMap JavaDoc(this.childNodes);
497         }
498         
499         Iterator JavaDoc childIterator = childNodesLocalCopy.values().iterator();
500         while (childIterator.hasNext()) {
501             Node child = (Node) childIterator.next();
502             if (child.isRemoved()) {
503                 childIterator.remove();
504             }
505         }
506         
507         synchronized (getReadOrAlterNodeLock()) {
508             this.childNodes.keySet().retainAll(childNodesLocalCopy.keySet());
509         }
510     }
511
512     /**
513      * Sets the node state to be removed.
514      * Note that this method is not synchronized but must be called from
515      * section of code synchronized on getReadOrAlterNodeLock() object.
516      */

517     protected void setRemoved() {
518         this.removed = true;
519     }
520
521     /**
522      * Notifies all listenters that this node has been changed.
523      * Note that this should be not be called from a synchronized context.
524      *
525      * @since carbon 1.1
526      */

527     protected void issueNodeChangedEvent() {
528         // get the listeners as an array in order to get a local copy and
529
// not have to synchronize during the iteration and calls to the
530
// nodeChanged method
531
NodeEventListener[] listeners =
532             (NodeEventListener[]) this.nodeListeners.toArray(
533                 new NodeEventListener[0]);
534
535         for (int i = 0; i < listeners.length; i++) {
536             listeners[i].nodeChanged(this);
537         }
538     }
539
540     /**
541      * Notifies all listenters that this node has been removed.
542      * Note that this should be not be called from a synchronized context.
543      *
544      * @since carbon 1.1
545      */

546     protected void issueNodeRemovedEvent() {
547         // get the listeners as an array in order to get a local copy and
548
// not have to synchronize during the iteration and calls to the
549
// nodeRemoved method
550
NodeEventListener[] listeners =
551             (NodeEventListener[]) this.nodeListeners.toArray(
552                 new NodeEventListener[0]);
553
554         for (int i = 0; i < listeners.length; i++) {
555             listeners[i].nodeRemoved(getAbsoluteName());
556         }
557     }
558     
559     /**
560      * Gets the lock object to synchronize upon when reading or altering the
561      * removed flag or the childNodes Map.
562      * <p>
563      * This method is final because the design of the abstract node
564      * implementations requires that the return value of this method never
565      * change once this object is constructed. The return value can be set
566      * in one of the constructors.
567      *
568      * @see AbstractNode#AbstractNode(Node, String, Object, Object)
569      *
570      * @return the lock object
571      *
572      * @since carbon 2.1
573      */

574     protected final Object JavaDoc getReadOrAlterNodeLock() {
575         return this.readOrAlterNodeLock;
576     }
577     
578     /**
579      * Gets the lock object to synchronized upon when creating new children
580      * or loading existing children. This lock ensures that child nodes are
581      * created only once.
582      * <p>
583      * This method is final because the design of the abstract node
584      * implementations requires that the return value of this method never
585      * change once this object is constructed. The return value can be set
586      * in one of the constructors.
587      *
588      * @see AbstractNode#AbstractNode(Node, String, Object, Object)
589      *
590      * @return the lock object
591      *
592      * @since carbon 2.1
593      */

594     protected final Object JavaDoc getAddOrLoadChildLock() {
595         return this.addOrLoadChildLock;
596     }
597 }
Popular Tags