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.inmemory;
023    
024    import java.util.HashMap;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.UUID;
029    import java.util.concurrent.locks.ReadWriteLock;
030    import java.util.concurrent.locks.ReentrantReadWriteLock;
031    import net.jcip.annotations.NotThreadSafe;
032    import org.jboss.dna.common.CommonI18n;
033    import org.jboss.dna.common.util.CheckArg;
034    import org.jboss.dna.graph.DnaLexicon;
035    import org.jboss.dna.graph.ExecutionContext;
036    import org.jboss.dna.graph.Location;
037    import org.jboss.dna.graph.properties.Name;
038    import org.jboss.dna.graph.properties.Path;
039    import org.jboss.dna.graph.properties.PathFactory;
040    import org.jboss.dna.graph.properties.PathNotFoundException;
041    import org.jboss.dna.graph.properties.Property;
042    import org.jboss.dna.graph.properties.PropertyFactory;
043    import org.jboss.dna.graph.properties.Path.Segment;
044    import org.jboss.dna.graph.properties.basic.BasicPath;
045    import org.jboss.dna.graph.requests.CopyBranchRequest;
046    import org.jboss.dna.graph.requests.CreateNodeRequest;
047    import org.jboss.dna.graph.requests.DeleteBranchRequest;
048    import org.jboss.dna.graph.requests.MoveBranchRequest;
049    import org.jboss.dna.graph.requests.ReadAllChildrenRequest;
050    import org.jboss.dna.graph.requests.ReadAllPropertiesRequest;
051    import org.jboss.dna.graph.requests.Request;
052    import org.jboss.dna.graph.requests.UpdatePropertiesRequest;
053    import org.jboss.dna.graph.requests.processor.RequestProcessor;
054    
055    /**
056     * @author Randall Hauch
057     */
058    @NotThreadSafe
059    public class InMemoryRepository {
060    
061        protected final ReadWriteLock lock = new ReentrantReadWriteLock();
062        private final String name;
063        private final UUID rootNodeUuid;
064        private final Map<UUID, Node> nodesByUuid = new HashMap<UUID, Node>();
065    
066        public InMemoryRepository( String name,
067                                   UUID rootNodeUUID ) {
068            CheckArg.isNotNull(rootNodeUUID, "rootNodeUUID");
069            CheckArg.isNotEmpty(name, "name");
070            this.name = name;
071            this.rootNodeUuid = rootNodeUUID;
072            // Create the root node ...
073            Node root = new Node(rootNodeUUID);
074            nodesByUuid.put(root.getUuid(), root);
075        }
076    
077        /**
078         * @return lock
079         */
080        public ReadWriteLock getLock() {
081            return lock;
082        }
083    
084        /**
085         * @return name
086         */
087        public String getName() {
088            return name;
089        }
090    
091        public Node getRoot() {
092            return nodesByUuid.get(this.rootNodeUuid);
093        }
094    
095        public Node getNode( UUID uuid ) {
096            assert uuid != null;
097            return nodesByUuid.get(uuid);
098        }
099    
100        protected Map<UUID, Node> getNodesByUuid() {
101            return nodesByUuid;
102        }
103    
104        public Node getNode( ExecutionContext context,
105                             String path ) {
106            assert context != null;
107            assert path != null;
108            return getNode(context.getValueFactories().getPathFactory().create(path));
109        }
110    
111        /**
112         * Find a node with the given path.
113         * 
114         * @param path the path to the node; may not be null
115         * @return the node with the path, or null if the node does not exist
116         */
117        public Node getNode( Path path ) {
118            assert path != null;
119            Node node = getRoot();
120            for (Path.Segment segment : path) {
121                Node desiredChild = null;
122                for (Node child : node.getChildren()) {
123                    if (child == null) continue;
124                    Path.Segment childName = child.getName();
125                    if (childName == null) continue;
126                    if (childName.equals(segment)) {
127                        desiredChild = child;
128                        break;
129                    }
130                }
131                if (desiredChild != null) {
132                    node = desiredChild;
133                } else {
134                    return null;
135                }
136            }
137            return node;
138        }
139    
140        /**
141         * Find the lowest existing node along the path.
142         * 
143         * @param path the path to the node; may not be null
144         * @return the lowest existing node along the path, or the root node if no node exists on the path
145         */
146        public Path getLowestExistingPath( Path path ) {
147            assert path != null;
148            Node node = getRoot();
149            int segmentNumber = 0;
150            for (Path.Segment segment : path) {
151                Node desiredChild = null;
152                for (Node child : node.getChildren()) {
153                    if (child == null) continue;
154                    Path.Segment childName = child.getName();
155                    if (childName == null) continue;
156                    if (childName.equals(segment)) {
157                        desiredChild = child;
158                        break;
159                    }
160                }
161                if (desiredChild != null) {
162                    node = desiredChild;
163                } else {
164                    return path.subpath(0, segmentNumber);
165                }
166                ++segmentNumber;
167            }
168            return BasicPath.ROOT;
169        }
170    
171        protected UUID generateUuid() {
172            return UUID.randomUUID();
173        }
174    
175        public void removeNode( ExecutionContext context,
176                                Node node ) {
177            assert context != null;
178            assert node != null;
179            assert getRoot().equals(node) != true;
180            Node parent = node.getParent();
181            assert parent != null;
182            parent.getChildren().remove(node);
183            correctSameNameSiblingIndexes(context, parent, node.getName().getName());
184            removeUuidReference(node);
185        }
186    
187        protected void removeUuidReference( Node node ) {
188            nodesByUuid.remove(node.getUuid());
189            for (Node child : node.getChildren()) {
190                removeUuidReference(child);
191            }
192        }
193    
194        /**
195         * Create a node at the supplied path. The parent of the new node must already exist.
196         * 
197         * @param context the environment; may not be null
198         * @param pathToNewNode the path to the new node; may not be null
199         * @return the new node (or root if the path specified the root)
200         */
201        public Node createNode( ExecutionContext context,
202                                String pathToNewNode ) {
203            assert context != null;
204            assert pathToNewNode != null;
205            Path path = context.getValueFactories().getPathFactory().create(pathToNewNode);
206            if (path.isRoot()) return getRoot();
207            Path parentPath = path.getParent();
208            Node parentNode = getNode(parentPath);
209            Name name = path.getLastSegment().getName();
210            return createNode(context, parentNode, name, null);
211        }
212    
213        /**
214         * Create a new node with the supplied name, as a child of the supplied parent.
215         * 
216         * @param context the execution context
217         * @param parentNode the parent node; may not be null
218         * @param name the name; may not be null
219         * @param uuid the UUID of the node, or null if the UUID is to be generated
220         * @return the new node
221         */
222        public Node createNode( ExecutionContext context,
223                                Node parentNode,
224                                Name name,
225                                UUID uuid ) {
226            assert context != null;
227            assert name != null;
228            if (parentNode == null) parentNode = getRoot();
229            if (uuid == null) uuid = generateUuid();
230            Node node = new Node(uuid);
231            nodesByUuid.put(node.getUuid(), node);
232            node.setParent(parentNode);
233            Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name);
234            node.setName(newName);
235            parentNode.getChildren().add(node);
236            correctSameNameSiblingIndexes(context, parentNode, name);
237            return node;
238        }
239    
240        protected void correctSameNameSiblingIndexes( ExecutionContext context,
241                                                      Node parentNode,
242                                                      Name name ) {
243            if (parentNode == null) return;
244            // Look for the highest existing index ...
245            List<Node> childrenWithSameNames = new LinkedList<Node>();
246            for (Node child : parentNode.getChildren()) {
247                if (child.getName().getName().equals(name)) childrenWithSameNames.add(child);
248            }
249            if (childrenWithSameNames.size() == 0) return;
250            if (childrenWithSameNames.size() == 1) {
251                Node childWithSameName = childrenWithSameNames.get(0);
252                Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, Path.NO_INDEX);
253                childWithSameName.setName(newName);
254                return;
255            }
256            int index = 1;
257            for (Node childWithSameName : childrenWithSameNames) {
258                Path.Segment segment = childWithSameName.getName();
259                if (segment.getIndex() != index) {
260                    Path.Segment newName = context.getValueFactories().getPathFactory().createSegment(name, index);
261                    childWithSameName.setName(newName);
262                }
263                ++index;
264            }
265        }
266    
267        /**
268         * Move the supplied node to the new parent. This method automatically removes the node from its existing parent, and also
269         * correctly adjusts the {@link Path.Segment#getIndex() index} to be correct in the new parent.
270         * 
271         * @param context
272         * @param node the node to be moved; may not be the {@link #getRoot() root}
273         * @param newParent the new parent; may not be the {@link #getRoot() root}
274         */
275        public void moveNode( ExecutionContext context,
276                              Node node,
277                              Node newParent ) {
278            assert context != null;
279            assert newParent != null;
280            assert node != null;
281            assert getRoot().equals(newParent) != true;
282            assert getRoot().equals(node) != true;
283            Node oldParent = node.getParent();
284            if (oldParent != null) {
285                if (oldParent.equals(newParent)) return;
286                boolean removed = oldParent.getChildren().remove(node);
287                assert removed == true;
288                node.setParent(null);
289                correctSameNameSiblingIndexes(context, oldParent, node.getName().getName());
290            }
291            node.setParent(newParent);
292            newParent.getChildren().add(node);
293            correctSameNameSiblingIndexes(context, newParent, node.getName().getName());
294        }
295    
296        public Node copyNode( ExecutionContext context,
297                              Node original,
298                              Node newParent,
299                              boolean recursive ) {
300            assert context != null;
301            assert original != null;
302            assert newParent != null;
303            // Get or create the new node ...
304            Node copy = createNode(context, newParent, original.getName().getName(), null);
305    
306            // Copy the properties ...
307            copy.getProperties().clear();
308            copy.getProperties().putAll(original.getProperties());
309            if (recursive) {
310                // Loop over each child and call this method ...
311                for (Node child : original.getChildren()) {
312                    copyNode(context, child, copy, true);
313                }
314            }
315            return copy;
316        }
317    
318        /**
319         * Get a request processor given the supplied environment and source name.
320         * 
321         * @param context the environment in which the commands are to be executed
322         * @param sourceName the name of the repository source
323         * @return the request processor; never null
324         */
325        /*package*/RequestProcessor getRequestProcessor( ExecutionContext context,
326                                                          String sourceName ) {
327            return new Processor(context, sourceName);
328        }
329    
330        protected class Processor extends RequestProcessor {
331            private final PathFactory pathFactory;
332            private final PropertyFactory propertyFactory;
333    
334            protected Processor( ExecutionContext context,
335                                 String sourceName ) {
336                super(sourceName, context);
337                pathFactory = context.getValueFactories().getPathFactory();
338                propertyFactory = context.getPropertyFactory();
339            }
340    
341            @Override
342            public void process( ReadAllChildrenRequest request ) {
343                Node node = getTargetNode(request, request.of());
344                if (node == null) return;
345                Path path = request.of().getPath();
346                // Get the names of the children ...
347                List<Node> children = node.getChildren();
348                for (Node child : children) {
349                    Segment childName = child.getName();
350                    Path childPath = pathFactory.create(path, childName);
351                    request.addChild(childPath, propertyFactory.create(DnaLexicon.UUID, child.getUuid()));
352                }
353                request.setActualLocationOfNode(new Location(path, node.getUuid()));
354            }
355    
356            @Override
357            public void process( ReadAllPropertiesRequest request ) {
358                Node node = getTargetNode(request, request.at());
359                if (node == null) return;
360                // Get the properties of the node ...
361                request.addProperty(propertyFactory.create(DnaLexicon.UUID, node.getUuid()));
362                for (Property property : node.getProperties().values()) {
363                    request.addProperty(property);
364                }
365                request.setActualLocationOfNode(new Location(request.at().getPath(), node.getUuid()));
366            }
367    
368            @Override
369            public void process( CopyBranchRequest request ) {
370                Node node = getTargetNode(request, request.from());
371                if (node == null) return;
372                // Look up the new parent, which must exist ...
373                Path newPath = request.into().getPath();
374                Path newParentPath = newPath.getParent();
375                Node newParent = getNode(newParentPath);
376                Node newNode = copyNode(getExecutionContext(), node, newParent, true);
377                newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, newNode.getName());
378                Location oldLocation = new Location(request.from().getPath(), node.getUuid());
379                Location newLocation = new Location(newPath, newNode.getUuid());
380                request.setActualLocations(oldLocation, newLocation);
381            }
382    
383            @Override
384            public void process( CreateNodeRequest request ) {
385                Path path = request.at().getPath();
386                CheckArg.isNotNull(path, "request.at().getPath()");
387                Node node = null;
388                if (!path.isRoot()) {
389                    Path parent = path.getParent();
390                    // Look up the parent node, which must exist ...
391                    Node parentNode = getNode(parent);
392                    if (parentNode == null) {
393                        Path lowestExisting = getLowestExistingPath(parent);
394                        throw new PathNotFoundException(request.at(), lowestExisting,
395                                                        InMemoryConnectorI18n.nodeDoesNotExist.text(parent));
396                    }
397                    UUID uuid = null;
398                    for (Property property : request.properties()) {
399                        if (property.getName().equals(DnaLexicon.UUID)) {
400                            uuid = getExecutionContext().getValueFactories().getUuidFactory().create(property.getValues().next());
401                            break;
402                        }
403                    }
404                    node = createNode(getExecutionContext(), parentNode, path.getLastSegment().getName(), uuid);
405                    path = getExecutionContext().getValueFactories().getPathFactory().create(parent, node.getName());
406                } else {
407                    node = getRoot();
408                }
409                // Now add the properties to the supplied node ...
410                for (Property property : request.properties()) {
411                    Name propName = property.getName();
412                    if (property.size() == 0) {
413                        node.getProperties().remove(propName);
414                        continue;
415                    }
416                    if (!propName.equals(DnaLexicon.UUID)) {
417                        node.getProperties().put(propName, property);
418                    }
419                }
420                assert node != null;
421                request.setActualLocationOfNode(new Location(path, node.getUuid()));
422            }
423    
424            @Override
425            public void process( DeleteBranchRequest request ) {
426                Node node = getTargetNode(request, request.at());
427                if (node == null) return;
428                removeNode(getExecutionContext(), node);
429                request.setActualLocationOfNode(new Location(request.at().getPath(), node.getUuid()));
430            }
431    
432            @Override
433            public void process( MoveBranchRequest request ) {
434                Node node = getTargetNode(request, request.from());
435                if (node == null) return;
436                // Look up the new parent, which must exist ...
437                Path newPath = request.into().getPath();
438                Path newParentPath = newPath.getParent();
439                Node newParent = getNode(newParentPath);
440                node.setParent(newParent);
441                newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, node.getName());
442                Location oldLocation = new Location(request.from().getPath(), node.getUuid());
443                Location newLocation = new Location(newPath, node.getUuid());
444                request.setActualLocations(oldLocation, newLocation);
445            }
446    
447            @Override
448            public void process( UpdatePropertiesRequest request ) {
449                Node node = getTargetNode(request, request.on());
450                if (node == null) return;
451                // Now set (or remove) the properties to the supplied node ...
452                for (Property property : request.properties()) {
453                    Name propName = property.getName();
454                    if (property.size() == 0) {
455                        node.getProperties().remove(propName);
456                        continue;
457                    }
458                    if (!propName.equals(DnaLexicon.UUID)) {
459                        node.getProperties().put(propName, property);
460                    }
461                }
462                request.setActualLocationOfNode(new Location(request.on().getPath(), node.getUuid()));
463            }
464    
465            protected Node getTargetNode( Request request,
466                                          Location location ) {
467                Path path = location.getPath();
468                if (path == null) {
469                    request.setError(new IllegalArgumentException(CommonI18n.argumentMayNotBeNull.text("location.getPath()")));
470                    return null;
471                }
472                // Look up the node with the supplied path ...
473                Node node = InMemoryRepository.this.getNode(path);
474                if (node == null) {
475                    Path lowestExisting = getLowestExistingPath(path);
476                    request.setError(new PathNotFoundException(location, lowestExisting,
477                                                               InMemoryConnectorI18n.nodeDoesNotExist.text(path)));
478                    return null;
479                }
480                return node;
481            }
482        }
483    }