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 }