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 }