001 /*
002 * Copyright 2004 Chris Nelson
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
007 * Unless required by applicable law or agreed to in writing,
008 * software distributed under the License is distributed on an "AS IS" BASIS,
009 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010 * See the License for the specific language governing permissions and limitations under the License.
011 */
012 package org.trails.component;
013
014 import org.apache.commons.logging.Log;
015 import org.apache.commons.logging.LogFactory;
016 import org.apache.hivemind.ApplicationRuntimeException;
017 import org.apache.hivemind.impl.MessageFormatter;
018 import org.apache.hivemind.service.ClassFabUtils;
019 import org.apache.tapestry.IAsset;
020 import org.apache.tapestry.IComponent;
021 import org.apache.tapestry.IRequestCycle;
022 import org.apache.tapestry.annotations.ComponentClass;
023 import org.apache.tapestry.annotations.InjectObject;
024 import org.apache.tapestry.annotations.Parameter;
025 import org.apache.tapestry.annotations.Persist;
026 import org.apache.tapestry.components.Block;
027 import org.apache.tapestry.contrib.table.components.TableMessages;
028 import org.apache.tapestry.contrib.table.components.TableView;
029 import org.apache.tapestry.contrib.table.model.ITableColumn;
030 import org.apache.tapestry.contrib.table.model.ITableColumnModel;
031 import org.apache.tapestry.contrib.table.model.common.AbstractTableColumn;
032 import org.apache.tapestry.contrib.table.model.common.BlockTableRendererSource;
033 import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
034 import org.apache.tapestry.event.PageBeginRenderListener;
035 import org.apache.tapestry.event.PageEvent;
036 import org.apache.tapestry.services.ExpressionEvaluator;
037 import org.trails.descriptor.IPropertyDescriptor;
038 import org.trails.persistence.PersistenceService;
039
040 import java.util.ArrayList;
041 import java.util.Iterator;
042 import java.util.List;
043
044 /**
045 * Produces a table for the list of instances
046 */
047 //@GlobalComponent
048 @ComponentClass(allowBody = true, allowInformalParameters = true)
049 public abstract class ObjectTable extends ClassDescriptorComponent implements PageBeginRenderListener
050 {
051 private static final Log LOG = LogFactory.getLog(ObjectTable.class);
052
053 public static final String LINK_COLUMN = "link" + AbstractTableColumn.VALUE_RENDERER_BLOCK_SUFFIX;
054
055 private ITableColumnModel m_objColumnModel = null;
056
057 @Parameter(defaultValue = "ognl:@java.lang.System@getProperty('org.apache.tapestry.disable-caching')")
058 public abstract boolean isCacheDisabled();
059
060 protected List<ITableColumn> columns;
061
062 @Parameter(required = false, defaultValue = "false", cache = true)
063 public abstract boolean isShowCollections();
064
065 public abstract void setShowCollections(boolean ShowCollections);
066
067 @InjectObject("service:trails.core.PersistenceService")
068 public abstract PersistenceService getPersistenceService();
069
070 /**
071 * The data to be displayed by the component.
072 *
073 * @return
074 */
075 @Parameter
076 public abstract List getInstances();
077
078 public abstract void setInstances(List instances);
079
080 /**
081 * If provided, the parameter is updated with the current row being rendered.
082 */
083 @Parameter(cache = false, defaultValue = "objectParameter")
084 public abstract Object getObject();
085
086 /**
087 * Returns the currently rendered table row. It's used when there is no binding for the "object" parameter. This method
088 * is for internal use only.
089 */
090 public abstract Object getObjectParameter();
091 public abstract void setObjectParameter(Object object);
092
093 /**
094 * The object representing the current column being rendered.
095 */
096 @Parameter(cache = false)
097 public abstract ITableColumn getColumn();
098
099 /**
100 * The CSS class of the table rows.
101 */
102 @Parameter(cache = false)
103 public abstract String getRowsClass();
104
105 /**
106 * The CSS class of the table columns.
107 */
108 @Parameter(cache = false)
109 public abstract String getColumnsClass();
110
111 /**
112 * The number of records displayed per page.
113 */
114 @Parameter(cache = false, defaultValue = "10")
115 public abstract int getPageSize();
116
117 /**
118 * If provided, the parameter is updated with the index of the loop on each iteration.
119 */
120 @Parameter
121 public abstract int getIndex();
122
123 /**
124 * The id of the column to initially sort the table by.
125 */
126 @Parameter
127 public abstract String getInitialSortColumn();
128
129 /**
130 * The order of the initial sorting. Set this parameter to 'false' to sort in an ascending order and to 'true' to sort
131 * in a descending one.
132 */
133 @Parameter(defaultValue = "false")
134 public abstract boolean getInitialSortOrder();
135
136 /**
137 * Defines how the table state (paging and sorting) will be persisted if no tableSessionStoreManager is defined. The
138 * possible values are 'session' (the default), 'client', 'client:page', and 'client:app'.
139 */
140 @Parameter(defaultValue = "literal:session")
141 public abstract String getPersist();
142
143 /**
144 * The image to use to describe a column sorted in a descending order.
145 */
146 @Parameter
147 public abstract IAsset getArrowDownAsset();
148
149 /**
150 * The image to use to describe a column sorted in an ascending order.
151 */
152 @Parameter
153 public abstract IAsset getArrowUpAsset();
154
155 /**
156 * @return The table columns to be displayed.
157 */
158 public List<ITableColumn> getColumns()
159 {
160 return !isCacheDisabled() && getColumnsCache() != null ? getColumnsCache() : columns;
161 }
162
163 @Persist
164 public abstract List<ITableColumn> getColumnsCache();
165
166 public abstract void setColumnsCache(List<ITableColumn> columns);
167
168 /**
169 * This parameter provides a new set of columns to be displayed after the ones provided by the IClassDescriptor or
170 * EditProperties parameters. The parameter works like the [<a href="http://tapestry.apache.org/tapestry4.1/tapestry-contrib/componentreference/table.html">contrib
171 * table</a>] 'columns' parameter. The parameter must be an array, a list, or an Iterator of ITableColumn objects, an
172 * ITableColumnModel, or a String describing the columns (see documentation).
173 */
174 @Parameter
175 public abstract Object getExtraColumns();
176
177 public abstract void setExtraColumns(Object o);
178
179 @InjectObject("service:tapestry.ognl.ExpressionEvaluator")
180 public abstract ExpressionEvaluator getEvaluator();
181
182 @Override
183 protected void prepareForRender(IRequestCycle cycle)
184 {
185 /**
186 * If getColumnsCache() == null means that pageBeginRender wasn't fired because the component is inside a Block
187 * in another page.
188 */
189 if (getColumnsCache() == null || isCacheDisabled())
190 {
191 columns = createColumns();
192 }
193 super.prepareForRender(cycle);
194 }
195
196 public void pageBeginRender(PageEvent event)
197 {
198 if (!event.getRequestCycle().isRewinding())
199 {
200 setColumnsCache(createColumns());
201 }
202 }
203
204 /**
205 * It creates a {@link ITableColumn} list out of the IClassDescriptor metadata. It's meant to be used as a {@link
206 * ITableColumnModel} by the inner table.
207 *
208 * @return A {@link ITableColumn} list
209 */
210 private List<ITableColumn> createColumns()
211 {
212 List<ITableColumn> columns = new ArrayList<ITableColumn>();
213 if (getClassDescriptor() != null)
214 {
215 LOG.debug("Creating Columns for " + getClassDescriptor().getPluralDisplayName());
216 for (IPropertyDescriptor descriptor : getPropertyDescriptors())
217 {
218 TrailsTableColumn tableColumn = new TrailsTableColumn(descriptor, getEvaluator());
219 tableColumn.loadSettings(getContainer());
220 columns.add(tableColumn);
221 Block block = (Block) getContainer().getComponents().get(
222 descriptor.getName() + AbstractTableColumn.VALUE_RENDERER_BLOCK_SUFFIX);
223 if (block == null)
224 {
225 if (getLinkProperty().equals(descriptor.getName())
226 && (getClassDescriptor().isAllowSave() || getClassDescriptor().isAllowRemove()))
227 {
228 /**
229 * Add a link for the edit page following these rules:
230 * a) Use the identifier descriptor if is displayable (summary=true ).
231 * b) Use the first displayable property if the identifier property is not displayable
232 * (summary=false)
233 *
234 */
235 Block linkBlock = (Block) getContainer().getComponents().get(LINK_COLUMN);
236 tableColumn.setValueRendererSource(new BlockTableRendererSource(
237 linkBlock != null ? linkBlock : (Block) getComponent(LINK_COLUMN)));
238 } else
239 {
240 alterTableColumn(descriptor, tableColumn);
241 }
242 }
243 }
244 if (getExtraColumns() != null)
245 {
246 addAll(columns, getTableColumnModel().getColumns());
247 }
248 } else
249 {
250 LOG.warn("NULL ClassDescriptor");
251 }
252 return columns;
253 }
254
255 /**
256 * Hook method to allow subclasses to modify the tableColumn,
257 *
258 * @param descriptor
259 * @param tableColumn
260 */
261 protected void alterTableColumn(IPropertyDescriptor descriptor, TrailsTableColumn tableColumn)
262 {
263
264 }
265
266 protected boolean shouldDisplay(IPropertyDescriptor descriptor)
267 {
268 return !descriptor.isHidden() && descriptor.isSummary() &&
269 ((descriptor.isCollection() && isShowCollections()) || (!descriptor.isCollection()));
270 }
271
272 /**
273 * @return
274 */
275 public String getIdentifierProperty()
276 {
277 return this.getIdentifierPropertyDescriptor().getName();
278
279 }
280
281 /**
282 * Returns the name of the property to be used as link to the editor. If the default Id property is not displayable
283 * then return the name of the first displayable property.
284 *
285 * @return
286 */
287 public String getLinkProperty()
288 {
289 IPropertyDescriptor propertyDescriptor = getIdentifierPropertyDescriptor();
290 if (!shouldDisplay(propertyDescriptor))
291 propertyDescriptor = getFirstDisplayableProperty();
292
293 return propertyDescriptor.getName();
294 }
295
296 /**
297 * Returns the first displayable property.
298 *
299 * @return
300 */
301 protected IPropertyDescriptor getFirstDisplayableProperty()
302 {
303 for (IPropertyDescriptor descriptor : getPropertyDescriptors())
304 {
305 if (shouldDisplay(descriptor))
306 {
307 return descriptor;
308
309 }
310 }
311
312 return null; // If we get here, that means we have no displayable descriptors
313 // TODO check if we should throw an exception instead
314 }
315
316 /**
317 * @return
318 */
319 protected IPropertyDescriptor getIdentifierPropertyDescriptor()
320 {
321 return (IPropertyDescriptor) getClassDescriptor().getIdentifierDescriptor();
322
323 }
324
325 /**
326 * It provides the source parameter to the inner [<a href="http://tapestry.apache.org/tapestry4.1/tapestry-contrib/componentreference/table.html">table
327 * </a>]
328 *
329 * @return The data to be displayed by the inner table.
330 */
331 public Object getSource()
332 {
333 return getInstances();
334 }
335
336 /**
337 * Returns the table column model as specified by the 'columns' binding. If the value of the 'columns' binding is of a
338 * type different than ITableColumnModel, this method makes the appropriate conversion.
339 *
340 * @return The table column model as specified by the 'columns' binding
341 */
342 protected ITableColumnModel getTableColumnModel()
343 {
344 Object objColumns = getExtraColumns();
345
346 if (objColumns == null) return null;
347
348 if (objColumns instanceof ITableColumnModel)
349 {
350 return (ITableColumnModel) objColumns;
351 }
352
353 if (objColumns instanceof Iterator)
354 {
355 // convert to List
356 Iterator objColumnsIterator = (Iterator) objColumns;
357 List arrColumnsList = new ArrayList();
358 addAll(arrColumnsList, objColumnsIterator);
359 objColumns = arrColumnsList;
360 }
361
362 if (objColumns instanceof List)
363 {
364 // validate that the list contains only ITableColumn instances
365 List arrColumnsList = (List) objColumns;
366 int nColumnsNumber = arrColumnsList.size();
367 for (int i = 0; i < nColumnsNumber; i++)
368 {
369 if (!(arrColumnsList.get(i) instanceof ITableColumn))
370 throw new ApplicationRuntimeException(columnsOnlyPlease(this));
371 }
372 // objColumns = arrColumnsList.toArray(new
373 // ITableColumn[nColumnsNumber]);
374 return new SimpleTableColumnModel(arrColumnsList);
375 }
376
377 if (objColumns instanceof ITableColumn[])
378 {
379 return new SimpleTableColumnModel(
380 (ITableColumn[]) objColumns);
381 }
382
383 if (objColumns instanceof String)
384 {
385 String strColumns = (String) objColumns;
386 if (getBinding("extraColumns").isInvariant())
387 {
388 // if the binding is invariant, create the columns only once
389 if (m_objColumnModel == null)
390 m_objColumnModel = generateTableColumnModel(strColumns);
391 return m_objColumnModel;
392 }
393
394 // if the binding is not invariant, create them every time
395 return generateTableColumnModel(strColumns);
396 }
397 throw new ApplicationRuntimeException(invalidTableColumns(this, objColumns));
398 }
399
400 /**
401 * Generate a table column model out of the description string provided. Entries in the description string are
402 * separated by commas. Each column entry is of the format name, name:expression, or name:displayName:expression. An
403 * entry prefixed with ! represents a non-sortable column. If the whole description string is prefixed with *, it
404 * represents columns to be included in a Form.
405 *
406 * @param strDesc the description of the column model to be generated
407 * @return a table column model based on the provided description
408 */
409 protected ITableColumnModel generateTableColumnModel(String strDesc)
410 {
411 return getTableViewComponent().getModelSource().generateTableColumnModel(
412 getTableViewComponent().getColumnSource(), strDesc, getTableViewComponent(), getContainer());
413 }
414
415 private void addAll(List arrColumnsList, Iterator objColumnsIterator)
416 {
417 while (objColumnsIterator.hasNext())
418 arrColumnsList.add(objColumnsIterator.next());
419 }
420
421 protected TableView getTableViewComponent()
422 {
423 return (TableView) getComponent("table").getComponent("tableView");
424 }
425
426 private static final MessageFormatter _formatter = new MessageFormatter(TableMessages.class);
427
428 static String columnsOnlyPlease(IComponent component)
429 {
430 return _formatter.format("columns-only-please", component.getExtendedId());
431 }
432
433 static String invalidTableColumns(IComponent component, Object columnSource)
434 {
435 return _formatter.format("invalid-table-column", component.getExtendedId(),
436 ClassFabUtils.getJavaClassName(columnSource.getClass()));
437 }
438
439 /*
440 // there are still issues with inherited binding.
441 @Component(id = "table", type = "contrib:Table", inheritInformalParameters = true,
442 bindings = {"row=object",
443 "rowsClass=rowsClass",
444 "column=column",
445 "columnsClass=columnsClass",
446 "index=index",
447 "source=source",
448 "columns=columns",
449 "pageSize=pageSize",
450 "initialSortColumn=initialSortColumn",
451 "initialSortOrder=initialSortOrder",
452 "persist=persist"})
453 public abstract Table getTable();
454 */
455
456
457 }