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    }