001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.repeater; 018 019import java.util.Collection; 020import java.util.Iterator; 021import java.util.NoSuchElementException; 022 023import org.apache.wicket.markup.html.navigation.paging.IPageableItems; 024import org.apache.wicket.model.IModel; 025 026 027/** 028 * An abstract repeater view that provides paging functionality to its subclasses. 029 * <p> 030 * The view is populated by overriding the <code>getItemModels(int offset, int count)</code> method 031 * and providing an iterator that returns models for items in the current page. The 032 * AbstractPageableView builds the items that will be rendered by looping over the models and 033 * calling the <code>newItem(String id, int index, IModel model)</code> to generate the child item 034 * container followed by <code>populateItem(Component item)</code> to let the user populate the 035 * newly created item container with with custom components. 036 * </p> 037 * 038 * @see org.apache.wicket.markup.repeater.RefreshingView 039 * @see org.apache.wicket.markup.html.navigation.paging.IPageable 040 * 041 * @author Igor Vaynberg (ivaynberg) 042 * 043 * @param <T> 044 * type of elements contained in the model's list 045 */ 046public abstract class AbstractPageableView<T> extends RefreshingView<T> implements IPageableItems 047{ 048 /** */ 049 private static final long serialVersionUID = 1L; 050 051 /** 052 * Keeps track of the number of items we show per page. The default is Integer.MAX_VALUE which 053 * effectively disables paging. 054 */ 055 private long itemsPerPage = Long.MAX_VALUE; 056 057 /** 058 * Keeps track of the current page number. 059 */ 060 private long currentPage; 061 062 /** 063 * <code>cachedItemCount</code> is used to cache the call to <code>internalGetItemCount()</code> 064 * for the duration of the request because that call can potentially be expensive ( a select 065 * count query ) and so we do not want to execute it multiple times. 066 */ 067 private transient Long cachedItemCount; 068 069 /** 070 * Constructor 071 * 072 * @param id 073 * @param model 074 * @see org.apache.wicket.Component#Component(String, IModel) 075 */ 076 public AbstractPageableView(String id, IModel<? extends Collection<? extends T>> model) 077 { 078 super(id, model); 079 } 080 081 /** @see org.apache.wicket.Component#Component(String) */ 082 public AbstractPageableView(String id) 083 { 084 super(id); 085 } 086 087 /** 088 * This method retrieves the subset of models for items in the current page and allows 089 * RefreshingView to generate items. 090 * 091 * @return iterator over models for items in the current page 092 */ 093 @Override 094 protected Iterator<IModel<T>> getItemModels() 095 { 096 long offset = getFirstItemOffset(); 097 long size = getViewSize(); 098 099 Iterator<IModel<T>> models = getItemModels(offset, size); 100 101 models = new CappedIteratorAdapter<T>(models, size); 102 103 return models; 104 } 105 106 /** 107 * @see org.apache.wicket.markup.repeater.AbstractRepeater#onBeforeRender() 108 */ 109 @Override 110 protected void onBeforeRender() 111 { 112 clearCachedItemCount(); 113 super.onBeforeRender(); 114 } 115 116 /** 117 * Returns an iterator over models for items in the current page 118 * 119 * @param offset 120 * index of first item in this page 121 * @param size 122 * number of items that will be shown in the current page 123 * @return an iterator over models for items in the current page 124 */ 125 protected abstract Iterator<IModel<T>> getItemModels(long offset, long size); 126 127 // ///////////////////////////////////////////////////////////////////////// 128 // ITEM COUNT CACHE 129 // ///////////////////////////////////////////////////////////////////////// 130 131 private void clearCachedItemCount() 132 { 133 cachedItemCount = null; 134 } 135 136 // ///////////////////////////////////////////////////////////////////////// 137 // PAGING 138 // ///////////////////////////////////////////////////////////////////////// 139 140 /** 141 * @return maximum number of items that will be shown per page 142 */ 143 @Override 144 public long getItemsPerPage() 145 { 146 return itemsPerPage; 147 } 148 149 /** 150 * Sets the maximum number of items to show per page. The current page will also be set to zero 151 * 152 * @param items 153 */ 154 @Override 155 public final void setItemsPerPage(long items) 156 { 157 if (items < 1) 158 { 159 throw new IllegalArgumentException("Argument [itemsPerPage] cannot be less than 1"); 160 } 161 162 if (itemsPerPage != items) 163 { 164 if (isVersioned()) 165 { 166 addStateChange(); 167 } 168 } 169 170 itemsPerPage = items; 171 172 // because items per page can effect the total number of pages we always 173 // reset the current page back to zero 174 setCurrentPage(0); 175 } 176 177 /** 178 * @return total item count 179 */ 180 protected abstract long internalGetItemCount(); 181 182 /** 183 * Get the row count. 184 * 185 * @see #getItemCount() 186 * 187 * @return total item count, but 0 if not visible in the hierarchy 188 */ 189 public final long getRowCount() 190 { 191 if (!isVisibleInHierarchy()) 192 { 193 return 0; 194 } 195 196 return getItemCount(); 197 } 198 199 /** 200 * Get the item count. Since dataprovider.size() could potentially be expensive, the item count 201 * is cached. 202 * 203 * @see #getRowCount() 204 * 205 * @return the item count 206 */ 207 @Override 208 public final long getItemCount() 209 { 210 if (cachedItemCount != null) 211 { 212 return cachedItemCount; 213 } 214 215 long count = internalGetItemCount(); 216 217 cachedItemCount = count; 218 return count; 219 } 220 221 /** 222 * @see org.apache.wicket.markup.html.navigation.paging.IPageable#getCurrentPage() 223 */ 224 @Override 225 public final long getCurrentPage() 226 { 227 long page = currentPage; 228 229 /* 230 * trim current page if its out of bounds this can happen if items are added/deleted between 231 * requests 232 */ 233 234 if (page > 0 && page >= getPageCount()) 235 { 236 page = Math.max(getPageCount() - 1, 0); 237 currentPage = page; 238 return page; 239 } 240 241 return page; 242 } 243 244 /** 245 * @see org.apache.wicket.markup.html.navigation.paging.IPageable#setCurrentPage(long) 246 */ 247 @Override 248 public final void setCurrentPage(long page) 249 { 250 if (currentPage != page) 251 { 252 if (isVersioned()) 253 { 254 addStateChange(); 255 256 } 257 } 258 currentPage = page; 259 } 260 261 /** 262 * @see org.apache.wicket.markup.html.navigation.paging.IPageable#getPageCount() 263 */ 264 @Override 265 public long getPageCount() 266 { 267 long total = getRowCount(); 268 long itemsPerPage = getItemsPerPage(); 269 long count = total / itemsPerPage; 270 271 if (itemsPerPage * count < total) 272 { 273 count++; 274 } 275 276 return count; 277 278 } 279 280 /** 281 * @return the index of the first visible item in the view 282 */ 283 public long getFirstItemOffset() 284 { 285 return getCurrentPage() * getItemsPerPage(); 286 } 287 288 289 /** 290 * @return the number of items visible 291 */ 292 public long getViewSize() 293 { 294 return Math.min(getItemsPerPage(), getRowCount() - getFirstItemOffset()); 295 } 296 297 // ///////////////////////////////////////////////////////////////////////// 298 // HELPER CLASSES 299 // ///////////////////////////////////////////////////////////////////////// 300 301 /** 302 * Iterator adapter that makes sure only the specified max number of items can be accessed from 303 * its delegate. 304 * 305 * @param <T> 306 * Model object type 307 */ 308 private static class CappedIteratorAdapter<T> implements Iterator<IModel<T>> 309 { 310 private final long max; 311 private long index; 312 private final Iterator<IModel<T>> delegate; 313 314 /** 315 * Constructor 316 * 317 * @param delegate 318 * delegate iterator 319 * @param max 320 * maximum number of items that can be accessed. 321 */ 322 public CappedIteratorAdapter(Iterator<IModel<T>> delegate, long max) 323 { 324 this.delegate = delegate; 325 this.max = max; 326 } 327 328 /** 329 * @see java.util.Iterator#remove() 330 */ 331 @Override 332 public void remove() 333 { 334 throw new UnsupportedOperationException(); 335 } 336 337 /** 338 * @see java.util.Iterator#hasNext() 339 */ 340 @Override 341 public boolean hasNext() 342 { 343 return (index < max) && delegate.hasNext(); 344 } 345 346 /** 347 * @see java.util.Iterator#next() 348 */ 349 @Override 350 public IModel<T> next() 351 { 352 if (index >= max) 353 { 354 throw new NoSuchElementException(); 355 } 356 index++; 357 return delegate.next(); 358 } 359 360 } 361 362 /** 363 * @see org.apache.wicket.Component#onDetach() 364 */ 365 @Override 366 protected void onDetach() 367 { 368 clearCachedItemCount(); 369 super.onDetach(); 370 } 371}