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 }