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 }