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