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    }