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