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