001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * Unless otherwise indicated, all code in JBoss DNA is licensed
010     * to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.jbosscache;
025    
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collections;
029    import java.util.LinkedList;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Set;
033    import java.util.UUID;
034    import java.util.concurrent.atomic.AtomicInteger;
035    import org.jboss.cache.Cache;
036    import org.jboss.cache.Fqn;
037    import org.jboss.cache.Node;
038    import org.jboss.dna.common.util.Logger;
039    import org.jboss.dna.graph.DnaLexicon;
040    import org.jboss.dna.graph.ExecutionContext;
041    import org.jboss.dna.graph.Location;
042    import org.jboss.dna.graph.connector.RepositorySourceException;
043    import org.jboss.dna.graph.property.Name;
044    import org.jboss.dna.graph.property.Path;
045    import org.jboss.dna.graph.property.PathFactory;
046    import org.jboss.dna.graph.property.PathNotFoundException;
047    import org.jboss.dna.graph.property.Property;
048    import org.jboss.dna.graph.property.PropertyFactory;
049    import org.jboss.dna.graph.property.UuidFactory;
050    import org.jboss.dna.graph.request.CloneWorkspaceRequest;
051    import org.jboss.dna.graph.request.CopyBranchRequest;
052    import org.jboss.dna.graph.request.CreateNodeRequest;
053    import org.jboss.dna.graph.request.CreateWorkspaceRequest;
054    import org.jboss.dna.graph.request.DeleteBranchRequest;
055    import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
056    import org.jboss.dna.graph.request.GetWorkspacesRequest;
057    import org.jboss.dna.graph.request.InvalidRequestException;
058    import org.jboss.dna.graph.request.InvalidWorkspaceException;
059    import org.jboss.dna.graph.request.MoveBranchRequest;
060    import org.jboss.dna.graph.request.ReadAllChildrenRequest;
061    import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
062    import org.jboss.dna.graph.request.Request;
063    import org.jboss.dna.graph.request.UpdatePropertiesRequest;
064    import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
065    import org.jboss.dna.graph.request.processor.RequestProcessor;
066    
067    /**
068     * A {@link RequestProcessor} implementation that operates upon a {@link Cache JBoss Cache} instance for each workspace in the
069     * {@link JBossCacheSource source}.
070     * <p>
071     * This processor only uses {@link Location} objects with {@link Location#getPath() paths}. Even though every node in the cache is
072     * automatically assigned a UUID (and all operations properly handle UUIDs), these UUIDs are not included in the {@link Location}
073     * objects because the processor is unable to search the cache to find nodes by UUID.
074     * </p>
075     */
076    public class JBossCacheRequestProcessor extends RequestProcessor {
077    
078        private final JBossCacheWorkspaces workspaces;
079        private final boolean creatingWorkspacesAllowed;
080        private final String defaultWorkspaceName;
081        private final PathFactory pathFactory;
082        private final PropertyFactory propertyFactory;
083        private final UuidFactory uuidFactory;
084    
085        /**
086         * @param sourceName the name of the source in which this processor is operating
087         * @param context the execution context in which this processor operates
088         * @param workspaces the manager for the workspaces
089         * @param defaultWorkspaceName the name of the default workspace; never null
090         * @param creatingWorkspacesAllowed true if clients can create new workspaces, or false otherwise
091         */
092        JBossCacheRequestProcessor( String sourceName,
093                                    ExecutionContext context,
094                                    JBossCacheWorkspaces workspaces,
095                                    String defaultWorkspaceName,
096                                    boolean creatingWorkspacesAllowed ) {
097            super(sourceName, context);
098            assert workspaces != null;
099            assert defaultWorkspaceName != null;
100            this.workspaces = workspaces;
101            this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
102            this.defaultWorkspaceName = defaultWorkspaceName;
103            this.pathFactory = context.getValueFactories().getPathFactory();
104            this.propertyFactory = context.getPropertyFactory();
105            this.uuidFactory = context.getValueFactories().getUuidFactory();
106        }
107    
108        @Override
109        public void process( ReadAllChildrenRequest request ) {
110            // Look up the cache and the node ...
111            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
112            if (cache == null) return;
113            Path nodePath = request.of().getPath();
114            Node<Name, Object> node = getNode(request, cache, nodePath);
115            if (node == null) return;
116    
117            // Get the names of the children, using the child list ...
118            Path.Segment[] childList = (Path.Segment[])node.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
119            if (childList != null) {
120                for (Path.Segment child : childList) {
121                    request.addChild(Location.create(pathFactory.create(nodePath, child)));
122                }
123            }
124            request.setActualLocationOfNode(Location.create(nodePath));
125            setCacheableInfo(request);
126        }
127    
128        @Override
129        public void process( ReadAllPropertiesRequest request ) {
130            // Look up the cache and the node ...
131            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
132            if (cache == null) return;
133            Path nodePath = request.at().getPath();
134            Node<Name, Object> node = getNode(request, cache, nodePath);
135            if (node == null) return;
136    
137            // Get the properties on the node ...
138            Map<Name, Object> dataMap = node.getData();
139            for (Map.Entry<Name, Object> data : dataMap.entrySet()) {
140                Name propertyName = data.getKey();
141                // Don't allow the child list property to be accessed
142                if (propertyName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue;
143                Object values = data.getValue();
144                Property property = propertyFactory.create(propertyName, values);
145                request.addProperty(property);
146            }
147            request.setActualLocationOfNode(Location.create(nodePath));
148            setCacheableInfo(request);
149        }
150    
151        @Override
152        public void process( CreateNodeRequest request ) {
153            // Look up the cache and the node ...
154            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
155            if (cache == null) return;
156            Path parent = request.under().getPath();
157            Node<Name, Object> parentNode = getNode(request, cache, parent);
158            if (parentNode == null) return;
159    
160            // Update the children to account for same-name siblings.
161            // This not only updates the FQN of the child nodes, but it also sets the property that stores the
162            // the array of Path.Segment for the children (since the cache doesn't maintain order).
163            Path.Segment newSegment = updateChildList(cache, parentNode, request.named(), getExecutionContext(), true);
164            Node<Name, Object> node = parentNode.addChild(Fqn.fromElements(newSegment));
165            assert checkChildren(parentNode);
166    
167            // Add the UUID property (if required), which may be overwritten by a supplied property ...
168            node.put(DnaLexicon.UUID, uuidFactory.create());
169            // Now add the properties to the supplied node ...
170            for (Property property : request.properties()) {
171                if (property.size() == 0) continue;
172                Name propName = property.getName();
173                Object value = null;
174                if (property.size() == 1) {
175                    value = property.iterator().next();
176                } else {
177                    value = property.getValuesAsArray();
178                }
179                node.put(propName, value);
180            }
181            Path nodePath = pathFactory.create(parent, newSegment);
182            request.setActualLocationOfNode(Location.create(nodePath));
183        }
184    
185        @Override
186        public void process( UpdatePropertiesRequest request ) {
187            // Look up the cache and the node ...
188            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
189            if (cache == null) return;
190            Path nodePath = request.on().getPath();
191            Node<Name, Object> node = getNode(request, cache, nodePath);
192            if (node == null) return;
193    
194            // Now set (or remove) the properties to the supplied node ...
195            for (Map.Entry<Name, Property> entry : request.properties().entrySet()) {
196                Name propName = entry.getKey();
197                // Don't allow the child list property to be removed or changed
198                if (propName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue;
199    
200                Property property = entry.getValue();
201                if (property == null) {
202                    node.remove(propName);
203                    continue;
204                }
205                Object value = null;
206                if (property.isSingle()) {
207                    value = property.iterator().next();
208                } else {
209                    value = property.getValuesAsArray();
210                }
211                node.put(propName, value);
212            }
213            request.setActualLocationOfNode(Location.create(nodePath));
214        }
215    
216        @Override
217        public void process( CopyBranchRequest request ) {
218            // Look up the caches ...
219            Cache<Name, Object> fromCache = getCache(request, request.fromWorkspace());
220            if (fromCache == null) return;
221            Cache<Name, Object> intoCache = getCache(request, request.intoWorkspace());
222            if (intoCache == null) return;
223    
224            // Look up the current node and the new parent (both of which must exist) ...
225            Path nodePath = request.from().getPath();
226            Node<Name, Object> node = getNode(request, fromCache, nodePath);
227            if (node == null) return;
228            Path newParentPath = request.into().getPath();
229            Node<Name, Object> newParent = getNode(request, intoCache, newParentPath);
230            if (newParent == null) return;
231    
232            boolean useSameUuids = fromCache != intoCache;
233            UUID uuid = uuidFactory.create(node.get(DnaLexicon.UUID));
234            UUID newNodeUuid = useSameUuids ? uuid : uuidFactory.create();
235    
236            // Copy the branch ...
237            Name desiredName = request.desiredName();
238            Path.Segment newSegment = copyNode(intoCache,
239                                               node,
240                                               newParent,
241                                               desiredName,
242                                               true,
243                                               useSameUuids,
244                                               newNodeUuid,
245                                               null,
246                                               getExecutionContext());
247    
248            Path newPath = pathFactory.create(newParentPath, newSegment);
249            request.setActualLocations(Location.create(nodePath), Location.create(newPath));
250        }
251    
252        @Override
253        public void process( DeleteBranchRequest request ) {
254            // Look up the cache and the node ...
255            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
256            if (cache == null) return;
257            Path nodePath = request.at().getPath();
258            Node<Name, Object> node = getNode(request, cache, nodePath);
259            if (node == null) return;
260    
261            Path.Segment nameOfRemovedNode = nodePath.getLastSegment();
262            Node<Name, Object> parent = node.getParent();
263            if (cache.removeNode(node.getFqn())) {
264                removeFromChildList(cache, parent, nameOfRemovedNode, getExecutionContext());
265                request.setActualLocationOfNode(Location.create(nodePath));
266            } else {
267                String msg = JBossCacheConnectorI18n.unableToDeleteBranch.text(getSourceName(), request.inWorkspace(), nodePath);
268                request.setError(new RepositorySourceException(msg));
269            }
270        }
271    
272        @Override
273        public void process( MoveBranchRequest request ) {
274            // Look up the caches ...
275            Cache<Name, Object> cache = getCache(request, request.inWorkspace());
276            if (cache == null) return;
277    
278            // Look up the current node and the new parent (both of which must exist) ...
279            Path nodePath = request.from().getPath();
280            Node<Name, Object> node = getNode(request, cache, nodePath);
281            if (node == null) return;
282            Path newParentPath = request.into().getPath();
283            Node<Name, Object> newParent = getNode(request, cache, newParentPath);
284            if (newParent == null) return;
285    
286            // Copy the branch and use the same UUIDs ...
287            Name desiredName = request.desiredName();
288            Path.Segment newSegment = copyNode(cache, node, newParent, desiredName, true, true, null, null, getExecutionContext());
289    
290            // Now delete the old node ...
291            Node<Name, Object> oldParent = node.getParent();
292            boolean removed = oldParent.removeChild(node.getFqn().getLastElement());
293            assert removed;
294            Path.Segment nameOfRemovedNode = nodePath.getLastSegment();
295            removeFromChildList(cache, oldParent, nameOfRemovedNode, getExecutionContext());
296    
297            Path newPath = pathFactory.create(newParentPath, newSegment);
298            request.setActualLocations(Location.create(nodePath), Location.create(newPath));
299        }
300    
301        /**
302         * {@inheritDoc}
303         * 
304         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
305         */
306        @Override
307        public void process( VerifyWorkspaceRequest request ) {
308            String workspaceName = request.workspaceName();
309            if (workspaceName == null) workspaceName = defaultWorkspaceName;
310    
311            Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, false);
312            if (cache == null) {
313                String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName);
314                request.setError(new InvalidWorkspaceException(msg));
315            } else {
316                Fqn<?> rootName = Fqn.root();
317                UUID uuid = uuidFactory.create(cache.get(rootName, DnaLexicon.UUID));
318                if (uuid == null) {
319                    uuid = uuidFactory.create();
320                    cache.put(rootName, DnaLexicon.UUID, uuid);
321                }
322                request.setActualRootLocation(Location.create(pathFactory.createRootPath()));
323                request.setActualWorkspaceName(workspaceName);
324            }
325        }
326    
327        /**
328         * {@inheritDoc}
329         * 
330         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest)
331         */
332        @Override
333        public void process( GetWorkspacesRequest request ) {
334            request.setAvailableWorkspaceNames(workspaces.getWorkspaceNames());
335        }
336    
337        /**
338         * {@inheritDoc}
339         * 
340         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
341         */
342        @Override
343        public void process( CreateWorkspaceRequest request ) {
344            String workspaceName = request.desiredNameOfNewWorkspace();
345            if (!creatingWorkspacesAllowed) {
346                String msg = JBossCacheConnectorI18n.unableToCreateWorkspaces.text(getSourceName(), workspaceName);
347                request.setError(new InvalidRequestException(msg));
348                return;
349            }
350            // Try to create the workspace ...
351            Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, creatingWorkspacesAllowed);
352            if (cache == null) {
353                String msg = JBossCacheConnectorI18n.unableToCreateWorkspace.text(getSourceName(), workspaceName);
354                request.setError(new InvalidWorkspaceException(msg));
355                return;
356            }
357            // Make sure the root node has a UUID ...
358            Fqn<?> rootName = Fqn.root();
359            UUID uuid = uuidFactory.create(cache.get(rootName, DnaLexicon.UUID));
360            if (uuid == null) {
361                uuid = uuidFactory.create();
362                cache.put(rootName, DnaLexicon.UUID, uuid);
363            }
364            request.setActualRootLocation(Location.create(pathFactory.createRootPath()));
365            request.setActualWorkspaceName(workspaceName);
366        }
367    
368        /**
369         * {@inheritDoc}
370         * 
371         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
372         */
373        @Override
374        public void process( CloneWorkspaceRequest request ) {
375            String fromWorkspaceName = request.nameOfWorkspaceToBeCloned();
376            String toWorkspaceName = request.desiredNameOfTargetWorkspace();
377            if (!creatingWorkspacesAllowed) {
378                String msg = JBossCacheConnectorI18n.unableToCloneWorkspaces.text(getSourceName(), fromWorkspaceName, toWorkspaceName);
379                request.setError(new InvalidRequestException(msg));
380                return;
381            }
382            // Make sure there is already a workspace that we're cloning ...
383            Cache<Name, Object> fromCache = workspaces.getWorkspace(fromWorkspaceName, false);
384            if (fromCache == null) {
385                String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), fromWorkspaceName);
386                request.setError(new InvalidWorkspaceException(msg));
387                return;
388            }
389    
390            // Try to create a new workspace with the target name ...
391            Cache<Name, Object> intoCache = workspaces.createWorkspace(toWorkspaceName);
392            if (intoCache == null) {
393                // Couldn't create it because one already exists ...
394                String msg = JBossCacheConnectorI18n.workspaceAlreadyExists.text(getSourceName(), toWorkspaceName);
395                request.setError(new InvalidWorkspaceException(msg));
396                return;
397            }
398    
399            // And finally copy the contents ...
400            Fqn<?> rootName = Fqn.root();
401            Node<Name, Object> fromRoot = fromCache.getNode(rootName);
402            Node<Name, Object> intoRoot = intoCache.getNode(rootName);
403            intoRoot.clearData();
404            intoRoot.putAll(fromRoot.getData());
405            ExecutionContext context = getExecutionContext();
406    
407            // Loop over each child and copy it ...
408            for (Node<Name, Object> child : fromRoot.getChildren()) {
409                copyNode(intoCache, child, intoRoot, null, true, true, null, null, context);
410            }
411    
412            // Copy the list of child segments in the root (this maintains the order of the children) ...
413            Path.Segment[] childNames = (Path.Segment[])fromRoot.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
414            intoRoot.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames);
415        }
416    
417        /**
418         * {@inheritDoc}
419         * 
420         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest)
421         */
422        @Override
423        public void process( DestroyWorkspaceRequest request ) {
424            if (!this.workspaces.removeWorkspace(request.workspaceName())) {
425                String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(request.workspaceName());
426                request.setError(new InvalidWorkspaceException(msg));
427            }
428        }
429    
430        // ----------------------------------------------------------------------------------------------------------------
431        // Utility methods
432        // ----------------------------------------------------------------------------------------------------------------
433    
434        /**
435         * Obtain the appropriate cache for the supplied workspace name, or set an error on the request if the workspace does not
436         * exist (and could not or should not be created).
437         * 
438         * @param request the request
439         * @param workspaceName the workspace name
440         * @return the cache, or null if there is no such workspace
441         */
442        protected Cache<Name, Object> getCache( Request request,
443                                                String workspaceName ) {
444            if (workspaceName == null) workspaceName = defaultWorkspaceName;
445            Cache<Name, Object> cache = workspaces.getWorkspace(workspaceName, creatingWorkspacesAllowed);
446            if (cache == null) {
447                String msg = JBossCacheConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName);
448                request.setError(new InvalidWorkspaceException(msg));
449            }
450            return cache;
451        }
452    
453        protected Fqn<?> getFullyQualifiedName( Path path ) {
454            assert path != null;
455            return Fqn.fromList(path.getSegmentsList());
456        }
457    
458        /**
459         * Get a relative fully-qualified name that consists only of the supplied segment.
460         * 
461         * @param pathSegment the segment from which the fully qualified name is to be created
462         * @return the relative fully-qualified name
463         */
464        protected Fqn<?> getFullyQualifiedName( Path.Segment pathSegment ) {
465            assert pathSegment != null;
466            return Fqn.fromElements(pathSegment);
467        }
468    
469        @SuppressWarnings( "unchecked" )
470        protected Path getPath( PathFactory factory,
471                                Fqn<?> fqn ) {
472            List<Path.Segment> segments = (List<Path.Segment>)fqn.peekElements();
473            return factory.create(factory.createRootPath(), segments);
474        }
475    
476        protected Node<Name, Object> getNode( Request request,
477                                              Cache<Name, Object> cache,
478                                              Path path ) {
479            ExecutionContext context = getExecutionContext();
480            if (path == null) {
481                String msg = JBossCacheConnectorI18n.locationsMustHavePath.text(getSourceName(), request);
482                request.setError(new InvalidRequestException(msg));
483                return null;
484            }
485            // Look up the node with the supplied path ...
486            Fqn<?> fqn = getFullyQualifiedName(path);
487            Node<Name, Object> node = cache.getNode(fqn);
488            if (node == null) {
489                String nodePath = path.getString(context.getNamespaceRegistry());
490                Path lowestExisting = null;
491                while (fqn != null) {
492                    fqn = fqn.getParent();
493                    node = cache.getNode(fqn);
494                    if (node != null) {
495                        lowestExisting = getPath(context.getValueFactories().getPathFactory(), fqn);
496                        fqn = null;
497                    }
498                }
499                request.setError(new PathNotFoundException(Location.create(path), lowestExisting,
500                                                           JBossCacheConnectorI18n.nodeDoesNotExist.text(nodePath)));
501                node = null;
502            }
503            return node;
504    
505        }
506    
507        protected Path.Segment copyNode( Cache<Name, Object> newCache,
508                                         Node<Name, Object> original,
509                                         Node<Name, Object> newParent,
510                                         Name desiredName,
511                                         boolean recursive,
512                                         boolean reuseOriginalUuids,
513                                         UUID uuidForCopyOfOriginal,
514                                         AtomicInteger count,
515                                         ExecutionContext context ) {
516            assert original != null;
517            assert newParent != null;
518            // Get or create the new node ...
519            Path.Segment name = desiredName != null ? context.getValueFactories().getPathFactory().createSegment(desiredName) : (Path.Segment)original.getFqn()
520                                                                                                                                                      .getLastElement();
521    
522            // Update the children to account for same-name siblings.
523            // This not only updates the FQN of the child nodes, but it also sets the property that stores the
524            // the array of Path.Segment for the children (since the cache doesn't maintain order).
525            Path.Segment newSegment = updateChildList(newCache, newParent, name.getName(), context, true);
526            Node<Name, Object> copy = newParent.addChild(getFullyQualifiedName(newSegment));
527            assert checkChildren(newParent);
528            // Copy the properties ...
529            copy.clearData();
530            copy.putAll(original.getData());
531            copy.remove(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST); // will be reset later ...
532    
533            // Generate a new UUID for the new node, overwriting any existing value from the original ...
534            if (reuseOriginalUuids) uuidForCopyOfOriginal = uuidFactory.create(original.get(DnaLexicon.UUID));
535            if (uuidForCopyOfOriginal == null) uuidForCopyOfOriginal = uuidFactory.create();
536            copy.put(DnaLexicon.UUID, uuidForCopyOfOriginal);
537    
538            if (count != null) count.incrementAndGet();
539            if (recursive) {
540                // Loop over each child and call this method ...
541                for (Node<Name, Object> child : original.getChildren()) {
542                    copyNode(newCache, child, copy, null, true, reuseOriginalUuids, null, count, context);
543                }
544            }
545            return newSegment;
546        }
547    
548        /**
549         * Update (or create) the array of {@link Path.Segment path segments} for the children of the supplied node. This array
550         * maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking this method will
551         * change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied
552         * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}.
553         * 
554         * @param cache the cache in which the parent exists ...
555         * @param parent the parent node; may not be null
556         * @param changedName the name that should be compared to the existing node siblings to determine whether the same-name
557         *        sibling indexes should be updated; may not be null
558         * @param context the execution context; may not be null
559         * @param addChildWithName true if a new child with the supplied name is to be added to the children (but which does not yet
560         *        exist in the node's children)
561         * @return the path segment for the new child, or null if <code>addChildWithName</code> was false
562         */
563        protected Path.Segment updateChildList( Cache<Name, Object> cache,
564                                                Node<Name, Object> parent,
565                                                Name changedName,
566                                                ExecutionContext context,
567                                                boolean addChildWithName ) {
568            assert parent != null;
569            assert changedName != null;
570            assert context != null;
571            Set<Node<Name, Object>> children = parent.getChildren();
572            if (children.isEmpty() && !addChildWithName) return null;
573    
574            // Go through the children, looking for any children with the same name as the 'changedName'
575            List<ChildInfo> childrenWithChangedName = new LinkedList<ChildInfo>();
576            Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
577            int index = 0;
578            if (childNames != null) {
579                for (Path.Segment childName : childNames) {
580                    if (childName.getName().equals(changedName)) {
581                        ChildInfo info = new ChildInfo(childName, index);
582                        childrenWithChangedName.add(info);
583                    }
584                    index++;
585                }
586            }
587            if (addChildWithName) {
588                // Make room for the new child at the end of the array ...
589                if (childNames == null) {
590                    childNames = new Path.Segment[1];
591                } else {
592                    int numExisting = childNames.length;
593                    Path.Segment[] newChildNames = new Path.Segment[numExisting + 1];
594                    System.arraycopy(childNames, 0, newChildNames, 0, numExisting);
595                    childNames = newChildNames;
596                }
597    
598                // And add a child info for the new node ...
599                ChildInfo info = new ChildInfo(null, index);
600                childrenWithChangedName.add(info);
601                Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName);
602                childNames[index++] = newSegment;
603            }
604            assert childNames != null;
605    
606            // Now process the children with the same name, which may include a child info for the new node ...
607            assert childrenWithChangedName.isEmpty() == false;
608            if (childrenWithChangedName.size() == 1) {
609                // The child should have no indexes ...
610                ChildInfo child = childrenWithChangedName.get(0);
611                if (child.segment != null && child.segment.hasIndex()) {
612                    // The existing child needs to have a new index ..
613                    Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName);
614                    // Replace the child with the correct FQN ...
615                    changeNodeName(cache, parent, child.segment, newSegment, context);
616                    // Change the segment in the child list ...
617                    childNames[child.childIndex] = newSegment;
618                }
619            } else {
620                // There is more than one child with the same name ...
621                int i = 0;
622                for (ChildInfo child : childrenWithChangedName) {
623                    if (child.segment != null) {
624                        // Determine the new name and index ...
625                        Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1);
626                        // Replace the child with the correct FQN ...
627                        changeNodeName(cache, parent, child.segment, newSegment, context);
628                        // Change the segment in the child list ...
629                        childNames[child.childIndex] = newSegment;
630                    } else {
631                        // Determine the new name and index ...
632                        Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1);
633                        childNames[child.childIndex] = newSegment;
634                    }
635                    ++i;
636                }
637            }
638    
639            // Record the list of children as a property on the parent ...
640            // (Do this last, as it doesn't need to be done if there's an exception in the above logic)
641            context.getLogger(getClass()).trace("Updating child list of {0} to: {1}", parent.getFqn(), Arrays.asList(childNames));
642            parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames); // replaces any existing value
643    
644            if (addChildWithName) {
645                // Return the segment for the new node ...
646                return childNames[childNames.length - 1];
647            }
648            return null;
649        }
650    
651        /**
652         * Update the array of {@link Path.Segment path segments} for the children of the supplied node, based upon a node being
653         * removed. This array maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking
654         * this method will change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied
655         * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}.
656         * 
657         * @param cache the cache in which the parent exists ...
658         * @param parent the parent node; may not be null
659         * @param removedNode the segment of the node that was removed, which signals to look for node with the same name; may not be
660         *        null
661         * @param context the execution context; may not be null
662         */
663        protected void removeFromChildList( Cache<Name, Object> cache,
664                                            Node<Name, Object> parent,
665                                            Path.Segment removedNode,
666                                            ExecutionContext context ) {
667            assert parent != null;
668            assert context != null;
669            Set<Node<Name, Object>> children = parent.getChildren();
670            if (children.isEmpty()) {
671                parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, null); // replaces any existing value
672                return;
673            }
674    
675            // Go through the children, looking for any children with the same name as the 'changedName'
676            Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
677            assert childNames != null;
678            int snsIndex = removedNode.getIndex();
679            int index = 0;
680            Path.Segment[] newChildNames = new Path.Segment[childNames.length - 1];
681            for (Path.Segment childName : childNames) {
682                if (!childName.getName().equals(removedNode.getName())) {
683                    newChildNames[index] = childName;
684                    index++;
685                } else {
686                    // The name matches ...
687                    if (childName.getIndex() < snsIndex) {
688                        // Just copy ...
689                        newChildNames[index] = childName;
690                        index++;
691                    } else if (childName.getIndex() == snsIndex) {
692                        // don't copy ...
693                    } else {
694                        // Append an updated segment ...
695                        Path.Segment newSegment = context.getValueFactories()
696                                                         .getPathFactory()
697                                                         .createSegment(childName.getName(), childName.getIndex() - 1);
698                        newChildNames[index] = newSegment;
699                        // Replace the child with the correct FQN ...
700                        changeNodeName(cache, parent, childName, newSegment, context);
701                        index++;
702                    }
703                }
704            }
705            parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, newChildNames); // replaces any existing value
706        }
707    
708        protected boolean checkChildren( Node<Name, Object> parent ) {
709            Path.Segment[] childNamesProperty = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
710            Set<Object> childNames = parent.getChildrenNames();
711            boolean result = true;
712            if (childNamesProperty.length != childNames.size()) result = false;
713            for (int i = 0; i != childNamesProperty.length; ++i) {
714                if (!childNames.contains(childNamesProperty[i])) result = false;
715            }
716            if (!result) {
717                List<Path.Segment> names = new ArrayList<Path.Segment>();
718                for (Object name : childNames) {
719                    names.add((Path.Segment)name);
720                }
721                Collections.sort(names);
722                Logger.getLogger(getClass()).trace("Child list on {0} is: {1}", parent.getFqn(), childNamesProperty);
723                Logger.getLogger(getClass()).trace("Children of {0} is: {1}", parent.getFqn(), names);
724            }
725            return result;
726        }
727    
728        /**
729         * Utility class used by the {@link #updateChildList(Cache, Node, Name, ExecutionContext, boolean)} method.
730         * 
731         * @author Randall Hauch
732         */
733        private static class ChildInfo {
734            protected final Path.Segment segment;
735            protected final int childIndex;
736    
737            protected ChildInfo( Path.Segment childSegment,
738                                 int childIndex ) {
739                this.segment = childSegment;
740                this.childIndex = childIndex;
741            }
742    
743        }
744    
745        /**
746         * Changes the name of the node in the cache (but does not update the list of child segments stored on the parent).
747         * 
748         * @param cache
749         * @param parent
750         * @param existing
751         * @param newSegment
752         * @param context
753         */
754        protected void changeNodeName( Cache<Name, Object> cache,
755                                       Node<Name, Object> parent,
756                                       Path.Segment existing,
757                                       Path.Segment newSegment,
758                                       ExecutionContext context ) {
759            assert parent != null;
760            assert existing != null;
761            assert newSegment != null;
762            assert context != null;
763    
764            if (existing.equals(newSegment)) return;
765            context.getLogger(getClass()).trace("Renaming {0} to {1} under {2}", existing, newSegment, parent.getFqn());
766            Node<Name, Object> existingChild = parent.getChild(existing);
767            assert existingChild != null;
768    
769            // JBoss Cache can move a node from one node to another node, but the move doesn't change the name;
770            // since you provide the FQN of the parent location, the name of the node cannot be changed.
771            // Therefore, to compensate, we need to create a new child, copy all of the data, move all of the child
772            // nodes of the old node, then remove the old node.
773    
774            // Create the new node ...
775            Node<Name, Object> newChild = parent.addChild(Fqn.fromElements(newSegment));
776            Fqn<?> newChildFqn = newChild.getFqn();
777    
778            // Copy the data ...
779            newChild.putAll(existingChild.getData());
780    
781            // Move the children ...
782            for (Node<Name, Object> grandChild : existingChild.getChildren()) {
783                cache.move(grandChild.getFqn(), newChildFqn);
784            }
785    
786            // Remove the existing ...
787            parent.removeChild(existing);
788        }
789    }