View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.store.jpa.util;
25  
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.ListIterator;
34  import java.util.Map;
35  import java.util.Set;
36  import net.jcip.annotations.NotThreadSafe;
37  import org.modeshape.common.util.StringUtil;
38  import org.modeshape.graph.Location;
39  import org.modeshape.graph.property.Name;
40  import org.modeshape.graph.property.NamespaceRegistry;
41  import org.modeshape.graph.property.Path;
42  import org.modeshape.graph.property.PathFactory;
43  
44  /**
45   * Represents a cache of the known node information, including a node's actual {@link Location} and the complete set of children.
46   */
47  @NotThreadSafe
48  public class RequestProcessorCache {
49  
50      private final Map<Long, WorkspaceCache> workspaceCaches = new HashMap<Long, WorkspaceCache>();
51      protected final PathFactory pathFactory;
52  
53      public RequestProcessorCache( PathFactory pathFactory ) {
54          assert pathFactory != null;
55          this.pathFactory = pathFactory;
56      }
57  
58      public Location getLocationFor( Long workspaceId,
59                                      Path node ) {
60          WorkspaceCache cache = workspaceCaches.get(workspaceId);
61          return cache != null ? cache.getLocationFor(node) : null;
62      }
63  
64      public void addNewNode( Long workspaceId,
65                              Location location ) {
66          WorkspaceCache cache = workspaceCaches.get(workspaceId);
67          if (cache == null) {
68              cache = new WorkspaceCache(workspaceId);
69              workspaceCaches.put(workspaceId, cache);
70          }
71          cache.addNewNode(location);
72      }
73  
74      public void clear( Long workspaceId ) {
75          workspaceCaches.remove(workspaceId);
76      }
77  
78      public LinkedList<Location> getAllChildren( Long workspaceId,
79                                                  Path parent ) {
80          WorkspaceCache cache = workspaceCaches.get(workspaceId);
81          return cache != null ? cache.getAllChildren(parent) : null;
82      }
83  
84      public void setAllChildren( Long workspaceId,
85                                  Path parent,
86                                  LinkedList<Location> children ) {
87          WorkspaceCache cache = workspaceCaches.get(workspaceId);
88          if (children == null) {
89              // Remove the children ...
90              if (cache != null) cache.setAllChildren(parent, null);
91          } else {
92              if (cache == null) {
93                  cache = new WorkspaceCache(workspaceId);
94                  workspaceCaches.put(workspaceId, cache);
95              }
96              cache.setAllChildren(parent, children);
97          }
98      }
99  
100     public boolean moveNode( Long workspaceId,
101                              Location oldLocation,
102                              int oldIndexInParent,
103                              Location newLocation ) {
104         WorkspaceCache cache = workspaceCaches.get(workspaceId);
105         if (cache == null) {
106             // No content from the workspace was cached, so do nothing ..
107             return false;
108         }
109         // The nodes moved within the 'from' workspace ...
110         return cache.moveNode(oldLocation, oldIndexInParent, newLocation);
111     }
112 
113     public boolean removeBranch( Long workspaceId,
114                                  Iterable<Location> locations ) {
115         WorkspaceCache cache = workspaceCaches.get(workspaceId);
116         return cache != null ? cache.removeBranch(locations) : false;
117     }
118 
119     public String getString( NamespaceRegistry namespaces ) {
120         StringBuilder sb = new StringBuilder();
121         for (WorkspaceCache cache : workspaceCaches.values()) {
122             sb.append(cache.getString(namespaces));
123         }
124         return sb.toString();
125     }
126 
127     /**
128      * {@inheritDoc}
129      * 
130      * @see java.lang.Object#toString()
131      */
132     @Override
133     public String toString() {
134         return getString(null);
135     }
136 
137     public class WorkspaceCache {
138         private final Long workspaceId;
139         private final Map<Path, Location> locationByPath = new HashMap<Path, Location>();
140         private final Map<Path, LinkedList<Location>> childrenByParentPath = new HashMap<Path, LinkedList<Location>>();
141 
142         WorkspaceCache( Long workspaceId ) {
143             this.workspaceId = workspaceId;
144         }
145 
146         public Location getLocationFor( Path node ) {
147             return locationByPath.get(node);
148         }
149 
150         public void addNewNode( Location location ) {
151             assert location != null;
152             Path path = location.getPath();
153             assert path != null;
154             locationByPath.put(path, location);
155         }
156 
157         public LinkedList<Location> getAllChildren( Path parent ) {
158             return childrenByParentPath.get(parent);
159         }
160 
161         public void setAllChildren( Path parent,
162                                     LinkedList<Location> children ) {
163             if (children == null) {
164                 childrenByParentPath.remove(parent);
165             } else {
166                 childrenByParentPath.put(parent, children);
167             }
168         }
169 
170         public boolean moveNode( Location oldLocation,
171                                  int oldIndexInParent,
172                                  Location newLocation ) {
173             assert oldLocation != null;
174             Path oldPath = oldLocation.getPath();
175             assert oldPath != null;
176 
177             // Now the locations of all nodes below the old location are invalid, as are all lists of children ...
178             // The easiest and most efficient thing to do would be to simply remove them from the cache ...
179             removeNodesBelow(oldPath, true);
180 
181             // Remove the node from the list of children for the old parent ...
182             LinkedList<Location> siblings = childrenByParentPath.get(oldPath.getParent());
183             boolean removed = false;
184             if (siblings != null) {
185                 removed = removeChildFromParentListOfChildren(siblings, oldLocation, -1);
186             }
187 
188             if (newLocation != null) {
189                 // If the children are cached for the new parent ...
190                 Path newPath = newLocation.getPath();
191                 assert newPath != null;
192                 LinkedList<Location> newSiblings = childrenByParentPath.get(newPath.getParent());
193                 if (newSiblings != null) {
194                     newSiblings.add(newLocation);
195                 }
196             }
197 
198             return removed;
199         }
200 
201         protected void removeNodesBelow( Path path,
202                                          boolean removeNodeAtSuppliedPath ) {
203             if (removeNodeAtSuppliedPath) {
204                 locationByPath.remove(path);
205                 childrenByParentPath.remove(path);
206             }
207             for (Iterator<Path> iter = locationByPath.keySet().iterator(); iter.hasNext();) {
208                 Path nextPath = iter.next();
209                 if (nextPath.isDecendantOf(path)) iter.remove();
210             }
211             for (Iterator<Path> iter = childrenByParentPath.keySet().iterator(); iter.hasNext();) {
212                 Path nextPath = iter.next();
213                 if (nextPath.isDecendantOf(path)) iter.remove();
214             }
215         }
216 
217         public boolean removeBranch( Iterable<Location> locations ) {
218             if (locations == null) return false;
219             Iterator<Location> iter = locations.iterator();
220             if (!iter.hasNext()) return false;
221 
222             Location topNode = iter.next();
223 
224             // Now remove all cached locations and child lists for each deleted node ...
225             boolean removed = false;
226             while (iter.hasNext()) {
227                 Location location = iter.next();
228                 Path path = location.getPath();
229                 assert path != null;
230                 if (locationByPath.remove(path) != null) removed = true;
231                 if (childrenByParentPath.remove(path) != null) removed = true;
232             }
233 
234             // The first node will be the root of the branch, so remove this from the parent's list of children
235             // and adjust all SNS indexes of same-name-siblings that appear after the removed node ...
236             Path path = topNode.getPath();
237             assert path != null;
238             LinkedList<Location> siblings = childrenByParentPath.get(path.getParent());
239             if (siblings != null) {
240                 removed = removeChildFromParentListOfChildren(siblings, topNode, -1);
241             }
242             childrenByParentPath.remove(path);
243 
244             return removed;
245         }
246 
247         protected boolean removeChildFromParentListOfChildren( LinkedList<Location> siblings,
248                                                                Location deletedNode,
249                                                                int expectedIndex ) {
250             assert siblings != null;
251             Path path = deletedNode.getPath();
252             Path parentPath = path.getParent();
253 
254             // Find and remove the deleted node ...
255             boolean removed = false;
256             int index = 0;
257             Path.Segment deletedNodeSegment = path.getLastSegment();
258             ListIterator<Location> iter = null;
259             if (expectedIndex > -1 && expectedIndex < siblings.size()) {
260                 Location locationAtExpectedIndex = siblings.get(expectedIndex);
261                 if (locationAtExpectedIndex.equals(deletedNode)) {
262                     siblings.remove(expectedIndex);
263                     removed = true;
264                     index = expectedIndex;
265                 }
266             }
267             if (!removed) {
268                 iter = siblings.listIterator();
269                 while (iter.hasNext()) {
270                     Location sibling = iter.next();
271                     Path.Segment segment = sibling.getPath().getLastSegment();
272                     if (segment.equals(deletedNodeSegment)) {
273                         iter.remove();
274                         removed = true;
275                         break;
276                     }
277                     ++index;
278                 }
279             }
280 
281             // Iterate starting at the supplied index, and adjust the locations for same-name-siblings ...
282             iter = siblings.listIterator(index);
283             Name name = deletedNodeSegment.getName();
284             while (iter.hasNext()) {
285                 Location laterSibling = iter.next();
286                 Path siblingPath = laterSibling.getPath();
287                 Path.Segment segment = siblingPath.getLastSegment();
288                 // If this sibling has the same name and appeared after deleted node, so decrement the SNS index ...
289                 if (segment.getName().equals(name)) {
290                     assert segment.getIndex() > 1;
291                     Path newPath = pathFactory.create(parentPath, name, segment.getIndex() - 1);
292                     Location newLocation = laterSibling.with(newPath);
293                     iter.set(newLocation);
294 
295                     // Remove the existing location for the old path ...
296                     locationByPath.remove(siblingPath);
297 
298                     // Remove all nodes below (not at) this sibling ...
299                     removeNodesBelow(siblingPath, false);
300 
301                     // Now put in the location for the modified sibling ...
302                     locationByPath.put(newPath, newLocation);
303                 }
304             }
305             return removed;
306         }
307 
308         public String getString( NamespaceRegistry namespaces ) {
309             StringBuilder sb = new StringBuilder();
310             sb.append("Workspace ");
311             sb.append(workspaceId);
312             sb.append("\n");
313             Set<Path> pathSet = new HashSet<Path>();
314             pathSet.addAll(locationByPath.keySet());
315             pathSet.addAll(childrenByParentPath.keySet());
316             List<Path> paths = new ArrayList<Path>(pathSet);
317             Collections.sort(paths);
318 
319             // For printing purposes, figure out the maximum length needed for the paths ...
320             int maxLength = 0;
321             for (Path path : paths) {
322                 String str = pathString(path, namespaces);
323                 maxLength = Math.max(maxLength, str.length());
324             }
325             for (Path path : paths) {
326                 Location loc = locationByPath.get(path);
327                 String str = pathString(path, namespaces);
328                 sb.append(StringUtil.justifyLeft(str, maxLength, ' '));
329                 if (loc != null) {
330                     // sb.append("\t\tlocation");
331                     sb.append("    @" + loc.getUuid());
332                 }
333                 LinkedList<Location> children = childrenByParentPath.get(path);
334                 if (children != null) {
335                     sb.append("\twith children: ");
336                     for (int i = 0; i < children.size(); i++) {
337                         Location child = children.get(i);
338                         String segmentString = pathSegmentString(child.getPath().getLastSegment(), namespaces);
339                         sb.append("\n");
340                         sb.append(StringUtil.justifyRight(segmentString, maxLength, ' '));
341                         sb.append("    @" + child.getUuid());
342                     }
343                 }
344                 sb.append("\n");
345             }
346             return sb.toString();
347         }
348 
349         protected String pathString( Path path,
350                                      NamespaceRegistry registry ) {
351             return path.getString(registry, null, null);
352         }
353 
354         protected String pathSegmentString( Path.Segment segment,
355                                             NamespaceRegistry registry ) {
356             return registry != null ? segment.getString(registry) : segment.getString();
357         }
358 
359         /**
360          * {@inheritDoc}
361          * 
362          * @see java.lang.Object#toString()
363          */
364         @Override
365         public String toString() {
366             return getString(null);
367         }
368     }
369 }