diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleItemResolver.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleItemResolver.java.todo similarity index 100% rename from ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleItemResolver.java.off rename to ccm-cms/src/main/java/com/arsdigita/cms/dispatcher/SimpleItemResolver.java.todo diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionContextBar.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionContextBar.java similarity index 100% rename from ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionContextBar.java.off rename to ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionContextBar.java diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionPage.java.off b/ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionPage.java similarity index 100% rename from ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionPage.java.off rename to ccm-cms/src/main/java/com/arsdigita/cms/ui/ContentSectionPage.java diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguages.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguages.java.todo index 7a3d21aec..2052edb7e 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguages.java.todo +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguages.java.todo @@ -48,7 +48,6 @@ import com.arsdigita.web.RedirectSignal; import com.arsdigita.web.URL; import org.libreccm.workflow.Workflow; import org.libreccm.workflow.WorkflowTemplate; -import org.apache.log4j.Logger; import java.util.Collection; import java.util.Iterator; @@ -71,8 +70,8 @@ public class ItemLanguages extends LayoutPanel { /** * Constructs a new ItemLanguages. * - * @param selectionModel the {@link ItemSelectionModel} which will supply the current - * item + * @param selectionModel the {@link ItemSelectionModel} which will supply + * the current item */ public ItemLanguages(final ItemSelectionModel selectionModel) { this.selectionModel = selectionModel; @@ -91,9 +90,11 @@ public class ItemLanguages extends LayoutPanel { form.setRedirecting(true); languageWidget = new LanguageWidget(ContentItem.LANGUAGE) { + protected void setupOptions() { // Don't do anything. } + }; try { @@ -119,18 +120,20 @@ public class ItemLanguages extends LayoutPanel { final PageState state = e.getPageState(); final OptionGroup optionGroup = (OptionGroup) e.getTarget(); final ContentPage item = (ContentPage) selectionModel. - getSelectedItem(state); + getSelectedItem(state); final Collection languages = LanguageUtil.convertToG11N( - LanguageUtil.getCreatableLanguages(item)); + LanguageUtil.getCreatableLanguages(item)); for (Iterator iter = languages.iterator(); iter.hasNext();) { final Pair pair = (Pair) iter.next(); final String langCode = (String) pair.getKey(); final GlobalizedMessage langName - = (GlobalizedMessage) pair.getValue(); + = (GlobalizedMessage) pair + .getValue(); optionGroup.addOption(new Option(langCode, new Label(langName))); } } + } /** @@ -139,10 +142,11 @@ public class ItemLanguages extends LayoutPanel { private class ProcessListener implements FormProcessListener { public final void process(final FormSectionEvent e) - throws FormProcessException { + throws FormProcessException { PageState state = e.getPageState(); String lang = (String) languageWidget.getValue(state); - ContentPage item = (ContentPage) selectionModel.getSelectedItem(state); + ContentPage item = (ContentPage) selectionModel.getSelectedItem( + state); ContentBundle bundle = item.getContentBundle(); String name = bundle.getName(); @@ -159,8 +163,8 @@ public class ItemLanguages extends LayoutPanel { // Apply default workflow WorkflowTemplate template - = ContentTypeWorkflowTemplate - .getWorkflowTemplate(section, type); + = ContentTypeWorkflowTemplate + .getWorkflowTemplate(section, type); if (template != null) { Workflow w = template.instantiateNewWorkflow(); w.setObjectID(item.getID()); @@ -172,8 +176,8 @@ public class ItemLanguages extends LayoutPanel { // redirect to ContentItemPage.AUTHORING_TAB of the new instance final String target = URL.getDispatcherPath() + ContentItemPage. - getItemURL(item, - ContentItemPage.AUTHORING_TAB); + getItemURL(item, + ContentItemPage.AUTHORING_TAB); throw new RedirectSignal(target, true); } else if (changeSubmit.isSelected(state)) { @@ -191,6 +195,7 @@ public class ItemLanguages extends LayoutPanel { } } } + } protected static final GlobalizedMessage gz(final String key) { @@ -200,4 +205,5 @@ public class ItemLanguages extends LayoutPanel { protected static final String lz(final String key) { return (String) gz(key).localize(); } + } diff --git a/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguagesTable.java.todo b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguagesTable.java.todo index 0aa27f364..3f8007e09 100755 --- a/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguagesTable.java.todo +++ b/ccm-cms/src/main/java/com/arsdigita/cms/ui/item/ItemLanguagesTable.java.todo @@ -18,7 +18,6 @@ */ package com.arsdigita.cms.ui.item; -import com.arsdigita.bebop.*; import com.arsdigita.bebop.event.TableActionAdapter; import com.arsdigita.bebop.event.TableActionEvent; import com.arsdigita.bebop.table.TableCellRenderer; diff --git a/ccm-core/src/main/java/com/arsdigita/bebop/event/EventListenerList.java b/ccm-core/src/main/java/com/arsdigita/bebop/event/EventListenerList.java index 2a1e756f7..5806849d5 100755 --- a/ccm-core/src/main/java/com/arsdigita/bebop/event/EventListenerList.java +++ b/ccm-core/src/main/java/com/arsdigita/bebop/event/EventListenerList.java @@ -24,7 +24,7 @@ import java.util.NoSuchElementException; /** * Convenience extensions to {@link javax.swing.event.EventListenerList * Swing's EventListenerList}. - * @version $Id$ + * */ public class EventListenerList extends javax.swing.event.EventListenerList { diff --git a/ccm-core/src/main/java/com/arsdigita/toolbox/ui/AbstractDataQueryBuilder.java.todo b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/AbstractDataQueryBuilder.java.todo new file mode 100644 index 000000000..c2279b2aa --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/AbstractDataQueryBuilder.java.todo @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.toolbox.ui; + +import org.libreccm.cdi.utils.CdiUtil; + +import javax.persistence.criteria.CriteriaQuery; + +/** + * + * @author Jens Pelzetter + * @param Type of entity retrieved by the query. + */ +public abstract class AbstractDataQueryBuilder implements DataQueryBuilder { + + /** + * Retrieves the {@link DataTableController} can creates a new + * {@code CriteriaQuery} using + * {@link DataTableController#createQuery(java.lang.Class)}. + * + * @param entityClass + * + * @return A new {@link CriteriaQuery} which can be further customised. + */ + protected CriteriaQuery createBaseQuery(final Class entityClass) { + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final DataTableController controller = cdiUtil.findBean( + DataTableController.class); + + return controller.createQuery(entityClass); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataQueryBuilder.java.todo b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataQueryBuilder.java.todo new file mode 100755 index 000000000..98862fb13 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataQueryBuilder.java.todo @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.toolbox.ui; + +import com.arsdigita.util.Lockable; +import com.arsdigita.bebop.PageState; + +import java.util.List; + +import javax.persistence.criteria.CriteriaQuery; + +/** + * This class is used by the {@link DataTable} class in order to construct a + * query during each request. + * + * In its original implementation this class used a {@code DataQuery}. Because + * this class is not longer available in JPA we had to change this class to use + * the JPA CriteriaQuery which is a rough equivalent in JPA. Also this class now + * uses generics. + * + * To create a {@link CriteriaQuery} the method + * {@link DataTableController#createQuery(java.lang.Class)} can be used. After + * the query is build + * {@link DataTableController#executeQuery(javax.persistence.criteria.CriteriaQuery)} + * can be used to execute the query. An instance of {@link DataTableController} + * can be obtained using the {@link org.libreccm.cdi.utils.CdiUtil} class. + * + * The abstract {@link AbstractDataQueryBuilder} can be used to avoid this + * boilerplate code. It provides the method + * {@link AbstractDataQueryBuilder#createBaseQuery(java.lang.Class)} which + * retrieves the {@link DataTableController} can creates a new + * {@link CriteriaQuery}. + * + * @param the type of the entities the query should return. + */ +public interface DataQueryBuilder extends Lockable { + + /** + * Perform all necessary database operations and return a {@link List} for + * the {@link DataTable} to use. + * + * @param table the parent DataTable + * @param state the page state + * + * @return + */ + CriteriaQuery makeDataQuery(DataTable table, PageState state); + + /** + * @return the name of the column in the query that serves as the primary + * key for the items + */ + String getKeyColumn(); + +} diff --git a/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataTable.java.todo b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataTable.java.todo new file mode 100755 index 000000000..b36500029 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/DataTable.java.todo @@ -0,0 +1,978 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.toolbox.ui; + +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.PaginationModelBuilder; +import com.arsdigita.bebop.Paginator; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.SingleSelectionModel; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.ControlLink; +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.RequestLocal; +import com.arsdigita.bebop.event.EventListenerList; +import com.arsdigita.bebop.event.TableActionEvent; +import com.arsdigita.bebop.event.TableActionListener; +import com.arsdigita.bebop.table.DefaultTableCellRenderer; +import com.arsdigita.bebop.table.DefaultTableColumnModel; +import com.arsdigita.bebop.table.TableCellRenderer; +import com.arsdigita.bebop.table.TableColumn; +import com.arsdigita.bebop.table.TableColumnModel; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.table.TableModelBuilder; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.util.LockableImpl; +import com.arsdigita.util.Assert; + +import com.arsdigita.globalization.GlobalizedMessage; + +import com.arsdigita.xml.Element; + +import java.util.Iterator; +import java.util.Map; +import java.util.HashMap; + +import java.math.BigDecimal; + +import org.apache.log4j.Logger; +import org.libreccm.cdi.utils.CdiUtil; + +import java.util.ResourceBundle; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Root; + +/** + *

General

+ * + * Wraps any {@link List} in a sortable Bebop {@link Table}. + * + * The {@link List} is supplied by the {@link DataQueryBuilder} class, which the + * user must implement. The DataQueryBuilder may dynamically + * construct the query during each request, or return the same named query for + * each request; the DataTable does not care where the query comes + * from. + * + * This class may contain multiple {@link QueryListener}s. These listeners will + * be fired whenever the query is about to be performed, thus giving the user a + * chance to set additional filters on the query. + * + * Columns may be added to the DataTable by calling the + * {@link #addColumn} method. The user may choose to make the column sortable or + * non-sortable; sortable columns will appear as links on the Web page which, + * when clicked, will sort the table by the specified column. See the + * documentation on the various addColumn methods for more + * information. + * + * + * This class sets the XSL "class" attribute to "dataTable" + * + *

Pagination

+ * + * DataTable also implements {@link PaginationModelBuilder}. This + * means that it could serve as the model builder for any {@link Paginator} + * component. Pagination of the query occurs after all the sorting and query + * events have finished. Consider a query which returns the rows "A B C D E F". + * If the paginator displays 3 rows per page, page 1 will contain "A B C" and + * page 2 will contain "D E F". If the user then clicks on the header in the + * DataTable, causing the query to be sorted in reverse order, page + * 1 will contain "F E D" and page 2 will contain "C B A". In order for + * pagination to work properly, the following pattern must be used: + * + *

+ * DataTable table = new DataTable(...);
+ * Paginator paginator = new Paginator(table, ...);
+ * table.setPaginator(paginator);
+ * 
+ * + * The setPaginator call is required due to a design flaw in the + * Paginator component. + *

+ * + *

Globalization

+ * + * The DataTable will ordinarily interpret the labels of its column + * headers as plain text, and spit them out on the screen verbatim. However, if + * setResouceBundle is called, DataTable will instead + * interpret the column header labels as keys into the specified resource + * bundle, thus attempting to globalize the column headers at runtime. + *

+ * + * @author Stanislav Freidin + * @author Jens Pelzetter + * @param Type of the entities in the table. + */ +public class DataTable extends Table implements PaginationModelBuilder { + + private DataQueryBuilder dataQueryBuilder; + private SingleSelectionModel orderModel; + private StringParameter dirParam; + private String resourceBundle; + private RequestLocal querySize; + private Paginator paginator; + + public static final String ORDER = "o"; + public static final String DIRECTION = "d"; + public static final String ASCENDING = "asc"; + public static final String DESCENDING = "desc"; + + private EventListenerList queryListeners; + + /** + * Construct a new DataTable. + * + * @param dataQueryBuilder the {@link DataQueryBuilder} that will be used + * for this browser + * @param orderModel the {@link SingleSelectionModel} that will be + * used to determine the column to order by + * @param resourceBundle the name of the resource bundle that will be used + * to globalise the column labels. If null, column + * labels will be printed verbatim to the screen. + */ + public DataTable(final DataQueryBuilder dataQueryBuilder, + final SingleSelectionModel orderModel, + final String resourceBundle) { + + super(new DataBuilderAdapter(), new DataTableColumnModel()); + this.dataQueryBuilder = dataQueryBuilder; + this.resourceBundle = resourceBundle; + + setOrderSelectionModel(orderModel); + addTableActionListener(new DataTableActionListener()); + queryListeners = new EventListenerList(); + + dirParam = new StringParameter(DIRECTION); + dirParam.setDefaultValue(ASCENDING); + + getHeader().setDefaultRenderer(new GlobalizedHeaderCellRenderer()); + + querySize = new RequestLocal(); + paginator = null; + + setClassAttr("dataTable"); + } + + /** + * Construct a new DataTable. + * + * @param dataQueryBuilder the {@link DataQueryBuilder} that will be used + * for this browser + * @param orderModel the {@link SingleSelectionModel} that will be + * used to determine the column to order by + */ + public DataTable(final DataQueryBuilder dataQueryBuilder, + final SingleSelectionModel orderModel) { + this(dataQueryBuilder, orderModel, null); + } + + /** + * Construct a new DataTable + * + * @param dataQueryBuilder the {@link DataQueryBuilder} that will be used + * for this browser + * + */ + public DataTable(final DataQueryBuilder dataQueryBuilder) { + this(dataQueryBuilder, + new ParameterSingleSelectionModel(new StringParameter(ORDER))); + } + + /** + * Register the ordering parameter + */ + @Override + public void register(final Page parent) { + super.register(parent); + parent.addComponentStateParam(this, + getOrderSelectionModel() + .getStateParameter()); + parent.addComponentStateParam(this, dirParam); + } + + /** + * Set the key of the default column which will be used to sort the entries + * + * @param attribute the default attribute to sort by + */ + public void setDefaultOrder(final String attribute) { + Assert.isUnlocked(this); + getOrderSelectionModel().getStateParameter() + .setDefaultValue(attribute); + } + + /** + * Get the key of the default column which will be used to sort the entries + * + * @return the default attribute to sort by, or null if no default has been + * set + */ + public String getDefaultOrder() { + return (String) getOrderSelectionModel().getStateParameter() + .getDefaultValue(); + } + + /** + * Add a column to this table. + * + * @param label The user-readable label for the column NOTE: depending + * on setResourceBundle() it is treated as plain text for + * output or key into bundle resulting in globalized + * Labels! + * @param attribute The name of the attribute in the DataQuery + * which will be used as the value for this column. + * @param isSortable true if it is possible to sort using this column, false + * otherwise + * @param renderer a {@link TableCellRenderer} that will be used to format + * the attribute as a string. + * + * @return the newly added column + */ + public TableColumn addColumn(final String label, + final String attribute, + final boolean isSortable, + final TableCellRenderer renderer) { + return addColumn(label, attribute, isSortable, renderer, null); + } + + /** + * Add a column to this table. + * + * @param label The user-readable label for the column NOTE: + * depending on setResourceBundle() it is treated as + * plain text for output or key into bundle resulting + * in globalised Labels! + * @param attribute The name of the attribute in the + * DataQuery which will be used as the + * value for this column. + * @param isSortable true if it is possible to sort using this column, + * false otherwise + * @param renderer a {@link TableCellRenderer} that will be used to + * format the attribute as a string. + * @param orderAttribute The name of the attribute which will be used as the + * column to order by. This key may be different from + * the attribute parameter. + * + * @return the newly added column + */ + public TableColumn addColumn(final String label, + final String attribute, + final boolean isSortable, + final TableCellRenderer renderer, + final String orderAttribute) { + DataTableColumnModel model = (DataTableColumnModel) getColumnModel(); + TableColumn column = new SortableTableColumn(model.size(), + label, + attribute, + isSortable, + renderer + ); + + model.add(column, orderAttribute); + + // Update the default sort order + if (isSortable && getDefaultOrder() == null) { + setDefaultOrder((orderAttribute == null) ? attribute + : orderAttribute); + } + + return column; + } + + /** + * Add a column to this table. + * + * @param label The user-readable label for the column NOTE: depending + * on setResourceBundle() it is treated as plain text for + * output or key into bundle resulting in globalized + * Labels! + * @param attribute The name of the attribute in the DataQuery + * which will be used as the value for this column. + * @param isSortable true if it is possible to sort using this column, false + * otherwise + * + * @return the newly added column + */ + public TableColumn addColumn(final String label, + final String attribute, + final boolean isSortable) { + return addColumn(label, + attribute, + isSortable, + new DefaultTableCellRenderer(false)); + } + + /** + * Add a column to this table. + * + * @param label The user-readable label for the column NOTE: depending + * on setResourceBundle() it is treated as plain text for + * output or key into bundle resulting in globalized + * Labels! + * @param attribute The name of the attribute in the DataQuery + * which will be used as the value for this column. + * + * @return the newly added column + */ + public TableColumn addColumn(final String label, final String attribute) { + return addColumn(label, attribute, false); + } + + /** + * Add a column to this table. The value for the column will not be supplied + * by the query; instead, it is the user's responsibility to supply the + * value through a custom {@link TableModel} or render it directly in the + * {@link TableCellRenderer}. Typically, this method will be used to add + * {@link ControlLink}s to the table. + * + * @param label The user-readable label for the column NOTE: depending on + * setResourceBundle() it is treated as plain text for + * output or key into bundle resulting in globalized Labels! + * @param renderer The cell renderer for the given column + * + * @return the newly added column + */ + public TableColumn addColumn(final String label, + final TableCellRenderer renderer) { + final TableColumnModel columnModel = getColumnModel(); + final TableColumn column = new TableColumn(columnModel.size(), label); + column.setCellRenderer(renderer); + column.setHeaderRenderer(new GlobalizedHeaderCellRenderer(false)); + columnModel.add(column); + return column; + } + + /** + * + * @return the {@link DataQueryBuilder} that creates a {@link DataQuery} for + * this table during each request + */ + public DataQueryBuilder getDataQueryBuilder() { + return dataQueryBuilder; + } + + /** + * @param builder the new {@link DataQueryBuilder} for this table + */ + public void setDataQueryBuilder(final DataQueryBuilder builder) { + Assert.isUnlocked(this); + dataQueryBuilder = builder; + } + + /** + * @return the {@link SingleSelectionModel} that will determine the order + */ + public SingleSelectionModel getOrderSelectionModel() { + return orderModel; + } + + /** + * Set the {@link SingleSelectionModel} that will determine the order for + * the items in the table. + * + * @param orderModel The new model + */ + public void setOrderSelectionModel( + final SingleSelectionModel orderModel) { + + Assert.isUnlocked(this); + this.orderModel = orderModel; + } + + /** + * Add a {@link QueryListener} to this table. The listener will be fired + * whenever the query is about to be performed. + * + * @param listener the new query listener + */ + public void addQueryListener(final QueryListener listener) { + Assert.isUnlocked(this); + queryListeners.add(QueryListener.class, listener); + } + + /** + * Remove a {@link QueryListener} from this table. + * + * @param listener the new query listener + */ + public void removeQueryListener(final QueryListener listener) { + Assert.isUnlocked(this); + queryListeners.remove(QueryListener.class, listener); + } + + /** + * Fire the query event listeners to indicate that a query is about to be + * performed + * + * @param state The page state + * @param query The {@link DataQuery} + */ + protected void fireQueryPending(final PageState state, + final CriteriaQuery query) { + final Iterator iterator = queryListeners + .getListenerIterator(QueryListener.class); + QueryEvent event = null; + + while (iterator.hasNext()) { + if (event == null) { + event = new QueryEvent<>(this, state, query); + } + iterator.next().queryPending(event); + } + } + + /** + * Set the column by which the table will be ordered + * + * @param state the page state + * @param attr the attribute by which the table will be sorted + */ + public void setOrder(final PageState state, final String attr) { + getOrderSelectionModel().setSelectedKey(state, attr); + } + + /** + * @param state the page state + * + * @return the column by which the table will be ordered + */ + public String getOrder(final PageState state) { + return getOrderSelectionModel().getSelectedKey(state); + } + + /** + * @param state the page state + * + * @return the order by which the currently selected column will be sorted; + * will be either ASCENDING or DESCENDING + */ + public String getOrderDirection(final PageState state) { + return (String) state.getValue(dirParam); + } + + /** + * Set the sort direction + * + * @param state the page state + * @param dir the direction in which the current column should be sorted; + * either ASCENDING or DESCENDING + */ + public void setOrderDirection(final PageState state, final String dir) { + Assert.isTrue(ASCENDING.equals(dir) || DESCENDING.equals(dir)); + state.setValue(dirParam, dir); + } + + /** + * Toggle the sort direction between ascending and descending + * + * @param state the page state + * + * @return the new order direction; will be either ASCENDING or DESCENDING + */ + public String toggleOrderDirection(final PageState state) { + String dir = getOrderDirection(state); + dir = (ASCENDING.equals(dir)) ? DESCENDING : ASCENDING; + setOrderDirection(state, dir); + return dir; + } + + /** + * Return the {@link DataQuery} that will be used during the current request + * + * @param state the page state for the current request + * + * @return the current DataQuery + */ + public CriteriaQuery getDataQuery(final PageState state) { + return ((DataQueryTableModel) getTableModel(state)).getDataQuery(); + } + + /** + * Paginate the query according to the paginator component. This method will + * be automatically called by the {@link Paginator} component to which this + * DataTable has been added as the model builder. + * + * @param paginator the parent Paginator + * @param state the current page state + * + * @return the total number of rows in the query + */ + @Override + public int getTotalSize(final Paginator paginator, final PageState state) { + final CriteriaQuery query = getDataQuery(state); + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final DataTableController controller = cdiUtil.findBean( + DataTableController.class); + + return controller.executeQuery(query).size(); + } + + /** + * Return the paginator component used by this table, or null if the table + * is not paginated. + * + * @return The paginator. + */ + public final Paginator getPaginator() { + return paginator; + } + + /** + * Set the paginator component used by this table, or null if the table + * should not be paginated. + * + * @param paginator The paginator to use. + */ + public final void setPaginator(final Paginator paginator) { + Assert.isUnlocked(this); + this.paginator = paginator; + } + + /** + * Return the RequestLocal used for storing the query size during the + * request + * + * @return The query size. + */ + protected final RequestLocal getQuerySizeLocal() { + return querySize; + } + + /** + * Lock this table + */ + @Override + public void lock() { + dataQueryBuilder.lock(); + super.lock(); + } + + // Export the current order + @Override + public void generateExtraXMLAttributes(final PageState state, + final Element element) { + String key = getOrder(state); + if (key != null) { + element.addAttribute("order", + Integer + .toString(getColumnModel().getIndex(key))); + } + String dir = getOrderDirection(state); + if (dir != null) { + element.addAttribute("direction", dir); + } + } + + /** + * Globalises the specified key. + * + * @param key The key of the message. + * + * @return The globalised message for the provided key. + */ + public GlobalizedMessage globalize(String key) { + return new GlobalizedMessage(key, resourceBundle); + } + + /** + * Return the resource bundle for globalisation, or null if no bundle was + * specified + * + * @return The fully qualified name of the {@link ResourceBundle} used by + * this {@code DataTable}. + */ + public String getResourceBundle() { + return resourceBundle; + } + + /** + * Set the resource bundle for globalisation, or null if no globalisation is + * needed. + * + * @param bundle The fully qualified name of the {@link ResourceBundle} to + * use. + */ + public void setResourceBundle(final String bundle) { + Assert.isUnlocked(this); + resourceBundle = bundle; + } + + /** + * A {@link TableColumn} that could potentially be sorted + */ + private static class SortableTableColumn extends TableColumn { + + private boolean sortable; + private SingleSelectionModel orderModel; + + /** + * Construct a new SortableTableColumn + * + * @param modelIndex the index of the column in the table model from + * which to retrieve values. + * @param value the value for the column header. + * @param key the key for the column header. + * @param isSortable whether the column is sortable or not + * @param renderer the renderer which will be used to render this + * column + */ + public SortableTableColumn(final int modelIndex, + final Object value, + final Object key, + final boolean isSortable, + final TableCellRenderer renderer + ) { + + super(modelIndex, value, key); + setSortable(isSortable); + setCellRenderer(renderer); + } + + /** + * Determine whether this column is sortable + * + * @param isSortable if true, the column will be sortable + */ + public void setSortable(final boolean isSortable) { + Assert.isUnlocked(this); + sortable = isSortable; + setHeaderRenderer(new GlobalizedHeaderCellRenderer(isSortable)); + } + + /** + * @return the {@link SingleSelectionModel} which is responsible for + * maintaining the sort order + */ + public SingleSelectionModel getOrderSelectionModel() { + return orderModel; + } + + /** + * @return true if this column is sortable, false otherwise + */ + public boolean isSortable() { + return sortable; + } + + } + + /** + * The action listener that will sort the {@link DataQuery} for this table + */ + private static class DataTableActionListener implements TableActionListener { + + @Override + public void cellSelected(final TableActionEvent event) { + } + + @Override + public void headSelected(final TableActionEvent event) { + final PageState state = event.getPageState(); + final DataTable table = (DataTable) event.getSource(); + + final int index = event.getColumn(); + final SortableTableColumn column = (SortableTableColumn) table + .getColumnModel().get(index); + + if (column != null) { + if (column.isSortable()) { + final DataTableColumnModel model + = (DataTableColumnModel) table + .getColumnModel(); + final String oldOrder = table.getOrder(state); + String newOrder = model.getColumnKey(column); + if (newOrder == null) { + newOrder = (String) column.getHeaderKey(); + } + if (oldOrder != null && oldOrder.equals(newOrder)) { + // Reverse direction + table.toggleOrderDirection(state); + } else { + table.setOrder(state, newOrder); + table.setOrderDirection(state, DataTable.ASCENDING); + } + } + } + } + + } + + /** + * Adapts a {@link DataQueryBuilder} into a {@link TableModelBuilder}. Wraps + * the query returned by the builder in a DataQueryTableModel. + * + * @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel + */ + protected static class DataBuilderAdapter extends LockableImpl + implements TableModelBuilder { + + /** + * Create a new DataBuilderAdapter + */ + public DataBuilderAdapter() { + super(); + } + + /** + * Obtain a {@link DataQuery} and apply query events to it. The query + * events may add additional filters to the query, among other things. + * Finally, retrieve the current sort column from the parent + * {@link DataTable} and apply it to the query + * + * @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel + * @param table the parent {@link DataTable} + * @param state the current page state + * + * @return the final {@link DataQuery}, which is now ready to be wrapped + * in a DataQueryTableModel + */ + protected CriteriaQuery createQuery(final DataTable table, + final PageState state) { + final CriteriaQuery query = table.getDataQueryBuilder() + .makeDataQuery(table, state); + + final Root root = query.getR + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + final DataTableController controller = cdiUtil.findBean( + DataTableController.class); + final CriteriaBuilder criteriaBuilder = controller + .getCriteriaBuilder(); + + String order = table.getOrder(state); + if (order != null) { + String dir = table.getOrderDirection(state); + if (dir != null) { + order += " " + dir; + } + query.orderBy(criteriaBuilder.asc()) + query.addOrder(order); + } + table.fireQueryPending(state, query); + + // Paginate the query if neccessary + if (table.getPaginator() != null) { + // Force the size to calculate before the range is set + if (table.getQuerySizeLocal().get(state) == null) { + table.getQuerySizeLocal().set(state, new BigDecimal(query + .size())); + } + + // Paginate the query + query + .setRange(new Integer(table.getPaginator().getFirst(state)), + new Integer(table.getPaginator().getLast(state) + + 1)); + } + + return query; + } + + /** + * Construct a DataQueryTableModel by wrapping the query. + * + * @param table the parent {@link DataTable} + * @param s the current page state + * + * @see com.arsdigita.toolbox.ui.DataTable.DataQueryTableModel + * @return a DataQueryTableModel that will iterate through the query + */ + public TableModel makeModel(Table table, PageState s) { + DataTable t = (DataTable) table; + DataQuery d = createQuery(t, s); + + if (d == null) { + return Table.EMPTY_MODEL; + } + + return new DataQueryTableModel(t, d, + t.getDataQueryBuilder() + .getKeyColumn()); + } + + } + + /** + * A TableModel which gets its data from a DataQuery. This TableModel is + * used in the {@link DataTable.DataBuilderAdapter} to iterate through the + * query returned by the {@link DataQueryBuilder} and generate rows for it + * on the screen. + */ + protected static class DataQueryTableModel implements TableModel { + + private DataQuery m_data; + private DataTableColumnModel m_cols; + private String m_keyColumn; + + /** + * Create a new DataQueryTableModel + * + * @param t the {@link DataTable} which needs this model + * @param data the {@link DataQuery to be wrapped} + * @param keyColumn the name of the column in the query which represents + * the primary key + * + * @pre data != null + * @pre keyColumn != null + * @pre t != null + * @pre t.getColumnModel() != null + */ + public DataQueryTableModel(DataTable t, DataQuery data, String keyColumn) { + m_data = data; + m_cols = (DataTableColumnModel) t.getColumnModel(); + m_keyColumn = keyColumn; + } + + public int getColumnCount() { + return m_cols.size(); + } + + public boolean nextRow() { + return m_data.next(); + } + + public Object getElementAt(int columnIndex) { + String key = (String) m_cols.get(columnIndex).getHeaderKey(); + if (key != null) { + return m_data.get(key); + } else { + return null; + } + } + + public Object getKeyAt(int columnIndex) { + String key = m_cols.getKeyAt(columnIndex); + if (key != null) { + return m_data.get(key); + } else { + return m_data.get(m_keyColumn); + } + } + + /** + * Return the original DataQuery. The query's cursor will be "pointing" + * at the current row + */ + public DataQuery getDataQuery() { + return m_data; + } + + } + + /** + * Always renders the table header as a link. Thus, it becomes possible to + * sort up and down by clicking the table column over and over.

+ * Also, globalizes the column labels if possible. + */ + protected static class GlobalizedHeaderCellRenderer + implements TableCellRenderer { + + private boolean m_active; + + public GlobalizedHeaderCellRenderer(boolean isActive) { + m_active = isActive; + } + + public GlobalizedHeaderCellRenderer() { + this(true); + } + + public Component getComponent(Table table, PageState state, Object value, + boolean isSelected, Object key, + int row, int column) { + DataTable t = (DataTable) table; + Label label; + + if (value == null) { + label = new Label(" ", false); + } else { + String str = value.toString(); + if (t.getResourceBundle() != null) { + label = new Label(t.globalize(str)); + } else { + label = new Label(str); + } + } + + if (m_active) { + return new ControlLink(label); + } else { + return label; + } + } + + } + + /** + * A special column model that maintains an alternate key for each column. + * The alternate key will be passed to the query in the + * addOrder method, thus sorting the query by the given column + * - making it possible to make the sort key differ from the attribute key + * for any given column. + *

+ * Note that each column ALREADY has a unique key, which can be retrieved by + * calling TableColumn.getHeaderKey(). This key will be used to + * provide the value for the column. + */ + protected static class DataTableColumnModel extends DefaultTableColumnModel { + + // The column keys are a property of the table and column + // combination so we store the values in the HashMap + private Map m_columnKeys = new HashMap(); + + public void add(TableColumn column, String columnKey) { + super.add(column); + setColumnKey(column, columnKey); + } + + public void add(int columnIndex, TableColumn column, String columnKey) { + super.add(columnIndex, column); + setColumnKey(column, columnKey); + } + + public String getColumnKey(TableColumn column) { + return (String) m_columnKeys.get(column); + } + + public String getKeyAt(int columnIndex) { + return getColumnKey(get(columnIndex)); + } + + public void setColumnKey(TableColumn column, String columnKey) { + m_columnKeys.put(column, columnKey); + } + + public void setColumnKey(int columnIndex, String columnKey) { + setColumnKey(get(columnIndex), columnKey); + } + + @Override + public void remove(TableColumn column) { + super.remove(column); + m_columnKeys.remove(column); + } + + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryEvent.java b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryEvent.java new file mode 100755 index 000000000..fa5dae504 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.toolbox.ui; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.event.PageEvent; + +import javax.persistence.criteria.CriteriaQuery; + +/** + * This event is fired by the {@link DataTable} class + * + * @param Type of the entities retrieved by the query. + * @see QueryListener + */ +public class QueryEvent extends PageEvent { + + private static final long serialVersionUID = -3616223193853983580L; + + private CriteriaQuery query; + + public QueryEvent(final Component source, + final PageState state, + final CriteriaQuery query) { + super(source, state); + this.query = query; + } + + public CriteriaQuery getDataQuery() { + return query; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryListener.java b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryListener.java new file mode 100755 index 000000000..b523895d1 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/toolbox/ui/QueryListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.toolbox.ui; + +import java.util.EventListener; + +/** + * This listener can be added to {@link DataTable} in order to + * set filters on the query, or perform other customisations. + */ +public interface QueryListener extends EventListener { + + void queryPending(QueryEvent event); + +}