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