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    }