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