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 }