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.graph.request; 025 026 import java.util.Arrays; 027 import java.util.HashMap; 028 import java.util.Iterator; 029 import java.util.LinkedList; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.NoSuchElementException; 033 import net.jcip.annotations.NotThreadSafe; 034 import org.jboss.dna.common.util.CheckArg; 035 import org.jboss.dna.common.util.HashCode; 036 import org.jboss.dna.graph.GraphI18n; 037 import org.jboss.dna.graph.Location; 038 import org.jboss.dna.graph.connector.RepositoryConnection; 039 import org.jboss.dna.graph.property.Name; 040 import org.jboss.dna.graph.property.Path; 041 import org.jboss.dna.graph.property.Property; 042 043 /** 044 * Instruction to read the properties and children of the nodes in the branch at the supplied location. The children of the nodes 045 * at the bottom of the branch are not read. 046 * 047 * @author Randall Hauch 048 */ 049 @NotThreadSafe 050 public class ReadBranchRequest extends CacheableRequest implements Iterable<Location> { 051 052 private static final long serialVersionUID = 1L; 053 054 public static final int DEFAULT_MAXIMUM_DEPTH = 2; 055 public static final int NO_MAXIMUM_DEPTH = Integer.MAX_VALUE; 056 057 private static class Node { 058 private final Location location; 059 private final Map<Name, Property> properties = new HashMap<Name, Property>(); 060 private List<Location> children; 061 062 protected Node( Location location ) { 063 assert location != null; 064 this.location = location; 065 } 066 067 protected Location getLocation() { 068 return location; 069 } 070 071 protected Map<Name, Property> getProperties() { 072 return properties; 073 } 074 075 protected List<Location> getChildren() { 076 return children; 077 } 078 079 protected void setChildren( List<Location> children ) { 080 this.children = children; 081 } 082 } 083 084 private final Location at; 085 private final String workspaceName; 086 private final int maxDepth; 087 private final Map<Path, Node> nodes = new HashMap<Path, Node>(); 088 private Location actualLocation; 089 090 /** 091 * Create a request to read the branch at the supplied location, to a maximum depth of 2. 092 * 093 * @param at the location of the branch 094 * @param workspaceName the name of the workspace containing the parent 095 * @throws IllegalArgumentException if the location or workspace name is null 096 */ 097 public ReadBranchRequest( Location at, 098 String workspaceName ) { 099 CheckArg.isNotNull(at, "at"); 100 CheckArg.isNotNull(workspaceName, "workspaceName"); 101 this.workspaceName = workspaceName; 102 this.at = at; 103 this.maxDepth = DEFAULT_MAXIMUM_DEPTH; 104 } 105 106 /** 107 * Create a request to read the branch (of given depth) at the supplied location. 108 * 109 * @param at the location of the branch 110 * @param workspaceName the name of the workspace containing the branch 111 * @param maxDepth the maximum depth to read 112 * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive 113 */ 114 public ReadBranchRequest( Location at, 115 String workspaceName, 116 int maxDepth ) { 117 CheckArg.isNotNull(at, "at"); 118 CheckArg.isPositive(maxDepth, "maxDepth"); 119 CheckArg.isNotNull(workspaceName, "workspaceName"); 120 this.workspaceName = workspaceName; 121 this.at = at; 122 this.maxDepth = maxDepth; 123 } 124 125 /** 126 * {@inheritDoc} 127 * 128 * @see org.jboss.dna.graph.request.Request#isReadOnly() 129 */ 130 @Override 131 public boolean isReadOnly() { 132 return true; 133 } 134 135 /** 136 * Get the location defining the top of the branch to be read 137 * 138 * @return the location of the branch; never null 139 */ 140 public Location at() { 141 return at; 142 } 143 144 /** 145 * Get the name of the workspace in which the branch exists. 146 * 147 * @return the name of the workspace; never null 148 */ 149 public String inWorkspace() { 150 return workspaceName; 151 } 152 153 /** 154 * Get the maximum depth of the branch that is to be read. 155 * 156 * @return the maximum depth; always positive 157 */ 158 public int maximumDepth() { 159 return maxDepth; 160 } 161 162 /** 163 * Return whether this branch contains the specified location. 164 * 165 * @param location the location 166 * @return true if this branch includes the location, or false otherwise 167 */ 168 public boolean includes( Location location ) { 169 if (location == null || !location.hasPath()) return false; 170 return this.nodes.containsKey(location.getPath()); 171 } 172 173 /** 174 * Return whether this branch contains the specified path. 175 * 176 * @param path the path 177 * @return true if this branch includes the path, or false otherwise 178 */ 179 public boolean includes( Path path ) { 180 if (path == null) return false; 181 return this.nodes.containsKey(path); 182 } 183 184 /** 185 * Get the location for the supplied path. 186 * 187 * @param path the path 188 * @return the location for the path, or null if the path is not known 189 */ 190 public Location getLocationFor( Path path ) { 191 Node node = nodes.get(path); 192 return node != null ? node.getLocation() : null; 193 } 194 195 /** 196 * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is 197 * indeed on the branch and that it is at a level prescribed by the request. 198 * 199 * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path} 200 * @param properties the properties on the node 201 * @throws IllegalArgumentException if the node is null 202 * @throws IllegalStateException if the request is frozen 203 */ 204 public void setProperties( Location node, 205 Property... properties ) { 206 checkNotFrozen(); 207 CheckArg.isNotNull(node, "node"); 208 assert node.hasPath(); 209 Node nodeObj = nodes.get(node.getPath()); 210 if (nodeObj == null) { 211 nodeObj = new Node(node); 212 nodes.put(node.getPath(), nodeObj); 213 } 214 Map<Name, Property> propertiesMap = nodeObj.getProperties(); 215 for (Property property : properties) { 216 propertiesMap.put(property.getName(), property); 217 } 218 } 219 220 /** 221 * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is 222 * indeed on the branch and that it is at a level prescribed by the request. 223 * 224 * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path} 225 * @param properties the properties on the node 226 * @throws IllegalArgumentException if the node is null 227 * @throws IllegalStateException if the request is frozen 228 */ 229 public void setProperties( Location node, 230 Iterable<Property> properties ) { 231 checkNotFrozen(); 232 CheckArg.isNotNull(node, "node"); 233 assert node.hasPath(); 234 Node nodeObj = nodes.get(node.getPath()); 235 if (nodeObj == null) { 236 nodeObj = new Node(node); 237 nodes.put(node.getPath(), nodeObj); 238 } 239 Map<Name, Property> propertiesMap = nodeObj.getProperties(); 240 for (Property property : properties) { 241 propertiesMap.put(property.getName(), property); 242 } 243 } 244 245 /** 246 * Record the children for a parent node in the branch. 247 * 248 * @param parent the location of the parent; must {@link Location#hasPath() have a path} 249 * @param children the location of each child, in the order they appear in the parent 250 * @throws IllegalStateException if the request is frozen 251 */ 252 public void setChildren( Location parent, 253 Location... children ) { 254 checkNotFrozen(); 255 CheckArg.isNotNull(parent, "parent"); 256 CheckArg.isNotNull(children, "children"); 257 assert parent.hasPath(); 258 Node nodeObj = nodes.get(parent.getPath()); 259 if (nodeObj == null) { 260 nodeObj = new Node(parent); 261 nodes.put(parent.getPath(), nodeObj); 262 } 263 nodeObj.setChildren(Arrays.asList(children)); 264 } 265 266 /** 267 * Record the children for a parent node in the branch. 268 * 269 * @param parent the location of the parent; must {@link Location#hasPath() have a path} 270 * @param children the location of each child, in the order they appear in the parent 271 * @throws IllegalStateException if the request is frozen 272 */ 273 public void setChildren( Location parent, 274 List<Location> children ) { 275 checkNotFrozen(); 276 CheckArg.isNotNull(parent, "parent"); 277 CheckArg.isNotNull(children, "children"); 278 assert parent.hasPath(); 279 Node nodeObj = nodes.get(parent.getPath()); 280 if (nodeObj == null) { 281 nodeObj = new Node(parent); 282 nodes.put(parent.getPath(), nodeObj); 283 } 284 nodeObj.setChildren(children); 285 } 286 287 // /** 288 // * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map 289 // maintains 290 // * the order that the nodes were {@link #setProperties(Location, Property...) added}. 291 // * 292 // * @return the branch information 293 // * @see #iterator() 294 // */ 295 // public Map<Path, Map<Name, Property>> getPropertiesByNode() { 296 // return nodeProperties; 297 // } 298 299 /** 300 * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map maintains 301 * the order that the nodes were {@link #setProperties(Location, Property...) added}. 302 * 303 * @param location the location of the node for which the properties are to be obtained 304 * @return the properties for the location, as a map keyed by the property name, or null if there is no such location 305 * @see #iterator() 306 */ 307 public Map<Name, Property> getPropertiesFor( Location location ) { 308 if (location == null || !location.hasPath()) return null; 309 Node node = nodes.get(location.getPath()); 310 return node != null ? node.getProperties() : null; 311 } 312 313 /** 314 * Get the children of the node at the supplied location. 315 * 316 * @param parent the location of the parent 317 * @return the children, or null if there are no children (or if the parent has not been read) 318 */ 319 public List<Location> getChildren( Location parent ) { 320 if (parent == null || !parent.hasPath()) return null; 321 Node node = nodes.get(parent.getPath()); 322 return node != null ? node.getChildren() : null; 323 } 324 325 /** 326 * {@inheritDoc} 327 * <p> 328 * The resulting iterator accesses the {@link Location} objects in the branch, in pre-order traversal order. 329 * </p> 330 * 331 * @see java.lang.Iterable#iterator() 332 */ 333 public Iterator<Location> iterator() { 334 final LinkedList<Location> queue = new LinkedList<Location>(); 335 if (getActualLocationOfNode() != null) { 336 Location actual = getActualLocationOfNode(); 337 if (actual != null) queue.addFirst(getActualLocationOfNode()); 338 } 339 return new Iterator<Location>() { 340 public boolean hasNext() { 341 return queue.peek() != null; 342 } 343 344 public Location next() { 345 // Add the children of the next node to the queue ... 346 Location next = queue.poll(); 347 if (next == null) throw new NoSuchElementException(); 348 List<Location> children = getChildren(next); 349 if (children != null && children.size() > 0) { 350 // We should only add the children if they are nodes in the branch, so check the first one... 351 Location firstChild = children.get(0); 352 if (includes(firstChild)) queue.addAll(0, children); 353 } 354 return next; 355 } 356 357 public void remove() { 358 throw new UnsupportedOperationException(); 359 } 360 }; 361 } 362 363 /** 364 * Sets the actual and complete location of the node being read. This method must be called when processing the request, and 365 * the actual location must have a {@link Location#getPath() path}. 366 * 367 * @param actual the actual location of the node being read, or null if the {@link #at() current location} should be used 368 * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same 369 * location} as the {@link #at() current location}, or if the actual location does not have a path. 370 */ 371 public void setActualLocationOfNode( Location actual ) { 372 if (!at.isSame(actual)) { // not same if actual is null 373 throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, at)); 374 } 375 assert actual != null; 376 if (!actual.hasPath()) { 377 throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual)); 378 } 379 this.actualLocation = actual; 380 } 381 382 /** 383 * Get the actual location of the node that was read. 384 * 385 * @return the actual location, or null if the actual location was not set 386 */ 387 public Location getActualLocationOfNode() { 388 return actualLocation; 389 } 390 391 /** 392 * {@inheritDoc} 393 * 394 * @see org.jboss.dna.graph.request.Request#cancel() 395 */ 396 @Override 397 public void cancel() { 398 super.cancel(); 399 this.actualLocation = null; 400 this.nodes.clear(); 401 } 402 403 /** 404 * {@inheritDoc} 405 * 406 * @see java.lang.Object#hashCode() 407 */ 408 @Override 409 public int hashCode() { 410 return HashCode.compute(at, workspaceName); 411 } 412 413 /** 414 * {@inheritDoc} 415 * 416 * @see java.lang.Object#equals(java.lang.Object) 417 */ 418 @Override 419 public boolean equals( Object obj ) { 420 if (obj == this) return true; 421 if (this.getClass().isInstance(obj)) { 422 ReadBranchRequest that = (ReadBranchRequest)obj; 423 if (!this.at().equals(that.at())) return false; 424 if (this.maximumDepth() != that.maximumDepth()) return false; 425 if (!this.inWorkspace().equals(that.inWorkspace())) return false; 426 return true; 427 } 428 return false; 429 } 430 431 /** 432 * {@inheritDoc} 433 * 434 * @see java.lang.Object#toString() 435 */ 436 @Override 437 public String toString() { 438 return "read branch " + at() + " in the \"" + workspaceName + "\" workspace to depth " + maximumDepth(); 439 } 440 }