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