View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.request;
25  
26  import java.util.Arrays;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.NoSuchElementException;
33  import net.jcip.annotations.NotThreadSafe;
34  import org.modeshape.common.util.CheckArg;
35  import org.modeshape.common.util.HashCode;
36  import org.modeshape.graph.GraphI18n;
37  import org.modeshape.graph.Location;
38  import org.modeshape.graph.connector.RepositoryConnection;
39  import org.modeshape.graph.property.Name;
40  import org.modeshape.graph.property.Path;
41  import org.modeshape.graph.property.Property;
42  
43  /**
44   * Instruction to read the properties and children of the nodes in the branch at the supplied location. The children of the nodes
45   * at the bottom of the branch are not read.
46   */
47  @NotThreadSafe
48  public class ReadBranchRequest extends CacheableRequest implements Iterable<Location> {
49  
50      private static final long serialVersionUID = 1L;
51  
52      public static final int DEFAULT_MAXIMUM_DEPTH = 2;
53      public static final int NO_MAXIMUM_DEPTH = Integer.MAX_VALUE;
54  
55      private static class Node {
56          private final Location location;
57          private final Map<Name, Property> properties = new HashMap<Name, Property>();
58          private List<Location> children;
59  
60          protected Node( Location location ) {
61              assert location != null;
62              this.location = location;
63          }
64  
65          protected Location getLocation() {
66              return location;
67          }
68  
69          protected Map<Name, Property> getProperties() {
70              return properties;
71          }
72  
73          protected List<Location> getChildren() {
74              return children;
75          }
76  
77          protected void setChildren( List<Location> children ) {
78              this.children = children;
79          }
80      }
81  
82      private final Location at;
83      private final String workspaceName;
84      private final int maxDepth;
85      private final Map<Path, Node> nodes = new HashMap<Path, Node>();
86      private Location actualLocation;
87  
88      /**
89       * Create a request to read the branch at the supplied location, to a maximum depth of 2.
90       * 
91       * @param at the location of the branch
92       * @param workspaceName the name of the workspace containing the parent
93       * @throws IllegalArgumentException if the location or workspace name is null
94       */
95      public ReadBranchRequest( Location at,
96                                String workspaceName ) {
97          CheckArg.isNotNull(at, "at");
98          CheckArg.isNotNull(workspaceName, "workspaceName");
99          this.workspaceName = workspaceName;
100         this.at = at;
101         this.maxDepth = DEFAULT_MAXIMUM_DEPTH;
102     }
103 
104     /**
105      * Create a request to read the branch (of given depth) at the supplied location.
106      * 
107      * @param at the location of the branch
108      * @param workspaceName the name of the workspace containing the branch
109      * @param maxDepth the maximum depth to read
110      * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
111      */
112     public ReadBranchRequest( Location at,
113                               String workspaceName,
114                               int maxDepth ) {
115         CheckArg.isNotNull(at, "at");
116         CheckArg.isPositive(maxDepth, "maxDepth");
117         CheckArg.isNotNull(workspaceName, "workspaceName");
118         this.workspaceName = workspaceName;
119         this.at = at;
120         this.maxDepth = maxDepth;
121     }
122 
123     /**
124      * {@inheritDoc}
125      * 
126      * @see org.modeshape.graph.request.Request#isReadOnly()
127      */
128     @Override
129     public boolean isReadOnly() {
130         return true;
131     }
132 
133     /**
134      * Get the location defining the top of the branch to be read
135      * 
136      * @return the location of the branch; never null
137      */
138     public Location at() {
139         return at;
140     }
141 
142     /**
143      * Get the name of the workspace in which the branch exists.
144      * 
145      * @return the name of the workspace; never null
146      */
147     public String inWorkspace() {
148         return workspaceName;
149     }
150 
151     /**
152      * Get the maximum depth of the branch that is to be read.
153      * 
154      * @return the maximum depth; always positive
155      */
156     public int maximumDepth() {
157         return maxDepth;
158     }
159 
160     /**
161      * Return whether this branch contains the specified location.
162      * 
163      * @param location the location
164      * @return true if this branch includes the location, or false otherwise
165      */
166     public boolean includes( Location location ) {
167         if (location == null || !location.hasPath()) return false;
168         return this.nodes.containsKey(location.getPath());
169     }
170 
171     /**
172      * Return whether this branch contains the specified path.
173      * 
174      * @param path the path
175      * @return true if this branch includes the path, or false otherwise
176      */
177     public boolean includes( Path path ) {
178         if (path == null) return false;
179         return this.nodes.containsKey(path);
180     }
181 
182     /**
183      * Get the location for the supplied path.
184      * 
185      * @param path the path
186      * @return the location for the path, or null if the path is not known
187      */
188     public Location getLocationFor( Path path ) {
189         Node node = nodes.get(path);
190         return node != null ? node.getLocation() : null;
191     }
192 
193     /**
194      * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
195      * indeed on the branch and that it is at a level prescribed by the request.
196      * 
197      * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
198      * @param properties the properties on the node
199      * @throws IllegalArgumentException if the node is null
200      * @throws IllegalStateException if the request is frozen
201      */
202     public void setProperties( Location node,
203                                Property... properties ) {
204         checkNotFrozen();
205         CheckArg.isNotNull(node, "node");
206         assert node.hasPath();
207         Node nodeObj = nodes.get(node.getPath());
208         if (nodeObj == null) {
209             nodeObj = new Node(node);
210             nodes.put(node.getPath(), nodeObj);
211         }
212         Map<Name, Property> propertiesMap = nodeObj.getProperties();
213         for (Property property : properties) {
214             propertiesMap.put(property.getName(), property);
215         }
216     }
217 
218     /**
219      * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
220      * indeed on the branch and that it is at a level prescribed by the request.
221      * 
222      * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
223      * @param properties the properties on the node
224      * @throws IllegalArgumentException if the node is null
225      * @throws IllegalStateException if the request is frozen
226      */
227     public void setProperties( Location node,
228                                Iterable<Property> properties ) {
229         checkNotFrozen();
230         CheckArg.isNotNull(node, "node");
231         assert node.hasPath();
232         Node nodeObj = nodes.get(node.getPath());
233         if (nodeObj == null) {
234             nodeObj = new Node(node);
235             nodes.put(node.getPath(), nodeObj);
236         }
237         Map<Name, Property> propertiesMap = nodeObj.getProperties();
238         for (Property property : properties) {
239             propertiesMap.put(property.getName(), property);
240         }
241     }
242 
243     /**
244      * Record the children for a parent node in the branch.
245      * 
246      * @param parent the location of the parent; must {@link Location#hasPath() have a path}
247      * @param children the location of each child, in the order they appear in the parent
248      * @throws IllegalStateException if the request is frozen
249      */
250     public void setChildren( Location parent,
251                              Location... children ) {
252         checkNotFrozen();
253         CheckArg.isNotNull(parent, "parent");
254         CheckArg.isNotNull(children, "children");
255         assert parent.hasPath();
256         Node nodeObj = nodes.get(parent.getPath());
257         if (nodeObj == null) {
258             nodeObj = new Node(parent);
259             nodes.put(parent.getPath(), nodeObj);
260         }
261         nodeObj.setChildren(Arrays.asList(children));
262     }
263 
264     /**
265      * Record the children for a parent node in the branch.
266      * 
267      * @param parent the location of the parent; must {@link Location#hasPath() have a path}
268      * @param children the location of each child, in the order they appear in the parent
269      * @throws IllegalStateException if the request is frozen
270      */
271     public void setChildren( Location parent,
272                              List<Location> children ) {
273         checkNotFrozen();
274         CheckArg.isNotNull(parent, "parent");
275         CheckArg.isNotNull(children, "children");
276         assert parent.hasPath();
277         Node nodeObj = nodes.get(parent.getPath());
278         if (nodeObj == null) {
279             nodeObj = new Node(parent);
280             nodes.put(parent.getPath(), nodeObj);
281         }
282         nodeObj.setChildren(children);
283     }
284 
285     // /**
286     // * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map
287     // maintains
288     // * the order that the nodes were {@link #setProperties(Location, Property...) added}.
289     // *
290     // * @return the branch information
291     // * @see #iterator()
292     // */
293     // public Map<Path, Map<Name, Property>> getPropertiesByNode() {
294     // return nodeProperties;
295     // }
296 
297     /**
298      * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map maintains
299      * the order that the nodes were {@link #setProperties(Location, Property...) added}.
300      * 
301      * @param location the location of the node for which the properties are to be obtained
302      * @return the properties for the location, as a map keyed by the property name, or null if there is no such location
303      * @see #iterator()
304      */
305     public Map<Name, Property> getPropertiesFor( Location location ) {
306         if (location == null || !location.hasPath()) return null;
307         Node node = nodes.get(location.getPath());
308         return node != null ? node.getProperties() : null;
309     }
310 
311     /**
312      * Get the children of the node at the supplied location.
313      * 
314      * @param parent the location of the parent
315      * @return the children, or null if there are no children (or if the parent has not been read)
316      */
317     public List<Location> getChildren( Location parent ) {
318         if (parent == null || !parent.hasPath()) return null;
319         Node node = nodes.get(parent.getPath());
320         return node != null ? node.getChildren() : null;
321     }
322 
323     /**
324      * {@inheritDoc}
325      * <p>
326      * The resulting iterator accesses the {@link Location} objects in the branch, in pre-order traversal order.
327      * </p>
328      * 
329      * @see java.lang.Iterable#iterator()
330      */
331     public Iterator<Location> iterator() {
332         final LinkedList<Location> queue = new LinkedList<Location>();
333         if (getActualLocationOfNode() != null) {
334             Location actual = getActualLocationOfNode();
335             if (actual != null) queue.addFirst(getActualLocationOfNode());
336         }
337         return new Iterator<Location>() {
338             public boolean hasNext() {
339                 return queue.peek() != null;
340             }
341 
342             public Location next() {
343                 // Add the children of the next node to the queue ...
344                 Location next = queue.poll();
345                 if (next == null) throw new NoSuchElementException();
346                 List<Location> children = getChildren(next);
347                 if (children != null && children.size() > 0) {
348                     // We should only add the children if they are nodes in the branch, so check the first one...
349                     Location firstChild = children.get(0);
350                     if (includes(firstChild)) queue.addAll(0, children);
351                 }
352                 return next;
353             }
354 
355             public void remove() {
356                 throw new UnsupportedOperationException();
357             }
358         };
359     }
360 
361     /**
362      * Sets the actual and complete location of the node being read. This method must be called when processing the request, and
363      * the actual location must have a {@link Location#getPath() path}.
364      * 
365      * @param actual the actual location of the node being read, or null if the {@link #at() current location} should be used
366      * @throws IllegalArgumentException if the actual location is null or does not have a path
367      */
368     public void setActualLocationOfNode( Location actual ) {
369         checkNotFrozen();
370         CheckArg.isNotNull(actual, "actual");
371         if (!actual.hasPath()) {
372             throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
373         }
374         this.actualLocation = actual;
375     }
376 
377     /**
378      * Get the actual location of the node that was read.
379      * 
380      * @return the actual location, or null if the actual location was not set
381      */
382     public Location getActualLocationOfNode() {
383         return actualLocation;
384     }
385 
386     /**
387      * {@inheritDoc}
388      * 
389      * @see org.modeshape.graph.request.Request#cancel()
390      */
391     @Override
392     public void cancel() {
393         super.cancel();
394         this.actualLocation = null;
395         this.nodes.clear();
396     }
397 
398     /**
399      * {@inheritDoc}
400      * 
401      * @see java.lang.Object#hashCode()
402      */
403     @Override
404     public int hashCode() {
405         return HashCode.compute(at, workspaceName);
406     }
407 
408     /**
409      * {@inheritDoc}
410      * 
411      * @see java.lang.Object#equals(java.lang.Object)
412      */
413     @Override
414     public boolean equals( Object obj ) {
415         if (obj == this) return true;
416         if (this.getClass().isInstance(obj)) {
417             ReadBranchRequest that = (ReadBranchRequest)obj;
418             if (!this.at().isSame(that.at())) return false;
419             if (this.maximumDepth() != that.maximumDepth()) return false;
420             if (!this.inWorkspace().equals(that.inWorkspace())) return false;
421             return true;
422         }
423         return false;
424     }
425 
426     /**
427      * {@inheritDoc}
428      * 
429      * @see java.lang.Object#toString()
430      */
431     @Override
432     public String toString() {
433         return "read branch " + at() + " in the \"" + workspaceName + "\" workspace to depth " + maximumDepth();
434     }
435 
436     @Override
437     public RequestType getType() {
438         return RequestType.READ_BRANCH;
439     }
440 
441     /**
442      * Obtain a copy of this request (without any results) with the new supplied maximum depth.
443      * 
444      * @param maxDepth the maximum depth for the new request
445      * @return the copy of thist request, but with the desired maximum depth
446      * @throws IllegalArgumentException if the maximum depth is not positive
447      */
448     public ReadBranchRequest withMaximumDepth( int maxDepth ) {
449         return new ReadBranchRequest(at, workspaceName, maxDepth);
450     }
451 }