001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.store.jpa.util;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.LinkedList;
032    import java.util.List;
033    import java.util.ListIterator;
034    import java.util.Map;
035    import java.util.Set;
036    import net.jcip.annotations.NotThreadSafe;
037    import org.jboss.dna.common.util.StringUtil;
038    import org.jboss.dna.graph.Location;
039    import org.jboss.dna.graph.property.Name;
040    import org.jboss.dna.graph.property.NamespaceRegistry;
041    import org.jboss.dna.graph.property.Path;
042    import org.jboss.dna.graph.property.PathFactory;
043    
044    /**
045     * Represents a cache of the known node information, including a node's actual {@link Location} and the complete set of children.
046     * 
047     * @author Randall Hauch
048     */
049    @NotThreadSafe
050    public class RequestProcessorCache {
051    
052        private final Map<Long, WorkspaceCache> workspaceCaches = new HashMap<Long, WorkspaceCache>();
053        protected final PathFactory pathFactory;
054    
055        public RequestProcessorCache( PathFactory pathFactory ) {
056            assert pathFactory != null;
057            this.pathFactory = pathFactory;
058        }
059    
060        public Location getLocationFor( Long workspaceId,
061                                        Path node ) {
062            WorkspaceCache cache = workspaceCaches.get(workspaceId);
063            return cache != null ? cache.getLocationFor(node) : null;
064        }
065    
066        public void addNewNode( Long workspaceId,
067                                Location location ) {
068            WorkspaceCache cache = workspaceCaches.get(workspaceId);
069            if (cache == null) {
070                cache = new WorkspaceCache(workspaceId);
071                workspaceCaches.put(workspaceId, cache);
072            }
073            cache.addNewNode(location);
074        }
075    
076        public LinkedList<Location> getAllChildren( Long workspaceId,
077                                                    Path parent ) {
078            WorkspaceCache cache = workspaceCaches.get(workspaceId);
079            return cache != null ? cache.getAllChildren(parent) : null;
080        }
081    
082        public void setAllChildren( Long workspaceId,
083                                    Path parent,
084                                    LinkedList<Location> children ) {
085            WorkspaceCache cache = workspaceCaches.get(workspaceId);
086            if (children == null) {
087                // Remove the children ...
088                if (cache != null) cache.setAllChildren(parent, null);
089            } else {
090                if (cache == null) {
091                    cache = new WorkspaceCache(workspaceId);
092                    workspaceCaches.put(workspaceId, cache);
093                }
094                cache.setAllChildren(parent, children);
095            }
096        }
097    
098        public boolean moveNode( Long workspaceId,
099                                 Location oldLocation,
100                                 int oldIndexInParent,
101                                 Location newLocation ) {
102            WorkspaceCache cache = workspaceCaches.get(workspaceId);
103            if (cache == null) {
104                // No content from the workspace was cached, so do nothing ..
105                return false;
106            }
107            // The nodes moved within the 'from' workspace ...
108            return cache.moveNode(oldLocation, oldIndexInParent, newLocation);
109        }
110    
111        public boolean removeBranch( Long workspaceId,
112                                     Iterable<Location> locations ) {
113            WorkspaceCache cache = workspaceCaches.get(workspaceId);
114            return cache != null ? cache.removeBranch(locations) : false;
115        }
116    
117        public String getString( NamespaceRegistry namespaces ) {
118            StringBuilder sb = new StringBuilder();
119            for (WorkspaceCache cache : workspaceCaches.values()) {
120                sb.append(cache.getString(namespaces));
121            }
122            return sb.toString();
123        }
124    
125        /**
126         * {@inheritDoc}
127         * 
128         * @see java.lang.Object#toString()
129         */
130        @Override
131        public String toString() {
132            return getString(null);
133        }
134    
135        public class WorkspaceCache {
136            private final Long workspaceId;
137            private final Map<Path, Location> locationByPath = new HashMap<Path, Location>();
138            private final Map<Path, LinkedList<Location>> childrenByParentPath = new HashMap<Path, LinkedList<Location>>();
139    
140            WorkspaceCache( Long workspaceId ) {
141                this.workspaceId = workspaceId;
142            }
143    
144            public Location getLocationFor( Path node ) {
145                return locationByPath.get(node);
146            }
147    
148            public void addNewNode( Location location ) {
149                assert location != null;
150                Path path = location.getPath();
151                assert path != null;
152                locationByPath.put(path, location);
153            }
154    
155            public LinkedList<Location> getAllChildren( Path parent ) {
156                return childrenByParentPath.get(parent);
157            }
158    
159            public void setAllChildren( Path parent,
160                                        LinkedList<Location> children ) {
161                if (children == null) {
162                    childrenByParentPath.remove(parent);
163                } else {
164                    childrenByParentPath.put(parent, children);
165                }
166            }
167    
168            public boolean moveNode( Location oldLocation,
169                                     int oldIndexInParent,
170                                     Location newLocation ) {
171                assert oldLocation != null;
172                Path oldPath = oldLocation.getPath();
173                assert oldPath != null;
174    
175                // Now the locations of all nodes below the old location are invalid, as are all lists of children ...
176                // The easiest and most efficient thing to do would be to simply remove them from the cache ...
177                removeNodesBelow(oldPath, true);
178    
179                // Remove the node from the list of children for the old parent ...
180                LinkedList<Location> siblings = childrenByParentPath.get(oldPath.getParent());
181                boolean removed = false;
182                if (siblings != null) {
183                    removed = removeChildFromParentListOfChildren(siblings, oldLocation, -1);
184                }
185    
186                if (newLocation != null) {
187                    // If the children are cached for the new parent ...
188                    Path newPath = newLocation.getPath();
189                    assert newPath != null;
190                    LinkedList<Location> newSiblings = childrenByParentPath.get(newPath.getParent());
191                    if (newSiblings != null) {
192                        newSiblings.add(newLocation);
193                    }
194                }
195    
196                return removed;
197            }
198    
199            protected void removeNodesBelow( Path path,
200                                             boolean removeNodeAtSuppliedPath ) {
201                if (removeNodeAtSuppliedPath) {
202                    locationByPath.remove(path);
203                    childrenByParentPath.remove(path);
204                }
205                for (Iterator<Path> iter = locationByPath.keySet().iterator(); iter.hasNext();) {
206                    Path nextPath = iter.next();
207                    if (nextPath.isDecendantOf(path)) iter.remove();
208                }
209                for (Iterator<Path> iter = childrenByParentPath.keySet().iterator(); iter.hasNext();) {
210                    Path nextPath = iter.next();
211                    if (nextPath.isDecendantOf(path)) iter.remove();
212                }
213            }
214    
215            public boolean removeBranch( Iterable<Location> locations ) {
216                if (locations == null) return false;
217                Iterator<Location> iter = locations.iterator();
218                if (!iter.hasNext()) return false;
219    
220                Location topNode = iter.next();
221    
222                // Now remove all cached locations and child lists for each deleted node ...
223                boolean removed = false;
224                while (iter.hasNext()) {
225                    Location location = iter.next();
226                    Path path = location.getPath();
227                    assert path != null;
228                    if (locationByPath.remove(path) != null) removed = true;
229                    if (childrenByParentPath.remove(path) != null) removed = true;
230                }
231    
232                // The first node will be the root of the branch, so remove this from the parent's list of children
233                // and adjust all SNS indexes of same-name-siblings that appear after the removed node ...
234                Path path = topNode.getPath();
235                assert path != null;
236                LinkedList<Location> siblings = childrenByParentPath.get(path.getParent());
237                if (siblings != null) {
238                    removed = removeChildFromParentListOfChildren(siblings, topNode, -1);
239                }
240                childrenByParentPath.remove(path);
241    
242                return removed;
243            }
244    
245            protected boolean removeChildFromParentListOfChildren( LinkedList<Location> siblings,
246                                                                   Location deletedNode,
247                                                                   int expectedIndex ) {
248                assert siblings != null;
249                Path path = deletedNode.getPath();
250                Path parentPath = path.getParent();
251    
252                // Find and remove the deleted node ...
253                boolean removed = false;
254                int index = 0;
255                Path.Segment deletedNodeSegment = path.getLastSegment();
256                ListIterator<Location> iter = null;
257                if (expectedIndex > -1 && expectedIndex < siblings.size()) {
258                    Location locationAtExpectedIndex = siblings.get(expectedIndex);
259                    if (locationAtExpectedIndex.equals(deletedNode)) {
260                        siblings.remove(expectedIndex);
261                        removed = true;
262                        index = expectedIndex;
263                    }
264                }
265                if (!removed) {
266                    iter = siblings.listIterator();
267                    while (iter.hasNext()) {
268                        Location sibling = iter.next();
269                        Path.Segment segment = sibling.getPath().getLastSegment();
270                        if (segment.equals(deletedNodeSegment)) {
271                            iter.remove();
272                            removed = true;
273                            break;
274                        }
275                        ++index;
276                    }
277                }
278    
279                // Iterate starting at the supplied index, and adjust the locations for same-name-siblings ...
280                iter = siblings.listIterator(index);
281                Name name = deletedNodeSegment.getName();
282                while (iter.hasNext()) {
283                    Location laterSibling = iter.next();
284                    Path siblingPath = laterSibling.getPath();
285                    Path.Segment segment = siblingPath.getLastSegment();
286                    // If this sibling has the same name and appeared after deleted node, so decrement the SNS index ...
287                    if (segment.getName().equals(name)) {
288                        assert segment.getIndex() > 1;
289                        Path newPath = pathFactory.create(parentPath, name, segment.getIndex() - 1);
290                        Location newLocation = laterSibling.with(newPath);
291                        iter.set(newLocation);
292    
293                        // Remove the existing location for the old path ...
294                        locationByPath.remove(siblingPath);
295    
296                        // Remove all nodes below (not at) this sibling ...
297                        removeNodesBelow(siblingPath, false);
298    
299                        // Now put in the location for the modified sibling ...
300                        locationByPath.put(newPath, newLocation);
301                    }
302                }
303                return removed;
304            }
305    
306            public String getString( NamespaceRegistry namespaces ) {
307                StringBuilder sb = new StringBuilder();
308                sb.append("Workspace ");
309                sb.append(workspaceId);
310                sb.append("\n");
311                Set<Path> pathSet = new HashSet<Path>();
312                pathSet.addAll(locationByPath.keySet());
313                pathSet.addAll(childrenByParentPath.keySet());
314                List<Path> paths = new ArrayList<Path>(pathSet);
315                Collections.sort(paths);
316    
317                // For printing purposes, figure out the maximum length needed for the paths ...
318                int maxLength = 0;
319                for (Path path : paths) {
320                    String str = pathString(path, namespaces);
321                    maxLength = Math.max(maxLength, str.length());
322                }
323                for (Path path : paths) {
324                    Location loc = locationByPath.get(path);
325                    String str = pathString(path, namespaces);
326                    sb.append(StringUtil.justifyLeft(str, maxLength, ' '));
327                    if (loc != null) {
328                        // sb.append("\t\tlocation");
329                        sb.append("    @" + loc.getUuid());
330                    }
331                    LinkedList<Location> children = childrenByParentPath.get(path);
332                    if (children != null) {
333                        sb.append("\twith children: ");
334                        for (int i = 0; i < children.size(); i++) {
335                            Location child = children.get(i);
336                            String segmentString = pathSegmentString(child.getPath().getLastSegment(), namespaces);
337                            sb.append("\n");
338                            sb.append(StringUtil.justifyRight(segmentString, maxLength, ' '));
339                            sb.append("    @" + child.getUuid());
340                        }
341                    }
342                    sb.append("\n");
343                }
344                return sb.toString();
345            }
346    
347            protected String pathString( Path path,
348                                         NamespaceRegistry registry ) {
349                return path.getString(registry, null, null);
350            }
351    
352            protected String pathSegmentString( Path.Segment segment,
353                                                NamespaceRegistry registry ) {
354                return registry != null ? segment.getString(registry) : segment.getString();
355            }
356    
357            /**
358             * {@inheritDoc}
359             * 
360             * @see java.lang.Object#toString()
361             */
362            @Override
363            public String toString() {
364                return getString(null);
365            }
366        }
367    }