/* * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * "The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations under * the License. * * The Original Code is ICEfaces 1.5 open source software code, released * November 5, 2006. The Initial Developer of the Original Code is ICEsoft * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C) * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved. * * Contributor(s): _____________________. * * Alternatively, the contents of this file may be used under the terms of * the GNU Lesser General Public License Version 2.1 or later (the "LGPL" * License), in which case the provisions of the LGPL License are * applicable instead of those above. If you wish to allow use of your * version of this file only under the terms of the LGPL License and not to * allow others to use your version of this file under the MPL, indicate * your decision by deleting the provisions above and replace them with * the notice and other provisions required by the LGPL License. If you do * not delete the provisions above, a recipient may use your version of * this file under either the MPL or the LGPL License." * */ package com.joe.utilities.core.data; import javax.faces.model.DataModel; /** * A special type of JSF DataModel to allow a datatable and datapaginator * to page through a large set of data without having to hold the entire * set of data in memory at once. * Any time a managed bean wants to avoid holding an entire dataset, * the managed bean should declare an inner class which extends this * class and implements the fetchData method. This method is called * as needed when the table requires data that isn't available in the * current data page held by this object. * This does require the managed bean (and in general the business * method that the managed bean uses) to provide the data wrapped in * a DataPage object that provides info on the full size of the dataset. */ @SuppressWarnings("unchecked") public abstract class PagedListDataModel<T> extends DataModel { int pageSize; int rowIndex; DataPage<T> page; // Triggers a fetch from the database protected boolean dirtyData = false; /* * Create a datamodel that pages through the data showing the specified * number of rows on each page. */ public PagedListDataModel(int pageSize) { super(); this.pageSize = pageSize; this.rowIndex = -1; this.page = null; } /** * Not used in this class; data is fetched via a callback to the * fetchData method rather than by explicitly assigning a list. */ @Override public void setWrappedData(Object o) { throw new UnsupportedOperationException("setWrappedData"); } @Override public int getRowIndex() { return rowIndex; } /** * Specify what the "current row" within the dataset is. Note that * the UIData component will repeatedly call this method followed * by getRowData to obtain the objects to render in the table. */ @Override public void setRowIndex(int index) { rowIndex = index; } /** * Return the total number of rows of data available (not just the * number of rows in the current page!). */ @Override public int getRowCount() { return getPage().getDatasetSize(); } /** * Return a DataPage object; if one is not currently available then * fetch one. Note that this doesn't ensure that the datapage * returned includes the current rowIndex row; see getRowData. */ private DataPage<T> getPage() { if (page != null) return page; int rowIndex = getRowIndex(); int startRow = rowIndex; if (rowIndex == -1) { // even when no row is selected, we still need a page // object so that we know the amount of data available. startRow = 0; } // invoke method on enclosing class page = fetchPage(startRow, pageSize); return page; } /** * Return the object corresponding to the current rowIndex. * If the DataPage object currently cached doesn't include that * index or the data is marked as dirty, then fetchPage is called * to retrieve the appropriate page. */ @Override public Object getRowData() { if (rowIndex < 0) { throw new IllegalArgumentException( "Invalid rowIndex for PagedListDataModel; not within page"); } // ensure page exists; if rowIndex is beyond dataset size, then // we should still get back a DataPage object with the dataset size // in it... if (page == null) { page = fetchPage(rowIndex, pageSize); } // Check if rowIndex is equal to startRow, // useful for dynamic sorting on pages if (rowIndex == page.getStartRow() && dirtyData) { page = fetchPage(rowIndex, pageSize); } int datasetSize = page.getDatasetSize(); int startRow = page.getStartRow(); int nRows = page.getData().size(); int endRow = startRow + nRows; if (rowIndex >= datasetSize) { throw new IllegalArgumentException("Invalid rowIndex"); } if (rowIndex < startRow) { page = fetchPage(rowIndex, pageSize); startRow = page.getStartRow(); } else if (rowIndex >= endRow) { page = fetchPage(rowIndex, pageSize); startRow = page.getStartRow(); } return page.getData().get(rowIndex - startRow); } @Override public Object getWrappedData() { return page.getData(); } /** * Return true if the rowIndex value is currently set to a * value that matches some element in the dataset. Note that * it may match a row that is not in the currently cached * DataPage; if so then when getRowData is called the * required DataPage will be fetched by calling fetchData. */ @Override public boolean isRowAvailable() { DataPage<T> page = getPage(); if (page == null) return false; int rowIndex = getRowIndex(); if (rowIndex < 0) { return false; } else if (rowIndex >= page.getDatasetSize()) { return false; } else { return true; } } /** * Method which must be implemented in cooperation with the * managed bean class to fetch data on demand. */ public abstract DataPage<T> fetchPage(int startRow, int pageSize); public boolean isDirtyData() { return dirtyData; } public void setDirtyData(boolean dirtyData) { this.dirtyData = dirtyData; } public void setDirtyData() { dirtyData = true; } }