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 }