001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry.contrib.table.components; 016 017 import java.io.Serializable; 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.Iterator; 021 import java.util.List; 022 023 import org.apache.hivemind.ApplicationRuntimeException; 024 import org.apache.tapestry.BaseComponent; 025 import org.apache.tapestry.IComponent; 026 import org.apache.tapestry.IMarkupWriter; 027 import org.apache.tapestry.IRequestCycle; 028 import org.apache.tapestry.contrib.table.model.IAdvancedTableColumnSource; 029 import org.apache.tapestry.contrib.table.model.IBasicTableModel; 030 import org.apache.tapestry.contrib.table.model.ITableColumn; 031 import org.apache.tapestry.contrib.table.model.ITableColumnModel; 032 import org.apache.tapestry.contrib.table.model.ITableDataModel; 033 import org.apache.tapestry.contrib.table.model.ITableModel; 034 import org.apache.tapestry.contrib.table.model.ITableModelSource; 035 import org.apache.tapestry.contrib.table.model.ITablePagingState; 036 import org.apache.tapestry.contrib.table.model.ITableSessionStateManager; 037 import org.apache.tapestry.contrib.table.model.ITableSessionStoreManager; 038 import org.apache.tapestry.contrib.table.model.common.BasicTableModelWrap; 039 import org.apache.tapestry.contrib.table.model.simple.SimpleListTableDataModel; 040 import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel; 041 import org.apache.tapestry.contrib.table.model.simple.SimpleTableModel; 042 import org.apache.tapestry.contrib.table.model.simple.SimpleTableState; 043 import org.apache.tapestry.event.PageBeginRenderListener; 044 import org.apache.tapestry.event.PageDetachListener; 045 import org.apache.tapestry.event.PageEvent; 046 047 /** 048 * A low level Table component that wraps all other low level Table components. This component 049 * carries the {@link org.apache.tapestry.contrib.table.model.ITableModel}that is used by the other 050 * Table components. Please see the documentation of 051 * {@link org.apache.tapestry.contrib.table.model.ITableModel}if you need to know more about how a 052 * table is represented. 053 * <p> 054 * This component also handles the saving of the state of the model using an 055 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}to determine what part 056 * of the model is to be saved and an 057 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}to determine how to 058 * save it. 059 * <p> 060 * Upon the beginning of a new request cycle when the table model is first needed, the model is 061 * obtained using the following process: 062 * <ul> 063 * <li>The persistent state of the table is loaded. If the tableSessionStoreManager binding has not 064 * been bound, the state is loaded from a persistent property within the component (it is null at 065 * the beginning). Otherwise the supplied 066 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}is used to load the 067 * persistent state. 068 * <li>The table model is recreated using the 069 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}that could be supplied 070 * using the tableSessionStateManager binding (but has a default value and is therefore not 071 * required). 072 * <li>If the {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}returns 073 * null, then a table model is taken from the tableModel binding. Thus, if the 074 * {@link org.apache.tapestry.contrib.table.model.common.NullTableSessionStateManager}is used, the 075 * table model would be taken from the tableModel binding every time. 076 * </ul> 077 * Just before the rendering phase the persistent state of the model is saved in the session. This 078 * process occurs in reverse: 079 * <ul> 080 * <li>The persistent state of the model is taken via the 081 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}. 082 * <li>If the tableSessionStoreManager binding has not been bound, the persistent state is saved as 083 * a persistent page property. Otherwise the supplied 084 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}is used to save the 085 * persistent state. Use of the 086 * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}is usually necessary 087 * when tables with the same model have to be used across multiple pages, and hence the state has to 088 * be saved in the Visit, rather than in a persistent component property. 089 * </ul> 090 * <p> 091 * <p> 092 * Please see the Component Reference for details on how to use this component. [ <a 093 * href="../../../../../../../ComponentReference/contrib.TableView.html">Component Reference </a>] 094 * 095 * @author mindbridge 096 */ 097 public abstract class TableView extends BaseComponent implements PageDetachListener, 098 PageBeginRenderListener, ITableModelSource 099 { 100 /** @since 4.0 */ 101 public abstract TableColumnModelSource getModelSource(); 102 103 /** @since 4.0 */ 104 public abstract IAdvancedTableColumnSource getColumnSource(); 105 106 // Component properties 107 private ITableSessionStateManager m_objDefaultSessionStateManager = null; 108 109 private ITableColumnModel m_objColumnModel = null; 110 111 // Transient objects 112 private ITableModel m_objTableModel; 113 114 private ITableModel m_objCachedTableModelValue; 115 116 // enhanced parameter methods 117 public abstract ITableModel getTableModelValue(); 118 119 public abstract Object getSource(); 120 121 public abstract Object getColumns(); 122 123 public abstract int getInitialPage(); 124 125 public abstract String getInitialSortColumn(); 126 127 public abstract boolean getInitialSortOrder(); 128 129 public abstract ITableSessionStateManager getTableSessionStateManager(); 130 131 public abstract ITableSessionStoreManager getTableSessionStoreManager(); 132 133 public abstract IComponent getColumnSettingsContainer(); 134 135 public abstract int getPageSize(); 136 137 public abstract String getPersist(); 138 139 // enhanced property methods 140 public abstract Serializable getSessionState(); 141 142 public abstract void setSessionState(Serializable sessionState); 143 144 public abstract Serializable getClientState(); 145 146 public abstract void setClientState(Serializable sessionState); 147 148 public abstract Serializable getClientAppState(); 149 150 public abstract void setClientAppState(Serializable sessionState); 151 152 /** 153 * The component constructor. Invokes the component member initializations. 154 */ 155 public TableView() 156 { 157 initialize(); 158 } 159 160 /** 161 * Invokes the component member initializations. 162 * 163 * @see org.apache.tapestry.event.PageDetachListener#pageDetached(PageEvent) 164 */ 165 public void pageDetached(PageEvent objEvent) 166 { 167 initialize(); 168 } 169 170 /** 171 * Initialize the component member variables. 172 */ 173 private void initialize() 174 { 175 m_objTableModel = null; 176 m_objCachedTableModelValue = null; 177 } 178 179 /** 180 * Resets the table by removing any stored table state. This means that the current column to 181 * sort on and the current page will be forgotten and all data will be reloaded. 182 */ 183 public void reset() 184 { 185 initialize(); 186 storeSessionState(null); 187 } 188 189 public ITableModel getCachedTableModelValue() 190 { 191 if (m_objCachedTableModelValue == null) 192 m_objCachedTableModelValue = getTableModelValue(); 193 return m_objCachedTableModelValue; 194 } 195 196 /** 197 * Returns the tableModel. 198 * 199 * @return ITableModel the table model used by the table components 200 */ 201 public ITableModel getTableModel() 202 { 203 // if null, first try to recreate the model from the session state 204 if (m_objTableModel == null) 205 { 206 Serializable objState = loadSessionState(); 207 ITableSessionStateManager objStateManager = getTableSessionStateManager(); 208 m_objTableModel = objStateManager.recreateTableModel(objState); 209 } 210 211 // if the session state does not help, get the model from the binding 212 if (m_objTableModel == null) 213 m_objTableModel = getCachedTableModelValue(); 214 215 // if the model from the binding is null, build a model from source and columns 216 if (m_objTableModel == null) 217 m_objTableModel = generateTableModel(null); 218 219 if (m_objTableModel == null) 220 throw new ApplicationRuntimeException(TableMessages.missingTableModel(this)); 221 222 return m_objTableModel; 223 } 224 225 /** 226 * Generate a table model using the 'source' and 'columns' parameters. 227 * 228 * @return the newly generated table model 229 */ 230 protected ITableModel generateTableModel(SimpleTableState objState) 231 { 232 // create a new table state if none is passed 233 if (objState == null) 234 { 235 objState = new SimpleTableState(); 236 objState.getSortingState().setSortColumn(getInitialSortColumn(), getInitialSortOrder()); 237 objState.getPagingState().setCurrentPage(getInitialPage()); 238 } 239 240 // update the page size if set in the parameter 241 if (isParameterBound("pageSize")) 242 objState.getPagingState().setPageSize(getPageSize()); 243 244 // get the column model. if not possible, return null. 245 ITableColumnModel objColumnModel = getTableColumnModel(); 246 if (objColumnModel == null) 247 return null; 248 249 Object objSourceValue = getSource(); 250 if (objSourceValue == null) 251 return null; 252 253 // if the source parameter is of type {@link IBasicTableModel}, 254 // create and return an appropriate wrapper 255 if (objSourceValue instanceof IBasicTableModel) 256 return new BasicTableModelWrap((IBasicTableModel) objSourceValue, objColumnModel, 257 objState); 258 259 // otherwise, the source parameter must contain the data to be displayed 260 ITableDataModel objDataModel = null; 261 if (objSourceValue instanceof Object[]) 262 objDataModel = new SimpleListTableDataModel((Object[]) objSourceValue); 263 else if (objSourceValue instanceof List) 264 objDataModel = new SimpleListTableDataModel((List) objSourceValue); 265 else if (objSourceValue instanceof Collection) 266 objDataModel = new SimpleListTableDataModel((Collection) objSourceValue); 267 else if (objSourceValue instanceof Iterator) 268 objDataModel = new SimpleListTableDataModel((Iterator) objSourceValue); 269 270 if (objDataModel == null) 271 throw new ApplicationRuntimeException(TableMessages.invalidTableSource( 272 this, 273 objSourceValue)); 274 275 return new SimpleTableModel(objDataModel, objColumnModel, objState); 276 } 277 278 /** 279 * Returns the table column model as specified by the 'columns' binding. If the value of the 280 * 'columns' binding is of a type different than ITableColumnModel, this method makes the 281 * appropriate conversion. 282 * 283 * @return The table column model as specified by the 'columns' binding 284 */ 285 protected ITableColumnModel getTableColumnModel() 286 { 287 Object objColumns = getColumns(); 288 289 if (objColumns == null) 290 return null; 291 292 if (objColumns instanceof ITableColumnModel) 293 { 294 return (ITableColumnModel) objColumns; 295 } 296 297 if (objColumns instanceof Iterator) 298 { 299 // convert to List 300 Iterator objColumnsIterator = (Iterator) objColumns; 301 List arrColumnsList = new ArrayList(); 302 addAll(arrColumnsList, objColumnsIterator); 303 objColumns = arrColumnsList; 304 } 305 306 if (objColumns instanceof List) 307 { 308 // validate that the list contains only ITableColumn instances 309 List arrColumnsList = (List) objColumns; 310 int nColumnsNumber = arrColumnsList.size(); 311 for (int i = 0; i < nColumnsNumber; i++) 312 { 313 if (!(arrColumnsList.get(i) instanceof ITableColumn)) 314 throw new ApplicationRuntimeException(TableMessages.columnsOnlyPlease(this)); 315 } 316 //objColumns = arrColumnsList.toArray(new ITableColumn[nColumnsNumber]); 317 return new SimpleTableColumnModel(arrColumnsList); 318 } 319 320 if (objColumns instanceof ITableColumn[]) 321 { 322 return new SimpleTableColumnModel((ITableColumn[]) objColumns); 323 } 324 325 if (objColumns instanceof String) 326 { 327 String strColumns = (String) objColumns; 328 if (getBinding("columns").isInvariant()) 329 { 330 // if the binding is invariant, create the columns only once 331 if (m_objColumnModel == null) 332 m_objColumnModel = generateTableColumnModel(strColumns); 333 return m_objColumnModel; 334 } 335 336 // if the binding is not invariant, create them every time 337 return generateTableColumnModel(strColumns); 338 } 339 340 throw new ApplicationRuntimeException(TableMessages.invalidTableColumns(this, objColumns)); 341 } 342 343 private void addAll(List arrColumnsList, Iterator objColumnsIterator) 344 { 345 while (objColumnsIterator.hasNext()) 346 arrColumnsList.add(objColumnsIterator.next()); 347 } 348 349 /** 350 * Generate a table column model out of the description string provided. Entries in the 351 * description string are separated by commas. Each column entry is of the format name, 352 * name:expression, or name:displayName:expression. An entry prefixed with ! represents a 353 * non-sortable column. If the whole description string is prefixed with *, it represents 354 * columns to be included in a Form. 355 * 356 * @param strDesc 357 * the description of the column model to be generated 358 * @return a table column model based on the provided description 359 */ 360 protected ITableColumnModel generateTableColumnModel(String strDesc) 361 { 362 IComponent objColumnSettingsContainer = getColumnSettingsContainer(); 363 IAdvancedTableColumnSource objColumnSource = getColumnSource(); 364 365 return getModelSource().generateTableColumnModel(objColumnSource, strDesc, this, objColumnSettingsContainer); 366 } 367 368 /** 369 * The default session state manager to be used in case no such manager is provided by the 370 * corresponding parameter. 371 * 372 * @return the default session state manager 373 */ 374 public ITableSessionStateManager getDefaultTableSessionStateManager() 375 { 376 if (m_objDefaultSessionStateManager == null) 377 m_objDefaultSessionStateManager = new TableViewSessionStateManager(this); 378 return m_objDefaultSessionStateManager; 379 } 380 381 /** 382 * Invoked when there is a modification of the table state and it needs to be saved 383 * 384 * @see org.apache.tapestry.contrib.table.model.ITableModelSource#fireObservedStateChange() 385 */ 386 public void fireObservedStateChange() 387 { 388 saveSessionState(); 389 } 390 391 /** 392 * Ensures that the table state is saved before the render phase begins in case there are 393 * modifications for which {@link #fireObservedStateChange()}has not been invoked. 394 * 395 * @see org.apache.tapestry.event.PageBeginRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent) 396 */ 397 public void pageBeginRender(PageEvent event) 398 { 399 // 'suspenders': save the table model if it has been already loaded. 400 // this means that if a change has been made explicitly in a listener, 401 // it will be saved. this is the last place before committing the changes 402 // where a save can occur 403 if (m_objTableModel != null) 404 saveSessionState(); 405 } 406 407 /** 408 * Saves the table state using the SessionStateManager to determine what to save and the 409 * SessionStoreManager to determine where to save it. 410 */ 411 protected void saveSessionState() 412 { 413 ITableModel objModel = getTableModel(); 414 Serializable objState = getTableSessionStateManager().getSessionState(objModel); 415 storeSessionState(objState); 416 } 417 418 /** 419 * Loads the table state using the SessionStoreManager. 420 * 421 * @return the stored table state 422 */ 423 protected Serializable loadSessionState() 424 { 425 ITableSessionStoreManager objManager = getTableSessionStoreManager(); 426 if (objManager != null) 427 return objManager.loadState(getPage().getRequestCycle()); 428 String strPersist = getPersist(); 429 if (strPersist.equals("client") || strPersist.equals("client:page")) 430 return getClientState(); 431 else if (strPersist.equals("client:app")) 432 return getClientAppState(); 433 else 434 return getSessionState(); 435 } 436 437 /** 438 * Stores the table state using the SessionStoreManager. 439 * 440 * @param objState 441 * the table state to store 442 */ 443 protected void storeSessionState(Serializable objState) 444 { 445 ITableSessionStoreManager objManager = getTableSessionStoreManager(); 446 if (objManager != null) 447 objManager.saveState(getPage().getRequestCycle(), objState); 448 else { 449 String strPersist = getPersist(); 450 if (strPersist.equals("client") || strPersist.equals("client:page")) 451 setClientState(objState); 452 else if (strPersist.equals("client:app")) 453 setClientAppState(objState); 454 else 455 setSessionState(objState); 456 } 457 } 458 459 /** 460 * Make sure that the values stored in the model are useable and correct. The changes made here 461 * are not saved. 462 */ 463 protected void validateValues() 464 { 465 ITableModel objModel = getTableModel(); 466 467 // make sure current page is within the allowed range 468 ITablePagingState objPagingState = objModel.getPagingState(); 469 int nCurrentPage = objPagingState.getCurrentPage(); 470 int nPageCount = objModel.getPageCount(); 471 if (nCurrentPage >= nPageCount) 472 { 473 // the current page is greater than the page count. adjust. 474 nCurrentPage = nPageCount - 1; 475 objPagingState.setCurrentPage(nCurrentPage); 476 } 477 if (nCurrentPage < 0) 478 { 479 // the current page is before the first page. adjust. 480 nCurrentPage = 0; 481 objPagingState.setCurrentPage(nCurrentPage); 482 } 483 } 484 485 /** 486 * Stores a pointer to this component in the Request Cycle while rendering so that wrapped 487 * components have access to it. 488 * 489 * @see org.apache.tapestry.BaseComponent#renderComponent(IMarkupWriter, IRequestCycle) 490 */ 491 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) 492 { 493 Object objOldValue = cycle.getAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE); 494 cycle.setAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE, this); 495 496 initialize(); 497 validateValues(); 498 super.renderComponent(writer, cycle); 499 500 cycle.setAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE, objOldValue); 501 } 502 503 }