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.LinkedList;
027 import java.util.List;
028 import org.jboss.dna.common.text.Inflector;
029 import org.jboss.dna.common.util.CheckArg;
030 import org.jboss.dna.common.util.HashCode;
031 import org.jboss.dna.graph.GraphI18n;
032 import org.jboss.dna.graph.Location;
033 import org.jboss.dna.graph.connector.RepositoryConnection;
034 import org.jboss.dna.graph.property.Path;
035 import org.jboss.dna.graph.property.Property;
036
037 /**
038 * Instruction to read a block of the children of a node, where the block is dictated by the {@link #startingAtIndex() starting
039 * index} and the {@link #count() maximum number of children} to include in the block. This command is useful when paging through
040 * a large number of children.
041 *
042 * @see ReadNextBlockOfChildrenRequest
043 * @author Randall Hauch
044 */
045 public class ReadBlockOfChildrenRequest extends CacheableRequest {
046
047 public static final int INDEX_NOT_USED = -1;
048
049 private static final long serialVersionUID = 1L;
050
051 private final Location of;
052 private final String workspaceName;
053 private final List<Location> children = new LinkedList<Location>();
054 private final int startingAtIndex;
055 private final int count;
056 private Location actualLocation;
057
058 /**
059 * Create a request to read a block of the children of a node at the supplied location. The block is defined by the starting
060 * index of the first child and the number of children to include. Note that this index is <i>not</i> the
061 * {@link Path.Segment#getIndex() same-name-sibiling index}, but rather is the index of the child as if the children were in
062 * an array.
063 *
064 * @param of the location of the node whose children are to be read
065 * @param workspaceName the name of the workspace containing the parent
066 * @param startingIndex the zero-based index of the first child to be included in the block
067 * @param count the maximum number of children that should be included in the block
068 * @throws IllegalArgumentException if the location or workspace name is null, if <code>startingIndex</code> is negative, or
069 * if <code>count</count> is less than 1.
070 */
071 public ReadBlockOfChildrenRequest( Location of,
072 String workspaceName,
073 int startingIndex,
074 int count ) {
075 CheckArg.isNotNull(of, "of");
076 CheckArg.isNonNegative(startingIndex, "startingIndex");
077 CheckArg.isPositive(count, "count");
078 CheckArg.isNotNull(workspaceName, "workspaceName");
079 this.workspaceName = workspaceName;
080 this.of = of;
081 this.startingAtIndex = startingIndex;
082 this.count = count;
083 }
084
085 /**
086 * {@inheritDoc}
087 *
088 * @see org.jboss.dna.graph.request.Request#isReadOnly()
089 */
090 @Override
091 public boolean isReadOnly() {
092 return true;
093 }
094
095 /**
096 * Get the location defining the node whose children are to be read.
097 *
098 * @return the location of the parent node; never null
099 */
100 public Location of() {
101 return of;
102 }
103
104 /**
105 * Get the name of the workspace in which the parent and children exist.
106 *
107 * @return the name of the workspace; never null
108 */
109 public String inWorkspace() {
110 return workspaceName;
111 }
112
113 /**
114 * Get the maximum number of children that may be returned in the block.
115 *
116 * @return the block's maximum count
117 * @see #startingAtIndex()
118 * @see #endingBefore()
119 */
120 public int count() {
121 return this.count;
122 }
123
124 /**
125 * Get the starting index of the block, which is the index of the first child to include. This index corresponds to the index
126 * of all children in the list, not the {@link Path.Segment#getIndex() same-name-sibiling index}.
127 *
128 * @return the (zero-based) child index at which this block starts; never negative and always less than
129 * {@link #endingBefore()}
130 * @see #endingBefore()
131 * @see #count()
132 */
133 public int startingAtIndex() {
134 return this.startingAtIndex;
135 }
136
137 /**
138 * Get the index past the last child that is to be included in the block. This index corresponds to the index of all children
139 * in the list, not the {@link Path.Segment#getIndex() same-name-sibiling index}.
140 *
141 * @return the index just past the last child included in the block; always positive and always greater than
142 * {@link #startingAtIndex()}.
143 * @see #startingAtIndex()
144 * @see #count()
145 */
146 public int endingBefore() {
147 return this.startingAtIndex + this.count;
148 }
149
150 /**
151 * Get the children that were read from the {@link RepositoryConnection} after the request was processed. Each child is
152 * represented by a location.
153 *
154 * @return the children that were read; never null
155 */
156 public List<Location> getChildren() {
157 return children;
158 }
159
160 /**
161 * Add to the list of children that has been read the supplied children with the given path and identification properties. The
162 * children are added in order.
163 *
164 * @param children the locations of the children that were read
165 * @throws IllegalArgumentException if the parameter is null
166 * @throws IllegalStateException if the request is frozen
167 * @see #addChild(Location)
168 * @see #addChild(Path, Property)
169 * @see #addChild(Path, Property, Property...)
170 */
171 public void addChildren( Iterable<Location> children ) {
172 checkNotFrozen();
173 CheckArg.isNotNull(children, "children");
174 for (Location child : children) {
175 if (child != null) this.children.add(child);
176 }
177 }
178
179 /**
180 * Add to the list of children that has been read the child with the given path and identification properties. The children
181 * should be added in order.
182 *
183 * @param child the location of the child that was read
184 * @throws IllegalArgumentException if the location is null
185 * @throws IllegalStateException if the request is frozen
186 * @see #addChild(Path, Property)
187 * @see #addChild(Path, Property, Property...)
188 */
189 public void addChild( Location child ) {
190 checkNotFrozen();
191 CheckArg.isNotNull(child, "child");
192 this.children.add(child);
193 }
194
195 /**
196 * Add to the list of children that has been read the child with the given path and identification properties. The children
197 * should be added in order.
198 *
199 * @param pathToChild the path of the child that was just read
200 * @param firstIdProperty the first identification property of the child that was just read
201 * @param remainingIdProperties the remaining identification properties of the child that was just read
202 * @throws IllegalArgumentException if the path or identification properties are null
203 * @throws IllegalStateException if the request is frozen
204 * @see #addChild(Location)
205 * @see #addChild(Path, Property)
206 */
207 public void addChild( Path pathToChild,
208 Property firstIdProperty,
209 Property... remainingIdProperties ) {
210 checkNotFrozen();
211 Location child = Location.create(pathToChild, firstIdProperty, remainingIdProperties);
212 this.children.add(child);
213 }
214
215 /**
216 * Add to the list of children that has been read the child with the given path and identification property. The children
217 * should be added in order.
218 *
219 * @param pathToChild the path of the child that was just read
220 * @param idProperty the identification property of the child that was just read
221 * @throws IllegalArgumentException if the path or identification properties are null
222 * @throws IllegalStateException if the request is frozen
223 * @see #addChild(Location)
224 * @see #addChild(Path, Property, Property...)
225 */
226 public void addChild( Path pathToChild,
227 Property idProperty ) {
228 checkNotFrozen();
229 Location child = Location.create(pathToChild, idProperty);
230 this.children.add(child);
231 }
232
233 /**
234 * Sets the actual and complete location of the node whose children have been read. This method must be called when processing
235 * the request, and the actual location must have a {@link Location#getPath() path}.
236 *
237 * @param actual the actual location of the node being read, or null if the {@link #of() current location} should be used
238 * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same
239 * location} as the {@link #of() current location}, or if the actual location does not have a path.
240 * @throws IllegalStateException if the request is frozen
241 */
242 public void setActualLocationOfNode( Location actual ) {
243 checkNotFrozen();
244 if (!of.isSame(actual)) { // not same if actual is null
245 throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, of));
246 }
247 assert actual != null;
248 if (!actual.hasPath()) {
249 throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
250 }
251 this.actualLocation = actual;
252 }
253
254 /**
255 * Get the actual location of the node whose children were read.
256 *
257 * @return the actual location, or null if the actual location was not set
258 */
259 public Location getActualLocationOfNode() {
260 return actualLocation;
261 }
262
263 /**
264 * {@inheritDoc}
265 *
266 * @see org.jboss.dna.graph.request.Request#cancel()
267 */
268 @Override
269 public void cancel() {
270 super.cancel();
271 this.actualLocation = null;
272 this.children.clear();
273 }
274
275 /**
276 * {@inheritDoc}
277 *
278 * @see java.lang.Object#hashCode()
279 */
280 @Override
281 public int hashCode() {
282 return HashCode.compute(of, workspaceName);
283 }
284
285 /**
286 * {@inheritDoc}
287 *
288 * @see java.lang.Object#equals(java.lang.Object)
289 */
290 @Override
291 public boolean equals( Object obj ) {
292 if (obj == this) return true;
293 if (this.getClass().isInstance(obj)) {
294 ReadBlockOfChildrenRequest that = (ReadBlockOfChildrenRequest)obj;
295 if (!this.of().equals(that.of())) return false;
296 if (this.startingAtIndex() != that.startingAtIndex()) return false;
297 if (this.count() != that.count()) return false;
298 if (!this.inWorkspace().equals(that.inWorkspace())) return false;
299 return true;
300 }
301 return false;
302 }
303
304 /**
305 * {@inheritDoc}
306 *
307 * @see java.lang.Object#toString()
308 */
309 @Override
310 public String toString() {
311 Inflector inflector = Inflector.getInstance();
312 if (count() == 1) {
313 return "read " + inflector.ordinalize(startingAtIndex()) + " thru " + inflector.ordinalize(endingBefore() - 1)
314 + " children of " + of() + " in the \"" + workspaceName + "\" workspace";
315 }
316 return "read " + inflector.ordinalize(startingAtIndex()) + " child of " + of() + " in the \"" + workspaceName
317 + "\" workspace";
318 }
319
320 }