001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.components;
029
030import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_CACHE;
031import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_CATEGORIES;
032import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_COPYRIGHT;
033import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_CREATED;
034import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_EXPIRED;
035import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_MODIFIED;
036import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_RELEASED;
037import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT;
038import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INTERNAL_RESOURCE_TYPE;
039import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IN_NAVIGATION;
040import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IS_FOLDER;
041import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION;
042import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT;
043import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PERMISSIONS;
044import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PROJECT;
045import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED;
046import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_NAME;
047import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_TYPE;
048import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_SITE_PATH;
049import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_SIZE;
050import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE;
051import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE_NAME;
052import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TITLE;
053import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TYPE_ICON;
054import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_CREATED;
055import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_LOCKED;
056import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_MODIFIED;
057
058import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
059import org.opencms.db.CmsResourceState;
060import org.opencms.file.CmsObject;
061import org.opencms.file.CmsProperty;
062import org.opencms.file.CmsPropertyDefinition;
063import org.opencms.file.CmsResource;
064import org.opencms.file.types.I_CmsResourceType;
065import org.opencms.i18n.CmsEncoder;
066import org.opencms.main.CmsException;
067import org.opencms.main.CmsLog;
068import org.opencms.main.OpenCms;
069import org.opencms.relations.CmsCategory;
070import org.opencms.relations.CmsCategoryService;
071import org.opencms.ui.A_CmsUI;
072import org.opencms.ui.CmsCssIcon;
073import org.opencms.ui.CmsVaadinUtils;
074import org.opencms.ui.util.I_CmsItemSorter;
075import org.opencms.util.CmsColorContrastCalculator;
076import org.opencms.util.CmsPath;
077import org.opencms.util.CmsStringUtil;
078import org.opencms.util.CmsUUID;
079import org.opencms.workplace.CmsWorkplaceMessages;
080import org.opencms.workplace.explorer.CmsResourceUtil;
081
082import java.util.ArrayList;
083import java.util.Arrays;
084import java.util.Collection;
085import java.util.Collections;
086import java.util.Comparator;
087import java.util.HashMap;
088import java.util.LinkedHashSet;
089import java.util.List;
090import java.util.Locale;
091import java.util.Map;
092import java.util.Set;
093import java.util.stream.Collectors;
094
095import org.apache.commons.lang3.ClassUtils;
096import org.apache.commons.logging.Log;
097
098import com.google.common.base.Joiner;
099import com.google.common.collect.ComparisonChain;
100import com.google.common.collect.Lists;
101import com.google.common.collect.Sets;
102import com.vaadin.event.dd.DropHandler;
103import com.vaadin.ui.Component;
104import com.vaadin.ui.Composite;
105import com.vaadin.ui.CustomComponent;
106import com.vaadin.v7.data.Item;
107import com.vaadin.v7.data.Property;
108import com.vaadin.v7.data.util.IndexedContainer;
109import com.vaadin.v7.data.util.converter.Converter;
110import com.vaadin.v7.shared.ui.label.ContentMode;
111import com.vaadin.v7.ui.AbstractSelect.ItemDescriptionGenerator;
112import com.vaadin.v7.ui.Label;
113import com.vaadin.v7.ui.Table;
114import com.vaadin.v7.ui.Table.RowHeaderMode;
115import com.vaadin.v7.ui.Table.TableDragMode;
116
117/**
118 * Generic table for displaying lists of resources.<p>
119 */
120@SuppressWarnings("deprecation")
121public class CmsResourceTable extends CustomComponent {
122
123    /**
124     * Comparator used for sorting the categories column.
125     */
126    public static class CategoryComparator implements Comparator<String> {
127
128        /** The collator used. */
129        private final com.ibm.icu.text.Collator m_collator = com.ibm.icu.text.Collator.getInstance(
130            com.ibm.icu.util.ULocale.ROOT);
131
132        /**
133         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
134         */
135        @Override
136        public int compare(String c1, String c2) {
137
138            // We want empty strings to come last, but otherwise just use case insensitive string order
139            if ("".equals(c1) && "".equals(c2)) {
140                return 0;
141            }
142            if ("".equals(c1)) {
143                // "" "foo"
144                return 1;
145            }
146            if ("".equals(c2)) {
147                // "foo" ""
148                return -1;
149            }
150            return m_collator.compare(c1, c2);
151        }
152
153    }
154
155    /**
156     * Widget for displaying a resource's categories in a table column.
157     *
158     * <p>For user experience reasons, this widget only loads the category data when needed, which is either when it is attached,
159     * or when other operations (sorting, filtering) need it.
160     */
161    public static class CategoryLabel extends Composite implements Comparable<CategoryLabel> {
162
163        /**
164         * Holds the data to display for a single category.
165         */
166        class CategoryItem {
167
168            /** The background color. */
169            private String m_background;
170
171            /** The title to display. */
172            private String m_title;
173
174            /** The category path. */
175            private String m_categoryPath;
176
177            /**
178             * Creates a new instance.
179             *
180             * @param title the title
181             * @param background the background color
182             */
183            public CategoryItem(String categoryPath, String title, String background) {
184
185                super();
186                m_categoryPath = categoryPath;
187                m_title = title;
188                m_background = background;
189            }
190
191            /**
192             * Gets the background color.
193             *
194             * @return the background color
195             */
196            public String getBackground() {
197
198                return m_background;
199            }
200
201            public String getCategoryPath() {
202
203                return m_categoryPath;
204            }
205
206            /**
207             * Gets the title.
208             *
209             * @return the title
210             */
211            public String getTitle() {
212
213                return m_title;
214            }
215        }
216
217        /** Serial version id. */
218        private static final long serialVersionUID = 1L;
219
220        /** True if the widget has been initialized. */
221        private boolean m_initialized;
222
223        /** The label used to display the categories. */
224        private Label m_label = new Label();
225
226        /** The locale. */
227        private Locale m_locale;
228
229        /** The resource utility wrapper. */
230        private CmsResourceUtil m_resUtil;
231
232        /** The categories value as a string, for tooltips, sorting and filtering. */
233        private String m_value = "";
234
235        /**
236         * Creates a new instance.
237         *
238         * @param resUtil the resource utility wrapper
239         * @param locale the workplace locale
240         */
241        public CategoryLabel(CmsResourceUtil resUtil, Locale locale) {
242
243            m_locale = locale;
244            m_resUtil = resUtil;
245            setCompositionRoot(m_label);
246            addStyleName("o-category-label");
247        }
248
249        /**
250         * @see com.vaadin.ui.AbstractComponent#attach()
251         */
252        @Override
253        public void attach() {
254
255            // Attach is only called when the user scrolls near the row in which this widget is located.
256            init();
257            super.attach();
258        }
259
260        /**
261         * @see java.lang.Comparable#compareTo(java.lang.Object)
262         */
263        @Override
264        public int compareTo(CategoryLabel o) {
265
266            // getValue() calls init()
267            return CATEGORY_COMPARATOR.compare(getValue(), o.getValue());
268
269        }
270
271        /**
272         * Gets the categories as a string (for tooltips, sorting and filtering).
273         *
274         * @return the category values
275         */
276        public String getValue() {
277
278            init();
279            return m_value;
280        }
281
282        /**
283         * Needed for filtering.
284         *
285         * @see java.lang.Object#toString()
286         */
287        @Override
288        public String toString() {
289
290            // getValue() calls init
291            return getValue();
292        }
293
294        /**
295         * Initializes the category data and the actual widget, unless it has already been initialized.
296         */
297        protected synchronized void init() {
298
299            if (!m_initialized) {
300                // We definitely don't want to repeatedly try to initialize the widget, since performance is the whole point.
301                // Even failure should count as being initialized. So we might as well set m_initialized right here, at the start.
302                m_initialized = true;
303                try {
304                    CmsObject cms = m_resUtil.getCms();
305                    CmsCategoryService catService = CmsCategoryService.getInstance();
306                    List<CmsCategory> categories = catService.readResourceCategories(cms, m_resUtil.getResource());
307                    categories = catService.localizeCategories(cms, categories, m_locale);
308
309                    Map<CmsPath, CmsCategory> categoriesByPath = categories.stream().collect(
310                        Collectors.toMap(cat -> new CmsPath(cat.getPath()), cat -> cat, (a, b) -> b));
311                    Set<CmsPath> parents = categories.stream().map(
312                        cat -> CmsResource.getParentFolder(cat.getPath())).filter(path -> path != null).map(
313                            path -> new CmsPath(path)).collect(Collectors.toSet());
314
315                    boolean removeParents = OpenCms.getWorkplaceManager().isExplorerCategoriesLeavesOnly();
316                    boolean fullPath = OpenCms.getWorkplaceManager().isExplorerCategoriesWithPath();
317                    List<CmsCategory> categoriesToDisplay = new ArrayList<>(categories);
318                    if (removeParents) {
319                        categoriesToDisplay.removeIf(cat -> parents.contains(new CmsPath(cat.getPath())));
320                    }
321
322                    List<CategoryItem> items = categoriesToDisplay.stream().map(
323                        cat -> new CategoryItem(
324                            cat.getPath(),
325                            fullPath ? getCompositeCategoryTitle(categoriesByPath, cat) : cat.getTitle(),
326                            cat.getBackground())).collect(Collectors.toList());
327                    Comparator<CategoryItem> comparator = (
328                        a,
329                        b) -> ComparisonChain.start().compare(a.getCategoryPath(), b.getCategoryPath()).result();
330                    Collections.sort(items, comparator);
331                    // Comma-separated list of titles, for tooltip, sorting and filtering
332                    m_value = items.stream().map(item -> item.getTitle()).collect(Collectors.joining(", "));
333                    m_label.setDescription(m_value);
334
335                    // Assemble HTML based on the titles and fill the widget with it.
336                    String html = items.stream().flatMap(item -> {
337                        String colorStyle = "";
338                        String bg = item.getBackground();
339                        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(bg)) {
340                            bg = bg.trim();
341                            String fgSuffix = "";
342                            try {
343                                fgSuffix = " color: " + m_contrastCalculator.getForeground(bg) + " !important;";
344                            } catch (Exception e) {
345                                LOG.error(e.getLocalizedMessage(), e);
346                            }
347                            colorStyle = " style='background-color: " + bg + " !important; " + fgSuffix + "' ";
348                        }
349                        return Arrays.asList(
350                            "<div class='o-category-label-category' ",
351                            colorStyle,
352                            ">",
353                            CmsEncoder.escapeXml(item.getTitle()),
354                            "</div>").stream();
355                    }).collect(Collectors.joining(""));
356
357                    m_label.setContentMode(ContentMode.HTML);
358                    m_label.setValue(html);
359
360                } catch (Exception e) {
361                    LOG.error(e.getLocalizedMessage(), e);
362                }
363            }
364        }
365    }
366
367    /**
368     * Helper class for easily configuring a set of columns to display, together with their visibility / collapsed status.<p>
369     */
370    public class ColumnBuilder {
371
372        /** The column entries configured so far. */
373        private List<ColumnEntry> m_columnEntries = Lists.newArrayList();
374
375        /**
376         * Sets up the table and its container using the columns configured so far.<p>
377         */
378        public void buildColumns() {
379
380            Set<CmsResourceTableProperty> visible = new LinkedHashSet<CmsResourceTableProperty>();
381            Set<CmsResourceTableProperty> collapsed = new LinkedHashSet<CmsResourceTableProperty>();
382            for (ColumnEntry entry : m_columnEntries) {
383                CmsResourceTableProperty prop = entry.getColumn();
384                m_container.addContainerProperty(prop, prop.getColumnType(), prop.getDefaultValue());
385                if (entry.isCollapsed()) {
386                    collapsed.add(entry.getColumn());
387                }
388                if (entry.isVisible()) {
389                    visible.add(entry.getColumn());
390                }
391            }
392            m_fileTable.setVisibleColumns(visible.toArray(new Object[0]));
393            Object[] collapsedColumnsArray = collapsed.toArray(new Object[0]);
394            setCollapsedColumns(collapsedColumnsArray);
395            for (CmsResourceTableProperty visibleProp : visible) {
396                String headerKey = visibleProp.getHeaderKey();
397                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(headerKey)) {
398                    m_fileTable.setColumnHeader(visibleProp, CmsVaadinUtils.getMessageText(headerKey));
399                } else {
400                    m_fileTable.setColumnHeader(visibleProp, "");
401                }
402                m_fileTable.setColumnCollapsible(visibleProp, visibleProp.isCollapsible());
403                if (visibleProp.getColumnWidth() > 0) {
404                    m_fileTable.setColumnWidth(visibleProp, visibleProp.getColumnWidth());
405                }
406                if (visibleProp.getExpandRatio() > 0) {
407                    m_fileTable.setColumnExpandRatio(visibleProp, visibleProp.getExpandRatio());
408                }
409                if (visibleProp.getConverter() != null) {
410                    m_fileTable.setConverter(visibleProp, visibleProp.getConverter());
411                }
412            }
413        }
414
415        /**
416         * Adds a new column.<p>
417         *
418         * @param prop the column
419         *
420         * @return this object
421         */
422        public ColumnBuilder column(CmsResourceTableProperty prop) {
423
424            column(prop, 0);
425            return this;
426        }
427
428        /**
429         * Adds a new column.<p<
430         *
431         * @param prop the column
432         * @param flags the flags for the column
433         *
434         * @return this object
435         */
436        public ColumnBuilder column(CmsResourceTableProperty prop, int flags) {
437
438            ColumnEntry entry = new ColumnEntry();
439            entry.setColumn(prop);
440            entry.setFlags(flags);
441            m_columnEntries.add(entry);
442            return this;
443        }
444    }
445
446    /**
447     * Contains the data for the given column, along with some flags to control visibility/collapsed status.<p>
448     *
449     */
450    public static class ColumnEntry {
451
452        /** The column. */
453        private CmsResourceTableProperty m_column;
454
455        /** The flags. */
456        private int m_flags;
457
458        /**
459         * Returns the column.<p>
460         *
461         * @return the column
462         */
463        public CmsResourceTableProperty getColumn() {
464
465            return m_column;
466        }
467
468        /**
469         * Returns the collapsed.<p>
470         *
471         * @return the collapsed
472         */
473        public boolean isCollapsed() {
474
475            return (m_flags & COLLAPSED) != 0;
476        }
477
478        /**
479         * Returns the visible.<p>
480         *
481         * @return the visible
482         */
483        public boolean isVisible() {
484
485            return 0 == (m_flags & INVISIBLE);
486        }
487
488        /**
489         * Sets the column.<p>
490         *
491         * @param column the column to set
492         */
493        public void setColumn(CmsResourceTableProperty column) {
494
495            m_column = column;
496        }
497
498        /**
499         * Sets the flags.<p>
500         *
501         * @param flags the flags to set
502         */
503        public void setFlags(int flags) {
504
505            m_flags = flags;
506        }
507
508        @Override
509        public String toString() {
510
511            return "ColumnEntry[" + getColumn().getId() + "," + m_flags + "]";
512        }
513
514    }
515
516    /**
517     * Interfaces for getting notified of column visibility/sort setting changes.
518     */
519    public interface ColumnSettingChangeHandler {
520
521        /**
522         * Called when column visibility or sorting is changed by the user.
523         */
524        void onColumnSettingsChanged();
525    }
526
527    /**
528     * Default description generator for table entries.
529     */
530    public static class DefaultItemDescriptionGenerator implements ItemDescriptionGenerator {
531
532        /** Serial version id.*/
533        private static final long serialVersionUID = 1L;
534
535        /**
536         * @see com.vaadin.v7.ui.AbstractSelect.ItemDescriptionGenerator#generateDescription(com.vaadin.ui.Component, java.lang.Object, java.lang.Object)
537         */
538        @SuppressWarnings("synthetic-access")
539        public String generateDescription(Component source, Object itemId, Object propertyId) {
540
541            Table table = (Table)source;
542            try {
543                if ((propertyId != null) && (itemId != null)) {
544                    Property prop = table.getContainerDataSource().getItem(itemId).getItemProperty(propertyId);
545                    Converter<String, Object> converter = table.getConverter(propertyId);
546                    if (CmsResourceTableProperty.PROPERTY_RESOURCE_NAME == propertyId) {
547                        // when working with the explorer, tool tips constantly showing up when
548                        // hovering over the file name accidentally seems more annoying than useful
549                        return null;
550                    } else if ((converter != null) && String.class.equals(converter.getPresentationType())) {
551                        return converter.convertToPresentation(
552                            prop.getValue(),
553                            String.class,
554                            A_CmsUI.get().getLocale());
555                    } else if (String.class.equals(prop.getType()) || ClassUtils.isPrimitiveOrWrapper(prop.getType())) {
556                        Object value = prop.getValue();
557                        if (value != null) {
558                            return CmsEncoder.escapeXml("" + value);
559                        }
560                    }
561                }
562            } catch (Exception e) {
563                LOG.warn(e.getLocalizedMessage(), e);
564            }
565            return null;
566        }
567    }
568
569    /**
570     * Provides item property values for additional table columns.<p>
571     */
572    public static interface I_ResourcePropertyProvider {
573
574        /**
575         * Adds the property values to the given item.<p>
576         *
577         * @param resourceItem the resource item
578         * @param cms the cms context
579         * @param resource the resource
580         * @param locale  the workplace locale
581         */
582        void addItemProperties(Item resourceItem, CmsObject cms, CmsResource resource, Locale locale);
583    }
584
585    /**
586     * Extending the indexed container to make the number of un-filtered items available.<p>
587     */
588    protected static class ItemContainer extends IndexedContainer {
589
590        /** The serial version id. */
591        private static final long serialVersionUID = -2033722658471550506L;
592
593        /**
594         * @see com.vaadin.v7.data.util.IndexedContainer#getSortableContainerPropertyIds()
595         */
596        @Override
597        public Collection<?> getSortableContainerPropertyIds() {
598
599            if (getItemSorter() instanceof I_CmsItemSorter) {
600                return ((I_CmsItemSorter)getItemSorter()).getSortableContainerPropertyIds(this);
601            } else {
602                return super.getSortableContainerPropertyIds();
603            }
604        }
605
606        /**
607         * Returns the number of items in the container, not considering any filters.<p>
608         *
609         * @return the number of items
610         */
611        protected int getItemCount() {
612
613            return getAllItemIds().size();
614        }
615    }
616
617    /** Flag to mark columns as initially collapsed.*/
618    public static final int COLLAPSED = 1;
619
620    /** Flag to mark columns as invisible. */
621    public static final int INVISIBLE = 2;
622
623    /** Static instance of the comparator used for categories. */
624    private static final CategoryComparator CATEGORY_COMPARATOR = new CategoryComparator();
625
626    /** The logger instance for this class. */
627    private static final Log LOG = CmsLog.getLog(CmsResourceTable.class);
628
629    /** Used for calculating foreground colors for categories. */
630    private static final CmsColorContrastCalculator m_contrastCalculator = new CmsColorContrastCalculator();
631
632    /** Serial version id. */
633    private static final long serialVersionUID = 1L;
634
635    /** The resource data container. */
636    protected ItemContainer m_container = new ItemContainer();
637
638    /** The table used to display the resource data. */
639    protected Table m_fileTable = new Table() {
640
641        /** If greater than 0, we are in a changeVariables call - which means that column changes probably are the direct result of user interaction with the table rather than automatic/programmatic changes. */
642        private long m_changingVariables;
643
644        /**
645         * @see com.vaadin.v7.ui.Table#changeVariables(java.lang.Object, java.util.Map)
646         */
647        public void changeVariables(Object source, java.util.Map<String, Object> variables) {
648
649            m_changingVariables += 1;
650            try {
651                super.changeVariables(source, variables);
652            } finally {
653                m_changingVariables -= 1;
654            }
655        }
656
657        /**
658         * @see com.vaadin.v7.ui.Table#setColumnCollapsed(java.lang.Object, boolean)
659         */
660        public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException {
661
662            super.setColumnCollapsed(propertyId, collapsed);
663            if (m_changingVariables > 0) {
664                if (m_columnSettingChangeHandler != null) {
665                    m_columnSettingChangeHandler.onColumnSettingsChanged();
666                }
667            }
668
669        };
670
671        /**
672         * @see com.vaadin.v7.ui.Table#sort(java.lang.Object[], boolean[])
673         */
674        public void sort(Object[] propertyId, boolean[] ascending) throws UnsupportedOperationException {
675
676            super.sort(propertyId, ascending);
677            if (m_changingVariables > 0) {
678                if (m_columnSettingChangeHandler != null) {
679                    m_columnSettingChangeHandler.onColumnSettingsChanged();
680                }
681            }
682        }
683    };
684
685    /** Property provider for additional columns. */
686    protected List<I_ResourcePropertyProvider> m_propertyProviders;
687
688    /** Handles column setting changes. */
689    private ColumnSettingChangeHandler m_columnSettingChangeHandler;
690
691    /**
692     * Creates a new instance.<p>
693     *
694     * This constructor does *not* set up the columns of the table; use the ColumnBuilder inner class for this.
695     */
696    public CmsResourceTable() {
697
698        m_propertyProviders = new ArrayList<I_ResourcePropertyProvider>();
699        m_fileTable.setContainerDataSource(m_container);
700        setCompositionRoot(m_fileTable);
701        m_fileTable.setRowHeaderMode(RowHeaderMode.HIDDEN);
702        m_fileTable.setItemDescriptionGenerator(new DefaultItemDescriptionGenerator());
703    }
704
705    /**
706     * Static helper method to initialize the 'standard' properties of a data item from a given resource.<p>
707     * @param resourceItem the resource item to fill
708     * @param cms the CMS context
709     * @param resource the resource
710     * @param locale the locale
711     */
712    public static void fillItemDefault(Item resourceItem, CmsObject cms, CmsResource resource, Locale locale) {
713
714        if (resource == null) {
715            LOG.error("Error rendering item for 'null' resource");
716            return;
717        }
718
719        if (resourceItem == null) {
720            LOG.error("Error rendering 'null' item for resource " + resource.getRootPath());
721            return;
722        }
723        if (cms == null) {
724            cms = A_CmsUI.getCmsObject();
725            LOG.warn("CmsObject was 'null', using thread local CmsObject");
726        }
727        CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource);
728        Map<String, CmsProperty> resourceProps = null;
729        try {
730            List<CmsProperty> props = cms.readPropertyObjects(resource, false);
731            resourceProps = new HashMap<String, CmsProperty>();
732            for (CmsProperty prop : props) {
733                resourceProps.put(prop.getName(), prop);
734            }
735        } catch (CmsException e1) {
736            LOG.debug("Unable to read properties for resource '" + resource.getRootPath() + "'.", e1);
737        }
738        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
739        if (resourceItem.getItemProperty(PROPERTY_TYPE_ICON) != null) {
740            resourceItem.getItemProperty(PROPERTY_TYPE_ICON).setValue(
741                new CmsResourceIcon(resUtil, resource.getState(), true));
742        }
743
744        if (resourceItem.getItemProperty(PROPERTY_PROJECT) != null) {
745            Label projectFlag = null;
746            switch (resUtil.getProjectState().getMode()) {
747                case 1:
748                    projectFlag = new Label(
749                        new CmsCssIcon(OpenCmsTheme.ICON_PROJECT_CURRENT).getHtml(resUtil.getLockedInProjectName()),
750                        ContentMode.HTML);
751                    break;
752                case 2:
753                    projectFlag = new Label(
754                        new CmsCssIcon(OpenCmsTheme.ICON_PROJECT_OTHER).getHtml(resUtil.getLockedInProjectName()),
755                        ContentMode.HTML);
756                    break;
757                case 5:
758                    projectFlag = new Label(
759                        new CmsCssIcon(OpenCmsTheme.ICON_PUBLISH).getHtml(resUtil.getLockedInProjectName()),
760                        ContentMode.HTML);
761                    break;
762                default:
763            }
764            resourceItem.getItemProperty(PROPERTY_PROJECT).setValue(projectFlag);
765        }
766
767        if (resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT) != null) {
768            resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT).setValue(Boolean.valueOf(resUtil.isInsideProject()));
769        }
770
771        if (resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED) != null) {
772            resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED).setValue(
773                Boolean.valueOf(resUtil.isReleasedAndNotExpired()));
774        }
775
776        if (resourceItem.getItemProperty(PROPERTY_RESOURCE_NAME) != null) {
777            resourceItem.getItemProperty(PROPERTY_RESOURCE_NAME).setValue(resource.getName());
778        }
779
780        if (resourceItem.getItemProperty(PROPERTY_SITE_PATH) != null) {
781            resourceItem.getItemProperty(PROPERTY_SITE_PATH).setValue(cms.getSitePath(resource));
782        }
783
784        if ((resourceItem.getItemProperty(PROPERTY_TITLE) != null) && (resourceProps != null)) {
785            resourceItem.getItemProperty(PROPERTY_TITLE).setValue(
786                resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_TITLE)
787                ? resourceProps.get(CmsPropertyDefinition.PROPERTY_TITLE).getValue()
788                : "");
789        }
790        boolean inNavigation = false;
791        if ((resourceItem.getItemProperty(PROPERTY_NAVIGATION_TEXT) != null) && (resourceProps != null)) {
792            resourceItem.getItemProperty(PROPERTY_NAVIGATION_TEXT).setValue(
793                resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_NAVTEXT)
794                ? resourceProps.get(CmsPropertyDefinition.PROPERTY_NAVTEXT).getValue()
795                : "");
796            inNavigation = resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_NAVTEXT);
797        }
798
799        if ((resourceItem.getItemProperty(PROPERTY_NAVIGATION_POSITION) != null) && (resourceProps != null)) {
800            try {
801                Float navPos = resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_NAVPOS)
802                ? Float.valueOf(resourceProps.get(CmsPropertyDefinition.PROPERTY_NAVPOS).getValue())
803                : (inNavigation ? Float.valueOf(Float.MAX_VALUE) : null);
804                resourceItem.getItemProperty(PROPERTY_NAVIGATION_POSITION).setValue(navPos);
805                inNavigation = navPos != null;
806            } catch (Exception e) {
807                LOG.debug("Error evaluating navPos property", e);
808            }
809        }
810
811        if (resourceItem.getItemProperty(PROPERTY_IN_NAVIGATION) != null) {
812            if (inNavigation
813                && (resourceProps != null)
814                && resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_NAVINFO)
815                && CmsClientSitemapEntry.HIDDEN_NAVIGATION_ENTRY.equals(
816                    resourceProps.get(CmsPropertyDefinition.PROPERTY_NAVINFO).getValue())) {
817                inNavigation = false;
818            }
819            resourceItem.getItemProperty(PROPERTY_IN_NAVIGATION).setValue(Boolean.valueOf(inNavigation));
820        }
821
822        if ((resourceItem.getItemProperty(PROPERTY_COPYRIGHT) != null) && (resourceProps != null)) {
823            resourceItem.getItemProperty(PROPERTY_COPYRIGHT).setValue(
824                resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_COPYRIGHT)
825                ? resourceProps.get(CmsPropertyDefinition.PROPERTY_COPYRIGHT).getValue()
826                : "");
827        }
828
829        if ((resourceItem.getItemProperty(PROPERTY_CACHE) != null) && (resourceProps != null)) {
830            resourceItem.getItemProperty(PROPERTY_CACHE).setValue(
831                resourceProps.containsKey(CmsPropertyDefinition.PROPERTY_CACHE)
832                ? resourceProps.get(CmsPropertyDefinition.PROPERTY_CACHE).getValue()
833                : "");
834        }
835
836        if (resourceItem.getItemProperty(PROPERTY_RESOURCE_TYPE) != null) {
837            resourceItem.getItemProperty(PROPERTY_RESOURCE_TYPE).setValue(
838                CmsWorkplaceMessages.getResourceTypeName(locale, type.getTypeName()));
839        }
840
841        if (resourceItem.getItemProperty(PROPERTY_INTERNAL_RESOURCE_TYPE) != null) {
842            resourceItem.getItemProperty(PROPERTY_INTERNAL_RESOURCE_TYPE).setValue(type.getTypeName());
843        }
844
845        if (resourceItem.getItemProperty(PROPERTY_IS_FOLDER) != null) {
846            resourceItem.getItemProperty(PROPERTY_IS_FOLDER).setValue(Boolean.valueOf(resource.isFolder()));
847        }
848
849        if (resourceItem.getItemProperty(PROPERTY_SIZE) != null) {
850            if (resource.isFile()) {
851                resourceItem.getItemProperty(PROPERTY_SIZE).setValue(Integer.valueOf(resource.getLength()));
852            }
853        }
854
855        if (resourceItem.getItemProperty(PROPERTY_PERMISSIONS) != null) {
856            resourceItem.getItemProperty(PROPERTY_PERMISSIONS).setValue(resUtil.getPermissionString());
857        }
858
859        if (resourceItem.getItemProperty(PROPERTY_DATE_MODIFIED) != null) {
860            resourceItem.getItemProperty(PROPERTY_DATE_MODIFIED).setValue(Long.valueOf(resource.getDateLastModified()));
861        }
862
863        if (resourceItem.getItemProperty(PROPERTY_USER_MODIFIED) != null) {
864            resourceItem.getItemProperty(PROPERTY_USER_MODIFIED).setValue(resUtil.getUserLastModified());
865        }
866
867        if (resourceItem.getItemProperty(PROPERTY_DATE_CREATED) != null) {
868            resourceItem.getItemProperty(PROPERTY_DATE_CREATED).setValue(Long.valueOf(resource.getDateCreated()));
869        }
870
871        if (resourceItem.getItemProperty(PROPERTY_USER_CREATED) != null) {
872            resourceItem.getItemProperty(PROPERTY_USER_CREATED).setValue(resUtil.getUserCreated());
873        }
874
875        if (resourceItem.getItemProperty(PROPERTY_DATE_RELEASED) != null) {
876            long release = resource.getDateReleased();
877            if (release != CmsResource.DATE_RELEASED_DEFAULT) {
878                resourceItem.getItemProperty(PROPERTY_DATE_RELEASED).setValue(Long.valueOf(release));
879            } else {
880                resourceItem.getItemProperty(PROPERTY_DATE_RELEASED).setValue(null);
881            }
882        }
883
884        if (resourceItem.getItemProperty(PROPERTY_DATE_EXPIRED) != null) {
885            long expire = resource.getDateExpired();
886            if (expire != CmsResource.DATE_EXPIRED_DEFAULT) {
887                resourceItem.getItemProperty(PROPERTY_DATE_EXPIRED).setValue(Long.valueOf(expire));
888            } else {
889                resourceItem.getItemProperty(PROPERTY_DATE_EXPIRED).setValue(null);
890            }
891        }
892
893        if (resourceItem.getItemProperty(PROPERTY_STATE_NAME) != null) {
894            resourceItem.getItemProperty(PROPERTY_STATE_NAME).setValue(resUtil.getStateName());
895        }
896
897        if (resourceItem.getItemProperty(PROPERTY_STATE) != null) {
898            resourceItem.getItemProperty(PROPERTY_STATE).setValue(resource.getState());
899        }
900
901        if (resourceItem.getItemProperty(PROPERTY_USER_LOCKED) != null) {
902            resourceItem.getItemProperty(PROPERTY_USER_LOCKED).setValue(resUtil.getLockedByName());
903        }
904
905        if (resourceItem.getItemProperty(PROPERTY_CATEGORIES) != null) {
906            CategoryLabel l = new CategoryLabel(resUtil, locale);
907            resourceItem.getItemProperty(PROPERTY_CATEGORIES).setValue(l);
908        }
909    }
910
911    /**
912     * Gets the CSS style name for the given resource state.<p>
913     *
914     * @param state the resource state
915     * @return the CSS style name
916     */
917    public static String getStateStyle(CmsResourceState state) {
918
919        String stateStyle = "";
920        if (state != null) {
921            if (state.isDeleted()) {
922                stateStyle = OpenCmsTheme.STATE_DELETED;
923            } else if (state.isNew()) {
924                stateStyle = OpenCmsTheme.STATE_NEW;
925            } else if (state.isChanged()) {
926                stateStyle = OpenCmsTheme.STATE_CHANGED;
927            }
928        }
929        return stateStyle;
930    }
931
932    /**
933     * Assembles the full title of a category from the title of its parents.
934     *
935     * @param categories the map of applicable categories by path
936     * @param category the category for which to build the title
937     *
938     * @return the combined title
939     */
940    private static String getCompositeCategoryTitle(Map<CmsPath, CmsCategory> categories, CmsCategory category) {
941
942        ArrayList<String> components = new ArrayList<>();
943        CmsCategory currentCategory = category;
944        while (currentCategory != null) {
945            components.add(currentCategory.getTitle());
946            CmsPath parentPath = new CmsPath(CmsResource.getParentFolder(currentCategory.getPath()));
947            currentCategory = categories.get(parentPath);
948        }
949        // The while loop iterated "up" the category tree, we want the category titles in "down" direction
950        Collections.reverse(components);
951        return Joiner.on(" / ").join(components);
952    }
953
954    /**
955     * Adds a property provider.<p>
956     *
957     * @param provider the property provider
958     */
959    public void addPropertyProvider(I_ResourcePropertyProvider provider) {
960
961        m_propertyProviders.add(provider);
962    }
963
964    /**
965     * Clears the value selection.<p>
966     */
967    public void clearSelection() {
968
969        m_fileTable.setValue(Collections.emptySet());
970    }
971
972    /**
973     * Fills the resource table.<p>
974     *
975     * @param cms the current CMS context
976     * @param resources the resources which should be displayed in the table
977     */
978    public void fillTable(CmsObject cms, List<CmsResource> resources) {
979
980        fillTable(cms, resources, true);
981    }
982
983    /**
984     * Fills the resource table.<p>
985     *
986     * @param cms the current CMS context
987     * @param resources the resources which should be displayed in the table
988     * @param clearFilter <code>true</code> to clear the search filter
989     */
990    public void fillTable(CmsObject cms, List<CmsResource> resources, boolean clearFilter) {
991
992        fillTable(cms, resources, clearFilter, true);
993    }
994
995    /**
996     * Fills the resource table.<p>
997     *
998     * @param cms the current CMS context
999     * @param resources the resources which should be displayed in the table
1000     * @param clearFilter <code>true</code> to clear the search filter
1001     * @param sort <code>true</code> to sort the table entries
1002     */
1003    public void fillTable(CmsObject cms, List<CmsResource> resources, boolean clearFilter, boolean sort) {
1004
1005        fillTable(cms, resources, clearFilter, true, false);
1006    }
1007
1008    /**
1009     * Fills the resource table.<p>
1010     *
1011     * @param cms the current CMS context
1012     * @param resources the resources which should be displayed in the table
1013     * @param clearFilter <code>true</code> to clear the search filter
1014     * @param sort <code>true</code> to sort the table entries
1015     * @param distinctResources whether to only show distinct resources
1016     */
1017    public void fillTable(
1018        CmsObject cms,
1019        List<CmsResource> resources,
1020        boolean clearFilter,
1021        boolean sort,
1022        boolean distinctResources) {
1023
1024        Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
1025        m_container.removeAllItems();
1026        if (clearFilter) {
1027            m_container.removeAllContainerFilters();
1028        }
1029        if (distinctResources) {
1030            Map<String, String> ids = new HashMap<String, String>();
1031            for (CmsResource resource : resources) {
1032                String id = resource.getStructureId().getStringValue();
1033                if (!ids.containsKey(id)) {
1034                    fillItem(cms, resource, wpLocale);
1035                    ids.put(id, "");
1036                }
1037            }
1038        } else {
1039            for (CmsResource resource : resources) {
1040                fillItem(cms, resource, wpLocale);
1041            }
1042        }
1043        if (sort) {
1044            m_fileTable.sort();
1045        }
1046        clearSelection();
1047    }
1048
1049    /**
1050     * Gets structure ids of resources for current folder in current sort order.<p>
1051     *
1052     * @return the structure ids of the current folder contents
1053     */
1054    @SuppressWarnings("unchecked")
1055    public List<CmsUUID> getAllIds() {
1056
1057        return itemIdsToUUIDs((List<String>)m_fileTable.getContainerDataSource().getItemIds());
1058    }
1059
1060    /**
1061     * Returns the number of currently visible items.<p>
1062     *
1063     * @return the number of currentliy visible items
1064     */
1065    public int getItemCount() {
1066
1067        return m_container.getItemCount();
1068    }
1069
1070    /**
1071     * Returns the structure id to the given string item id.<p>
1072     *
1073     * @param itemId the item id
1074     *
1075     * @return the structure id
1076     */
1077    public CmsUUID getUUIDFromItemID(String itemId) {
1078
1079        return new CmsUUID(itemId);
1080    }
1081
1082    /**
1083     * Returns if the column with the given property id is visible and not collapsed.<p>
1084     *
1085     * @param propertyId the property id
1086     *
1087     * @return <code>true</code> if the column is visible
1088     */
1089    public boolean isColumnVisible(CmsResourceTableProperty propertyId) {
1090
1091        return Arrays.asList(m_fileTable.getVisibleColumns()).contains(propertyId)
1092            && !m_fileTable.isColumnCollapsed(propertyId);
1093    }
1094
1095    /**
1096     * Removes a property provider.<p>
1097     *
1098     * @param provider the provider to remove
1099     */
1100    public void removePropertyProvider(I_ResourcePropertyProvider provider) {
1101
1102        m_propertyProviders.remove(provider);
1103    }
1104
1105    /**
1106     * Selects all resources.<p>
1107     */
1108    public void selectAll() {
1109
1110        m_fileTable.setValue(m_fileTable.getItemIds());
1111    }
1112
1113    /**
1114     * Sets the list of collapsed columns.<p>
1115     *
1116     * @param collapsedColumns the list of collapsed columns
1117     */
1118    public void setCollapsedColumns(Object... collapsedColumns) {
1119
1120        Set<Object> collapsedSet = Sets.newHashSet();
1121        for (Object collapsed : collapsedColumns) {
1122            collapsedSet.add(collapsed);
1123        }
1124        for (Object key : m_fileTable.getVisibleColumns()) {
1125            boolean isCollapsed = collapsedSet.contains(key);
1126            internalSetColumnCollapsed(key, isCollapsed);
1127        }
1128    }
1129
1130    /**
1131     * Sets the column setting change handler.
1132     * @param columnSettingChangeHandler the handler instance
1133     */
1134    public void setColumnSettingChangeHandler(ColumnSettingChangeHandler columnSettingChangeHandler) {
1135
1136        m_columnSettingChangeHandler = columnSettingChangeHandler;
1137    }
1138
1139    /**
1140     * Sets the table drag mode.<p>
1141     *
1142     * @param dragMode the drag mode
1143     */
1144    public void setDragMode(TableDragMode dragMode) {
1145
1146        m_fileTable.setDragMode(dragMode);
1147    }
1148
1149    /**
1150     * Sets the table drop handler.<p>
1151     *
1152     * @param handler the drop handler
1153     */
1154    public void setDropHandler(DropHandler handler) {
1155
1156        m_fileTable.setDropHandler(handler);
1157    }
1158
1159    /**
1160     * Selects an given object in table.<p>
1161     *
1162     * @param o object to be selected.
1163     */
1164    public void setValue(Set<String> o) {
1165
1166        m_fileTable.setValue(o);
1167    }
1168
1169    /**
1170     * Fills the file item data.<p>
1171     *
1172     * @param cms the cms context
1173     * @param resource the resource
1174     * @param locale the workplace locale
1175     */
1176    protected void fillItem(CmsObject cms, CmsResource resource, Locale locale) {
1177
1178        Item resourceItem = m_container.getItem(resource.getStructureId().toString());
1179        if (resourceItem == null) {
1180            resourceItem = m_container.addItem(resource.getStructureId().toString());
1181        }
1182        fillItemDefault(resourceItem, cms, resource, locale);
1183        for (I_ResourcePropertyProvider provider : m_propertyProviders) {
1184            provider.addItemProperties(resourceItem, cms, resource, locale);
1185        }
1186    }
1187
1188    protected void internalSetColumnCollapsed(Object key, boolean collapsed) {
1189
1190        m_fileTable.setColumnCollapsed(key, collapsed);
1191    }
1192
1193    /**
1194     * Transforms the given item ids into UUIDs.<p>
1195     *
1196     * @param itemIds the item ids
1197     *
1198     * @return the UUIDs
1199     */
1200    protected List<CmsUUID> itemIdsToUUIDs(Collection<String> itemIds) {
1201
1202        List<CmsUUID> ids = new ArrayList<CmsUUID>();
1203        for (String itemId : itemIds) {
1204            if (itemId != null) {
1205                ids.add(getUUIDFromItemID(itemId));
1206            }
1207        }
1208        return ids;
1209    }
1210}