001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.connector.jbosscache;
023
024 import java.util.ArrayList;
025 import java.util.Collections;
026 import java.util.LinkedList;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Set;
030 import java.util.UUID;
031 import java.util.concurrent.TimeUnit;
032 import javax.transaction.xa.XAResource;
033 import org.jboss.cache.Cache;
034 import org.jboss.cache.Fqn;
035 import org.jboss.cache.Node;
036 import org.jboss.dna.common.util.StringUtil;
037 import org.jboss.dna.graph.ExecutionContext;
038 import org.jboss.dna.graph.cache.CachePolicy;
039 import org.jboss.dna.graph.commands.CopyBranchCommand;
040 import org.jboss.dna.graph.commands.CopyNodeCommand;
041 import org.jboss.dna.graph.commands.CreateNodeCommand;
042 import org.jboss.dna.graph.commands.DeleteBranchCommand;
043 import org.jboss.dna.graph.commands.GetChildrenCommand;
044 import org.jboss.dna.graph.commands.GetPropertiesCommand;
045 import org.jboss.dna.graph.commands.GraphCommand;
046 import org.jboss.dna.graph.commands.MoveBranchCommand;
047 import org.jboss.dna.graph.commands.RecordBranchCommand;
048 import org.jboss.dna.graph.commands.SetPropertiesCommand;
049 import org.jboss.dna.graph.commands.executor.AbstractCommandExecutor;
050 import org.jboss.dna.graph.commands.executor.CommandExecutor;
051 import org.jboss.dna.graph.connectors.RepositoryConnection;
052 import org.jboss.dna.graph.connectors.RepositorySourceException;
053 import org.jboss.dna.graph.connectors.RepositorySourceListener;
054 import org.jboss.dna.graph.properties.Name;
055 import org.jboss.dna.graph.properties.NameFactory;
056 import org.jboss.dna.graph.properties.Path;
057 import org.jboss.dna.graph.properties.PathFactory;
058 import org.jboss.dna.graph.properties.PathNotFoundException;
059 import org.jboss.dna.graph.properties.Property;
060 import org.jboss.dna.graph.properties.PropertyFactory;
061 import org.jboss.dna.graph.properties.ValueFactory;
062 import org.jboss.dna.graph.properties.Path.Segment;
063
064 /**
065 * The repository connection to a JBoss Cache instance.
066 *
067 * @author Randall Hauch
068 */
069 public class JBossCacheConnection implements RepositoryConnection {
070
071 protected static final RepositorySourceListener NO_OP_LISTENER = new RepositorySourceListener() {
072
073 /**
074 * {@inheritDoc}
075 */
076 public void notify( String sourceName,
077 Object... events ) {
078 // do nothing
079 }
080 };
081
082 private Name uuidPropertyName;
083 private final JBossCacheSource source;
084 private final Cache<Name, Object> cache;
085 private RepositorySourceListener listener = NO_OP_LISTENER;
086
087 JBossCacheConnection( JBossCacheSource source,
088 Cache<Name, Object> cache ) {
089 assert source != null;
090 assert cache != null;
091 this.source = source;
092 this.cache = cache;
093 }
094
095 /**
096 * @return cache
097 */
098 /*package*/Cache<Name, Object> getCache() {
099 return cache;
100 }
101
102 /**
103 * {@inheritDoc}
104 */
105 public String getSourceName() {
106 return source.getName();
107 }
108
109 /**
110 * {@inheritDoc}
111 */
112 public CachePolicy getDefaultCachePolicy() {
113 return source.getDefaultCachePolicy();
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 public XAResource getXAResource() {
120 return null;
121 }
122
123 /**
124 * {@inheritDoc}
125 */
126 public boolean ping( long time,
127 TimeUnit unit ) {
128 this.cache.getRoot();
129 return true;
130 }
131
132 /**
133 * {@inheritDoc}
134 */
135 public void setListener( RepositorySourceListener listener ) {
136 this.listener = listener != null ? listener : NO_OP_LISTENER;
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 public void close() {
143 // do nothing
144 }
145
146 /**
147 * {@inheritDoc}
148 */
149 public void execute( ExecutionContext context,
150 GraphCommand... commands ) throws RepositorySourceException {
151 // Now execute the commands ...
152 CommandExecutor executor = new Executor(context, this.getSourceName());
153 for (GraphCommand command : commands) {
154 executor.execute(command);
155 }
156 }
157
158 /**
159 * @return listener
160 */
161 protected RepositorySourceListener getListener() {
162 return this.listener;
163 }
164
165 /**
166 * Utility method to calculate (if required) and obtain the name that should be used to store the UUID values for each node.
167 * This method may be called without regard to synchronization, since it should return the same value if it happens to be
168 * called concurrently while not yet initialized.
169 *
170 * @param context the execution context
171 * @return the name, or null if the UUID should not be stored
172 */
173 protected Name getUuidPropertyName( ExecutionContext context ) {
174 if (uuidPropertyName == null) {
175 NameFactory nameFactory = context.getValueFactories().getNameFactory();
176 uuidPropertyName = nameFactory.create(this.source.getUuidPropertyName());
177 }
178 return this.uuidPropertyName;
179 }
180
181 protected Fqn<?> getFullyQualifiedName( Path path ) {
182 assert path != null;
183 return Fqn.fromList(path.getSegmentsList());
184 }
185
186 /**
187 * Get a relative fully-qualified name that consists only of the supplied segment.
188 *
189 * @param pathSegment the segment from which the fully qualified name is to be created
190 * @return the relative fully-qualified name
191 */
192 protected Fqn<?> getFullyQualifiedName( Path.Segment pathSegment ) {
193 assert pathSegment != null;
194 return Fqn.fromElements(pathSegment);
195 }
196
197 @SuppressWarnings( "unchecked" )
198 protected Path getPath( PathFactory factory,
199 Fqn<?> fqn ) {
200 List<Path.Segment> segments = (List<Path.Segment>)fqn.peekElements();
201 return factory.create(factory.createRootPath(), segments);
202 }
203
204 protected Node<Name, Object> getNode( ExecutionContext context,
205 Path path ) {
206 // Look up the node with the supplied path ...
207 Fqn<?> fqn = getFullyQualifiedName(path);
208 Node<Name, Object> node = cache.getNode(fqn);
209 if (node == null) {
210 String nodePath = path.getString(context.getNamespaceRegistry());
211 Path lowestExisting = null;
212 while (fqn != null) {
213 fqn = fqn.getParent();
214 node = cache.getNode(fqn);
215 if (node != null) {
216 lowestExisting = getPath(context.getValueFactories().getPathFactory(), fqn);
217 fqn = null;
218 }
219 }
220 throw new PathNotFoundException(path, lowestExisting, JBossCacheConnectorI18n.nodeDoesNotExist.text(nodePath));
221 }
222 return node;
223
224 }
225
226 protected UUID generateUuid() {
227 return UUID.randomUUID();
228 }
229
230 protected int copyNode( Node<Name, Object> original,
231 Node<Name, Object> newParent,
232 boolean recursive,
233 Name uuidProperty,
234 ExecutionContext context ) {
235 assert original != null;
236 assert newParent != null;
237 // Get or create the new node ...
238 Segment name = (Segment)original.getFqn().getLastElement();
239
240 // Update the children to account for same-name siblings.
241 // This not only updates the FQN of the child nodes, but it also sets the property that stores the
242 // the array of Path.Segment for the children (since the cache doesn't maintain order).
243 Path.Segment newSegment = updateChildList(newParent, name.getName(), context, true);
244 Node<Name, Object> copy = newParent.addChild(getFullyQualifiedName(newSegment));
245 assert checkChildren(newParent);
246 // Copy the properties ...
247 copy.clearData();
248 copy.putAll(original.getData());
249 if (uuidProperty != null) {
250 // Generate a new UUID for the new node, overwriting any existing value from the original ...
251 copy.put(uuidProperty, generateUuid());
252 }
253 int numNodesCopied = 1;
254 if (recursive) {
255 // Loop over each child and call this method ...
256 for (Node<Name, Object> child : original.getChildren()) {
257 numNodesCopied += copyNode(child, copy, true, uuidProperty, context);
258 }
259 }
260 return numNodesCopied;
261 }
262
263 /**
264 * Update (or create) the array of {@link Path.Segment path segments} for the children of the supplied node. This array
265 * maintains the ordered list of children (since the {@link Cache} does not maintain the order). Invoking this method will
266 * change any existing children that a {@link Path.Segment#getName() name part} that matches the supplied
267 * <code>changedName</code> to have the appropriate {@link Path.Segment#getIndex() same-name sibling index}.
268 *
269 * @param parent the parent node; may not be null
270 * @param changedName the name that should be compared to the existing node siblings to determine whether the same-name
271 * sibling indexes should be updated; may not be null
272 * @param context the execution context; may not be null
273 * @param addChildWithName true if a new child with the supplied name is to be added to the children (but which does not yet
274 * exist in the node's children)
275 * @return the path segment for the new child, or null if <code>addChildWithName</code> was false
276 */
277 protected Path.Segment updateChildList( Node<Name, Object> parent,
278 Name changedName,
279 ExecutionContext context,
280 boolean addChildWithName ) {
281 assert parent != null;
282 assert changedName != null;
283 assert context != null;
284 Set<Node<Name, Object>> children = parent.getChildren();
285 if (children.isEmpty() && !addChildWithName) return null;
286
287 // Go through the children, looking for any children with the same name as the 'changedName'
288 List<ChildInfo> childrenWithChangedName = new LinkedList<ChildInfo>();
289 Path.Segment[] childNames = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
290 int index = 0;
291 if (childNames != null) {
292 for (Path.Segment childName : childNames) {
293 if (childName.getName().equals(changedName)) {
294 ChildInfo info = new ChildInfo(childName, index);
295 childrenWithChangedName.add(info);
296 }
297 index++;
298 }
299 }
300 if (addChildWithName) {
301 // Make room for the new child at the end of the array ...
302 if (childNames == null) {
303 childNames = new Path.Segment[1];
304 } else {
305 int numExisting = childNames.length;
306 Path.Segment[] newChildNames = new Path.Segment[numExisting + 1];
307 System.arraycopy(childNames, 0, newChildNames, 0, numExisting);
308 childNames = newChildNames;
309 }
310
311 // And add a child info for the new node ...
312 ChildInfo info = new ChildInfo(null, index);
313 childrenWithChangedName.add(info);
314 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName);
315 childNames[index++] = newSegment;
316 }
317 assert childNames != null;
318
319 // Now process the children with the same name, which may include a child info for the new node ...
320 assert childrenWithChangedName.isEmpty() == false;
321 if (childrenWithChangedName.size() == 1) {
322 // The child should have no indexes ...
323 ChildInfo child = childrenWithChangedName.get(0);
324 if (child.segment != null && child.segment.hasIndex()) {
325 // The existing child needs to have a new index ..
326 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName);
327 // Replace the child with the correct FQN ...
328 changeNodeName(parent, child.segment, newSegment, context);
329 // Change the segment in the child list ...
330 childNames[child.childIndex] = newSegment;
331 }
332 } else {
333 // There is more than one child with the same name ...
334 int i = 0;
335 for (ChildInfo child : childrenWithChangedName) {
336 if (child.segment != null) {
337 // Determine the new name and index ...
338 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1);
339 // Replace the child with the correct FQN ...
340 changeNodeName(parent, child.segment, newSegment, context);
341 // Change the segment in the child list ...
342 childNames[child.childIndex] = newSegment;
343 } else {
344 // Determine the new name and index ...
345 Path.Segment newSegment = context.getValueFactories().getPathFactory().createSegment(changedName, i + 1);
346 childNames[child.childIndex] = newSegment;
347 }
348 ++i;
349 }
350 }
351
352 // Record the list of children as a property on the parent ...
353 // (Do this last, as it doesn't need to be done if there's an exception in the above logic)
354 context.getLogger(getClass()).trace("Updating child list of {0} to: {1}",
355 parent.getFqn(),
356 StringUtil.readableString(childNames));
357 parent.put(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST, childNames); // replaces any existing value
358
359 if (addChildWithName) {
360 // Return the segment for the new node ...
361 return childNames[childNames.length - 1];
362 }
363 return null;
364 }
365
366 protected boolean checkChildren( Node<Name, Object> parent ) {
367 Path.Segment[] childNamesProperty = (Path.Segment[])parent.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
368 Set<Object> childNames = parent.getChildrenNames();
369 boolean result = true;
370 if (childNamesProperty.length != childNames.size()) result = false;
371 for (int i = 0; i != childNamesProperty.length; ++i) {
372 if (!childNames.contains(childNamesProperty[i])) result = false;
373 }
374 if (!result) {
375 List<Path.Segment> names = new ArrayList<Path.Segment>();
376 for (Object name : childNames) {
377 names.add((Path.Segment)name);
378 }
379 Collections.sort(names);
380 // Logger.getLogger(getClass()).trace("Child list on {0} is: {1}",
381 // parent.getFqn(),
382 // StringUtil.readableString(childNamesProperty));
383 // Logger.getLogger(getClass()).trace("Children of {0} is: {1}", parent.getFqn(), StringUtil.readableString(names));
384 }
385 return result;
386 }
387
388 /**
389 * Utility class used by the {@link JBossCacheConnection#updateChildList(Node, Name, ExecutionContext, boolean)} method.
390 *
391 * @author Randall Hauch
392 */
393 private static class ChildInfo {
394 protected final Path.Segment segment;
395 protected final int childIndex;
396
397 protected ChildInfo( Path.Segment childSegment,
398 int childIndex ) {
399 this.segment = childSegment;
400 this.childIndex = childIndex;
401 }
402
403 }
404
405 /**
406 * Changes the name of the node in the cache (but does not update the list of child segments stored on the parent).
407 *
408 * @param parent
409 * @param existing
410 * @param newSegment
411 * @param context
412 */
413 protected void changeNodeName( Node<Name, Object> parent,
414 Path.Segment existing,
415 Path.Segment newSegment,
416 ExecutionContext context ) {
417 assert parent != null;
418 assert existing != null;
419 assert newSegment != null;
420 assert context != null;
421
422 if (existing.equals(newSegment)) return;
423 context.getLogger(getClass()).trace("Renaming {0} to {1} under {2}", existing, newSegment, parent.getFqn());
424 Node<Name, Object> existingChild = parent.getChild(existing);
425 assert existingChild != null;
426
427 // JBoss Cache can move a node from one node to another node, but the move doesn't change the name;
428 // since you provide the FQN of the parent location, the name of the node cannot be changed.
429 // Therefore, to compensate, we need to create a new child, copy all of the data, move all of the child
430 // nodes of the old node, then remove the old node.
431
432 // Create the new node ...
433 Node<Name, Object> newChild = parent.addChild(Fqn.fromElements(newSegment));
434 Fqn<?> newChildFqn = newChild.getFqn();
435
436 // Copy the data ...
437 newChild.putAll(existingChild.getData());
438
439 // Move the children ...
440 for (Node<Name, Object> grandChild : existingChild.getChildren()) {
441 cache.move(grandChild.getFqn(), newChildFqn);
442 }
443
444 // Remove the existing ...
445 parent.removeChild(existing);
446 }
447
448 protected class Executor extends AbstractCommandExecutor {
449
450 private final PropertyFactory propertyFactory;
451 private final ValueFactory<UUID> uuidFactory;
452
453 protected Executor( ExecutionContext context,
454 String sourceName ) {
455 super(context, sourceName);
456 this.propertyFactory = context.getPropertyFactory();
457 this.uuidFactory = context.getValueFactories().getUuidFactory();
458 }
459
460 @Override
461 public void execute( CreateNodeCommand command ) {
462 Path path = command.getPath();
463 Path parent = path.getParent();
464 // Look up the parent node, which must exist ...
465 Node<Name, Object> parentNode = getNode(parent);
466
467 // Update the children to account for same-name siblings.
468 // This not only updates the FQN of the child nodes, but it also sets the property that stores the
469 // the array of Path.Segment for the children (since the cache doesn't maintain order).
470 Path.Segment newSegment = updateChildList(parentNode, path.getLastSegment().getName(), getExecutionContext(), true);
471 Node<Name, Object> node = parentNode.addChild(Fqn.fromElements(newSegment));
472 assert checkChildren(parentNode);
473
474 // Add the UUID property (if required), which may be overwritten by a supplied property ...
475 Name uuidPropertyName = getUuidPropertyName(getExecutionContext());
476 if (uuidPropertyName != null) {
477 node.put(uuidPropertyName, generateUuid());
478 }
479 // Now add the properties to the supplied node ...
480 for (Property property : command.getProperties()) {
481 if (property.size() == 0) continue;
482 Name propName = property.getName();
483 Object value = null;
484 if (property.size() == 1) {
485 value = property.iterator().next();
486 } else {
487 value = property.getValuesAsArray();
488 }
489 node.put(propName, value);
490 }
491 }
492
493 @Override
494 public void execute( GetChildrenCommand command ) {
495 Node<Name, Object> node = getNode(command.getPath());
496 Name uuidPropertyName = getUuidPropertyName(getExecutionContext());
497 // Get the names of the children, using the child list ...
498 Path.Segment[] childList = (Path.Segment[])node.get(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST);
499 for (Path.Segment child : childList) {
500 // We have the child segment, but we need the UUID property ...
501 Node<Name, Object> childNode = node.getChild(child);
502 Object uuid = childNode.get(uuidPropertyName);
503 if (uuid == null) {
504 uuid = generateUuid();
505 childNode.put(uuidPropertyName, uuid);
506 } else {
507 uuid = uuidFactory.create(uuid);
508 }
509 Property uuidProperty = propertyFactory.create(uuidPropertyName, uuid);
510 command.addChild(child, uuidProperty);
511 }
512 }
513
514 @Override
515 public void execute( GetPropertiesCommand command ) {
516 Node<Name, Object> node = getNode(command.getPath());
517 Map<Name, Object> dataMap = node.getData();
518 for (Map.Entry<Name, Object> data : dataMap.entrySet()) {
519 Name propertyName = data.getKey();
520 // Don't allow the child list property to be accessed
521 if (propertyName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue;
522 Object values = data.getValue();
523 Property property = propertyFactory.create(propertyName, values);
524 command.setProperty(property);
525 }
526 }
527
528 @Override
529 public void execute( SetPropertiesCommand command ) {
530 Node<Name, Object> node = getNode(command.getPath());
531 // Now set (or remove) the properties to the supplied node ...
532 for (Property property : command.getProperties()) {
533 Name propName = property.getName();
534 // Don't allow the child list property to be removed or changed
535 if (propName.equals(JBossCacheLexicon.CHILD_PATH_SEGMENT_LIST)) continue;
536 if (property.size() == 0) {
537 node.remove(propName);
538 continue;
539 }
540 Object value = null;
541 if (property.size() == 1) {
542 value = property.iterator().next();
543 } else {
544 value = property.getValuesAsArray();
545 }
546 node.put(propName, value);
547 }
548 }
549
550 @Override
551 public void execute( DeleteBranchCommand command ) {
552 Node<Name, Object> node = getNode(command.getPath());
553 node.getParent().removeChild(node.getFqn().getLastElement());
554 }
555
556 @Override
557 public void execute( CopyNodeCommand command ) {
558 Node<Name, Object> node = getNode(command.getPath());
559 // Look up the new parent, which must exist ...
560 Path newPath = command.getNewPath();
561 Node<Name, Object> newParent = getNode(newPath.getParent());
562 copyNode(node, newParent, false, null, getExecutionContext());
563 }
564
565 @Override
566 public void execute( CopyBranchCommand command ) {
567 Node<Name, Object> node = getNode(command.getPath());
568 // Look up the new parent, which must exist ...
569 Path newPath = command.getNewPath();
570 Node<Name, Object> newParent = getNode(newPath.getParent());
571 copyNode(node, newParent, true, null, getExecutionContext());
572 }
573
574 @Override
575 public void execute( MoveBranchCommand command ) {
576 Node<Name, Object> node = getNode(command.getPath());
577 boolean recursive = true;
578 Name uuidProperty = getUuidPropertyName(getExecutionContext());
579 // Look up the new parent, which must exist ...
580 Path newPath = command.getNewPath();
581 Node<Name, Object> newParent = getNode(newPath.getParent());
582 copyNode(node, newParent, recursive, uuidProperty, getExecutionContext());
583
584 // Now delete the old node ...
585 Node<Name, Object> oldParent = node.getParent();
586 boolean removed = oldParent.removeChild(node.getFqn().getLastElement());
587 assert removed;
588 }
589
590 @Override
591 public void execute( RecordBranchCommand command ) {
592 Node<Name, Object> node = getNode(command.getPath());
593 recordNode(command, node);
594 }
595
596 protected void recordNode( RecordBranchCommand command,
597 Node<Name, Object> node ) {
598 // Record the properties ...
599 Map<Name, Object> dataMap = node.getData();
600 List<Property> properties = new LinkedList<Property>();
601 for (Map.Entry<Name, Object> data : dataMap.entrySet()) {
602 Name propertyName = data.getKey();
603 Object values = data.getValue();
604 Property property = propertyFactory.create(propertyName, values);
605 properties.add(property);
606 }
607 command.record(command.getPath(), properties);
608 // Now record the children ...
609 for (Node<Name, Object> child : node.getChildren()) {
610 recordNode(command, child);
611 }
612 }
613
614 protected Node<Name, Object> getNode( Path path ) {
615 return JBossCacheConnection.this.getNode(getExecutionContext(), path);
616 }
617
618 }
619
620 }