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 }