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 }